Skip to main content

Module masked

Module masked 

Source
Expand description

First-order arithmetic masking for ML-DSA secret polynomials (DPA / template-attack countermeasure). Available with the sca-protected Cargo feature. First-order arithmetic masking for ML-DSA polynomials.

Same idea as the ML-KEM masked module but adapted to the ML-DSA arithmetic (q = 8 380 417, polynomial coefficients held as i32, NTT goes all the way down to length-1 components).

Each secret polynomial is represented as two additive shares modulo q: s = (s₀ + s₁) mod q. All operations on secret data manipulate the shares independently, so a first-order side-channel attacker observing one share at a time learns nothing about the unmasked value.

§Sensitive operations protected

In dsa::sign_internal, the secret polynomials s1, s2, t0 are NTT-transformed once before the rejection-sampling loop, then multiplied by the per-iteration challenge polynomial c (which is public — verifier recomputes it):

  ŝ1, ŝ2, t̂0 ← NTT(s1), NTT(s2), NTT(t0)
  for each rejection iteration:
      ĉ ← NTT(c)
      cs1[i]  ← ĉ · ŝ1[i]      // secret × public
      cs2[i]  ← ĉ · ŝ2[i]      // secret × public
      ct0[i]  ← ĉ · t̂0[i]      // secret × public

The masked variants in this module replace ŝ1, ŝ2, t̂0 with MaskedPoly containers and provide a pointwise_mul_public that multiplies each share independently. Because ĉ is public, no secret×secret multiplication occurs and first-order masking is sufficient.

§Available operations

FunctionDescription
MaskedPoly::maskSplit a plaintext polynomial into two shares
MaskedPoly::unmaskReconstruct the polynomial from shares
MaskedPoly::refreshRe-randomize shares (prevents correlation buildup)
MaskedPoly::zeroizeDSE-resistant wipe of both shares
masked_nttForward NTT applied to each share
masked_ntt_invInverse NTT applied to each share
masked_pointwise_mul_publicMasked × public pointwise mul (returns a MaskedPoly)

§Masked y pipeline (sca-masked-y)

MaskedPoly::expand_mask samples y directly as two shares drawn from SHAKE256. The shares propagate through masked_ntt and masked_mat_vec_mul / masked_mat_vec_mul_lazy so that the intermediate w = A·y stays in masked form until the rejection loop commits to emitting it. This closes the DPA recovery of s1 from z = y + c·s1 that exists on any unmasked implementation. See Side-channel analysis of masked y-sampling in ML-DSA (IACR ePrint 2025/276) and the countermeasure chapter at doc/sca/countermeasures/ml_dsa.rst, section DPA on y — the sca-masked-y pipeline.

§References

  • Hardware masking of ML-DSA (IACR ePrint 2024, doc/papers/eprint2024_mldsa_hw_masking.pdf) — reference construction, we follow the same share topology.
  • Side-channel analysis of masked y-sampling in ML-DSA (IACR ePrint 2025/276) — basis for MaskedPoly::expand_mask + propagation through the linear stage.
  • Physical security considerations for ML-DSA (NIST, 2025) — masking recommendation for high-assurance profiles.

§Where to look next

  • Countermeasure description and threat analysis: doc/sca/countermeasures/ml_dsa.rst, sections DPA — first- order masking of secret polynomials and DPA on y — the sca-masked-y pipeline.
  • Call sites: crate::ml_dsa::dsa::sign_internal (look for #[cfg(feature = "sca-protected")] and #[cfg(feature = "sca-masked-y")] blocks).

§Scope and residual risk

Masking here is first-order. The shipped Tier-1 item T1-A (A3, refresh shares at the start of every rejection iteration, head-of-loop refresh block in dsa.rs) raises the effort required by a higher-order DPA that combines leakage across iterations. Going beyond first-order (full higher-order masking) is tracked as Tier-4 T4-C.

Structs§

MaskedPoly
A polynomial split into two additive shares modulo q.

Functions§

masked_mat_vec_mul
Masked matrix-vector multiplication in the NTT domain: for each output row i, compute sum_j (A_hat[i][j] · y_hat_m[j]) as a masked accumulator.
masked_mat_vec_mul_lazy
Low-memory variant of masked_mat_vec_mul: recomputes each a_hat[i][j] polynomial on-the-fly from the public seed rho via SHAKE128 instead of holding the full k×l matrix in memory.
masked_ntt
Apply the forward NTT to each share independently.
masked_ntt_inv
Apply the inverse NTT to each share independently.
masked_pointwise_mul_public
Pointwise multiply a masked polynomial by a public polynomial in NTT domain. Returns a fresh MaskedPoly holding the product.