pub struct MaskedPoly {
pub share0: [i32; 256],
pub share1: [i32; 256],
}Expand description
A polynomial split into two additive shares modulo q.
Maintains the invariant unmask()[i] = (share0[i] + share1[i]) mod q
for all i in 0..N. Both shares are stored with coefficients in
[0, q-1]. Neither share alone reveals any information about the
underlying polynomial.
Fields§
First additive share.
Second additive share.
Implementations§
Source§impl MaskedPoly
impl MaskedPoly
Sourcepub const fn zero() -> Self
pub const fn zero() -> Self
Build an all-zero MaskedPoly. Both shares are zero, so
unmask() returns the zero polynomial. Useful as a stack
initializer for fixed-size arrays of masked polynomials.
Sourcepub fn sample_expand_mask(
rho_double_prime: &[u8; 64],
nonce: u16,
gamma1: i32,
bitlen_gamma1_minus1: usize,
) -> Self
pub fn sample_expand_mask( rho_double_prime: &[u8; 64], nonce: u16, gamma1: i32, bitlen_gamma1_minus1: usize, ) -> Self
Masked sampling of a masking vector polynomial from a SHAKE256
stream — the DPA-safe replacement for sample::expand_mask.
Implements ExpandMask (FIPS 204 Algorithm 34) but produces a
two-share arithmetic representation (share0, share1) directly,
without ever materializing the unmasked y coefficient in a stack
or heap slot.
§Threat model
Boolean-masked y is attackable with ~300 traces per the Hermelink-Ning-Petri result (ePrint 2025/276). Arithmetic masking is more robust but still requires careful implementation: the key invariant is that the unmasked coefficient value must only exist transiently in a CPU register, never be written to RAM.
§Implementation
For each coefficient:
- Decode the unmasked
y_ifrom SHAKE256 output bytes into a stack-locallet y_i: i32 = ...(register-scoped). - Draw a fresh random mask
r_ifrom the same SHAKE256 stream (a separate squeeze block). - Compute
share1_i = r_i mod q,share0_i = (y_i - r_i) mod q. - Write both shares to the output
MaskedPoly. y_iandr_igo out of scope immediately.
The two SHAKE256 streams (y bits and mask bits) are drawn from
the same state: we first squeeze the packed-y bytes, then
squeeze additional bytes for the mask. This keeps the function
deterministic for a given rho'' || nonce, so the signature
remains reproducible (ACVP-compatible).
§Arguments
rho_double_prime— 64-byte seed (FIPS 204).nonce— the per-polynomial nonce (kappa + r).gamma1— the Γ₁ parameter for the current ML-DSA level.bitlen_gamma1_minus1— bit length used by ExpandMask (17 or 19).
Sourcepub fn mask(
poly: &[i32; 256],
rng: &mut dyn CryptoRng,
) -> Result<Self, MlDsaError>
pub fn mask( poly: &[i32; 256], rng: &mut dyn CryptoRng, ) -> Result<Self, MlDsaError>
Split a plaintext polynomial into two random additive shares.
Generates a uniformly distributed share1 ∈ [0, q-1]^N from the
RNG, then sets share0 = poly - share1 mod q. The intermediate
random bytes are zeroized after use.
§Errors
Returns MlDsaError::RngFailure if the RNG fails.
Sourcepub fn unmask(&self) -> [i32; 256]
pub fn unmask(&self) -> [i32; 256]
Reconstruct the plaintext polynomial from the two shares.
Result coefficients are in [0, q-1]. The returned polynomial
is unmasked secret data and should be zeroized after use.
Sourcepub fn refresh(&mut self, rng: &mut dyn CryptoRng) -> Result<(), MlDsaError>
pub fn refresh(&mut self, rng: &mut dyn CryptoRng) -> Result<(), MlDsaError>
Re-randomize the shares without changing the unmasked value.
Draws a fresh random polynomial r and updates the shares as
share0' = share0 - r mod q, share1' = share1 + r mod q.
The sum is preserved: share0' + share1' ≡ share0 + share1.
Refreshing prevents higher-order correlation buildup when the
same masked polynomial is reused across multiple operations.