diff --git a/crypto/mldsa-lowmemory/src/aux_functions.rs b/crypto/mldsa-lowmemory/src/aux_functions.rs index 27264207..5eaf55f3 100644 --- a/crypto/mldsa-lowmemory/src/aux_functions.rs +++ b/crypto/mldsa-lowmemory/src/aux_functions.rs @@ -1,12 +1,14 @@ //! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. -// use crate::matrix::{Matrix, Vector}; use crate::mldsa::{G, H, POLY_T0PACKED_LEN}; -use crate::mldsa::{ - MLDSA44_GAMMA1, MLDSA44_GAMMA2, MLDSA65_GAMMA1, MLDSA65_GAMMA2, N, POLY_T1PACKED_LEN, d, q, +use crate::mldsa::{N, POLY_T1PACKED_LEN, d, q}; +use crate::params::{ + GAMMA1_2_POW_17, GAMMA1_2_POW_19, GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, + MLDSAParams, }; use crate::polynomial::Polynomial; use bouncycastle_core::traits::XOF; +use bouncycastle_utils::secret::ZeroizablePrimitive; /// Algorithm 14 CoeffFromThreeBytes(๐‘0, ๐‘1, ๐‘2) /// Output: An integer modulo ๐‘ž or โŠฅ. @@ -32,8 +34,8 @@ pub(crate) fn coeff_from_three_bytes(b: &[u8; 3]) -> Result { /// Input: Integer ๐‘ โˆˆ {0, 1, โ€ฆ , 15}. /// Output: An integer between โˆ’๐œ‚ and ๐œ‚, or โŠฅ. #[inline(always)] -pub(crate) fn coeff_from_half_byte(b: u8) -> Result { - if ETA == 2 && b < 15 { +pub(crate) fn coeff_from_half_byte(b: u8) -> Result { + if P::eta == 2 && b < 15 { // Original code is bad because '%' is not constant-time. // Ok(2 - (b % 5) as i32) // TODO: Verify whether this function is constant time and whether it can be further optimized @@ -44,7 +46,7 @@ pub(crate) fn coeff_from_half_byte(b: u8) -> Result { }; Ok(2 - b as i32) } else { - if ETA == 4 && b < 9 { Ok(4 - b as i32) } else { Err(()) } + if P::eta == 4 && b < 9 { Ok(4 - b as i32) } else { Err(()) } } } @@ -64,16 +66,6 @@ pub(crate) fn simple_bit_pack_t1(w: &Polynomial) -> [u8; POLY_T1PACKED_LEN] { output } -/// As defined in Algorithm 17, this gives the length of a packed bitstring representing a polynomial -/// whose coefficients have been rounded to \[-eta, eta], which is 32*bitlen(2*eta). -pub const fn bitlen_eta(eta: usize) -> usize { - match eta { - 2 => 32 * 3, - 4 => 32 * 4, - _ => panic!("Invalid eta value"), - } -} - /// A variant of Algorithm 17 BitPack specific to a=eta, b=eta /// Encodes a polynomial ๐‘ค into a byte string. /// Input: ๐‘Ž, ๐‘ โˆˆ โ„• and ๐‘ค โˆˆ ๐‘… such that the coefficients of ๐‘ค are all in \[โˆ’eta, eta]. @@ -81,14 +73,14 @@ pub const fn bitlen_eta(eta: usize) -> usize { // `match ETA` folds away per monomorphization (ETA is a const generic), so ETA = 2 // and ETA = 4 each compile to just their own arm, leaving no dispatch at runtime. #[inline(always)] -pub(crate) fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { - debug_assert_eq!(r.len(), bitlen_eta(ETA)); +pub(crate) fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { + debug_assert_eq!(r.len(), P::POLY_ETA_PACKED_LEN); // temp swap space let mut t: [u8; 8] = [0; 8]; - match ETA { - // MLDSA44 and MLDSA87 + match P::eta { + // MLDSA-44 and MLDSA-87 2 => { let eta: i32 = 2; for i in 0..N / 8 { @@ -106,7 +98,7 @@ pub(crate) fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { r[3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5); } } - // MLDSA65 + // MLDSA-65 4 => { let eta: i32 = 4; for i in 0..N / 2 { @@ -163,20 +155,22 @@ pub(crate) fn bit_pack_t0(t0: &Polynomial) -> [u8; POLY_T0PACKED_LEN] { } /// A variant of Algorithm 17 specific to packing z in the signature value in \[โˆ’๐›พ1 + 1, ๐›พ1]. -pub(crate) fn bitpack_gamma1( - z: &Polynomial, - out: &mut [u8; POLY_Z_PACKED_LEN], -) { +/// The destination is a slice rather than a `P::PolyZPacked`: the only caller writes straight into +/// its window of the signature buffer, which is chunked at runtime because the chunk size +/// `P::POLY_Z_PACKED_LEN` cannot be a const generic argument. +pub(crate) fn bitpack_gamma1(z: &Polynomial, out: &mut [u8]) { + debug_assert_eq!(out.len(), P::POLY_Z_PACKED_LEN); out.fill(0); let mut t: [u32; 4] = [0; 4]; - match GAMMA1 { - MLDSA44_GAMMA1 => { + match P::gamma1 { + // MLDSA-44 + GAMMA1_2_POW_17 => { for i in 0..N / 4 { - t[0] = (GAMMA1 - z[4 * i]) as u32; - t[1] = (GAMMA1 - z[4 * i + 1]) as u32; - t[2] = (GAMMA1 - z[4 * i + 2]) as u32; - t[3] = (GAMMA1 - z[4 * i + 3]) as u32; + t[0] = (P::gamma1 - z[4 * i]) as u32; + t[1] = (P::gamma1 - z[4 * i + 1]) as u32; + t[2] = (P::gamma1 - z[4 * i + 2]) as u32; + t[3] = (P::gamma1 - z[4 * i + 3]) as u32; out[9 * i] = t[0] as u8; out[9 * i + 1] = (t[0] >> 8) as u8; @@ -189,11 +183,11 @@ pub(crate) fn bitpack_gamma1( out[9 * i + 8] = (t[3] >> 10) as u8; } } - // MLDSA-65 and 87 have the same GAMMA1 value - MLDSA65_GAMMA1 => { + // MLDSA-65 and -87 have the same GAMMA1 value + GAMMA1_2_POW_19 => { for i in 0..N / 2 { - t[0] = (GAMMA1 - z[2 * i]) as u32; - t[1] = (GAMMA1 - z[2 * i + 1]) as u32; + t[0] = (P::gamma1 - z[2 * i]) as u32; + t[1] = (P::gamma1 - z[2 * i + 1]) as u32; out[5 * i] = t[0] as u8; out[5 * i + 1] = (t[0] >> 8) as u8; @@ -215,8 +209,6 @@ pub(crate) fn bitpack_gamma1( /// /// Note: caller is responsible for ensuring correct input array size pub(crate) fn simple_bit_unpack_t1(v: &[u8; POLY_T1PACKED_LEN]) -> Polynomial { - // debug_assert_eq!(v.len(), POLY_T1PACKED_LEN); - let mut w = Polynomial::new(); for i in 0..N / 4 { @@ -239,10 +231,10 @@ pub(crate) fn simple_bit_unpack_t1(v: &[u8; POLY_T1PACKED_LEN]) -> Polynomial { // `match ETA` folds away per monomorphization (ETA is a const generic), so ETA = 2 // and ETA = 4 each compile to just their own arm, leaving no dispatch at runtime. #[inline(always)] -pub(crate) fn bit_unpack_eta_out(v: &[u8], w: &mut Polynomial) { - debug_assert_eq!(v.len(), bitlen_eta(ETA)); +pub(crate) fn bit_unpack_eta_out(v: &[u8], w: &mut Polynomial) { + debug_assert_eq!(v.len(), P::POLY_ETA_PACKED_LEN); - match ETA { + match P::eta { // MLDSA44 and MLDSA87 2 => { let eta: i32 = 2; @@ -291,11 +283,12 @@ pub(crate) fn bit_unpack_eta_out(v: &[u8], w: &mut Polynomial) // `match ETA` folds away per monomorphization (ETA is a const generic), so ETA = 2 // and ETA = 4 each compile to just their own arm, leaving no dispatch at runtime. #[inline(always)] -pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { +pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { let mut w = Polynomial::new(); - match GAMMA1 { - MLDSA44_GAMMA1 => { + match P::gamma1 { + // MLDSA-44 + GAMMA1_2_POW_17 => { // const gamma1: i32 = 1<<17; for i in 0..N / 4 { w[4 * i] = (((v[9 * i] as i32) | ((v[9 * i + 1] as i32) << 8)) @@ -311,14 +304,14 @@ pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { | ((v[9 * i + 8] as i32) << 10)) & 0x3FFFF; - w[4 * i] = GAMMA1 - w[4 * i]; - w[4 * i + 1] = GAMMA1 - w[4 * i + 1]; - w[4 * i + 2] = GAMMA1 - w[4 * i + 2]; - w[4 * i + 3] = GAMMA1 - w[4 * i + 3]; + w[4 * i] = P::gamma1 - w[4 * i]; + w[4 * i + 1] = P::gamma1 - w[4 * i + 1]; + w[4 * i + 2] = P::gamma1 - w[4 * i + 2]; + w[4 * i + 3] = P::gamma1 - w[4 * i + 3]; } } - // MLDSA-65 and 87 have the same GAMMA1 value - MLDSA65_GAMMA1 => { + // MLDSA-65 and -87 have the same GAMMA1 value + GAMMA1_2_POW_19 => { // const gamma1: i32 = 1<<19; for i in 0..N / 2 { w[2 * i] = (((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) @@ -328,8 +321,8 @@ pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { | ((v[5 * i + 4] as i32) << 12)) & 0xFFFFF; - w[2 * i] = GAMMA1 - w[2 * i]; - w[2 * i + 1] = GAMMA1 - w[2 * i + 1]; + w[2 * i] = P::gamma1 - w[2 * i]; + w[2 * i + 1] = P::gamma1 - w[2 * i + 1]; } } _ => { @@ -341,51 +334,40 @@ pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { } /// Part of unpacking the sig value -pub(crate) fn unpack_c_tilde(sig: &[u8]) -> &[u8; LAMBDA_over_4] { - sig[..LAMBDA_over_4].try_into().unwrap() +pub(crate) fn unpack_c_tilde(sig: &[u8]) -> P::SigCTilde { + let mut c_tilde = ::ZEROED; + c_tilde.as_mut().copy_from_slice(&sig[..P::C_TILDE_LEN]); + c_tilde } + /// Part of unpacking the sig value -pub(crate) fn unpack_z_row< - const GAMMA1: i32, - const GAMMA1_MINUS_BETA: i32, - const LAMBDA_over_4: usize, - const POLY_Z_PACKED_LEN: usize, - const SIG_LEN: usize, ->( +pub(crate) fn unpack_z_row( idx: usize, sig: &[u8; SIG_LEN], ) -> Result { - // assert: idx < l, but here there is no access to l + debug_assert!(idx < P::l); // skip to the start of the z's - let pos = LAMBDA_over_4; - let z = bit_unpack_gamma1::( - &sig[pos + idx * POLY_Z_PACKED_LEN..pos + (idx + 1) * POLY_Z_PACKED_LEN], + let pos = P::C_TILDE_LEN; + let z = bit_unpack_gamma1::

( + &sig[pos + idx * P::POLY_Z_PACKED_LEN..pos + (idx + 1) * P::POLY_Z_PACKED_LEN], ); // Perform the norm check from // Alg 8; Line 13 (first half) return [[ ||๐ณ||โˆž < ๐›พ1 โˆ’ ๐›ฝ]] - if z.check_norm::() { Err(()) } else { Ok(z) } + if z.check_norm(P::gamma1_minus_beta) { Err(()) } else { Ok(z) } } /// Part of unpacking the sig value -pub(crate) fn unpack_h_row< - const GAMMA1: i32, - const k: usize, - const l: usize, - const OMEGA: i32, - const LAMBDA_over_4: usize, - const POLY_Z_PACKED_LEN: usize, - const SIG_LEN: usize, ->( +pub(crate) fn unpack_h_row( row: usize, sig: &[u8; SIG_LEN], ) -> Option { - debug_assert!(row < k); + debug_assert!(row < P::k); let mut h = Polynomial::new(); // skip over the other stuff in the encoded sig value - let pos = LAMBDA_over_4 + l * POLY_Z_PACKED_LEN; + let pos = P::C_TILDE_LEN + P::l * P::POLY_Z_PACKED_LEN; // This inlines Algorithm 21 HintBitUnpack(๐‘ฆ) @@ -394,15 +376,15 @@ pub(crate) fn unpack_h_row< // let mut idx = 0usize; // This row calc is a bit weird because technically it's supposed to be done at the end // of the previous loop - let idx = if row == 0 { 0 } else { sig[pos + OMEGA as usize + row - 1] as usize }; + let idx = if row == 0 { 0 } else { sig[pos + P::omega as usize + row - 1] as usize }; // 3: for ๐‘– from 0 to ๐‘˜ โˆ’ 1 do // โ–ท reconstruct ๐ก[๐‘–] // for i in 0..k { // 4: if ๐‘ฆ[๐œ” + ๐‘–] < Index or ๐‘ฆ[๐œ” + ๐‘–] > ๐œ” then return โŠฅ // mutants note: don't have test vectors that exercise this condition - if sig[pos + (OMEGA as usize) + row] < (idx as u8) - || sig[pos + (OMEGA as usize) + row] > OMEGA as u8 + if sig[pos + (P::omega as usize) + row] < (idx as u8) + || sig[pos + (P::omega as usize) + row] > P::omega as u8 { return None; } @@ -410,7 +392,7 @@ pub(crate) fn unpack_h_row< // 6: First โ† Index // 7: while Index < ๐‘ฆ[๐œ” + ๐‘–] do // โ–ท ๐‘ฆ[๐œ” + ๐‘–] says how far one can advance Index - for j in idx..sig[pos + OMEGA as usize + row] as usize { + for j in idx..sig[pos + P::omega as usize + row] as usize { // 8: if Index > First then // 9: if ๐‘ฆ[Index โˆ’ 1] โ‰ฅ ๐‘ฆ[Index] then return โŠฅ // โ–ท malformed input @@ -427,9 +409,9 @@ pub(crate) fn unpack_h_row< // โ–ท read any leftover bytes in the first ๐œ” bytes of ๐‘ฆ for malformed (nonzero) bytes // mutants note: - if row == k - 1 { - let idx = sig[pos + OMEGA as usize + row] as usize; - for j in idx..OMEGA as usize { + if row == P::k - 1 { + let idx = sig[pos + P::omega as usize + row] as usize; + for j in idx..P::omega as usize { if sig[pos + j] != 0 { return None; } @@ -443,9 +425,7 @@ pub(crate) fn unpack_h_row< /// Samples a polynomial ๐‘ โˆˆ ๐‘… with coefficients from {โˆ’1, 0, 1} and Hamming weight ๐œ โ‰ค 64. /// Input: A seed ๐œŒ โˆˆ ๐”น๐œ†/4 /// Output: A polynomial ๐‘ in ๐‘…. -pub(crate) fn sample_in_ball( - rho: &[u8; LAMBDA_over_4], -) -> Polynomial { +pub(crate) fn sample_in_ball(rho: &P::SigCTilde) -> Polynomial { // 1: ๐‘ โ† 0 let mut c = Polynomial::new(); @@ -453,7 +433,7 @@ pub(crate) fn sample_in_ball( // 3: ctx โ† H.Absorb(ctx, ๐œŒ) // 4: (ctx, ๐‘ ) โ† H.Squeeze(ctx, 8) let mut h = H::new(); - h.absorb(rho).expect("absorb before squeeze is infallible"); + h.absorb(rho.as_ref()).expect("absorb before squeeze is infallible"); let mut s = [0u8; 8]; h.squeeze_out(&mut s); @@ -469,7 +449,7 @@ pub(crate) fn sample_in_ball( // let mut pos = 8; // let mut b; let mut j = [0u8]; - for i in (N - TAU as usize)..N { + for i in (N - P::tau as usize)..N { // 7: (ctx, ๐‘—) โ† H.Squeeze(ctx, 1) // Note: At first, it might seem to be faster to pre-squeeze a buffer outside the loop. // However, after experimentation and testing, the difference is not noticeable. @@ -557,7 +537,7 @@ pub(crate) fn rej_ntt_poly(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { /// This is supposed to take a rho: [u8; 66], which is: ๐œŒ||IntegerToBytes(๐‘ , 1)||IntegerToBytes(๐‘Ÿ, 1) /// but to avoid needing to copy bytes and allocate more memory, /// here that is split into a [u8;64] and a [u8;2] -pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2]) -> Polynomial { +pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2]) -> Polynomial { let mut a = Polynomial::new(); let mut j: usize = 0; let mut h = H::new(); @@ -574,8 +554,8 @@ pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2] let mut idx: usize = 0; while j < N { - let z0 = coeff_from_half_byte::(z_arr[idx] & 0x0F); // equiv to % 16 (but faster, and more importantly, constant-time) - let z1 = coeff_from_half_byte::(z_arr[idx] >> 4); // equiv to div_floor(16) (but faster, and more importantly, constant-time) + let z0 = coeff_from_half_byte::

(z_arr[idx] & 0x0F); // equiv to % 16 (but faster, and more importantly, constant-time) + let z1 = coeff_from_half_byte::

(z_arr[idx] >> 4); // equiv to div_floor(16) (but faster, and more importantly, constant-time) if z0.is_ok() { a[j] = z0.unwrap(); @@ -600,21 +580,19 @@ pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2] /// Samples a vector ๐ฒ โˆˆ ๐‘…โ„“ such that each polynomial ๐ฒ[๐‘Ÿ] has coefficients between โˆ’๐›พ1 + 1 and ๐›พ1. /// Input: A seed ๐œŒ โˆˆ ๐”น64 and a nonnegative integer ๐œ‡. /// Output: Vector ๐ฒ โˆˆ ๐‘…โ„“ . -pub(crate) fn expand_mask_poly( - rho: &[u8; 64], - nonce: u16, -) -> Polynomial { +pub(crate) fn expand_mask_poly(rho: &[u8; 64], nonce: u16) -> Polynomial { // 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) // โ–ท ๐›พ1 is always a power of 2 // 3: ๐œŒโ€ฒ โ† ๐œŒ||IntegerToBytes(๐œ‡ + ๐‘Ÿ, 2) - // 32c = GAMMA1_MASK_LEN; // 4: ๐‘ฃ โ† H(๐œŒโ€ฒ, 32๐‘) + // The 32๐‘ bytes squeezed on line 4 are exactly `P::POLY_Z_PACKED_LEN`, so the buffer for them + // is `P::PolyZPacked`; see the docs on `MLDSAParams::POLY_Z_PACKED_LEN`. let mut h = H::new(); h.absorb(rho).expect("absorb before squeeze is infallible"); h.absorb(&nonce.to_le_bytes()).expect("absorb before squeeze is infallible"); - let mut v = [0u8; GAMMA1_MASK_LEN]; - h.squeeze_out(&mut v); - bit_unpack_gamma1::(&v) + let mut v = ::ZEROED; + h.squeeze_out(v.as_mut()); + bit_unpack_gamma1::

(v.as_ref()) } /// Algorithm 35 Power2Round(๐‘Ÿ) @@ -657,7 +635,7 @@ fn test_power_2_round() { // the hope here is that the compiler will aggressively inline this function, // and optimize away the branching. #[inline(always)] -pub(crate) fn decompose(r: i32) -> (i32, i32) { +pub(crate) fn decompose(r: i32) -> (i32, i32) { // 1: ๐‘Ÿ+ โ† ๐‘Ÿ mod ๐‘ž // 2: ๐‘Ÿ0 โ† ๐‘Ÿ+ modยฑ(2๐›พ2) // 3: if ๐‘Ÿ+ โˆ’ ๐‘Ÿ0 = ๐‘ž โˆ’ 1 then @@ -672,14 +650,15 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { let mut r1: i32; let mut r0 = (r + 127) >> 7; - match GAMMA2 { - MLDSA44_GAMMA2 => { + match P::gamma2 { + // MLDSO-44 + GAMMA2_Q_MINUS_1_OVER_88 => { // (q - 1) / 88 r0 = (r0 * 11275 + (1 << 23)) >> 24; r0 ^= ((43 - r0) >> 31) & r0; } - // ML-DSA65 and 87 have the same GAMMA2 - MLDSA65_GAMMA2 => { + // ML-DSA-65 and -87 have the same GAMMA2 + GAMMA2_Q_MINUS_1_OVER_32 => { // (q - 1) / 32; r0 = (r0 * 1025 + (1 << 21)) >> 22; r0 &= 15; @@ -690,7 +669,7 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { } } - r1 = r - r0 * 2 * GAMMA2; + r1 = r - r0 * 2 * P::gamma2; // mutants note: the choice of (q - 1) is a bit arbitrary in that after doing the bit-shifting, // this seems to work out mathematically equivalent to doing q/2, or (q+3)/2, but here it is left as (q-1)/2 @@ -704,10 +683,10 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { /// Returns ๐‘Ÿ1 from the output of Decompose (๐‘Ÿ). /// Input: ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: Integer ๐‘Ÿ1. -pub(crate) fn high_bits(r: i32) -> i32 { +pub(crate) fn high_bits(r: i32) -> i32 { // 1: (๐‘Ÿ1, ๐‘Ÿ0) โ† Decompose(๐‘Ÿ) // 2: return ๐‘Ÿ1 - let (r1, _) = decompose::(r); + let (r1, _) = decompose::

(r); r1 } @@ -715,10 +694,10 @@ pub(crate) fn high_bits(r: i32) -> i32 { /// Returns ๐‘Ÿ0 from the output of Decompose (๐‘Ÿ). /// Input: ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: Integer ๐‘Ÿ0. -pub(crate) fn low_bits(r: i32) -> i32 { +pub(crate) fn low_bits(r: i32) -> i32 { // 1: (๐‘Ÿ1, ๐‘Ÿ0) โ† Decompose(๐‘Ÿ) // 2: return ๐‘Ÿ0 - let (_, r0) = decompose::(r); + let (_, r0) = decompose::

(r); r0 } @@ -726,27 +705,28 @@ pub(crate) fn low_bits(r: i32) -> i32 { /// Computes hint bit indicating whether adding ๐‘ง to ๐‘Ÿ alters the high bits of ๐‘Ÿ. /// Input: ๐‘ง, ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: Boolean. -pub(crate) fn make_hint(z: i32, r: i32) -> i32 { +pub(crate) fn make_hint(z: i32, r: i32) -> i32 { + // Naรฏve implementation: // // 1: ๐‘Ÿ1 โ† HighBits(๐‘Ÿ) - // let r1 = high_bits::(r); + // let r1 = high_bits::

(r); // // // 2: ๐‘ฃ1 โ† HighBits(๐‘Ÿ + ๐‘ง) - // let v1 = high_bits::(r + z); + // let v1 = high_bits::

(r + z); // // // 3: return [[๐‘Ÿ1 โ‰  ๐‘ฃ1]] // if r1 != v1 { 1 } else { 0 } // By the powers of someone much more clever than me, this is equivalent. // mutants note: we do not have KATs that exercise all branches of this if - if z <= GAMMA2 || z > q - GAMMA2 || (z == q - GAMMA2 && r == 0) { 0 } else { 1 } + if z <= P::gamma2 || z > q - P::gamma2 || (z == q - P::gamma2 && r == 0) { 0 } else { 1 } } /// Algorithm 40 UseHint(โ„Ž, ๐‘Ÿ) /// Returns the high bits of ๐‘Ÿ adjusted according to hint โ„Ž. /// Input: Boolean โ„Ž, ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: ๐‘Ÿ1 โˆˆ โ„ค with 0 โ‰ค ๐‘Ÿ1 โ‰ค (๐‘žโˆ’1) / 2*gamma2). -pub(super) fn use_hint(a: i32, hint: i32) -> i32 { - let (a0, a1) = decompose::(a); +pub(super) fn use_hint(a: i32, hint: i32) -> i32 { + let (a0, a1) = decompose::

(a); if hint == 0 { return a0; @@ -754,8 +734,9 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { debug_assert!(hint == 1); - match GAMMA2 { - MLDSA44_GAMMA2 => { + match P::gamma2 { + // MLDSA-44 + GAMMA2_Q_MINUS_1_OVER_88 => { // mutants note: this passes unit tests if it's a1 >= 0 // it is left like this because it matches the spec if a1 > 0 { @@ -764,8 +745,8 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { if a0 == 0 { 43 } else { a0 - 1 } } } - // ML-DSA65 and 87 have the same GAMMA2 - MLDSA65_GAMMA2 => { + // ML-DSA65 and -87 have the same GAMMA2 + GAMMA2_Q_MINUS_1_OVER_32 => { // mutants note: this passes unit tests if it's a0 >= 0 // it is left like this because it matches the spec if a1 > 0 { (a0 + 1) & 15 } else { (a0 - 1) & 15 } diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index 33ccdff1..9b8599d7 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -66,29 +66,15 @@ //! But a simple [`HashMLDSA::keygen`] is provided. use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; -use crate::mldsa::{ - MLDSA44_BETA, MLDSA44_C_TILDE, MLDSA44_ETA, MLDSA44_FULL_SK_LEN, MLDSA44_GAMMA1, - MLDSA44_GAMMA1_MASK_LEN, MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2, MLDSA44_GAMMA2_MINUS_BETA, - MLDSA44_LAMBDA, MLDSA44_LAMBDA_over_4, MLDSA44_OMEGA, MLDSA44_PK_LEN, - MLDSA44_POLY_W1_PACKED_LEN, MLDSA44_POLY_Z_PACKED_LEN, MLDSA44_S1_PACKED_LEN, - MLDSA44_S2_PACKED_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN, MLDSA44_TAU, MLDSA44_k, MLDSA44_l, -}; -use crate::mldsa::{MLDSA44_T1_PACKED_LEN, MLDSA65_T1_PACKED_LEN, MLDSA87_T1_PACKED_LEN}; -use crate::mldsa::{ - MLDSA65_BETA, MLDSA65_C_TILDE, MLDSA65_ETA, MLDSA65_FULL_SK_LEN, MLDSA65_GAMMA1, - MLDSA65_GAMMA1_MASK_LEN, MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2, MLDSA65_GAMMA2_MINUS_BETA, - MLDSA65_LAMBDA, MLDSA65_LAMBDA_over_4, MLDSA65_OMEGA, MLDSA65_PK_LEN, - MLDSA65_POLY_W1_PACKED_LEN, MLDSA65_POLY_Z_PACKED_LEN, MLDSA65_S1_PACKED_LEN, - MLDSA65_S2_PACKED_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN, MLDSA65_TAU, MLDSA65_k, MLDSA65_l, -}; -use crate::mldsa::{ - MLDSA87_BETA, MLDSA87_C_TILDE, MLDSA87_ETA, MLDSA87_FULL_SK_LEN, MLDSA87_GAMMA1, - MLDSA87_GAMMA1_MASK_LEN, MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2, MLDSA87_GAMMA2_MINUS_BETA, - MLDSA87_LAMBDA, MLDSA87_LAMBDA_over_4, MLDSA87_OMEGA, MLDSA87_PK_LEN, - MLDSA87_POLY_W1_PACKED_LEN, MLDSA87_POLY_Z_PACKED_LEN, MLDSA87_S1_PACKED_LEN, - MLDSA87_S2_PACKED_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN, MLDSA87_TAU, MLDSA87_k, MLDSA87_l, -}; +use crate::mldsa::{MLDSA44_FULL_SK_LEN, MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; +use crate::mldsa::{MLDSA65_FULL_SK_LEN, MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; +use crate::mldsa::{MLDSA87_FULL_SK_LEN, MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPublicKeyInternalTrait}; +use crate::params::{ + HashMLDSA44_with_SHA256Params, HashMLDSA44_with_SHA512Params, HashMLDSA65_with_SHA256Params, + HashMLDSA65_with_SHA512Params, HashMLDSA87_with_SHA256Params, HashMLDSA87_with_SHA512Params, + HashMLDSAParams, +}; use crate::{ MLDSA, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, @@ -100,9 +86,7 @@ use bouncycastle_core::traits::{ SignatureVerifier, Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha2::{SHA256, SHA512}; use core::marker::PhantomData; - // Imports needed only for docs #[allow(unused_imports)] use crate::mldsa::MuBuilder; @@ -124,153 +108,73 @@ pub const HASH_ML_DSA_87_WITH_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; /*** Pub Types ***/ -/// The HashML-DSA-44_with_SHA512 signature algorithm. +impl< + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PH_LEN: usize, + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, +> Algorithm for HashMLDSA +{ + const ALG_NAME: &'static str = P::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; +} + +/// The HashML-DSA-44_with_SHA256 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA256 = HashMLDSA< - SHA256, - 32, + HashMLDSA44_with_SHA256Params, + MLDSA44PublicKey, + MLDSA44PrivateKey, + { HashMLDSA44_with_SHA256Params::PH_LEN }, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, MLDSA44_SIG_LEN, - MLDSA44PublicKey, - MLDSA44PrivateKey, - MLDSA44_TAU, - MLDSA44_LAMBDA, - MLDSA44_GAMMA1, - MLDSA44_GAMMA2, - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, - MLDSA44_BETA, - MLDSA44_OMEGA, - MLDSA44_C_TILDE, - MLDSA44_POLY_Z_PACKED_LEN, - MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_S1_PACKED_LEN, - MLDSA44_S2_PACKED_LEN, - MLDSA44_T1_PACKED_LEN, - MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MINUS_BETA, - MLDSA44_GAMMA2_MINUS_BETA, - MLDSA44_GAMMA1_MASK_LEN, >; -impl Algorithm for HashMLDSA44_with_SHA256 { - const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} - /// The HashML-DSA-65_with_SHA256 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA65_with_SHA256 = HashMLDSA< - SHA256, - 32, + HashMLDSA65_with_SHA256Params, + MLDSA65PublicKey, + MLDSA65PrivateKey, + { HashMLDSA65_with_SHA256Params::PH_LEN }, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, MLDSA65_SIG_LEN, - MLDSA65PublicKey, - MLDSA65PrivateKey, - MLDSA65_TAU, - MLDSA65_LAMBDA, - MLDSA65_GAMMA1, - MLDSA65_GAMMA2, - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, - MLDSA65_BETA, - MLDSA65_OMEGA, - MLDSA65_C_TILDE, - MLDSA65_POLY_Z_PACKED_LEN, - MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_S1_PACKED_LEN, - MLDSA65_S2_PACKED_LEN, - MLDSA65_T1_PACKED_LEN, - MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MINUS_BETA, - MLDSA65_GAMMA2_MINUS_BETA, - MLDSA65_GAMMA1_MASK_LEN, >; -impl Algorithm for HashMLDSA65_with_SHA256 { - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} - /// The HashML-DSA-87_with_SHA256 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA87_with_SHA256 = HashMLDSA< - SHA256, - 32, + HashMLDSA87_with_SHA256Params, + MLDSA87PublicKey, + MLDSA87PrivateKey, + { HashMLDSA87_with_SHA256Params::PH_LEN }, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, MLDSA87_SIG_LEN, - MLDSA87PublicKey, - MLDSA87PrivateKey, - MLDSA87_TAU, - MLDSA87_LAMBDA, - MLDSA87_GAMMA1, - MLDSA87_GAMMA2, - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, - MLDSA87_BETA, - MLDSA87_OMEGA, - MLDSA87_C_TILDE, - MLDSA87_POLY_Z_PACKED_LEN, - MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_S1_PACKED_LEN, - MLDSA87_S2_PACKED_LEN, - MLDSA87_T1_PACKED_LEN, - MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MINUS_BETA, - MLDSA87_GAMMA2_MINUS_BETA, - MLDSA87_GAMMA1_MASK_LEN, >; -impl Algorithm for HashMLDSA87_with_SHA256 { - const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} - /// The HashML-DSA-44_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA512 = HashMLDSA< - SHA512, - 64, + HashMLDSA44_with_SHA512Params, + MLDSA44PublicKey, + MLDSA44PrivateKey, + { HashMLDSA44_with_SHA512Params::PH_LEN }, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, MLDSA44_SIG_LEN, - MLDSA44PublicKey, - MLDSA44PrivateKey, - MLDSA44_TAU, - MLDSA44_LAMBDA, - MLDSA44_GAMMA1, - MLDSA44_GAMMA2, - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, - MLDSA44_BETA, - MLDSA44_OMEGA, - MLDSA44_C_TILDE, - MLDSA44_POLY_Z_PACKED_LEN, - MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_S1_PACKED_LEN, - MLDSA44_S2_PACKED_LEN, - MLDSA44_T1_PACKED_LEN, - MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MINUS_BETA, - MLDSA44_GAMMA2_MINUS_BETA, - MLDSA44_GAMMA1_MASK_LEN, >; - -impl Algorithm for HashMLDSA44_with_SHA512 { - const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} /// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 } impl AlgorithmOID for HashMLDSA44_with_SHA512 { const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 32]; @@ -281,39 +185,15 @@ impl AlgorithmOID for HashMLDSA44_with_SHA512 { /// The HashML-DSA-65_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA65_with_SHA512 = HashMLDSA< - SHA512, - 64, + HashMLDSA65_with_SHA512Params, + MLDSA65PublicKey, + MLDSA65PrivateKey, + { HashMLDSA65_with_SHA512Params::PH_LEN }, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, MLDSA65_SIG_LEN, - MLDSA65PublicKey, - MLDSA65PrivateKey, - MLDSA65_TAU, - MLDSA65_LAMBDA, - MLDSA65_GAMMA1, - MLDSA65_GAMMA2, - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, - MLDSA65_BETA, - MLDSA65_OMEGA, - MLDSA65_C_TILDE, - MLDSA65_POLY_Z_PACKED_LEN, - MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_S1_PACKED_LEN, - MLDSA65_S2_PACKED_LEN, - MLDSA65_T1_PACKED_LEN, - MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MINUS_BETA, - MLDSA65_GAMMA2_MINUS_BETA, - MLDSA65_GAMMA1_MASK_LEN, >; - -impl Algorithm for HashMLDSA65_with_SHA512 { - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} /// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-65-with-sha512 { sigAlgs 33 } impl AlgorithmOID for HashMLDSA65_with_SHA512 { const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 33]; @@ -324,39 +204,15 @@ impl AlgorithmOID for HashMLDSA65_with_SHA512 { /// The HashML-DSA-87_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA87_with_SHA512 = HashMLDSA< - SHA512, - 64, + HashMLDSA87_with_SHA512Params, + MLDSA87PublicKey, + MLDSA87PrivateKey, + { HashMLDSA87_with_SHA512Params::PH_LEN }, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, MLDSA87_SIG_LEN, - MLDSA87PublicKey, - MLDSA87PrivateKey, - MLDSA87_TAU, - MLDSA87_LAMBDA, - MLDSA87_GAMMA1, - MLDSA87_GAMMA2, - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, - MLDSA87_BETA, - MLDSA87_OMEGA, - MLDSA87_C_TILDE, - MLDSA87_POLY_Z_PACKED_LEN, - MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_S1_PACKED_LEN, - MLDSA87_S2_PACKED_LEN, - MLDSA87_T1_PACKED_LEN, - MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MINUS_BETA, - MLDSA87_GAMMA2_MINUS_BETA, - MLDSA87_GAMMA1_MASK_LEN, >; - -impl Algorithm for HashMLDSA87_with_SHA512 { - const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} /// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-87-with-sha512 { sigAlgs 34 } impl AlgorithmOID for HashMLDSA87_with_SHA512 { const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 34]; @@ -371,55 +227,17 @@ impl AlgorithmOID for HashMLDSA87_with_SHA512 { /// by specifying the hash function to use (in the verifier), and specifying the bytes of the OID to /// to use as its domain separator in constructing the message representative M'. pub struct HashMLDSA< - HASH: Hash + AlgorithmOID + Default, - const HASH_LEN: usize, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > { - _phantom: PhantomData<(PK, SK)>, + _phantom: PhantomData<(P, PK, SK)>, signer_rnd: Option<[u8; MLDSA_RND_LEN]>, @@ -433,7 +251,7 @@ pub struct HashMLDSA< pk: Option, /// Hash function instance for streaming message hashing - hash: HASH, + hash: P::PreHash, /// Since HashML-DSA does message buffering in the external pre-hash, not in mu, /// this needs to be saved for later @@ -442,83 +260,16 @@ pub struct HashMLDSA< } impl< - HASH: Hash + AlgorithmOID + Default, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> - HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> HashMLDSA { /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. /// @@ -527,64 +278,12 @@ impl< /// Keys are interchangeable between MLDSA and HashMLDSA. /// Error condition: basically only on RNG failures. pub fn keygen() -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen() + MLDSA::::keygen() } /// Imports a secret key from a seed. pub fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen_internal(seed) + MLDSA::::keygen_internal(seed) } /// Algorithm 7 ML-DSA.Sign_internal(๐‘ ๐‘˜, ๐‘€โ€ฒ, ๐‘Ÿ๐‘›๐‘‘) @@ -651,40 +350,15 @@ impl< h.absorb(&[1u8]).expect("absorb before squeeze is infallible"); h.absorb(&[ctx.len() as u8]).expect("absorb before squeeze is infallible"); h.absorb(ctx).expect("absorb before squeeze is infallible"); - h.absorb(HASH::OID_DER).expect("absorb before squeeze is infallible"); + h.absorb(::OID_DER) + .expect("absorb before squeeze is infallible"); h.absorb(ph).expect("absorb before squeeze is infallible"); let mut mu = [0u8; MLDSA_MU_LEN]; let bytes_written = h.squeeze_out(&mut mu); debug_assert_eq!(bytes_written, MLDSA_MU_LEN); // 24: ๐œŽ โ† ML-DSA.Sign_internal(๐‘ ๐‘˜, ๐‘€', ๐‘Ÿ๐‘›๐‘‘) - let bytes_written = MLDSA::< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::sign_mu_deterministic_out(sk, &mu, rnd, output)?; + let bytes_written = MLDSA::::sign_mu_deterministic_out(sk, &mu, rnd, output)?; Ok(bytes_written) } @@ -725,7 +399,7 @@ impl< sk: None, seed: Some(seed.clone()), pk: None, - hash: HASH::default(), + hash: ::default(), ctx, ctx_len, }) @@ -733,83 +407,17 @@ impl< } impl< - HASH: Hash + AlgorithmOID + Default, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > Signer - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > + for HashMLDSA { /// Algorithm 4 HashML-DSA.Sign(๐‘ ๐‘˜, ๐‘€ , ๐‘๐‘ก๐‘ฅ, PH) /// Generate a โ€œpre-hashโ€ ML-DSA signature. @@ -829,7 +437,7 @@ impl< output.fill(0); let mut ph_m = [0u8; PH_LEN]; - _ = HASH::default().hash_out(msg, &mut ph_m); + _ = ::default().hash_out(msg, &mut ph_m); Self::sign_ph_out(sk, &ph_m, ctx, output) } @@ -841,7 +449,7 @@ impl< sk: Some(sk.clone()), seed: None, pk: None, - hash: HASH::default(), + hash: ::default(), ctx, ctx_len, }) @@ -899,87 +507,21 @@ impl< } impl< - HASH: Hash + AlgorithmOID + Default, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > SignatureVerifier - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > + for HashMLDSA { fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; - _ = HASH::default().hash_out(msg, &mut ph_m); + _ = ::default().hash_out(msg, &mut ph_m); Self::verify_ph(pk, &ph_m, ctx, sig) } @@ -992,7 +534,7 @@ impl< sk: None, seed: None, pk: Some(pk.clone()), - hash: HASH::default(), + hash: ::default(), ctx, ctx_len, }) @@ -1013,83 +555,17 @@ impl< } impl< - HASH: Hash + AlgorithmOID + Default, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > PHSigner - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > + for HashMLDSA { fn sign_ph( sk: &SK, @@ -1121,83 +597,17 @@ impl< } impl< - HASH: Hash + AlgorithmOID + Default, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > PHSignatureVerifier - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > + for HashMLDSA { fn verify_ph( pk: &PK, @@ -1229,37 +639,14 @@ impl< h.absorb(&[1u8]).expect("absorb before squeeze is infallible"); h.absorb(&[ctx.len() as u8]).expect("absorb before squeeze is infallible"); h.absorb(ctx).expect("absorb before squeeze is infallible"); - h.absorb(HASH::OID_DER).expect("absorb before squeeze is infallible"); + h.absorb(::OID_DER) + .expect("absorb before squeeze is infallible"); h.absorb(ph).expect("absorb before squeeze is infallible"); let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); - MLDSA::< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::verify_mu(pk, &mu, sig_sized) + MLDSA::::verify_mu( + pk, &mu, sig_sized, + ) } } diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 02b03d40..51a4780b 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -234,6 +234,7 @@ pub mod hash_mldsa; mod low_memory_helpers; pub mod mldsa; mod mldsa_keys; +mod params; mod polynomial; /*** Exported types ***/ diff --git a/crypto/mldsa-lowmemory/src/low_memory_helpers.rs b/crypto/mldsa-lowmemory/src/low_memory_helpers.rs index 81505ef5..7fb15a42 100644 --- a/crypto/mldsa-lowmemory/src/low_memory_helpers.rs +++ b/crypto/mldsa-lowmemory/src/low_memory_helpers.rs @@ -2,13 +2,11 @@ //! and other intermediate values by never holding the whole thing in memory at once, but re-constructing //! what it needs in pieces, which generally means handling the matrices and vectors row-wise or entry-wise. -use crate::aux_functions::{ - bit_unpack_eta_out, bitlen_eta, expand_mask_poly, rej_ntt_poly, unpack_z_row, -}; -use crate::mldsa::d; +use crate::aux_functions::{bit_unpack_eta_out, expand_mask_poly, rej_ntt_poly, unpack_z_row}; +use crate::params::MLDSAParams; use crate::polynomial::Polynomial; use bouncycastle_core::errors::SignatureError; -use bouncycastle_utils::secret::Secret; +use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; #[inline(always)] pub(crate) fn expandA_elem(rho: &[u8; 32], i: usize, j: usize) -> Polynomial { @@ -17,19 +15,19 @@ pub(crate) fn expandA_elem(rho: &[u8; 32], i: usize, j: usize) -> Polynomial { /// Compute a row of the core signing operation /// Alg 7: 12: ๐ฐ โ† NTTโˆ’1(๐€_hat โˆ˜ NTT(๐ฒ)) -pub(crate) fn compute_w_row( +pub(crate) fn compute_w_row( rho: &[u8; 32], rho_p_p: &[u8; 64], kappa: u16, row: usize, ) -> Polynomial { - let mut y_hat = expand_mask_poly::(rho_p_p, kappa); + let mut y_hat = expand_mask_poly::

(rho_p_p, kappa); y_hat.ntt(); let mut acc = rej_ntt_poly(rho, &[0u8, row as u8]); acc.multiply_ntt(&y_hat); - for col in 1..l { - y_hat = expand_mask_poly::(rho_p_p, kappa + col as u16); + for col in 1..P::l { + y_hat = expand_mask_poly::

(rho_p_p, kappa + col as u16); y_hat.ntt(); let mut tmp = rej_ntt_poly(rho, &[col as u8, row as u8]); tmp.multiply_ntt(&y_hat); @@ -42,14 +40,7 @@ pub(crate) fn compute_w_row( +pub(crate) fn compute_wp_approx_row( rho: &[u8; 32], sig: &[u8; SIG_LEN], t1: &Polynomial, @@ -64,18 +55,13 @@ pub(crate) fn compute_wp_approx_row< // ) // โ–ท ๐ฐ'_approx = ๐€๐ณ โˆ’ ๐‘๐ญ1 โ‹… 2^๐‘‘ - let mut z_i = - unpack_z_row::( - 0, sig, - )?; + let mut z_i = unpack_z_row::(0, sig)?; z_i.ntt(); let mut Az_acc = rej_ntt_poly(rho, &[0u8, idx as u8]); Az_acc.multiply_ntt(&z_i); - for col in 1..l { - z_i = unpack_z_row::( - col, sig, - )?; + for col in 1..P::l { + z_i = unpack_z_row::(col, sig)?; z_i.ntt(); // [Optimization Note]: @@ -88,7 +74,7 @@ pub(crate) fn compute_wp_approx_row< let ct1 = compute_ct1(t1.clone(), c.clone()); fn compute_ct1(mut t1_i: Polynomial, mut c: Polynomial) -> Polynomial { - t1_i.shift_left::(); + t1_i.shift_left_d(); t1_i.ntt(); c.ntt(); t1_i.multiply_ntt(&c); @@ -103,18 +89,14 @@ pub(crate) fn compute_wp_approx_row< Ok(Az_acc) } -pub(crate) fn compute_z_component< - const GAMMA1: i32, - const GAMMA1_MASK_LEN: usize, - const GAMMA1_MINUS_BETA: i32, ->( +pub(crate) fn compute_z_component( s1: &Polynomial, rho_p_p: &[u8; 64], c_hat: &Polynomial, kappa: u16, col: usize, ) -> Result, SignatureError> { - let y = expand_mask_poly::(rho_p_p, kappa + col as u16); + let y = expand_mask_poly::

(rho_p_p, kappa + col as u16); let mut s1_hat = s1.clone(); s1_hat.ntt(); s1_hat.multiply_ntt(c_hat); @@ -123,10 +105,10 @@ pub(crate) fn compute_z_component< let mut z = cs1; z.add_ntt(&y); - if z.check_norm::() { Ok(None) } else { Ok(Some(z)) } + if z.check_norm(P::gamma1_minus_beta) { Ok(None) } else { Ok(Some(z)) } } -pub(crate) fn compute_w0cs2_component( +pub(crate) fn compute_w0cs2_component( s2: &Polynomial, w: &Polynomial, c_hat: &Polynomial, @@ -144,12 +126,12 @@ pub(crate) fn compute_w0cs2_component(); + w0cs2.low_bits::

(); w0cs2.sub(&cs2); - if w0cs2.check_norm::() { None } else { Some(w0cs2) } + if w0cs2.check_norm(P::gamma2_minus_beta) { None } else { Some(w0cs2) } } -pub(crate) fn compute_ct0_component( +pub(crate) fn compute_ct0_component( t0_row: &Polynomial, c_hat: &Polynomial, ) -> Option { @@ -159,18 +141,20 @@ pub(crate) fn compute_ct0_component( let mut ct0 = t0_hat; // rename ct0.inv_ntt(); - if ct0.check_norm::() { None } else { Some(ct0) } + if ct0.check_norm(P::gamma2) { None } else { Some(ct0) } } /// Unpack a single s value from the packed representation. -pub(crate) fn s_unpack( - s_packed: &Secret<[u8; S_PACKED_LEN]>, +/// +/// `B` is the packed buffer type, which is `P::S1Packed` or `P::S2Packed` depending on which of +/// the two secret vectors is being unpacked. +pub(crate) fn s_unpack>( + s_packed: &Secret, idx: usize, ) -> Polynomial { let mut s = Polynomial::new(); - bit_unpack_eta_out::( - &s_packed[idx * bitlen_eta(eta)..(idx + 1) * bitlen_eta(eta)], - &mut s, - ); + let packed = (**s_packed).as_ref(); + let width = P::POLY_ETA_PACKED_LEN; + bit_unpack_eta_out::

(&packed[idx * width..(idx + 1) * width], &mut s); s } diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index 87e618c0..b1658579 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -384,15 +384,14 @@ //! } //! ``` -use crate::aux_functions::{ - bitlen_eta, bitpack_gamma1, sample_in_ball, unpack_c_tilde, unpack_h_row, -}; +use crate::aux_functions::{bitpack_gamma1, sample_in_ball, unpack_c_tilde, unpack_h_row}; use crate::low_memory_helpers::{ compute_ct0_component, compute_w_row, compute_w0cs2_component, compute_wp_approx_row, compute_z_component, s_unpack, }; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPrivateKeyTrait}; use crate::mldsa_keys::{MLDSAPublicKeyInternalTrait, MLDSAPublicKeyTrait}; +use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams}; use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, @@ -413,7 +412,7 @@ use crate::hash_mldsa; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait}; #[allow(unused_imports)] use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner}; -use bouncycastle_utils::secret::Secret; +use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; /*** Constants ***/ /// @@ -441,110 +440,34 @@ pub const MLDSA_SEED_LEN: usize = 32; pub(crate) const POLY_T0PACKED_LEN: usize = 416; pub(crate) const POLY_T1PACKED_LEN: usize = 320; -/* ML-DSA-44 params */ +/*** Re-exporting length constants that a caller will need instead of the entire Params objects which contains a bunch of internal algorithm detail ***/ -/// Length of the \[u8] holding a ML-DSA-44 public key. -pub const MLDSA44_PK_LEN: usize = 1312; -/// Length of the \[u8] holding a ML-DSA-44 private key, which in this implementation is just a 32-byte seed. -pub const MLDSA44_SK_LEN: usize = MLDSA_SEED_LEN; +/// Length of the \[u8] holding an ML-DSA-44 public key. +pub const MLDSA44_PK_LEN: usize = MLDSA44Params::PK_LEN; +/// Length of the \[u8] holding an ML-DSA-44 private key, which in this implementation is just a 32-byte seed. +pub const MLDSA44_SK_LEN: usize = MLDSA44Params::SK_LEN; /// The length of the FIPS representation of the private key, which can be produced by [`MLDSAPrivateKeyTrait::encode_full_sk`] -pub const MLDSA44_FULL_SK_LEN: usize = 2560; -/// Length of the \[u8] holding a ML-DSA-44 signature value. -pub const MLDSA44_SIG_LEN: usize = 2420; -pub(crate) const MLDSA44_TAU: i32 = 39; -pub(crate) const MLDSA44_LAMBDA: i32 = 128; -pub(crate) const MLDSA44_GAMMA1: i32 = 1 << 17; -pub(crate) const MLDSA44_GAMMA2: i32 = (q - 1) / 88; // mutants note: because of the bitshifting, the "- 1" ends up not mattering -pub(crate) const MLDSA44_k: usize = 4; -pub(crate) const MLDSA44_l: usize = 4; -pub(crate) const MLDSA44_ETA: usize = 2; -pub(crate) const MLDSA44_BETA: i32 = 78; -pub(crate) const MLDSA44_OMEGA: i32 = 80; - -// Useful derived values -pub(crate) const MLDSA44_C_TILDE: usize = 32; -pub(crate) const MLDSA44_POLY_Z_PACKED_LEN: usize = 576; -pub(crate) const MLDSA44_POLY_W1_PACKED_LEN: usize = 192; -pub(crate) const MLDSA44_S1_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_l; // 384 bytes -pub(crate) const MLDSA44_S2_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_k; // 384 bytes -pub(crate) const MLDSA44_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA44_k; // 768 bytes -pub(crate) const MLDSA44_LAMBDA_over_4: usize = 128 / 4; -pub(crate) const MLDSA44_GAMMA1_MINUS_BETA: i32 = MLDSA44_GAMMA1 - MLDSA44_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here. -pub(crate) const MLDSA44_GAMMA2_MINUS_BETA: i32 = MLDSA44_GAMMA2 - MLDSA44_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here. - -// Alg 32 -// 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) -pub(crate) const MLDSA44_GAMMA1_MASK_LEN: usize = 576; // 32*(1 + bitlen (๐›พ1 โˆ’ 1) ) - -/* ML-DSA-65 params */ - -/// Length of the \[u8] holding a ML-DSA-65 public key. -pub const MLDSA65_PK_LEN: usize = 1952; -/// Length of the \[u8] holding a ML-DSA-65 private key, which in this implementation is just a 32-byte seed. -pub const MLDSA65_SK_LEN: usize = MLDSA_SEED_LEN; +pub const MLDSA44_FULL_SK_LEN: usize = MLDSA44Params::FULL_SK_LEN; +/// Length of the \[u8] holding an ML-DSA-44 signature value. +pub const MLDSA44_SIG_LEN: usize = MLDSA44Params::SIG_LEN; + +/// Length of the \[u8] holding an ML-DSA-65 public key. +pub const MLDSA65_PK_LEN: usize = MLDSA65Params::PK_LEN; +/// Length of the \[u8] holding an ML-DSA-65 private key, which in this implementation is just a 32-byte seed. +pub const MLDSA65_SK_LEN: usize = MLDSA65Params::SK_LEN; /// The length of the FIPS representation of the private key, which can be produced by [`MLDSAPrivateKeyTrait::encode_full_sk`] -pub const MLDSA65_FULL_SK_LEN: usize = 4032; -/// Length of the \[u8] holding a ML-DSA-65 signature value. -pub const MLDSA65_SIG_LEN: usize = 3309; -pub(crate) const MLDSA65_TAU: i32 = 49; -pub(crate) const MLDSA65_LAMBDA: i32 = 192; -pub(crate) const MLDSA65_GAMMA1: i32 = 1 << 19; -pub(crate) const MLDSA65_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering -pub(crate) const MLDSA65_k: usize = 6; -pub(crate) const MLDSA65_l: usize = 5; -pub(crate) const MLDSA65_ETA: usize = 4; -pub(crate) const MLDSA65_BETA: i32 = 196; -pub(crate) const MLDSA65_OMEGA: i32 = 55; - -// Useful derived values -pub(crate) const MLDSA65_C_TILDE: usize = 48; -pub(crate) const MLDSA65_POLY_Z_PACKED_LEN: usize = 640; -pub(crate) const MLDSA65_POLY_W1_PACKED_LEN: usize = 128; -pub(crate) const MLDSA65_S1_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_l; // 640 bytes -pub(crate) const MLDSA65_S2_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_k; // 768 bytes -pub(crate) const MLDSA65_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA65_k; // 1152 bytes -pub(crate) const MLDSA65_LAMBDA_over_4: usize = 192 / 4; -pub(crate) const MLDSA65_GAMMA1_MINUS_BETA: i32 = MLDSA65_GAMMA1 - MLDSA65_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here. -pub(crate) const MLDSA65_GAMMA2_MINUS_BETA: i32 = MLDSA65_GAMMA2 - MLDSA65_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here. - -// Alg 32 -// 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) -pub(crate) const MLDSA65_GAMMA1_MASK_LEN: usize = 640; - -/* ML-DSA-87 params */ - -/// Length of the \[u8] holding a ML-DSA-87 public key. -pub const MLDSA87_PK_LEN: usize = 2592; -/// Length of the \[u8] holding a ML-DSA-87 private key, which in this implementation is just a 32-byte seed. -pub const MLDSA87_SK_LEN: usize = MLDSA_SEED_LEN; +pub const MLDSA65_FULL_SK_LEN: usize = MLDSA65Params::FULL_SK_LEN; +/// Length of the \[u8] holding an ML-DSA-65 signature value. +pub const MLDSA65_SIG_LEN: usize = MLDSA65Params::SIG_LEN; + +/// Length of the \[u8] holding an ML-DSA-87 public key. +pub const MLDSA87_PK_LEN: usize = MLDSA87Params::PK_LEN; +/// Length of the \[u8] holding an ML-DSA-87 private key, which in this implementation is just a 32-byte seed. +pub const MLDSA87_SK_LEN: usize = MLDSA87Params::SK_LEN; /// The length of the FIPS representation of the private key, which can be produced by [`MLDSAPrivateKeyTrait::encode_full_sk`] -pub const MLDSA87_FULL_SK_LEN: usize = 4896; -/// Length of the \[u8] holding a ML-DSA-87 signature value. -pub const MLDSA87_SIG_LEN: usize = 4627; -pub(crate) const MLDSA87_TAU: i32 = 60; -pub(crate) const MLDSA87_LAMBDA: i32 = 256; -pub(crate) const MLDSA87_GAMMA1: i32 = 1 << 19; -pub(crate) const MLDSA87_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering -pub(crate) const MLDSA87_k: usize = 8; -pub(crate) const MLDSA87_l: usize = 7; -pub(crate) const MLDSA87_ETA: usize = 2; -pub(crate) const MLDSA87_BETA: i32 = 120; -pub(crate) const MLDSA87_OMEGA: i32 = 75; - -// Useful derived values -pub(crate) const MLDSA87_C_TILDE: usize = 64; -pub(crate) const MLDSA87_POLY_Z_PACKED_LEN: usize = 640; -pub(crate) const MLDSA87_POLY_W1_PACKED_LEN: usize = 128; -pub(crate) const MLDSA87_S1_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_l; // 672 bytes -pub(crate) const MLDSA87_S2_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_k; // 768 bytes -pub(crate) const MLDSA87_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA87_k; // 1024 bytes -pub(crate) const MLDSA87_LAMBDA_over_4: usize = 256 / 4; -pub(crate) const MLDSA87_GAMMA1_MINUS_BETA: i32 = MLDSA87_GAMMA1 - MLDSA87_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here. -pub(crate) const MLDSA87_GAMMA2_MINUS_BETA: i32 = MLDSA87_GAMMA2 - MLDSA87_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here. - -// Alg 32 -// 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) -pub(crate) const MLDSA87_GAMMA1_MASK_LEN: usize = 640; +pub const MLDSA87_FULL_SK_LEN: usize = MLDSA87Params::FULL_SK_LEN; +/// Length of the \[u8] holding an ML-DSA-87 signature value. +pub const MLDSA87_SIG_LEN: usize = MLDSA87Params::SIG_LEN; // Typedefs just to make the algorithms look more like the FIPS 204 sample code. pub(crate) type H = SHAKE256; @@ -554,175 +477,84 @@ pub(crate) type G = SHAKE128; /// The ML-DSA-44 algorithm. pub type MLDSA44 = MLDSA< + MLDSA44Params, + MLDSA44PublicKey, + MLDSA44PrivateKey, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, MLDSA44_SIG_LEN, - MLDSA44PublicKey, - MLDSA44PrivateKey, - MLDSA44_TAU, - MLDSA44_LAMBDA, - MLDSA44_GAMMA1, - MLDSA44_GAMMA2, - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, - MLDSA44_BETA, - MLDSA44_OMEGA, - MLDSA44_C_TILDE, - MLDSA44_POLY_Z_PACKED_LEN, - MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_S1_PACKED_LEN, - MLDSA44_S2_PACKED_LEN, - MLDSA44_T1_PACKED_LEN, - MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MINUS_BETA, - MLDSA44_GAMMA2_MINUS_BETA, - MLDSA44_GAMMA1_MASK_LEN, >; -impl Algorithm for MLDSA44 { - const ALG_NAME: &'static str = ML_DSA_44_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } -impl AlgorithmOID for MLDSA44 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; -} - /// The ML-DSA-65 algorithm. pub type MLDSA65 = MLDSA< + MLDSA65Params, + MLDSA65PublicKey, + MLDSA65PrivateKey, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, MLDSA65_SIG_LEN, - MLDSA65PublicKey, - MLDSA65PrivateKey, - MLDSA65_TAU, - MLDSA65_LAMBDA, - MLDSA65_GAMMA1, - MLDSA65_GAMMA2, - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, - MLDSA65_BETA, - MLDSA65_OMEGA, - MLDSA65_C_TILDE, - MLDSA65_POLY_Z_PACKED_LEN, - MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_S1_PACKED_LEN, - MLDSA65_S2_PACKED_LEN, - MLDSA65_T1_PACKED_LEN, - MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MINUS_BETA, - MLDSA65_GAMMA2_MINUS_BETA, - MLDSA65_GAMMA1_MASK_LEN, >; -impl Algorithm for MLDSA65 { - const ALG_NAME: &'static str = ML_DSA_65_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } -impl AlgorithmOID for MLDSA65 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; -} - /// The ML-DSA-87 algorithm. pub type MLDSA87 = MLDSA< + MLDSA87Params, + MLDSA87PublicKey, + MLDSA87PrivateKey, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, MLDSA87_SIG_LEN, - MLDSA87PublicKey, - MLDSA87PrivateKey, - MLDSA87_TAU, - MLDSA87_LAMBDA, - MLDSA87_GAMMA1, - MLDSA87_GAMMA2, - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, - MLDSA87_BETA, - MLDSA87_OMEGA, - MLDSA87_C_TILDE, - MLDSA87_POLY_Z_PACKED_LEN, - MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_S1_PACKED_LEN, - MLDSA87_S2_PACKED_LEN, - MLDSA87_T1_PACKED_LEN, - MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MINUS_BETA, - MLDSA87_GAMMA2_MINUS_BETA, - MLDSA87_GAMMA1_MASK_LEN, >; -impl Algorithm for MLDSA87 { - const ALG_NAME: &'static str = ML_DSA_87_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, +> Algorithm for MLDSA +{ + const ALG_NAME: &'static str = P::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } -impl AlgorithmOID for MLDSA87 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; + +/// The OIDs NIST assigned in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 }, +/// id-ml-dsa-65 { sigAlgs 18 } and id-ml-dsa-87 { sigAlgs 19 }. As with [`Algorithm`], the values +/// belong to the parameter set, so one impl covers all three. +impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, +> AlgorithmOID for MLDSA +{ + const OID: &'static [u32] = P::OID; + const OID_DER: &'static [u8] = P::OID_DER; } /// The core internal implementation of the ML-DSA algorithm. /// This needs to be public for the compiler to be able to find it, but there shouldn't ever /// be a need to use this directly. Please use the named public types. pub struct MLDSA< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_VEC_H_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > { - _phantom: PhantomData<(PK, SK)>, + _phantom: PhantomData<(P, PK, SK)>, /// used for streaming the message for both signing and verifying mu_builder: MuBuilder, @@ -740,79 +572,15 @@ pub struct MLDSA< } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> - MLDSA< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> MLDSA { /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds. /// @@ -831,95 +599,16 @@ impl< } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> - MLDSATrait< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - LAMBDA, - GAMMA2, - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - eta, - > - for MLDSA< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - eta, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> MLDSATrait + for MLDSA { /*** Key Generation and PK / SK consistency checks ***/ @@ -1089,8 +778,8 @@ impl< // They are uncompresso as-needed, and only one polynomial at a time. // Storing these in memory can be avoided, but then all the sites where they are used // will require calls to sk.compute_s1_row() and sk.compute_s2_row(), which are fairly expensive. - let s1_packed: Secret<[u8; S1_PACKED_LEN]> = sk.compute_s1_packed(); - let s2_packed: Secret<[u8; S2_PACKED_LEN]> = sk.compute_s2_packed(); + let s1_packed: Secret = sk.compute_s1_packed(); + let s2_packed: Secret = sk.compute_s2_packed(); // 6: ๐œ‡ โ† H(BytesToBits(๐‘ก๐‘Ÿ)||๐‘€ โ€ฒ, 64) // skip: mu has already been provided @@ -1111,14 +800,14 @@ impl< // โ–ท initialize counter ๐œ… let mut kappa: u16 = 0; - let z_offset = LAMBDA_over_4; - let hint_offset = LAMBDA_over_4 + l * POLY_Z_PACKED_LEN; + let z_offset = P::C_TILDE_LEN; + let hint_offset = P::C_TILDE_LEN + P::l * P::POLY_Z_PACKED_LEN; loop { // FIPS 204 s. 6.2 allows: // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value." // mutants note: there is no test for this because we don't have access to a KAT that will exceed this limit. - if kappa > 1000 * k as u16 { + if kappa > 1000 * P::k as u16 { return Err(SignatureError::GenericError( "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.", )); @@ -1129,45 +818,39 @@ impl< // scope for hash let mut hash = H::new(); hash.absorb(mu).expect("absorb before squeeze is infallible"); - for row in 0..k { - let mut w = compute_w_row::( - &sk.rho(), - &rho_p_p, - kappa, - row, - ); - w.high_bits::(); - hash.absorb(&w.w1_encode::()) + for row in 0..P::k { + let mut w = compute_w_row::

(&sk.rho(), &rho_p_p, kappa, row); + w.high_bits::

(); + hash.absorb(w.w1_encode::

().as_ref()) .expect("absorb before squeeze is infallible"); } - let mut sig_val_c_tilde = [0u8; LAMBDA_over_4]; - hash.squeeze_out(&mut sig_val_c_tilde); + let mut sig_val_c_tilde = ::ZEROED; + hash.squeeze_out(sig_val_c_tilde.as_mut()); sig_val_c_tilde }; // 16: ๐‘ โˆˆ ๐‘…๐‘ž โ† SampleInBall(c_tilde) // 17: ๐‘_hat โ† NTT(๐‘) // optimization note: c_hat is used basically until the end, it can't really be scoped - let mut c_hat = sample_in_ball::(&sig_val_c_tilde); + let mut c_hat = sample_in_ball::

(&sig_val_c_tilde); c_hat.ntt(); output.fill(0); - output[..LAMBDA_over_4].copy_from_slice(&sig_val_c_tilde); + output[..P::C_TILDE_LEN].copy_from_slice(sig_val_c_tilde.as_ref()); - let (z_chunks, z_remainder) = output[z_offset..z_offset + l * POLY_Z_PACKED_LEN] - .as_chunks_mut::(); - debug_assert_eq!(z_chunks.len(), l); - debug_assert_eq!(z_remainder.len(), 0); + // The ๐ณ coordinates occupy `P::l` consecutive `P::POLY_Z_PACKED_LEN`-byte windows + // starting at `z_offset`. + debug_assert!(z_offset + P::l * P::POLY_Z_PACKED_LEN <= SIG_LEN); // 18-23 (z path): compute and encode each z polynomial directly into the caller buffer. let mut rejected = false; - for col in 0..l { - let z = match compute_z_component::( + for col in 0..P::l { + let z = match compute_z_component::

( // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. // weirdly, in perf testing, this actually caused memory usage to go by a small amount; // maybe because re-computing the intermediates adds more to the widest point of the alg? // &sk.compute_s1_row(col), - &s_unpack::(&s1_packed, col), + &s_unpack::(&s1_packed, col), &rho_p_p, &c_hat, kappa, @@ -1180,25 +863,25 @@ impl< } }; - bitpack_gamma1::(&z, &mut z_chunks[col]); + let start = z_offset + col * P::POLY_Z_PACKED_LEN; + bitpack_gamma1::

(&z, &mut output[start..start + P::POLY_Z_PACKED_LEN]); } if rejected { // mutants note: we don't have access to a test vector that exercises this - kappa += l as u16; + kappa += P::l as u16; continue; } // 19-28 (hint path): recompute rows as needed and write the packed hint directly. let mut hint_count = 0usize; - for row in 0..k { - let mut w = - compute_w_row::(&sk.rho(), &rho_p_p, kappa, row); - let mut tmp = match compute_w0cs2_component::( + for row in 0..P::k { + let mut w = compute_w_row::

(&sk.rho(), &rho_p_p, kappa, row); + let mut tmp = match compute_w0cs2_component::

( // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. // &sk.compute_s2_row(row), - &s_unpack::(&s2_packed, row), + &s_unpack::(&s2_packed, row), &w, &c_hat, ) { @@ -1209,7 +892,7 @@ impl< } }; - let ct0 = match compute_ct0_component::( + let ct0 = match compute_ct0_component::

( // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. // &sk.compute_t0_row(row), &c_hat) { @@ -1226,13 +909,13 @@ impl< tmp.add_ntt(&ct0); tmp.conditional_add_q(); - w.high_bits::(); - let (hint_row, weight) = tmp.make_hint_row::(&w); + w.high_bits::

(); + let (hint_row, weight) = tmp.make_hint_row::

(&w); let next_hint_count = hint_count + weight as usize; // mutants note: don't have a test vector that exercises this condition, // not even in bc-test-data - if next_hint_count > OMEGA as usize { + if next_hint_count > P::omega as usize { rejected = true; break; } @@ -1244,11 +927,11 @@ impl< } } debug_assert_eq!(hint_count, next_hint_count); - output[hint_offset + OMEGA as usize + row] = hint_count as u8; + output[hint_offset + P::omega as usize + row] = hint_count as u8; } if rejected { - kappa += l as u16; + kappa += P::l as u16; continue; } @@ -1325,40 +1008,24 @@ impl< // skip because this function is being handed mu // 8: ๐‘ โˆˆ ๐‘…๐‘ž โ† SampleInBall(c_tilde) - let c = sample_in_ball::(unpack_c_tilde(sig)); + let c = sample_in_ball::

(&unpack_c_tilde::

(sig)); // 12: ๐‘_tilde_p โ† H(๐œ‡||w1Encode(๐ฐ1'), ๐œ†/4) // โ–ท hash it; this should match ๐‘_tilde let mut hash = H::new(); hash.absorb(mu).expect("absorb before squeeze is infallible"); - for row in 0..k { + for row in 0..P::k { let mut wp_approx = match { // 9: ๐ฐโ€ฒ_approx โ† NTTโˆ’1(๐€_hat โˆ˜ NTT(๐ณ) โˆ’ NTT(๐‘) โˆ˜ NTT(๐ญ1 โ‹… 2^๐‘‘)) - compute_wp_approx_row::< - GAMMA1, - GAMMA1_MINUS_BETA, - l, - POLY_Z_PACKED_LEN, - LAMBDA_over_4, - SIG_LEN, - >(pk.rho(), sig, &pk.unpack_t1_row(row), &c, row) + compute_wp_approx_row::(pk.rho(), sig, &pk.unpack_t1_row(row), &c, row) } { Ok(wp_approx) => wp_approx, // means the norm check on z failed Err(_) => return Err(SignatureError::SignatureVerificationFailed), }; - let h_i = match unpack_h_row::< - GAMMA1, - k, - l, - OMEGA, - LAMBDA_over_4, - POLY_Z_PACKED_LEN, - SIG_LEN, - >(row, &sig) - { + let h_i = match unpack_h_row::(row, &sig) { Some(h_i) => h_i, // means there were more than OMEGA bits set in the hint None => return Err(SignatureError::SignatureVerificationFailed), @@ -1366,19 +1033,22 @@ impl< // 10: ๐ฐ1โ€ฒ โ† UseHint(๐ก, ๐ฐ'_approx) // โ–ท reconstruction of signerโ€™s commitment - wp_approx.use_hint::(&h_i); - hash.absorb(&wp_approx.w1_encode::()) + wp_approx.use_hint::

(&h_i); + hash.absorb(wp_approx.w1_encode::

().as_ref()) .expect("absorb before squeeze is infallible"); } - let mut c_tilde_p = [0u8; LAMBDA_over_4]; - hash.squeeze_out(&mut c_tilde_p); + let mut c_tilde_p = ::ZEROED; + hash.squeeze_out(c_tilde_p.as_mut()); // Verification is also done in constant time // 13 (second half): return [[ ||๐ณ||โˆž < ๐›พ1 โˆ’ ๐›ฝ]] and [[๐‘ ฬƒ = ๐‘โ€ฒ ]] // note: the first half of this check (the norm check) is buried in unpack_z_row(), // which is called from compute_wp_approx_row() - if bouncycastle_utils::ct::ct_eq_bytes(unpack_c_tilde::(sig), &c_tilde_p) { + if bouncycastle_utils::ct::ct_eq_bytes( + unpack_c_tilde::

(sig).as_ref(), + c_tilde_p.as_ref(), + ) { Ok(()) } else { Err(SignatureError::SignatureVerificationFailed) @@ -1388,40 +1058,14 @@ impl< /// Trait for all three of the ML-DSA algorithm variants. pub trait MLDSATrait< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const ETA: usize, >: Sized { /// Runs a key generation using the library's default RNG, seeded from the OS. @@ -1436,7 +1080,7 @@ pub trait MLDSATrait< // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { // Source the seed from the provided RNG - if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if rng.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; } let mut seed = KeyMaterial::<32>::new(); @@ -1615,79 +1259,15 @@ pub trait MLDSATrait< } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> Signer - for MLDSA< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> Signer for MLDSA { fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; @@ -1769,79 +1349,16 @@ impl< } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait - + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > + MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - ETA, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - >, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > SignatureVerifier - for MLDSA< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > + for MLDSA { fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; diff --git a/crypto/mldsa-lowmemory/src/mldsa_keys.rs b/crypto/mldsa-lowmemory/src/mldsa_keys.rs index 2863068a..76477c63 100644 --- a/crypto/mldsa-lowmemory/src/mldsa_keys.rs +++ b/crypto/mldsa-lowmemory/src/mldsa_keys.rs @@ -1,30 +1,18 @@ use crate::aux_functions::{ - bit_pack_eta, bit_pack_t0, bitlen_eta, power_2_round, rej_bounded_poly, simple_bit_pack_t1, + bit_pack_eta, bit_pack_t0, power_2_round, rej_bounded_poly, simple_bit_pack_t1, simple_bit_unpack_t1, }; use crate::low_memory_helpers::{expandA_elem, s_unpack}; -use crate::mldsa::{H, N, POLY_T0PACKED_LEN}; -use crate::mldsa::{ - MLDSA44_ETA, MLDSA44_FULL_SK_LEN, MLDSA44_GAMMA2, MLDSA44_LAMBDA, MLDSA44_PK_LEN, - MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_SK_LEN, MLDSA44_k, MLDSA44_l, -}; -use crate::mldsa::{ - MLDSA44_T1_PACKED_LEN, MLDSA65_T1_PACKED_LEN, MLDSA87_T1_PACKED_LEN, POLY_T1PACKED_LEN, -}; -use crate::mldsa::{ - MLDSA65_ETA, MLDSA65_FULL_SK_LEN, MLDSA65_GAMMA2, MLDSA65_LAMBDA, MLDSA65_PK_LEN, - MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_SK_LEN, MLDSA65_k, MLDSA65_l, -}; -use crate::mldsa::{ - MLDSA87_ETA, MLDSA87_FULL_SK_LEN, MLDSA87_GAMMA2, MLDSA87_LAMBDA, MLDSA87_PK_LEN, - MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_SK_LEN, MLDSA87_k, MLDSA87_l, -}; -use crate::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME}; +use crate::mldsa::{H, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN}; +use crate::mldsa::{MLDSA44_FULL_SK_LEN, MLDSA44_PK_LEN, MLDSA44_SK_LEN}; +use crate::mldsa::{MLDSA65_FULL_SK_LEN, MLDSA65_PK_LEN, MLDSA65_SK_LEN}; +use crate::mldsa::{MLDSA87_FULL_SK_LEN, MLDSA87_PK_LEN, MLDSA87_SK_LEN}; +use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams}; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{SecurityStrength, SignaturePrivateKey, SignaturePublicKey, XOF}; -use bouncycastle_utils::secret::Secret; +use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; use core::fmt; use core::fmt::{Debug, Display, Formatter}; use core::ops::DerefMut; @@ -36,63 +24,37 @@ use crate::polynomial::Polynomial; /* Pub Types */ /// ML-DSA-44 Public Key -pub type MLDSA44PublicKey = MLDSAPublicKey; +pub type MLDSA44PublicKey = MLDSAPublicKey; /// ML-DSA-44 Private Key -pub type MLDSA44PrivateKey = MLDSASeedPrivateKey< - MLDSA44_LAMBDA, - MLDSA44_GAMMA2, - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, - MLDSA44_S1_PACKED_LEN, - MLDSA44_S2_PACKED_LEN, - MLDSA44_T1_PACKED_LEN, - MLDSA44_PK_LEN, - MLDSA44_SK_LEN, - MLDSA44_FULL_SK_LEN, ->; +pub type MLDSA44PrivateKey = + MLDSASeedPrivateKey; /// ML-DSA-65 Public Key -pub type MLDSA65PublicKey = MLDSAPublicKey; +pub type MLDSA65PublicKey = MLDSAPublicKey; /// ML-DSA-65 Private Key -pub type MLDSA65PrivateKey = MLDSASeedPrivateKey< - MLDSA65_LAMBDA, - MLDSA65_GAMMA2, - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, - MLDSA65_S1_PACKED_LEN, - MLDSA65_S2_PACKED_LEN, - MLDSA65_T1_PACKED_LEN, - MLDSA65_PK_LEN, - MLDSA65_SK_LEN, - MLDSA65_FULL_SK_LEN, ->; +pub type MLDSA65PrivateKey = + MLDSASeedPrivateKey; /// ML-DSA-87 Public Key -pub type MLDSA87PublicKey = MLDSAPublicKey; +pub type MLDSA87PublicKey = MLDSAPublicKey; /// ML-DSA-87 Private Key -pub type MLDSA87PrivateKey = MLDSASeedPrivateKey< - MLDSA87_LAMBDA, - MLDSA87_GAMMA2, - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, - MLDSA87_S1_PACKED_LEN, - MLDSA87_S2_PACKED_LEN, - MLDSA87_T1_PACKED_LEN, - MLDSA87_PK_LEN, - MLDSA87_SK_LEN, - MLDSA87_FULL_SK_LEN, ->; +pub type MLDSA87PrivateKey = + MLDSASeedPrivateKey; /// An ML-DSA public key. -#[derive(Clone)] -pub struct MLDSAPublicKey { +pub struct MLDSAPublicKey { pub(crate) rho: [u8; 32], - pub(crate) t1_packed: [u8; T1_PACKED_LEN], + pub(crate) t1_packed: P::T1Packed, +} + +// Written out rather than derived: `#[derive(Clone)]` would demand `P: Clone`, and `P` is a +// marker for the parameter set that is never stored, only used to name the field types. +impl Clone for MLDSAPublicKey { + fn clone(&self) -> Self { + Self { rho: self.rho, t1_packed: self.t1_packed } + } } /// General trait for all ML-DSA public keys types. -pub trait MLDSAPublicKeyTrait: +pub trait MLDSAPublicKeyTrait: SignaturePublicKey { /// Algorithm 23 pkDecode(๐‘๐‘˜) @@ -110,15 +72,10 @@ pub trait MLDSAPublicKeyTrait [u8; 64]; } -pub(crate) trait MLDSAPublicKeyInternalTrait< - const k: usize, - const T1_PACKED_LEN: usize, - const PK_LEN: usize, -> -{ +pub(crate) trait MLDSAPublicKeyInternalTrait { /// Not exposing a constructor publicly because the user should get an instance either by /// running a keygen, or by decoding an existing key. - fn new(rho: [u8; 32], t1_packed: [u8; T1_PACKED_LEN]) -> Self; + fn new(rho: [u8; 32], t1_packed: P::T1Packed) -> Self; /// Get a ref to rho fn rho(&self) -> &[u8; 32]; @@ -127,11 +84,13 @@ pub(crate) trait MLDSAPublicKeyInternalTrait< fn unpack_t1_row(&self, row: usize) -> Polynomial; } -impl - MLDSAPublicKeyTrait for MLDSAPublicKey +impl MLDSAPublicKeyTrait + for MLDSAPublicKey { fn pk_decode(pk: &[u8; PK_LEN]) -> Self { - Self { rho: pk[..32].try_into().unwrap(), t1_packed: pk[32..].try_into().unwrap() } + let mut t1_packed = ::ZEROED; + t1_packed.as_mut().copy_from_slice(&pk[32..]); + Self { rho: pk[..32].try_into().unwrap(), t1_packed } } fn compute_tr(&self) -> [u8; 64] { @@ -142,11 +101,10 @@ impl } } -impl - MLDSAPublicKeyInternalTrait - for MLDSAPublicKey +impl MLDSAPublicKeyInternalTrait + for MLDSAPublicKey { - fn new(rho: [u8; 32], t1_packed: [u8; T1_PACKED_LEN]) -> Self { + fn new(rho: [u8; 32], t1_packed: P::T1Packed) -> Self { Self { rho, t1_packed } } @@ -156,16 +114,14 @@ impl fn unpack_t1_row(&self, row: usize) -> Polynomial { simple_bit_unpack_t1( - &self.t1_packed[row * POLY_T1PACKED_LEN..(row + 1) * POLY_T1PACKED_LEN] + self.t1_packed.as_ref()[row * POLY_T1PACKED_LEN..(row + 1) * POLY_T1PACKED_LEN] .try_into() - .unwrap(), + .expect("a T1Packed row is exactly POLY_T1PACKED_LEN bytes"), ) } } -impl SignaturePublicKey - for MLDSAPublicKey -{ +impl SignaturePublicKey for MLDSAPublicKey { /// Algorithm 22 pkEncode(๐œŒ, ๐ญ1) /// Encodes a public key for ML-DSA into a byte string. /// Input:๐œŒ โˆˆ ๐”น32, ๐ญ1 โˆˆ ๐‘…๐‘˜ with coefficients in [0, 2bitlen (๐‘žโˆ’1)โˆ’๐‘‘ โˆ’ 1]. @@ -186,7 +142,7 @@ impl SignatureP out.fill(0); out[..32].copy_from_slice(&self.rho); - out[32..].copy_from_slice(&self.t1_packed); + out[32..].copy_from_slice(self.t1_packed.as_ref()); PK_LEN } @@ -202,14 +158,9 @@ impl SignatureP } } -impl Eq - for MLDSAPublicKey -{ -} +impl Eq for MLDSAPublicKey {} -impl PartialEq - for MLDSAPublicKey -{ +impl PartialEq for MLDSAPublicKey { fn eq(&self, other: &Self) -> bool { let self_encoded = self.encode(); let other_encoded = other.encode(); @@ -217,41 +168,31 @@ impl PartialEq } } -impl fmt::Debug - for MLDSAPublicKey -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; - write!(f, "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.compute_tr(),) +impl fmt::Debug for MLDSAPublicKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", + P::ALG_NAME, + self.compute_tr(), + ) } } -impl Display - for MLDSAPublicKey -{ +impl Display for MLDSAPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; - write!(f, "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.compute_tr(),) + write!( + f, + "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", + P::ALG_NAME, + self.compute_tr(), + ) } } /// General trait for all ML-DSA private keys types. pub trait MLDSAPrivateKeyTrait< - const k: usize, - const l: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, + P: MLDSAParams, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, @@ -269,7 +210,7 @@ pub trait MLDSAPrivateKeyTrait< /// or else compute `tr` once and store it. fn tr(&self) -> [u8; 64]; /// Returns the full public key, and has the side-effect of setting the public key hash tr in this MLDSASeedSK object. - fn derive_pk(&self) -> MLDSAPublicKey; + fn derive_pk(&self) -> MLDSAPublicKey; /// This produces the full private key in the encoding specified in FIPS 204 Algorithm 24 skEncode() /// so that it is compatible with other implementations. /// @@ -294,20 +235,13 @@ pub trait MLDSAPrivateKeyTrait< } /// Internal structure for holding a seed-based private key for ML-DSA. -#[derive(Clone, PartialEq, Eq)] pub struct MLDSASeedPrivateKey< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, + P: MLDSAParams, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, > { + _phantom: core::marker::PhantomData

, // note: KeyMaterial is inherently Secret seed: KeyMaterial<32>, // public seed rho does not need to be secret @@ -315,108 +249,61 @@ pub struct MLDSASeedPrivateKey< rho_prime: Secret<[u8; 64]>, K: Secret<[u8; 32]>, } -impl< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const PK_LEN: usize, - const SK_LEN: usize, - const FULL_SK_LEN: usize, -> Debug - for MLDSASeedPrivateKey< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > +// Written out rather than derived: the derives would demand `P: Clone` / `P: Eq`, and `P` is a +// marker for the parameter set that is never stored, only used to name the field types. +impl Clone + for MLDSASeedPrivateKey +{ + fn clone(&self) -> Self { + Self { + _phantom: core::marker::PhantomData, + seed: self.seed.clone(), + rho: self.rho, + rho_prime: self.rho_prime.clone(), + K: self.K.clone(), + } + } +} + +impl PartialEq + for MLDSASeedPrivateKey +{ + fn eq(&self, other: &Self) -> bool { + // Compared through `KeyMaterial`/`Secret`'s own `PartialEq`, which is constant-time: do + // not deref to the inner arrays, as that would select the array's variable-time `==`. + let seed = self.seed == other.seed; + let rho = self.rho == other.rho; + let rho_prime = self.rho_prime == other.rho_prime; + let K = self.K == other.K; + seed & rho & rho_prime & K + } +} + +impl Eq + for MLDSASeedPrivateKey +{ +} + +impl Debug + for MLDSASeedPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; + let alg = P::ALG_NAME; write!(f, "MLDSASeedPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.tr(),) } } -impl< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const PK_LEN: usize, - const SK_LEN: usize, - const FULL_SK_LEN: usize, -> Display - for MLDSASeedPrivateKey< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > +impl Display + for MLDSASeedPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; + let alg = P::ALG_NAME; write!(f, "MLDSASeedPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.tr(),) } } -impl< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const PK_LEN: usize, - const SK_LEN: usize, - const FULL_SK_LEN: usize, -> - MLDSASeedPrivateKey< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > +impl + MLDSASeedPrivateKey { /// Create a new MLDSASeedPrivateKey from a 32-byte KeyMaterial. /// Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87), @@ -430,13 +317,13 @@ impl< )); } - if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if seed.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(SignatureError::KeyGenError("SecurityStrength")); } let (rho, rho_prime, K) = Self::compute_rhos_and_K(&seed); - Ok(Self { seed: seed.clone(), rho, rho_prime, K }) + Ok(Self { _phantom: core::marker::PhantomData, seed: seed.clone(), rho, rho_prime, K }) } fn compute_rhos_and_K( @@ -451,8 +338,8 @@ impl< let mut h = H::default(); h.absorb(seed.ref_to_bytes()).expect("absorb before squeeze is infallible"); - h.absorb(&(k as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); - h.absorb(&(l as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); + h.absorb(&(P::k as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); + h.absorb(&(P::l as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); let bytes_written = h.squeeze_out(&mut rho); debug_assert_eq!(bytes_written, 32); let bytes_written = h.squeeze_out(rho_prime.deref_mut()); @@ -466,26 +353,26 @@ impl< fn compute_t_row( &self, idx: usize, - s1_packed: &Secret<[u8; S1_PACKED_LEN]>, - s2_packed: &Secret<[u8; S2_PACKED_LEN]>, + s1_packed: &Secret, + s2_packed: &Secret, ) -> Polynomial { - debug_assert!(idx < k); + debug_assert!(idx < P::k); // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of expanded from the compressed form. // let mut s1 = self.compute_s1_row(0); - let mut s1_hat_i = s_unpack::(s1_packed, 0); + let mut s1_hat_i = s_unpack::(s1_packed, 0); s1_hat_i.ntt(); let mut t_i = { let mut t_hat_i = expandA_elem(&self.rho, idx, 0); t_hat_i.multiply_ntt(&s1_hat_i); - for col in 1..l { + for col in 1..P::l { // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of expanded from the compressed form. // s1 = self.compute_s1_row(col); - let mut s1_hat = s_unpack::(s1_packed, col); + let mut s1_hat = s_unpack::(s1_packed, col); s1_hat.ntt(); let mut A_elem = expandA_elem(&self.rho, idx, col); A_elem.multiply_ntt(&s1_hat); @@ -499,7 +386,7 @@ impl< // [Optimization Note]: // This is one of the places that a row of s2 can be re-computed instead of unpacked from the compressed form. // let s2 = self.compute_s2_row(idx); - let s2 = s_unpack::(s2_packed, idx); + let s2 = s_unpack::(s2_packed, idx); t_i.add_ntt(&s2); t_i.conditional_add_q(); @@ -507,37 +394,11 @@ impl< } } -impl< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const PK_LEN: usize, - const SK_LEN: usize, - const FULL_SK_LEN: usize, -> SignaturePrivateKey - for MLDSASeedPrivateKey< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > +impl + SignaturePrivateKey for MLDSASeedPrivateKey { /// Encodes the private key seed. fn encode(&self) -> [u8; SK_LEN] { - debug_assert_eq!(SK_LEN, /* seed */ 32); - self.seed.ref_to_bytes().try_into().unwrap() } @@ -564,42 +425,9 @@ impl< } } -impl< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const PK_LEN: usize, - const SK_LEN: usize, - const FULL_SK_LEN: usize, -> - MLDSAPrivateKeyTrait< - k, - l, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > - for MLDSASeedPrivateKey< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > +impl + MLDSAPrivateKeyTrait + for MLDSASeedPrivateKey { fn from_keymaterial(seed: &KeyMaterial<32>) -> Result { Self::new(seed) @@ -610,26 +438,26 @@ impl< } fn tr(&self) -> [u8; 64] { - let pk: MLDSAPublicKey = self.derive_pk(); + let pk: MLDSAPublicKey = self.derive_pk(); pk.compute_tr() } - fn derive_pk(&self) -> MLDSAPublicKey { + fn derive_pk(&self) -> MLDSAPublicKey { // The goal here is to get t1, which is built and compressed one row at a time. - let s1_packed: Secret<[u8; S1_PACKED_LEN]> = self.compute_s1_packed(); - let s2_packed: Secret<[u8; S2_PACKED_LEN]> = self.compute_s2_packed(); + let s1_packed: Secret = self.compute_s1_packed(); + let s2_packed: Secret = self.compute_s2_packed(); - let mut t1_packed = [0u8; T1_PACKED_LEN]; - debug_assert_eq!(T1_PACKED_LEN, POLY_T1PACKED_LEN * k); + let mut t1_packed = ::ZEROED; + debug_assert_eq!(P::T1_PACKED_LEN, POLY_T1PACKED_LEN * P::k); - for i in 0..k { - t1_packed[i * POLY_T1PACKED_LEN..(i + 1) * POLY_T1PACKED_LEN].copy_from_slice( + for i in 0..P::k { + t1_packed.as_mut()[i * POLY_T1PACKED_LEN..(i + 1) * POLY_T1PACKED_LEN].copy_from_slice( &simple_bit_pack_t1(&self.compute_t1_row(i, &s1_packed, &s2_packed)), ); } - MLDSAPublicKey::::new(self.rho.clone(), t1_packed) + MLDSAPublicKey::::new(self.rho.clone(), t1_packed) } fn encode_full_sk(&self) -> [u8; FULL_SK_LEN] { let mut out = [0; FULL_SK_LEN]; @@ -655,21 +483,21 @@ impl< // 3: ๐‘ ๐‘˜ โ† ๐‘ ๐‘˜ || BitPack (๐ฌ1[๐‘–], ๐œ‚, ๐œ‚) // 4: end for let s1_packed = self.compute_s1_packed(); - out[off..off + S1_PACKED_LEN].copy_from_slice(&*s1_packed); - off += S1_PACKED_LEN; + out[off..off + P::S1_PACKED_LEN].copy_from_slice((*s1_packed).as_ref()); + off += P::S1_PACKED_LEN; // 5: for ๐‘– from 0 to ๐‘˜ โˆ’ 1 do // 6: ๐‘ ๐‘˜ โ† ๐‘ ๐‘˜ || BitPack (๐ฌ2[๐‘–], ๐œ‚, ๐œ‚) // 7: end for let s2_packed = self.compute_s2_packed(); - out[off..off + S2_PACKED_LEN].copy_from_slice(&*s2_packed); - off += S2_PACKED_LEN; + out[off..off + P::S2_PACKED_LEN].copy_from_slice((*s2_packed).as_ref()); + off += P::S2_PACKED_LEN; // 8: for ๐‘– from 0 to ๐‘˜ โˆ’ 1 do // 9: ๐‘ ๐‘˜ โ† ๐‘ ๐‘˜ || BitPack (๐ญ0[๐‘–], 2๐‘‘โˆ’1 โˆ’ 1, 2๐‘‘โˆ’1) // 10: end for - debug_assert_eq!(off + k * POLY_T0PACKED_LEN, FULL_SK_LEN); - for row in 0..k { + debug_assert_eq!(off + P::k * POLY_T0PACKED_LEN, FULL_SK_LEN); + for row in 0..P::k { let t0_i = self.compute_t0_row(row, &s1_packed, &s2_packed); out[off..off + POLY_T0PACKED_LEN].copy_from_slice(&bit_pack_t0(&t0_i)); off += POLY_T0PACKED_LEN; @@ -685,13 +513,7 @@ impl< } pub(crate) trait MLDSAPrivateKeyInternalTrait< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, + P: MLDSAParams, const PK_LEN: usize, const SK_LEN: usize, >: Sized @@ -706,7 +528,7 @@ pub(crate) trait MLDSAPrivateKeyInternalTrait< /// Private key component. /// The packed representation sticks around for the whole computation, so /// we'll wrap in as a Secret. - fn compute_s1_packed(&self) -> Secret<[u8; S1_PACKED_LEN]>; + fn compute_s1_packed(&self) -> Secret; /// A single entry of a privacy key vector. /// These tend to be used very transiently, so we won't bother wrapping it as a Secret. @@ -715,62 +537,28 @@ pub(crate) trait MLDSAPrivateKeyInternalTrait< /// Private key component. /// The packed representation sticks around for the whole computation, so /// we'll wrap in as a Secret. - fn compute_s2_packed(&self) -> Secret<[u8; S2_PACKED_LEN]>; + fn compute_s2_packed(&self) -> Secret; /// Public key component. fn compute_t0_row( &self, idx: usize, - s1_packed: &Secret<[u8; S1_PACKED_LEN]>, - s2_packed: &Secret<[u8; S2_PACKED_LEN]>, + s1_packed: &Secret, + s2_packed: &Secret, ) -> Polynomial; /// Public key component. fn compute_t1_row( &self, idx: usize, - s1_packed: &Secret<[u8; S1_PACKED_LEN]>, - s2_packed: &Secret<[u8; S2_PACKED_LEN]>, + s1_packed: &Secret, + s2_packed: &Secret, ) -> Polynomial; } -impl< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const PK_LEN: usize, - const SK_LEN: usize, - const FULL_SK_LEN: usize, -> - MLDSAPrivateKeyInternalTrait< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - PK_LEN, - SK_LEN, - > - for MLDSASeedPrivateKey< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > +impl + MLDSAPrivateKeyInternalTrait + for MLDSASeedPrivateKey { fn rho(&self) -> &[u8; 32] { &self.rho @@ -781,35 +569,31 @@ impl< } fn compute_s1_row(&self, idx: usize) -> Polynomial { - debug_assert!(idx < l); - rej_bounded_poly::(&self.rho_prime, &(idx as u16).to_le_bytes()) + debug_assert!(idx < P::l); + rej_bounded_poly::

(&self.rho_prime, &(idx as u16).to_le_bytes()) } - fn compute_s1_packed(&self) -> Secret<[u8; S1_PACKED_LEN]> { - let mut s1_packed: Secret<[u8; S1_PACKED_LEN]> = Secret::new(); - for idx in 0..l { + fn compute_s1_packed(&self) -> Secret { + let mut s1_packed: Secret = Secret::new(); + let width = P::POLY_ETA_PACKED_LEN; + for idx in 0..P::l { let s1_i = self.compute_s1_row(idx); - bit_pack_eta::( - &s1_i, - &mut s1_packed[idx * bitlen_eta(eta)..(idx + 1) * bitlen_eta(eta)], - ); + bit_pack_eta::

(&s1_i, &mut s1_packed.as_mut()[idx * width..(idx + 1) * width]); } s1_packed } fn compute_s2_row(&self, idx: usize) -> Polynomial { - debug_assert!(idx < k); - rej_bounded_poly::(&self.rho_prime, &((idx + l) as u16).to_le_bytes()) + debug_assert!(idx < P::k); + rej_bounded_poly::

(&self.rho_prime, &((idx + P::l) as u16).to_le_bytes()) } - fn compute_s2_packed(&self) -> Secret<[u8; S2_PACKED_LEN]> { - let mut s2_packed: Secret<[u8; S2_PACKED_LEN]> = Secret::new(); - for idx in 0..k { + fn compute_s2_packed(&self) -> Secret { + let mut s2_packed: Secret = Secret::new(); + let width = P::POLY_ETA_PACKED_LEN; + for idx in 0..P::k { let s2_i = self.compute_s2_row(idx); - bit_pack_eta::( - &s2_i, - &mut s2_packed[idx * bitlen_eta(eta)..(idx + 1) * bitlen_eta(eta)], - ); + bit_pack_eta::

(&s2_i, &mut s2_packed.as_mut()[idx * width..(idx + 1) * width]); } s2_packed } @@ -817,8 +601,8 @@ impl< fn compute_t0_row( &self, idx: usize, - s1_packed: &Secret<[u8; S1_PACKED_LEN]>, - s2_packed: &Secret<[u8; S2_PACKED_LEN]>, + s1_packed: &Secret, + s2_packed: &Secret, ) -> Polynomial { let mut t0 = self.compute_t_row(idx, s1_packed, s2_packed); for j in 0..N { @@ -831,8 +615,8 @@ impl< fn compute_t1_row( &self, idx: usize, - s1_packed: &Secret<[u8; S1_PACKED_LEN]>, - s2_packed: &Secret<[u8; S2_PACKED_LEN]>, + s1_packed: &Secret, + s2_packed: &Secret, ) -> Polynomial { let mut t1 = self.compute_t_row(idx, s1_packed, s2_packed); for j in 0..N { diff --git a/crypto/mldsa-lowmemory/src/params.rs b/crypto/mldsa-lowmemory/src/params.rs new file mode 100644 index 00000000..a07f1200 --- /dev/null +++ b/crypto/mldsa-lowmemory/src/params.rs @@ -0,0 +1,520 @@ +//! The three ML-DSA parameter sets of FIPS 204, Section 4, as a sealed trait with one type per set. +//! +//! This mirrors `bouncycastle_mldsa::params`, minus the vector and matrix types: this crate never +//! materializes ๐€ฬ‚ or a whole polynomial vector, so the only parameter-sized types it needs are +//! byte buffers. +//! +//! # Derived parameters +//! +//! FIPS 204, Table 1 assigns eight independent values per set (๐œ, ๐œ†, ๐›พ1, ๐›พ2, (๐‘˜, โ„“), ๐œ‚, ๐œ”) plus the +//! three sizes of Table 2. Everything else this implementation needs is a function of those, so it +//! is written once as a defaulted associated const rather than three times as a hand-computed +//! number. `params::tests` checks every derivation against the values tabulated in FIPS 204. + +use crate::hash_mldsa::{ + HASH_ML_DSA_44_with_SHA256_NAME, HASH_ML_DSA_44_with_SHA512_NAME, + HASH_ML_DSA_65_WITH_SHA256_NAME, HASH_ML_DSA_65_WITH_SHA512_NAME, + HASH_ML_DSA_87_WITH_SHA512_NAME, HASH_ML_DSA_87_with_SHA256_NAME, +}; +use crate::mldsa::{ + ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME, MLDSA_SEED_LEN, POLY_T1PACKED_LEN, q, +}; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, Hash, HashAlgParams, SecurityStrength}; +use bouncycastle_sha2::{SHA256, SHA512}; +use bouncycastle_utils::secret::ZeroizablePrimitive; + +/// `bitlen ๐‘ฅ`, the length of the binary expansion of ๐‘ฅ (FIPS 204, Section 2.3). +/// +/// `bitlen 0` is 0; every use below has a positive argument. +pub(crate) const fn bitlen(x: u32) -> usize { + if x == 0 { 0 } else { x.ilog2() as usize + 1 } +} + +/// A fixed-size byte buffer whose length depends on the parameter set. +/// +/// [`ZeroizablePrimitive`] rather than [`Default`] supplies the all-zero value, because `Default` +/// for arrays stops at 32 elements and every buffer here is longer than that. +trait ByteBuffer: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]> {} +impl ByteBuffer for [u8; N] {} + +/// A crate-private (aka "sealed") trait that prevents a new ML-DSA parameter set from being defined +/// outside this crate. +trait MLDSAParamsInternalTrait {} + +/// One ML-DSA parameter set: the values of FIPS 204, Table 1 and Table 2, and the types whose size +/// they determine. +/// +/// Sealed via a private supertrait, so [`MLDSA44Params`], [`MLDSA65Params`] and [`MLDSA87Params`] +/// are the only implementations. +pub trait MLDSAParams: MLDSAParamsInternalTrait { + /* FIPS 204, Table 1: the values assigned by each parameter set. */ + + /// ๐œ, the number of ยฑ1's in the polynomial ๐‘. + const tau: i32; + /// ๐œ†, the collision strength of ๐‘ฬƒ, in bits. + const lambda: i32; + /// ๐›พ1, the coefficient range of ๐ฒ. Always a power of two. + const gamma1: i32; + /// ๐›พ2, the low-order rounding range. + const gamma2: i32; + /// ๐‘˜, the number of rows of ๐€. + const k: usize; + /// โ„“, the number of columns of ๐€. + const l: usize; + /// ๐œ‚, the private key range. + const eta: usize; + /// ๐œ”, the maximum number of 1's in the hint ๐ก. + const omega: i32; + + /* FIPS 204, Table 2: sizes in bytes of keys and signatures. */ + + /// The length of an encoded public key. + const PK_LEN: usize; + /// The length of the FIPS 204 encoding of a private key. + /// + /// Named `FULL_SK_LEN` rather than `SK_LEN` because this crate's private keys are held as the + /// 32-byte seed ๐œ‰ and expanded on demand; see [`MLDSAParams::SK_LEN`]. + const FULL_SK_LEN: usize; + /// The length of a signature. + const SIG_LEN: usize; + + /* Algorithm meta-data */ + + /// The algorithm name, as reported by `Algorithm::ALG_NAME`. + const ALG_NAME: &'static str; + /// The strength claimed for this parameter set, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. + const MAX_SECURITY_STRENGTH: SecurityStrength; + /// The OID in component form, as reported by `AlgorithmOID::OID`. + const OID: &'static [u32]; + /// The DER encoding of [`MLDSAParams::OID`], as reported by `AlgorithmOID::OID_DER`. + const OID_DER: &'static [u8]; + + /* Derived. Never written out per parameter set -- see the module docs. */ + + /// The length of a private key as this crate stores it: the 32-byte seed ๐œ‰, for every + /// parameter set. FIPS 204, Section 4 notes that ๐œ‰ "is sufficient to generate the other parts + /// of the private key". + const SK_LEN: usize = MLDSA_SEED_LEN; + + /// ๐›ฝ, which FIPS 204, Table 1 defines as "๐›ฝ = ๐œ โ‹… ๐œ‚". + const beta: i32 = Self::tau * Self::eta as i32; + + /// The length of the commitment hash ๐‘ฬƒ, which FIPS 204, Algorithm 26 (sigEncode) gives as + /// ๐‘ฬƒ โˆˆ ๐”น^(๐œ†/4). + const C_TILDE_LEN: usize = Self::lambda as usize / 4; + + /// The packed length of one coordinate of ๐ณ: FIPS 204, Algorithm 26 (sigEncode) writes each of + /// the โ„“ coordinates as ๐”น^(32โ‹…(1+bitlen (๐›พ1โˆ’1))). + /// + /// This is also the number of bytes ExpandMask squeezes per coordinate: FIPS 204, + /// Algorithm 34, line 1 sets ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) and line 4 squeezes 32๐‘ bytes. + const POLY_Z_PACKED_LEN: usize = 32 * (1 + bitlen(Self::gamma1 as u32 - 1)); + + /// The packed length of one coordinate of ๐ฐ1: FIPS 204, Algorithm 28 (w1Encode) outputs + /// ๐”น^(32๐‘˜โ‹…bitlen ((๐‘žโˆ’1)/(2๐›พ2)โˆ’1)) for all ๐‘˜ coordinates together. + const POLY_W1_PACKED_LEN: usize = 32 * bitlen(((q - 1) / (2 * Self::gamma2)) as u32 - 1); + + /// The packed length of one coordinate of ๐ฌ1 or ๐ฌ2: FIPS 204, Algorithm 24 (skEncode), line 3 + /// packs each with BitPack(๐ฌ1[๐‘–], ๐œ‚, ๐œ‚), and Algorithm 17 (BitPack) outputs + /// ๐”น^(32โ‹…bitlen (๐‘Ž+๐‘)), so 32โ‹…bitlen (2๐œ‚). + const POLY_ETA_PACKED_LEN: usize = 32 * bitlen(2 * Self::eta as u32); + + /// The packed length of the whole of ๐ฌ1, i.e. all โ„“ coordinates. + const S1_PACKED_LEN: usize = Self::POLY_ETA_PACKED_LEN * Self::l; + + /// The packed length of the whole of ๐ฌ2, i.e. all ๐‘˜ coordinates. + const S2_PACKED_LEN: usize = Self::POLY_ETA_PACKED_LEN * Self::k; + + /// The packed length of the whole of ๐ญ1, i.e. ๐‘˜ coordinates of SimpleBitPack output. + const T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * Self::k; + + /// ๐›พ1 โˆ’ ๐›ฝ, the rejection bound on โ€–๐ณโ€–โˆž (FIPS 204, Algorithm 7, line 23). + const gamma1_minus_beta: i32 = Self::gamma1 - Self::beta; + + /// ๐›พ2 โˆ’ ๐›ฝ, the rejection bound on โ€–๐ซ0โ€–โˆž (FIPS 204, Algorithm 7, line 23). + const gamma2_minus_beta: i32 = Self::gamma2 - Self::beta; + + /* Types whose size depends on the parameter set. */ + + /// The commitment hash ๐‘ฬƒ, of [`MLDSAParams::C_TILDE_LEN`] bytes. + type SigCTilde: ByteBuffer; + /// One packed coordinate of ๐ณ, of [`MLDSAParams::POLY_Z_PACKED_LEN`] bytes. + /// + /// ExpandMask squeezes into a buffer of this same length; see + /// [`MLDSAParams::POLY_Z_PACKED_LEN`]. + type PolyZPacked: ByteBuffer; + /// One packed coordinate of ๐ฐ1, of [`MLDSAParams::POLY_W1_PACKED_LEN`] bytes. + type PolyW1Packed: ByteBuffer; + /// The whole of ๐ฌ1 packed, of [`MLDSAParams::S1_PACKED_LEN`] bytes. + type S1Packed: ByteBuffer; + /// The whole of ๐ฌ2 packed, of [`MLDSAParams::S2_PACKED_LEN`] bytes. + type S2Packed: ByteBuffer; + /// The whole of ๐ญ1 packed, of [`MLDSAParams::T1_PACKED_LEN`] bytes. + type T1Packed: ByteBuffer; +} + +/// The ML-DSA-44 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA44Params; +/// The ML-DSA-65 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA65Params; +/// The ML-DSA-87 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA87Params; + +impl MLDSAParamsInternalTrait for MLDSA44Params {} +impl MLDSAParamsInternalTrait for MLDSA65Params {} +impl MLDSAParamsInternalTrait for MLDSA87Params {} + +impl MLDSAParams for MLDSA44Params { + const tau: i32 = 39; + const lambda: i32 = 128; + const gamma1: i32 = 1 << 17; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 88; + const k: usize = 4; + const l: usize = 4; + const eta: usize = 2; + const omega: i32 = 80; + + const PK_LEN: usize = 1312; + const FULL_SK_LEN: usize = 2560; + const SIG_LEN: usize = 2420; + + const ALG_NAME: &'static str = ML_DSA_44_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; + + type SigCTilde = [u8; 32]; // ๐œ†/4 = 128/4 + type PolyZPacked = [u8; 576]; // 32 * (1 + bitlen(2^17 - 1)) = 32 * 18 + type PolyW1Packed = [u8; 192]; // 32 * bitlen(44 - 1) = 32 * 6 + type S1Packed = [u8; 384]; // 96 * 4 + type S2Packed = [u8; 384]; // 96 * 4 + type T1Packed = [u8; 1280]; // 320 * 4 +} + +impl MLDSAParams for MLDSA65Params { + const tau: i32 = 49; + const lambda: i32 = 192; + const gamma1: i32 = 1 << 19; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 32; + const k: usize = 6; + const l: usize = 5; + const eta: usize = 4; + const omega: i32 = 55; + + const PK_LEN: usize = 1952; + const FULL_SK_LEN: usize = 4032; + const SIG_LEN: usize = 3309; + + const ALG_NAME: &'static str = ML_DSA_65_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; + + type SigCTilde = [u8; 48]; // ๐œ†/4 = 192/4 + type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 + type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 + type S1Packed = [u8; 640]; // 128 * 5 + type S2Packed = [u8; 768]; // 128 * 6 + type T1Packed = [u8; 1920]; // 320 * 6 +} + +impl MLDSAParams for MLDSA87Params { + const tau: i32 = 60; + const lambda: i32 = 256; + const gamma1: i32 = 1 << 19; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 32; + const k: usize = 8; + const l: usize = 7; + const eta: usize = 2; + const omega: i32 = 75; + + const PK_LEN: usize = 2592; + const FULL_SK_LEN: usize = 4896; + const SIG_LEN: usize = 4627; + + const ALG_NAME: &'static str = ML_DSA_87_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; + + type SigCTilde = [u8; 64]; // ๐œ†/4 = 256/4 + type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 + type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 + type S1Packed = [u8; 672]; // 96 * 7 + type S2Packed = [u8; 768]; // 96 * 8 + type T1Packed = [u8; 2560]; // 320 * 8 +} + +/// The two distinct values ๐›พ1 takes across the three parameter sets (FIPS 204, Table 1). +/// +/// The bit-packing routines have one layout per distinct ๐›พ1, so they dispatch on these rather than +/// on the parameter set. ML-DSA-65 and ML-DSA-87 share the second value. +pub(crate) const GAMMA1_2_POW_17: i32 = MLDSA44Params::gamma1; +/// See [`GAMMA1_2_POW_17`]. +pub(crate) const GAMMA1_2_POW_19: i32 = MLDSA65Params::gamma1; + +/// The two distinct values ๐›พ2 takes across the three parameter sets (FIPS 204, Table 1). +/// +/// As with ๐›พ1, the routines that depend on ๐›พ2 have one form per distinct value rather than one per +/// parameter set. ML-DSA-65 and ML-DSA-87 share the second value. +pub(crate) const GAMMA2_Q_MINUS_1_OVER_88: i32 = MLDSA44Params::gamma2; +/// See [`GAMMA2_Q_MINUS_1_OVER_88`]. +pub(crate) const GAMMA2_Q_MINUS_1_OVER_32: i32 = MLDSA65Params::gamma2; + +/// The weaker of two security strengths. +/// +/// [`SecurityStrength`]'s discriminants are assigned in increasing order of strength, so comparing +/// them as integers orders them. A `const fn` because the strength of a HashML-DSA pairing is a +/// defaulted associated const. +const fn weaker_of(a: SecurityStrength, b: SecurityStrength) -> SecurityStrength { + if (a as u8) <= (b as u8) { a } else { b } +} + +/// A crate-private (aka "sealed") trait that prevents a new HashML-DSA pairing from being defined +/// outside this crate. +trait HashMLDSAParamsInternalTrait {} + +/// One HashML-DSA algorithm: an ML-DSA parameter set paired with a pre-hash function. +/// +/// FIPS 204, Algorithm 4 (HashML-DSA.Sign) leaves the choice of PH open, so an instantiation is a +/// pairing rather than a single parameter set. Everything that varies across the pairings lives +/// here, so [`crate::hash_mldsa::HashMLDSA`] takes one type rather than a parameter set plus a +/// hash function plus a digest length. +/// +/// Sealed via a private supertrait, so the six types below are the only implementations. +pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { + /// The ML-DSA parameter set underneath. + type MLDSA: MLDSAParams; + /// PH, the pre-hash function. + type PreHash: Hash + HashAlgParams + AlgorithmOID + Default; + + /// The algorithm name, as reported by `Algorithm::ALG_NAME`. + /// + /// Written out per pairing rather than derived: it is the two component names spliced + /// together, and `&'static str` cannot be concatenated in a const context. + const ALG_NAME: &'static str; + + /* Derived. Never written out per pairing. */ + + /// The length of the pre-hash `ph`, which is just PH's output length. + const PH_LEN: usize = ::OUTPUT_LEN; + + /// The strength claimed for the pairing, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. + /// + /// A HashML-DSA signature is no stronger than either of its two components, so this is the + /// weaker of the two. That is what caps, for example, HashML-DSA-87_with_SHA256 at 128 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = weaker_of( + ::MAX_SECURITY_STRENGTH, + ::MAX_SECURITY_STRENGTH, + ); +} + +/// The HashML-DSA-44_with_SHA256 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA44_with_SHA256Params; +/// The HashML-DSA-65_with_SHA256 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA65_with_SHA256Params; +/// The HashML-DSA-87_with_SHA256 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA87_with_SHA256Params; +/// The HashML-DSA-44_with_SHA512 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA44_with_SHA512Params; +/// The HashML-DSA-65_with_SHA512 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA65_with_SHA512Params; +/// The HashML-DSA-87_with_SHA512 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA87_with_SHA512Params; + +impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA256Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA256Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA256Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA512Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA512Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA512Params {} + +impl HashMLDSAParams for HashMLDSA44_with_SHA256Params { + type MLDSA = MLDSA44Params; + type PreHash = SHA256; + const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA256_NAME; +} +impl HashMLDSAParams for HashMLDSA65_with_SHA256Params { + type MLDSA = MLDSA65Params; + type PreHash = SHA256; + const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA256_NAME; +} +impl HashMLDSAParams for HashMLDSA87_with_SHA256Params { + type MLDSA = MLDSA87Params; + type PreHash = SHA256; + const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA256_NAME; +} +impl HashMLDSAParams for HashMLDSA44_with_SHA512Params { + type MLDSA = MLDSA44Params; + type PreHash = SHA512; + const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA512_NAME; +} +impl HashMLDSAParams for HashMLDSA65_with_SHA512Params { + type MLDSA = MLDSA65Params; + type PreHash = SHA512; + const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; +} +impl HashMLDSAParams for HashMLDSA87_with_SHA512Params { + type MLDSA = MLDSA87Params; + type PreHash = SHA512; + const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mldsa::d; + + /// FIPS 204, Table 1, transcribed column by column: the eight values each parameter set + /// assigns. `(tau, lambda, gamma1, gamma2, k, l, eta, omega)`. + const TABLE_1: [(i32, i32, i32, i32, usize, usize, usize, i32); 3] = [ + (39, 128, 131072, (q - 1) / 88, 4, 4, 2, 80), + (49, 192, 524288, (q - 1) / 32, 6, 5, 4, 55), + (60, 256, 524288, (q - 1) / 32, 8, 7, 2, 75), + ]; + + /// FIPS 204, Table 2, transcribed row by row: `(private key, public key, signature)` in bytes. + /// The private key column is the full FIPS encoding, which this crate calls `FULL_SK_LEN`. + const TABLE_2: [(usize, usize, usize); 3] = + [(2560, 1312, 2420), (4032, 1952, 3309), (4896, 2592, 4627)]; + + /// FIPS 204, Table 1 also tabulates ๐›ฝ, which it labels "๐›ฝ = ๐œ โ‹… ๐œ‚". + const TABLE_1_BETA: [i32; 3] = [78, 196, 120]; + + fn check_table_1(i: usize) { + let (tau, lambda, gamma1, gamma2, k, l, eta, omega) = TABLE_1[i]; + assert_eq!(P::tau, tau, "{}: ๐œ", P::ALG_NAME); + assert_eq!(P::lambda, lambda, "{}: ๐œ†", P::ALG_NAME); + assert_eq!(P::gamma1, gamma1, "{}: ๐›พ1", P::ALG_NAME); + assert_eq!(P::gamma2, gamma2, "{}: ๐›พ2", P::ALG_NAME); + assert_eq!(P::k, k, "{}: ๐‘˜", P::ALG_NAME); + assert_eq!(P::l, l, "{}: โ„“", P::ALG_NAME); + assert_eq!(P::eta, eta, "{}: ๐œ‚", P::ALG_NAME); + assert_eq!(P::omega, omega, "{}: ๐œ”", P::ALG_NAME); + assert_eq!(P::beta, TABLE_1_BETA[i], "{}: ๐›ฝ = ๐œ โ‹… ๐œ‚", P::ALG_NAME); + } + + fn check_table_2(i: usize) { + let (full_sk_len, pk_len, sig_len) = TABLE_2[i]; + assert_eq!(P::FULL_SK_LEN, full_sk_len, "{}: private key size", P::ALG_NAME); + assert_eq!(P::PK_LEN, pk_len, "{}: public key size", P::ALG_NAME); + assert_eq!(P::SIG_LEN, sig_len, "{}: signature size", P::ALG_NAME); + // This crate stores the seed, not the expanded key, for every parameter set. + assert_eq!(P::SK_LEN, 32, "{}: stored private key size", P::ALG_NAME); + } + + /// Each of the three sizes of Table 2 also has a formula in FIPS 204, and the two must agree. + /// Table 2 is what is written down above; this is what re-derives it. + fn check_table_2_formulas() { + // Algorithm 22 (pkEncode): ๐‘๐‘˜ โˆˆ ๐”น^(32+32๐‘˜(bitlen (๐‘žโˆ’1)โˆ’๐‘‘)). + let pk_len = 32 + 32 * P::k * (bitlen((q - 1) as u32) - d as usize); + assert_eq!(P::PK_LEN, pk_len, "{}: Algorithm 22 output size", P::ALG_NAME); + + // Algorithm 24 (skEncode): ๐‘ ๐‘˜ โˆˆ ๐”น^(32+32+64+32โ‹…((๐‘˜+โ„“)โ‹…bitlen (2๐œ‚)+๐‘‘๐‘˜)). + let full_sk_len = + 32 + 32 + 64 + 32 * ((P::k + P::l) * bitlen(2 * P::eta as u32) + d as usize * P::k); + assert_eq!(P::FULL_SK_LEN, full_sk_len, "{}: Algorithm 24 output size", P::ALG_NAME); + + // Algorithm 26 (sigEncode): ๐œŽ โˆˆ ๐”น^(๐œ†/4+โ„“โ‹…32โ‹…(1+bitlen (๐›พ1โˆ’1))+๐œ”+๐‘˜). + let sig_len = P::lambda as usize / 4 + + P::l * 32 * (1 + bitlen(P::gamma1 as u32 - 1)) + + P::omega as usize + + P::k; + assert_eq!(P::SIG_LEN, sig_len, "{}: Algorithm 26 output size", P::ALG_NAME); + } + + /// The associated types must be exactly as long as the consts that describe them; they are + /// written out by hand per parameter set, so this guards against a typo in one of them. + fn check_associated_type_sizes() { + for (got, want, what) in [ + (size_of::(), P::C_TILDE_LEN, "SigCTilde"), + (size_of::(), P::POLY_Z_PACKED_LEN, "PolyZPacked"), + (size_of::(), P::POLY_W1_PACKED_LEN, "PolyW1Packed"), + (size_of::(), P::S1_PACKED_LEN, "S1Packed"), + (size_of::(), P::S2_PACKED_LEN, "S2Packed"), + (size_of::(), P::T1_PACKED_LEN, "T1Packed"), + ] { + assert_eq!(got, want, "{}: {} vs its length const", P::ALG_NAME, what); + } + } + + #[test] + fn test_parameter_sets_match_fips204_table_1() { + check_table_1::(0); + check_table_1::(1); + check_table_1::(2); + } + + #[test] + fn test_sizes_match_fips204_table_2() { + check_table_2::(0); + check_table_2::(1); + check_table_2::(2); + } + + #[test] + fn test_table_2_sizes_agree_with_the_encoding_formulas() { + check_table_2_formulas::(); + check_table_2_formulas::(); + check_table_2_formulas::(); + } + + #[test] + fn test_associated_types_are_the_length_their_consts_claim() { + check_associated_type_sizes::(); + check_associated_type_sizes::(); + check_associated_type_sizes::(); + } + + #[test] + fn test_bitlen_matches_its_definition() { + // FIPS 204 Section 2.3 defines bitlen ๐‘ฅ as the length of the binary expansion of ๐‘ฅ. + assert_eq!(bitlen(0), 0); + assert_eq!(bitlen(1), 1); + assert_eq!(bitlen(2), 2); + assert_eq!(bitlen(3), 2); + assert_eq!(bitlen(4), 3); + // The two arguments the derivations above actually use, plus bitlen(๐‘ž โˆ’ 1) = 23. + assert_eq!(bitlen((1 << 17) - 1), 17); + assert_eq!(bitlen((1 << 19) - 1), 19); + assert_eq!(bitlen((q - 1) as u32), 23); + } + + #[test] + fn test_gamma_dispatch_constants_cover_every_parameter_set() { + // The packing routines dispatch on these; a parameter set whose ๐›พ is neither value would + // fall through to a panic at runtime rather than fail to compile, so pin them here. + for gamma1 in [MLDSA44Params::gamma1, MLDSA65Params::gamma1, MLDSA87Params::gamma1] { + assert!(gamma1 == GAMMA1_2_POW_17 || gamma1 == GAMMA1_2_POW_19); + } + for gamma2 in [MLDSA44Params::gamma2, MLDSA65Params::gamma2, MLDSA87Params::gamma2] { + assert!(gamma2 == GAMMA2_Q_MINUS_1_OVER_88 || gamma2 == GAMMA2_Q_MINUS_1_OVER_32); + } + assert_ne!(GAMMA1_2_POW_17, GAMMA1_2_POW_19); + assert_ne!(GAMMA2_Q_MINUS_1_OVER_88, GAMMA2_Q_MINUS_1_OVER_32); + } +} diff --git a/crypto/mldsa-lowmemory/src/polynomial.rs b/crypto/mldsa-lowmemory/src/polynomial.rs index 51e81ea4..6b38bbbe 100644 --- a/crypto/mldsa-lowmemory/src/polynomial.rs +++ b/crypto/mldsa-lowmemory/src/polynomial.rs @@ -1,7 +1,9 @@ //! Represents a polynomial over the ML-DSA ring. use crate::aux_functions::{high_bits, low_bits, make_hint, use_hint}; -use crate::mldsa::{MLDSA44_POLY_W1_PACKED_LEN, MLDSA65_POLY_W1_PACKED_LEN, N, q, q_inv}; +use crate::mldsa::{N, d, q, q_inv}; +use crate::params::{GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, MLDSAParams}; +use bouncycastle_utils::secret::ZeroizablePrimitive; use core::ops::{Index, IndexMut}; /// A polynomial over the ML-DSA ring. @@ -75,19 +77,25 @@ impl Polynomial { } } - pub(crate) fn high_bits(&mut self) { + pub(crate) fn high_bits(&mut self) { for i in 0..N { - self[i] = high_bits::(self[i]); + self[i] = high_bits::

(self[i]); } } - pub(crate) fn low_bits(&mut self) { + pub(crate) fn low_bits(&mut self) { for i in 0..N { - self[i] = low_bits::(self[i]); + self[i] = low_bits::

(self[i]); } } - pub(crate) fn check_norm(&self) -> bool { + /// Tests whether any coefficient has absolute value at least `bound`. + /// + /// `bound` is a runtime argument rather than a const generic because every call site passes a + /// value derived from the parameter set (๐›พ1 โˆ’ ๐›ฝ, ๐›พ2 โˆ’ ๐›ฝ, or ๐›พ2), and an associated const of a + /// type parameter cannot be used as a const generic argument. It is still a constant after + /// monomorphization, so this costs nothing. + pub(crate) fn check_norm(&self, bound: i32) -> bool { // Fine that this is not constant-time (returns true early) because it is used in a rejection loop. // IE the early quit here leads to rejection and continuing to the top of the rejection loop, or failing // the signature validation. @@ -97,33 +105,38 @@ impl Polynomial { // if bound > (q - 1) / 8 { // return true; // } - // but since BOUND is a constant here, a debug_assert is done to ensure the value is what is expected. - debug_assert!(BOUND <= (q - 1) / 8); + // but since every caller passes a parameter-set constant, a debug_assert is done instead + // to ensure the value is what is expected. + debug_assert!(bound <= (q - 1) / 8); let mut t: i32; for x in self.coeffs.iter() { t = *x >> 31; t = *x - (t & (2 * *x)); - if t >= BOUND { + if t >= bound { return true; } } false } - pub(crate) fn shift_left(&mut self) { + /// Multiplies every coefficient by 2^๐‘‘. + /// + /// ๐‘‘ is 13 for all three parameter sets (FIPS 204, Table 1), so it is read from the global + /// constant rather than being passed in. + pub(crate) fn shift_left_d(&mut self) { for x in self.coeffs.iter_mut() { *x <<= d; } } /// Creates the hint vector, and also returns its hamming weight (ie the number of 1's). - pub(crate) fn make_hint_row(&self, r: &Self) -> (Self, i32) { + pub(crate) fn make_hint_row(&self, r: &Self) -> (Self, i32) { let mut out = Polynomial::new(); let mut count = 0i32; for i in 0..N { - let x = make_hint::(self[i], r[i]); + let x = make_hint::

(self[i], r[i]); out[i] = x; count += x; } @@ -131,7 +144,14 @@ impl Polynomial { (out, count) } - pub(crate) fn w1_encode(&self) -> [u8; POLY_W1_PACKED_LEN] { + /// SimpleBitPack(๐ฐ1[๐‘–], (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1), the per-coordinate body of + /// FIPS 204, Algorithm 28 (w1Encode), line 3. + /// + /// The coefficient width is bitlen ((๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1), which is 6 bits for ๐›พ2 = (๐‘ž โˆ’ 1)/88 and + /// 4 bits for ๐›พ2 = (๐‘ž โˆ’ 1)/32, so there is one packing layout per distinct ๐›พ2 rather than one + /// per parameter set. `P::gamma2` is a constant after monomorphization, so only the matching + /// arm survives. + pub(crate) fn w1_encode(&self) -> P::PolyW1Packed { // It might seem counter-intuitive for a low-memory implementation to create a tmp buffer // rather than work in the provided buffer, but experimentation shows that // rust is roughly an order of magnitude faster working in a scope-local array than @@ -141,18 +161,21 @@ impl Polynomial { // several hundred physical memory writes. // So while it looks odd to use a scope variable in a low-memory implementation, it's way faster // while seemingly maintaining the same physical memory footprint. - let mut r = [0u8; POLY_W1_PACKED_LEN]; + let mut out = ::ZEROED; + let r = out.as_mut(); - match POLY_W1_PACKED_LEN { - MLDSA44_POLY_W1_PACKED_LEN => { + match P::gamma2 { + // ML-DSA-44: (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1 = 43, so four 6-bit coefficients pack into three bytes. + GAMMA2_Q_MINUS_1_OVER_88 => { for i in 0..N / 4 { r[3 * i] = ((self[4 * i]) as u8) | ((self[4 * i + 1] << 6) as u8); r[3 * i + 1] = ((self[4 * i + 1] >> 2) as u8) | ((self[4 * i + 2] << 4) as u8); r[3 * i + 2] = ((self[4 * i + 2] >> 4) as u8) | ((self[4 * i + 3] << 2) as u8); } } - // ML-DSA65 and 87 share a POLY_W1_PACKED_LEN value - MLDSA65_POLY_W1_PACKED_LEN => { + // ML-DSA-65 and ML-DSA-87 share this ๐›พ2: (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1 = 15, so two 4-bit + // coefficients pack into one byte. + GAMMA2_Q_MINUS_1_OVER_32 => { for i in 0..N / 2 { r[i] = ((self[2 * i]) | (self[2 * i + 1] << 4)) as u8; } @@ -162,7 +185,7 @@ impl Polynomial { } } - r + out } /// Algorithm 41 NTT(๐‘ค) @@ -244,9 +267,9 @@ impl Polynomial { } } - pub(crate) fn use_hint(&mut self, h: &Polynomial) { + pub(crate) fn use_hint(&mut self, h: &Polynomial) { for i in 0..N { - self[i] = use_hint::(self[i], h[i]); + self[i] = use_hint::

(self[i], h[i]); } } } diff --git a/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs index 92ffb445..6920ca41 100644 --- a/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs @@ -6,7 +6,7 @@ mod hash_mldsa_tests { use super::*; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Hash, PHSignatureVerifier}; + use bouncycastle_core::traits::{Hash, PHSignatureVerifier, PHSigner}; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_mldsa_lowmemory::{ HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA256, @@ -234,4 +234,68 @@ mod hash_mldsa_tests { _ => panic!("Expected error"), } } + + #[test] + fn algorithm_names_strengths_and_oids() { + use bouncycastle_core::traits::{Algorithm, AlgorithmOID, SecurityStrength}; + + // `Algorithm` is implemented once, generically over the pairing, so nothing else states + // these per algorithm. + assert_eq!(HashMLDSA44_with_SHA256::ALG_NAME, "HashML-DSA-44_with_SHA256"); + assert_eq!(HashMLDSA65_with_SHA256::ALG_NAME, "HashML-DSA-65_with_SHA256"); + assert_eq!(HashMLDSA87_with_SHA256::ALG_NAME, "HashML-DSA-87_with_SHA256"); + assert_eq!(HashMLDSA44_with_SHA512::ALG_NAME, "HashML-DSA-44_with_SHA512"); + assert_eq!(HashMLDSA65_with_SHA512::ALG_NAME, "HashML-DSA-65_with_SHA512"); + assert_eq!(HashMLDSA87_with_SHA512::ALG_NAME, "HashML-DSA-87_with_SHA512"); + + // Derived as the weaker of the two components: SHA-256 caps every pairing it appears in at + // 128 bits; with SHA-512 the ML-DSA parameter set is what binds. + assert_eq!(HashMLDSA44_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA65_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA87_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA44_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA65_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(HashMLDSA87_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + + // NIST's Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 }, + // -65- { sigAlgs 33 }, -87- { sigAlgs 34 }. The three SHA-256 pairings carry no OID in + // this implementation, which is why `AlgorithmOID` is still written out per alias rather + // than derived from the pairing like the name and strength above. + assert_eq!(HashMLDSA44_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 32]); + assert_eq!(HashMLDSA65_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 33]); + assert_eq!(HashMLDSA87_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 34]); + + for (oid, der) in [ + (HashMLDSA44_with_SHA512::OID, HashMLDSA44_with_SHA512::OID_DER), + (HashMLDSA65_with_SHA512::OID, HashMLDSA65_with_SHA512::OID_DER), + (HashMLDSA87_with_SHA512::OID, HashMLDSA87_with_SHA512::OID_DER), + ] { + assert_eq!(der[0], 0x06, "DER tag must be OBJECT IDENTIFIER"); + assert_eq!(der[1] as usize, der.len() - 2, "DER length must match the content"); + assert_eq!( + &der[2..], + &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, *oid.last().unwrap() as u8] + ); + } + } + + #[test] + fn prehash_lengths_match_the_hash_functions() { + // `PH_LEN` is still a const generic on `HashMLDSA` -- `PHSigner` takes it as one -- but + // no alias hard-codes it: each passes `{ ...Params::PH_LEN }`, which is the pre-hash's own + // `HashAlgParams::OUTPUT_LEN`. So there is only one value, and nothing in the chain can + // disagree with itself. What is left to check is whether that value matches the digest + // the hash actually produces, which is what this test does. + let msg = b"The quick brown fox"; + let ph256: [u8; 32] = SHA256::default().hash(msg).try_into().unwrap(); + let ph512: [u8; 64] = SHA512::default().hash(msg).try_into().unwrap(); + + let (pk, sk) = HashMLDSA65_with_SHA256::keygen().unwrap(); + let sig = HashMLDSA65_with_SHA256::sign_ph(&sk, &ph256, None).unwrap(); + HashMLDSA65_with_SHA256::verify_ph(&pk, &ph256, None, &sig).unwrap(); + + let (pk, sk) = HashMLDSA65_with_SHA512::keygen().unwrap(); + let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph512, None).unwrap(); + HashMLDSA65_with_SHA512::verify_ph(&pk, &ph512, None, &sig).unwrap(); + } } diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index c1b4848d..69832aa2 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -907,6 +907,62 @@ mod mldsa_tests { MLDSA44::sign_mu_deterministic_out(&sk, &mu, [1u8; 32], &mut sig_buf).unwrap(); MLDSA44::verify(&pk, msg, None, &sig_buf).unwrap(); } + + #[test] + fn algorithm_names_and_oids() { + use bouncycastle_core::traits::{Algorithm, AlgorithmOID}; + + // `Algorithm` and `AlgorithmOID` are implemented once, generically over the parameter set, + // so nothing else states these per algorithm. Pinned here so that a wrong wiring of the + // blanket impls, or a typo in a parameter set, is a test failure rather than a silently + // mislabelled algorithm or an unparseable OID. + assert_eq!(MLDSA44::ALG_NAME, "ML-DSA-44"); + assert_eq!(MLDSA65::ALG_NAME, "ML-DSA-65"); + assert_eq!(MLDSA87::ALG_NAME, "ML-DSA-87"); + + assert_eq!(MLDSA44::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(MLDSA65::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(MLDSA87::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + + // NIST's Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 }, + // id-ml-dsa-65 { sigAlgs 18 }, id-ml-dsa-87 { sigAlgs 19 }. + assert_eq!(MLDSA44::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 17]); + assert_eq!(MLDSA65::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 18]); + assert_eq!(MLDSA87::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 19]); + + for (oid, der) in [ + (MLDSA44::OID, MLDSA44::OID_DER), + (MLDSA65::OID, MLDSA65::OID_DER), + (MLDSA87::OID, MLDSA87::OID_DER), + ] { + assert_eq!(der[0], 0x06, "DER tag must be OBJECT IDENTIFIER"); + assert_eq!(der[1] as usize, der.len() - 2, "DER length must match the content"); + assert_eq!( + &der[2..], + &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, *oid.last().unwrap() as u8] + ); + } + } + + #[test] + fn stored_private_key_is_the_seed_but_the_full_encoding_is_the_fips_one() { + // This crate is the seed-holding implementation, so `SK_LEN` is 32 for every parameter + // set while the parameter set's `FULL_SK_LEN` is FIPS 204, Table 2's private key column. + // The two are separate consts; this pins that they have not been conflated. + assert_eq!([MLDSA44_SK_LEN, MLDSA65_SK_LEN, MLDSA87_SK_LEN], [32, 32, 32]); + assert_eq!([MLDSA44_PK_LEN, MLDSA65_PK_LEN, MLDSA87_PK_LEN], [1312, 1952, 2592]); + assert_eq!([MLDSA44_SIG_LEN, MLDSA65_SIG_LEN, MLDSA87_SIG_LEN], [2420, 3309, 4627]); + + // `FULL_SK_LEN` is not a public constant, so it is checked through the encoding it sizes. + let seed = KeyMaterial256::from_bytes_as_type(&[7u8; 32], KeyType::Seed).unwrap(); + let (_, sk44) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let (_, sk65) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let (_, sk87) = MLDSA87::keygen_from_seed(&seed).unwrap(); + assert_eq!(sk44.encode().len(), 32, "the stored private key is the seed"); + assert_eq!(sk44.encode_full_sk().len(), 2560); + assert_eq!(sk65.encode_full_sk().len(), 4032); + assert_eq!(sk87.encode_full_sk().len(), 4896); + } } struct Kat { diff --git a/crypto/mldsa/src/aux_functions.rs b/crypto/mldsa/src/aux_functions.rs index 9eadbb89..bf0c2f91 100644 --- a/crypto/mldsa/src/aux_functions.rs +++ b/crypto/mldsa/src/aux_functions.rs @@ -1,14 +1,14 @@ //! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. -use crate::matrix::{Matrix, Vector}; -use crate::mldsa::{G, H, q_inv}; -use crate::mldsa::{ - MLDSA44_GAMMA1, MLDSA44_GAMMA2, MLDSA65_GAMMA1, MLDSA65_GAMMA2, N, POLY_T0PACKED_LEN, - POLY_T1PACKED_LEN, d, q, +use crate::matrix::{MatrixTrait, VectorTrait}; +use crate::mldsa::{G, H, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN, d, q, q_inv}; +use crate::params::{ + GAMMA1_2_POW_17, GAMMA1_2_POW_19, GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, + MLDSAParams, }; use crate::polynomial::Polynomial; use bouncycastle_core::traits::XOF; -use bouncycastle_utils::secret::Secret; +use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; /// Algorithm 14 CoeffFromThreeBytes(๐‘0, ๐‘1, ๐‘2) /// Output: An integer modulo ๐‘ž or โŠฅ. @@ -34,8 +34,8 @@ pub(crate) fn coeff_from_three_bytes(b: &[u8; 3]) -> Result { /// Input: Integer ๐‘ โˆˆ {0, 1, โ€ฆ , 15}. /// Output: An integer between โˆ’๐œ‚ and ๐œ‚, or โŠฅ. #[inline(always)] -pub(crate) fn coeff_from_half_byte(b: u8) -> Result { - if ETA == 2 && b < 15 { +pub(crate) fn coeff_from_half_byte(b: u8) -> Result { + if P::eta == 2 && b < 15 { // Original code is bad because '%' is not constant-time. // Ok(2 - (b % 5) as i32) // I'm still not convinced this is constant-time, but maybe it's closer? And I can't come up with anything better. @@ -46,7 +46,7 @@ pub(crate) fn coeff_from_half_byte(b: u8) -> Result { }; Ok(2 - b as i32) } else { - if ETA == 4 && b < 9 { Ok(4 - b as i32) } else { Err(()) } + if P::eta == 4 && b < 9 { Ok(4 - b as i32) } else { Err(()) } } } @@ -66,16 +66,6 @@ pub(crate) fn simple_bit_pack_t1(w: &Polynomial) -> [u8; POLY_T1PACKED_LEN] { output } -/// As defined in Algorithm 17, this gives the length of a packed bitstring representing a polynomial -/// whose coefficients have been rounded to \[-eta, eta], which is 32*bitlen(2*eta). -pub const fn bitlen_eta(eta: usize) -> usize { - match eta { - 2 => 32 * 3, - 4 => 32 * 4, - _ => panic!("Invalid eta value"), - } -} - /// A variant of Algorithm 17 BitPack specific to a=eta, b=eta /// Encodes a polynomial ๐‘ค into a byte string. /// Input: ๐‘Ž, ๐‘ โˆˆ โ„• and ๐‘ค โˆˆ ๐‘… such that the coefficients of ๐‘ค are all in \[โˆ’eta, eta]. @@ -84,13 +74,17 @@ pub const fn bitlen_eta(eta: usize) -> usize { // the hope here is that the compiler will aggressively inline this function, // and optimize away the branching. #[inline(always)] -pub(crate) fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { - debug_assert!(r.len() >= bitlen_eta(ETA)); +pub(crate) fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { + // `>=` rather than `==`: skEncode reuses one buffer sized for the largest ๐œ‚ across all three + // parameter sets and copies out only the first `POLY_ETA_PACKED_LEN` bytes, so for ๐œ‚ = 2 the + // buffer is deliberately longer than what gets written. Exactly that many bytes are written out + // either way, so this still catches an undersized buffer. + debug_assert!(r.len() >= P::POLY_ETA_PACKED_LEN); // temp swap space let mut t: [u8; 8] = [0; 8]; - match ETA { + match P::eta { // MLDSA44 and MLDSA87 2 => { let eta: i32 = 2; @@ -166,19 +160,21 @@ pub(crate) fn bit_pack_t0(t0: &Polynomial) -> [u8; POLY_T0PACKED_LEN] { } /// A variant of Algorithm 17 specific to packing z in the signature value in \[โˆ’๐›พ1 + 1, ๐›พ1]. -pub(crate) fn bitpack_gamma1( - z: &Polynomial, -) -> [u8; POLY_Z_PACKED_LEN] { - let mut r = [0u8; POLY_Z_PACKED_LEN]; +pub(crate) fn bitpack_gamma1(z: &Polynomial) -> P::PolyZPacked { + let mut out = ::ZEROED; + let r = out.as_mut(); let mut t: [u32; 4] = [0; 4]; - match GAMMA1 { - MLDSA44_GAMMA1 => { + // One layout per distinct ๐›พ1 rather than one per parameter set; `P::gamma1` is a constant + // after monomorphization, so only the matching arm survives. + match P::gamma1 { + // MLDSA-44 + GAMMA1_2_POW_17 => { for i in 0..N / 4 { - t[0] = (GAMMA1 - z[4 * i]) as u32; - t[1] = (GAMMA1 - z[4 * i + 1]) as u32; - t[2] = (GAMMA1 - z[4 * i + 2]) as u32; - t[3] = (GAMMA1 - z[4 * i + 3]) as u32; + t[0] = (P::gamma1 - z[4 * i]) as u32; + t[1] = (P::gamma1 - z[4 * i + 1]) as u32; + t[2] = (P::gamma1 - z[4 * i + 2]) as u32; + t[3] = (P::gamma1 - z[4 * i + 3]) as u32; r[9 * i] = t[0] as u8; r[9 * i + 1] = (t[0] >> 8) as u8; @@ -191,11 +187,11 @@ pub(crate) fn bitpack_gamma1( r[9 * i + 8] = (t[3] >> 10) as u8; } } - // MLDSA-65 and 87 have the same GAMMA1 value - MLDSA65_GAMMA1 => { + // MLDSA-65 and -87 have the same GAMMA1 value + GAMMA1_2_POW_19 => { for i in 0..N / 2 { - t[0] = (GAMMA1 - z[2 * i]) as u32; - t[1] = (GAMMA1 - z[2 * i + 1]) as u32; + t[0] = (P::gamma1 - z[2 * i]) as u32; + t[1] = (P::gamma1 - z[2 * i + 1]) as u32; r[5 * i] = t[0] as u8; r[5 * i + 1] = (t[0] >> 8) as u8; @@ -209,7 +205,7 @@ pub(crate) fn bitpack_gamma1( } } - r + out } /// A specific instantiation of Algorithm 18 SimpleBitUnpack(v, ๐‘) with the constants set for unpacking the t1 vector @@ -241,12 +237,12 @@ pub(crate) fn simple_bit_unpack_t1(v: &[u8; POLY_T1PACKED_LEN]) -> Polynomial { // the hope here is that the compiler will aggressively inline this function, // and optimize away the branching. #[inline(always)] -pub(crate) fn bit_unpack_eta(v: &[u8]) -> Polynomial { - debug_assert_eq!(v.len(), bitlen_eta(ETA)); +pub(crate) fn bit_unpack_eta(v: &[u8]) -> Polynomial { + debug_assert_eq!(v.len(), P::POLY_ETA_PACKED_LEN); let mut w = Polynomial::new(); - match ETA { + match P::eta { // MLDSA44 and MLDSA87 2 => { let eta: i32 = 2; @@ -331,11 +327,12 @@ pub(crate) fn bit_unpack_t0(a: &[u8; POLY_T0PACKED_LEN]) -> Polynomial { /// When ๐‘Ž + ๐‘ + 1 is a power of 2, the coefficients are in [โˆ’๐‘Ž, ๐‘]. /// /// Note: caller is responsible for ensuring correct input array size -pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { +pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { let mut w = Polynomial::new(); - match GAMMA1 { - MLDSA44_GAMMA1 => { + match P::gamma1 { + // MLDSA-44 + GAMMA1_2_POW_17 => { for i in 0..N / 4 { w[4 * i] = (((v[9 * i] as i32) | ((v[9 * i + 1] as i32) << 8)) | ((v[9 * i + 2] as i32) << 16)) @@ -350,14 +347,14 @@ pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { | ((v[9 * i + 8] as i32) << 10)) & 0x3FFFF; - w[4 * i] = GAMMA1 - w[4 * i]; - w[4 * i + 1] = GAMMA1 - w[4 * i + 1]; - w[4 * i + 2] = GAMMA1 - w[4 * i + 2]; - w[4 * i + 3] = GAMMA1 - w[4 * i + 3]; + w[4 * i] = P::gamma1 - w[4 * i]; + w[4 * i + 1] = P::gamma1 - w[4 * i + 1]; + w[4 * i + 2] = P::gamma1 - w[4 * i + 2]; + w[4 * i + 3] = P::gamma1 - w[4 * i + 3]; } } - // MLDSA-65 and 87 have the same GAMMA1 value - MLDSA65_GAMMA1 => { + // MLDSA-65 and -87 have the same GAMMA1 value + GAMMA1_2_POW_19 => { for i in 0..N / 2 { w[2 * i] = (((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) | ((v[5 * i + 2] as i32) << 16)) @@ -366,8 +363,8 @@ pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { | ((v[5 * i + 4] as i32) << 12)) & 0xFFFFF; - w[2 * i] = GAMMA1 - w[2 * i]; - w[2 * i + 1] = GAMMA1 - w[2 * i + 1]; + w[2 * i] = P::gamma1 - w[2 * i]; + w[2 * i + 1] = P::gamma1 - w[2 * i + 1]; } } _ => { @@ -384,43 +381,36 @@ pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { /// Output: Signature ๐œŽ โˆˆ ๐”น๐œ†/4+โ„“โ‹…32โ‹…(1+bitlen (๐›พ1โˆ’1))+๐œ”+๐‘˜. /// /// Returns the number of bytes written to the output buffer. -pub(crate) fn sig_encode< - const GAMMA1: i32, - const k: usize, - const l: usize, - const LAMBDA_over_4: usize, - const OMEGA: i32, - const POLY_Z_PACKED_LEN: usize, - const SIG_LEN: usize, ->( - c_tilde: &[u8; LAMBDA_over_4], - z: &Vector, - h: &Vector, +pub(crate) fn sig_encode( + c_tilde: &P::SigCTilde, + z: &P::VecL, + h: &P::VecK, output: &mut [u8; SIG_LEN], ) -> usize { + debug_assert_eq!(SIG_LEN, P::SIG_LEN); output.fill(0); let mut pos = 0; - output[..LAMBDA_over_4].copy_from_slice(c_tilde); - pos += LAMBDA_over_4; + output[..P::C_TILDE_LEN].copy_from_slice(c_tilde.as_ref()); + pos += P::C_TILDE_LEN; - for i in 0..l { - output[pos..pos + POLY_Z_PACKED_LEN] - .copy_from_slice(&bitpack_gamma1::(&z.elems[i])); - pos += POLY_Z_PACKED_LEN; + for i in 0..P::l { + output[pos..pos + P::POLY_Z_PACKED_LEN] + .copy_from_slice(bitpack_gamma1::

(&z.elems()[i]).as_ref()); + pos += P::POLY_Z_PACKED_LEN; } // This inlines Algorithm 20 HintBitPack(๐ก) let mut m: usize = 0; - for i in 0..k { + for i in 0..P::k { for j in 0..N { - if h.elems[i][j] != 0 { + if h.elems()[i][j] != 0 { output[pos + m] = j as u8; m += 1; } - output[pos + OMEGA as usize + i] = m as u8; + output[pos + P::omega as usize + i] = m as u8; } } @@ -432,29 +422,22 @@ pub(crate) fn sig_encode< /// Input: Signature ๐œŽ โˆˆ ๐”น๐œ†/4+โ„“โ‹…32โ‹…(1+bitlen (๐›พ1โˆ’1))+๐œ”+๐‘˜. /// Output: ๐‘ โˆˆ ๐”น๐œ†/4, ๐ณ โˆˆ ๐‘…โ„“ with coefficients in \[โˆ’๐›พ1 + 1, ๐›พ1], ๐ก โˆˆ ๐‘…๐‘˜, or โŠฅ. /// Output: (c_tilde, z, h) -pub(crate) fn sig_decode< - const GAMMA1: i32, - const k: usize, - const l: usize, - const LAMBDA_over_4: usize, - const OMEGA: i32, - const POLY_Z_PACKED_LEN: usize, - const SIG_LEN: usize, ->( +pub(crate) fn sig_decode( sig: &[u8; SIG_LEN], -) -> Result<([u8; LAMBDA_over_4], Vector, Vector), ()> { - let mut c_tilde = [0u8; LAMBDA_over_4]; - let mut z: Vector = Vector::::new(); - let mut h: Vector = Vector::::new(); +) -> Result<(P::SigCTilde, P::VecL, P::VecK), ()> { + debug_assert_eq!(SIG_LEN, P::SIG_LEN); + let mut c_tilde = ::ZEROED; + let mut z = P::VecL::new(); + let mut h = P::VecK::new(); let mut pos: usize = 0; - c_tilde.copy_from_slice(&sig[..LAMBDA_over_4]); - pos += LAMBDA_over_4; + c_tilde.as_mut().copy_from_slice(&sig[..P::C_TILDE_LEN]); + pos += P::C_TILDE_LEN; - for i in 0..l { - z.elems[i] = bit_unpack_gamma1::(&sig[pos..pos + POLY_Z_PACKED_LEN]); - pos += POLY_Z_PACKED_LEN; + for i in 0..P::l { + z.elems_mut()[i] = bit_unpack_gamma1::

(&sig[pos..pos + P::POLY_Z_PACKED_LEN]); + pos += P::POLY_Z_PACKED_LEN; } // This inlines Algorithm 21 HintBitUnpack(๐‘ฆ) @@ -465,12 +448,12 @@ pub(crate) fn sig_decode< // 3: for ๐‘– from 0 to ๐‘˜ โˆ’ 1 do // โ–ท reconstruct ๐ก[๐‘–] - for i in 0..k { + for i in 0..P::k { // 4: if ๐‘ฆ[๐œ” + ๐‘–] < Index or ๐‘ฆ[๐œ” + ๐‘–] > ๐œ” then return โŠฅ // todo: this needs a specific test for malformed signature values. Maybe crucible coveres this case? // ... could hide an assert here and see if it triggers. - if sig[pos + (OMEGA as usize) + i] < (idx as u8) - || sig[pos + (OMEGA as usize) + i] > OMEGA as u8 + if sig[pos + (P::omega as usize) + i] < (idx as u8) + || sig[pos + (P::omega as usize) + i] > P::omega as u8 { return Err(()); } @@ -478,7 +461,7 @@ pub(crate) fn sig_decode< // 6: First โ† Index // 7: while Index < ๐‘ฆ[๐œ” + ๐‘–] do // โ–ท ๐‘ฆ[๐œ” + ๐‘–] says how far one can advance Index - for j in idx..sig[pos + OMEGA as usize + i] as usize { + for j in idx..sig[pos + P::omega as usize + i] as usize { // 8: if Index > First then // 9: if ๐‘ฆ[Index โˆ’ 1] โ‰ฅ ๐‘ฆ[Index] then return โŠฅ // โ–ท malformed input @@ -486,17 +469,17 @@ pub(crate) fn sig_decode< return Err(()); } // 12: ๐ก[๐‘–]_๐‘ฆ[Index] โ† 1 - h.elems[i][sig[pos + j] as usize] = 1; + h.elems_mut()[i][sig[pos + j] as usize] = 1; // 13: Index โ† Index + 1 // > done by for loop } - idx = sig[pos + OMEGA as usize + i] as usize; + idx = sig[pos + P::omega as usize + i] as usize; } // โ–ท read any leftover bytes in the first ๐œ” bytes of ๐‘ฆ for malformed (nonzero) bytes - for j in idx..OMEGA as usize { + for j in idx..P::omega as usize { if sig[pos + j] != 0 { return Err(()); } @@ -509,9 +492,7 @@ pub(crate) fn sig_decode< /// Samples a polynomial ๐‘ โˆˆ ๐‘… with coefficients from {โˆ’1, 0, 1} and Hamming weight ๐œ โ‰ค 64. /// Input: A seed ๐œŒ โˆˆ ๐”น๐œ†/4 /// Output: A polynomial ๐‘ in ๐‘…. -pub(crate) fn sample_in_ball( - rho: &[u8; LAMBDA_over_4], -) -> Polynomial { +pub(crate) fn sample_in_ball(rho: &P::SigCTilde) -> Polynomial { // 1: ๐‘ โ† 0 let mut c = Polynomial::new(); @@ -519,7 +500,7 @@ pub(crate) fn sample_in_ball( // 3: ctx โ† H.Absorb(ctx, ๐œŒ) // 4: (ctx, ๐‘ ) โ† H.Squeeze(ctx, 8) let mut h = H::new(); - h.absorb(rho).expect("absorb before squeeze is infallible"); + h.absorb(rho.as_ref()).expect("absorb before squeeze is infallible"); let mut s = [0u8; 8]; h.squeeze_out(&mut s); @@ -535,7 +516,7 @@ pub(crate) fn sample_in_ball( // let mut pos = 8; // let mut b; let mut j = [0u8]; - for i in (N - TAU as usize)..N { + for i in (N - P::tau as usize)..N { // 7: (ctx, ๐‘—) โ† H.Squeeze(ctx, 1) // Note: Even though it may appear that pre-squeezing a buffer outside the loop would be faster, // testing it both ways doesn't make a noticeable difference, so this has been left as is @@ -624,7 +605,7 @@ pub(crate) fn rej_ntt_poly(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { /// This is supposed to take a rho: [u8; 66], which is: ๐œŒ||IntegerToBytes(๐‘ , 1)||IntegerToBytes(๐‘Ÿ, 1) /// but to avoid needing to copy bytes and allocate more memory, /// that is split into a [u8;64] and a [u8;2] -pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2]) -> Polynomial { +pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2]) -> Polynomial { let mut a = Polynomial::new(); let mut j: usize = 0; let mut h = H::new(); @@ -640,8 +621,8 @@ pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2] let mut idx: usize = 0; while j < N { - let z0 = coeff_from_half_byte::(z_arr[idx] & 0x0F); // equiv to % 16 (but faster, and more importantly, constant-time) - let z1 = coeff_from_half_byte::(z_arr[idx] >> 4); // equiv to div_floor(16) (but faster, and more importantly, constant-time) + let z0 = coeff_from_half_byte::

(z_arr[idx] & 0x0F); // equiv to % 16 (but faster, and more importantly, constant-time) + let z1 = coeff_from_half_byte::

(z_arr[idx] >> 4); // equiv to div_floor(16) (but faster, and more importantly, constant-time) if z0.is_ok() { a[j] = z0.unwrap(); @@ -667,12 +648,12 @@ pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2] /// in other words: derives the public matrix from the public seed. /// Input: A seed ๐œŒ โˆˆ ๐”น32 .ฬ‚ /// Output: Matrix ร‚ โˆˆ (๐‘‡๐‘ž)๐‘˜ร—โ„“ . -pub(crate) fn expandA(rho: &[u8; 32]) -> Matrix { - let mut A_hat = Matrix::::new(); +pub(crate) fn expandA(rho: &[u8; 32]) -> P::MatrixA { + let mut A_hat = P::MatrixA::new(); - for r in 0..k { - for s in 0..l { - A_hat.elems[r][s] = rej_ntt_poly(rho, &[s as u8, r as u8]); + for r in 0..P::k { + for s in 0..P::l { + A_hat.set_elem(r, s, rej_ntt_poly(rho, &[s as u8, r as u8])); } } @@ -685,18 +666,16 @@ pub(crate) fn expandA(rho: &[u8; 32]) -> Matrix< /// Input: A seed ๐œŒ โˆˆ ๐”น64 . /// Output: Vectors ๐ฌ1, ๐ฌ2 of secret polynomials in ๐‘… /// Note that this returns Secret> because s1, s2 are always part of a private key. -pub(crate) fn expandS( - rho: &[u8; 64], -) -> (Secret>, Secret>) { - let mut s1: Secret> = Secret::new(); - let mut s2: Secret> = Secret::new(); - - for r in 0..l { - s1.elems[r] = rej_bounded_poly::(rho, &(r as u16).to_le_bytes()); +pub(crate) fn expandS(rho: &[u8; 64]) -> (Secret, Secret) { + let mut s1: Secret = Secret::new(); + let mut s2: Secret = Secret::new(); + + for r in 0..P::l { + s1.elems_mut()[r] = rej_bounded_poly::

(rho, &(r as u16).to_le_bytes()); } - for r in 0..k { - s2.elems[r] = rej_bounded_poly::(rho, &(r as u16 + l as u16).to_le_bytes()); + for r in 0..P::k { + s2.elems_mut()[r] = rej_bounded_poly::

(rho, &(r as u16 + P::l as u16).to_le_bytes()); } (s1, s2) @@ -704,13 +683,13 @@ pub(crate) fn expandS( /// Implements the meta-function described in FIPS 204 section 7.4 for applying power_2_round to a vector. /// ((๐ซ1\[๐‘–])๐‘—, (๐ซ0\[๐‘–])๐‘—) = Power2Round((๐ซ\[๐‘–])๐‘—). -pub(crate) fn power_2_round_vec(v: &Vector) -> (Vector, Vector) { - let mut r1 = Vector::::new(); - let mut r0 = Vector::::new(); +pub(crate) fn power_2_round_vec(v: &V) -> (V, V) { + let mut r1 = V::new(); + let mut r0 = V::new(); - for i in 0..LEN { + for i in 0..V::LEN { for j in 0..N { - (r1.elems[i][j], r0.elems[i][j]) = power_2_round(v.elems[i][j]); + (r1.elems_mut()[i][j], r0.elems_mut()[i][j]) = power_2_round(v.elems()[i][j]); } } @@ -721,17 +700,15 @@ pub(crate) fn power_2_round_vec(v: &Vector) -> (Vector( - rho: &[u8; 64], - mu: u16, -) -> Vector { - let mut y = Vector::::new(); +pub(crate) fn expand_mask(rho: &[u8; 64], mu: u16) -> P::VecL { + let mut y = P::VecL::new(); // 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) // โ–ท ๐›พ1 is always a power of 2 - // 32c = GAMMA1_MASK_LEN; + // The 32๐‘ bytes squeezed on line 4 are exactly `P::POLY_Z_PACKED_LEN`, so the buffer for them + // is `P::PolyZPacked`; see the docs on [`MLDSAParams::POLY_Z_PACKED_LEN`]. - for r in 0..l { + for r in 0..P::l { // 3: ๐œŒโ€ฒ โ† ๐œŒ||IntegerToBytes(๐œ‡ + ๐‘Ÿ, 2) // 4: ๐‘ฃ โ† H(๐œŒโ€ฒ, 32๐‘) let v = { @@ -739,13 +716,13 @@ pub(crate) fn expand_mask::ZEROED; + h.squeeze_out(v.as_mut()); v }; // 5: ๐ฒ[๐‘Ÿ] โ† BitUnpack(๐‘ฃ, ๐›พ1 โˆ’ 1, ๐›พ1) - y.elems[r] = bit_unpack_gamma1::(&v); + y.elems_mut()[r] = bit_unpack_gamma1::

(v.as_ref()); } y @@ -788,7 +765,7 @@ fn test_power_2_round() { /// Decomposes ๐‘Ÿ into (๐‘Ÿ1, ๐‘Ÿ0) such that ๐‘Ÿ โ‰ก ๐‘Ÿ1(2๐›พ2) + ๐‘Ÿ0 mod ๐‘ž. /// Input: ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: Integers (๐‘Ÿ1, ๐‘Ÿ0). -pub(crate) fn decompose(r: i32) -> (i32, i32) { +pub(crate) fn decompose(r: i32) -> (i32, i32) { // 1: ๐‘Ÿ+ โ† ๐‘Ÿ mod ๐‘ž // 2: ๐‘Ÿ0 โ† ๐‘Ÿ+ modยฑ(2๐›พ2) // 3: if ๐‘Ÿ+ โˆ’ ๐‘Ÿ0 = ๐‘ž โˆ’ 1 then @@ -803,14 +780,15 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { let mut r1: i32; let mut r0 = (r + 127) >> 7; - match GAMMA2 { - MLDSA44_GAMMA2 => { + match P::gamma2 { + // MLDSA-44 + GAMMA2_Q_MINUS_1_OVER_88 => { // (q - 1) / 88 r0 = (r0 * 11275 + (1 << 23)) >> 24; r0 ^= ((43 - r0) >> 31) & r0; } - // ML-DSA65 and 87 have the same GAMMA2 - MLDSA65_GAMMA2 => { + // ML-DSA-65 and -87 have the same GAMMA2 + GAMMA2_Q_MINUS_1_OVER_32 => { // (q - 1) / 32; r0 = (r0 * 1025 + (1 << 21)) >> 22; r0 &= 15; @@ -821,7 +799,7 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { } } - r1 = r - r0 * 2 * GAMMA2; + r1 = r - r0 * 2 * P::gamma2; // mutants note: the choice of (q - 1) is a bit arbitrary in that after doing the bit-shifting, // this seems to work out mathematically equivalent if doing q/2, or (q+3)/2, but here it is left as (q-1)/2 @@ -835,10 +813,10 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { /// Returns ๐‘Ÿ1 from the output of Decompose (๐‘Ÿ). /// Input: ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: Integer ๐‘Ÿ1. -pub(crate) fn high_bits(r: i32) -> i32 { +pub(crate) fn high_bits(r: i32) -> i32 { // 1: (๐‘Ÿ1, ๐‘Ÿ0) โ† Decompose(๐‘Ÿ) // 2: return ๐‘Ÿ1 - let (r1, _) = decompose::(r); + let (r1, _) = decompose::

(r); r1 } @@ -846,10 +824,10 @@ pub(crate) fn high_bits(r: i32) -> i32 { /// Returns ๐‘Ÿ0 from the output of Decompose (๐‘Ÿ). /// Input: ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: Integer ๐‘Ÿ0. -pub(crate) fn low_bits(r: i32) -> i32 { +pub(crate) fn low_bits(r: i32) -> i32 { // 1: (๐‘Ÿ1, ๐‘Ÿ0) โ† Decompose(๐‘Ÿ) // 2: return ๐‘Ÿ0 - let (_, r0) = decompose::(r); + let (_, r0) = decompose::

(r); r0 } @@ -857,32 +835,29 @@ pub(crate) fn low_bits(r: i32) -> i32 { /// Computes hint bit indicating whether adding ๐‘ง to ๐‘Ÿ alters the high bits of ๐‘Ÿ. /// Input: ๐‘ง, ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: Boolean. -pub(crate) fn make_hint(z: i32, r: i32) -> i32 { +pub(crate) fn make_hint(z: i32, r: i32) -> i32 { // // 1: ๐‘Ÿ1 โ† HighBits(๐‘Ÿ) - // let r1 = high_bits::(r); + // let r1 = high_bits::

(r); // // // 2: ๐‘ฃ1 โ† HighBits(๐‘Ÿ + ๐‘ง) - // let v1 = high_bits::(r + z); + // let v1 = high_bits::

(r + z); // // // 3: return [[๐‘Ÿ1 โ‰  ๐‘ฃ1]] // if r1 != v1 { 1 } else { 0 } // By the powers of someone much more clever than me, this is equivalent. // mutants note: we do not have KATs that exercise all branches of this if - if z <= GAMMA2 || z > q - GAMMA2 || (z == q - GAMMA2 && r == 0) { 0 } else { 1 } + if z <= P::gamma2 || z > q - P::gamma2 || (z == q - P::gamma2 && r == 0) { 0 } else { 1 } } /// Creates the hint vector from two Vector's, and also returns its hamming weight (ie the number of 1's). -pub(crate) fn make_hint_vecs( - r: &Vector, - s: &Vector, -) -> (Vector, i32) { - let mut out = Vector::::new(); +pub(crate) fn make_hint_vecs(r: &P::VecK, s: &P::VecK) -> (P::VecK, i32) { + let mut out = P::VecK::new(); let mut count = 0i32; - for i in 0..k { - let (w, c) = r.elems[i].make_hint::(&s.elems[i]); - out.elems[i] = w; + for i in 0..P::k { + let (w, c) = r.elems()[i].make_hint::

(&s.elems()[i]); + out.elems_mut()[i] = w; // mutants note: this chains up to hint_hamming_weight > OMEGA and there is no test KAT that triggers this branch count += c; @@ -895,8 +870,8 @@ pub(crate) fn make_hint_vecs( /// Returns the high bits of ๐‘Ÿ adjusted according to hint โ„Ž. /// Input: Boolean โ„Ž, ๐‘Ÿ โˆˆ โ„ค๐‘ž. /// Output: ๐‘Ÿ1 โˆˆ โ„ค with 0 โ‰ค ๐‘Ÿ1 โ‰ค (๐‘žโˆ’1) / 2*gamma2). -pub(super) fn use_hint(a: i32, hint: i32) -> i32 { - let (a0, a1) = decompose::(a); +pub(super) fn use_hint(a: i32, hint: i32) -> i32 { + let (a0, a1) = decompose::

(a); if hint == 0 { return a0; @@ -904,8 +879,8 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { debug_assert!(hint == 1); - match GAMMA2 { - MLDSA44_GAMMA2 => { + match P::gamma2 { + GAMMA2_Q_MINUS_1_OVER_88 => { // mutants note: this passes unit tests if it's a1 >= 0 // it is left like this because it matches the spec if a1 > 0 { @@ -915,7 +890,7 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { } } // ML-DSA65 and 87 have the same GAMMA2 - MLDSA65_GAMMA2 => { + GAMMA2_Q_MINUS_1_OVER_32 => { // mutants note: this passes unit tests if it's a1 >= 0 // it is left like this because it matches the spec if a1 > 0 { (a0 + 1) & 15 } else { (a0 - 1) & 15 } @@ -926,23 +901,20 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { } } -pub(crate) fn use_hint_polys( +pub(crate) fn use_hint_polys( wp_approx: &Polynomial, h: &Polynomial, out: &mut Polynomial, ) { for i in 0..N { - out[i] = use_hint::(wp_approx[i], h[i]); + out[i] = use_hint::

(wp_approx[i], h[i]); } } -pub(crate) fn use_hint_vecs( - h: &Vector, - wp_approx: &Vector, -) -> Vector { - let mut out = Vector::::new(); - for i in 0..k { - use_hint_polys::(&wp_approx.elems[i], &h.elems[i], &mut out.elems[i]); +pub(crate) fn use_hint_vecs(h: &P::VecK, wp_approx: &P::VecK) -> P::VecK { + let mut out = P::VecK::new(); + for i in 0..P::k { + use_hint_polys::

(&wp_approx.elems()[i], &h.elems()[i], &mut out.elems_mut()[i]); } out diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 5e1b535a..35747605 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -66,29 +66,19 @@ //! But a simple [`HashMLDSA::keygen`] is provided. use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; -use crate::mldsa::{ - MLDSA44_BETA, MLDSA44_C_TILDE, MLDSA44_ETA, MLDSA44_GAMMA1, MLDSA44_GAMMA1_MASK_LEN, - MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2, MLDSA44_GAMMA2_MINUS_BETA, MLDSA44_LAMBDA, - MLDSA44_LAMBDA_over_4, MLDSA44_OMEGA, MLDSA44_PK_LEN, MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_POLY_Z_PACKED_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN, MLDSA44_TAU, MLDSA44_k, MLDSA44_l, -}; -use crate::mldsa::{ - MLDSA65_BETA, MLDSA65_C_TILDE, MLDSA65_ETA, MLDSA65_GAMMA1, MLDSA65_GAMMA1_MASK_LEN, - MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2, MLDSA65_GAMMA2_MINUS_BETA, MLDSA65_LAMBDA, - MLDSA65_LAMBDA_over_4, MLDSA65_OMEGA, MLDSA65_PK_LEN, MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_POLY_Z_PACKED_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN, MLDSA65_TAU, MLDSA65_k, MLDSA65_l, -}; -use crate::mldsa::{ - MLDSA87_BETA, MLDSA87_C_TILDE, MLDSA87_ETA, MLDSA87_GAMMA1, MLDSA87_GAMMA1_MASK_LEN, - MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2, MLDSA87_GAMMA2_MINUS_BETA, MLDSA87_LAMBDA, - MLDSA87_LAMBDA_over_4, MLDSA87_OMEGA, MLDSA87_PK_LEN, MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_POLY_Z_PACKED_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN, MLDSA87_TAU, MLDSA87_k, MLDSA87_l, -}; +use crate::mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; +use crate::mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; +use crate::mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPublicKeyInternalTrait}; +use crate::params::{ + HashMLDSA44_with_SHA256Params, HashMLDSA44_with_SHA512Params, HashMLDSA65_with_SHA256Params, + HashMLDSA65_with_SHA512Params, HashMLDSA87_with_SHA256Params, HashMLDSA87_with_SHA512Params, + HashMLDSAParams, MLDSAParams, +}; use crate::{ MLDSA, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPrivateKeyTrait, - MLDSAPublicKeyExpanded, MLDSAPublicKeyTrait, Matrix, + MLDSAPublicKeyExpanded, MLDSAPublicKeyTrait, }; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; @@ -97,7 +87,6 @@ use bouncycastle_core::traits::{ SignatureVerifier, Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha2::{SHA256, SHA512}; use core::marker::PhantomData; // Imports needed only for docs @@ -121,137 +110,53 @@ pub const HASH_ML_DSA_87_WITH_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; /*** Pub Types ***/ -/// The HashML-DSA-44_with_SHA512 signature algorithm. +/// The HashML-DSA-44_with_SHA256 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA256 = HashMLDSA< - SHA256, - 32, + HashMLDSA44_with_SHA256Params, + MLDSA44PublicKey, + MLDSA44PrivateKey, + { HashMLDSA44_with_SHA256Params::PH_LEN }, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN, - MLDSA44PublicKey, - MLDSA44PrivateKey, - MLDSA44_TAU, - MLDSA44_LAMBDA, - MLDSA44_GAMMA1, - MLDSA44_GAMMA2, - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, - MLDSA44_BETA, - MLDSA44_OMEGA, - MLDSA44_C_TILDE, - MLDSA44_POLY_Z_PACKED_LEN, - MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MINUS_BETA, - MLDSA44_GAMMA2_MINUS_BETA, - MLDSA44_GAMMA1_MASK_LEN, >; -impl Algorithm for HashMLDSA44_with_SHA256 { - const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} - /// The HashML-DSA-65_with_SHA256 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA65_with_SHA256 = HashMLDSA< - SHA256, - 32, + HashMLDSA65_with_SHA256Params, + MLDSA65PublicKey, + MLDSA65PrivateKey, + { HashMLDSA65_with_SHA256Params::PH_LEN }, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN, - MLDSA65PublicKey, - MLDSA65PrivateKey, - MLDSA65_TAU, - MLDSA65_LAMBDA, - MLDSA65_GAMMA1, - MLDSA65_GAMMA2, - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, - MLDSA65_BETA, - MLDSA65_OMEGA, - MLDSA65_C_TILDE, - MLDSA65_POLY_Z_PACKED_LEN, - MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MINUS_BETA, - MLDSA65_GAMMA2_MINUS_BETA, - MLDSA65_GAMMA1_MASK_LEN, >; -impl Algorithm for HashMLDSA65_with_SHA256 { - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} - /// The HashML-DSA-87_with_SHA256 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA87_with_SHA256 = HashMLDSA< - SHA256, - 32, + HashMLDSA87_with_SHA256Params, + MLDSA87PublicKey, + MLDSA87PrivateKey, + { HashMLDSA87_with_SHA256Params::PH_LEN }, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN, - MLDSA87PublicKey, - MLDSA87PrivateKey, - MLDSA87_TAU, - MLDSA87_LAMBDA, - MLDSA87_GAMMA1, - MLDSA87_GAMMA2, - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, - MLDSA87_BETA, - MLDSA87_OMEGA, - MLDSA87_C_TILDE, - MLDSA87_POLY_Z_PACKED_LEN, - MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MINUS_BETA, - MLDSA87_GAMMA2_MINUS_BETA, - MLDSA87_GAMMA1_MASK_LEN, >; -impl Algorithm for HashMLDSA87_with_SHA256 { - const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} - /// The HashML-DSA-44_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA512 = HashMLDSA< - SHA512, - 64, + HashMLDSA44_with_SHA512Params, + MLDSA44PublicKey, + MLDSA44PrivateKey, + { HashMLDSA44_with_SHA512Params::PH_LEN }, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN, - MLDSA44PublicKey, - MLDSA44PrivateKey, - MLDSA44_TAU, - MLDSA44_LAMBDA, - MLDSA44_GAMMA1, - MLDSA44_GAMMA2, - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, - MLDSA44_BETA, - MLDSA44_OMEGA, - MLDSA44_C_TILDE, - MLDSA44_POLY_Z_PACKED_LEN, - MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MINUS_BETA, - MLDSA44_GAMMA2_MINUS_BETA, - MLDSA44_GAMMA1_MASK_LEN, >; - -impl Algorithm for HashMLDSA44_with_SHA512 { - const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} /// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 } impl AlgorithmOID for HashMLDSA44_with_SHA512 { const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 32]; @@ -262,35 +167,14 @@ impl AlgorithmOID for HashMLDSA44_with_SHA512 { /// The HashML-DSA-65_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA65_with_SHA512 = HashMLDSA< - SHA512, - 64, + HashMLDSA65_with_SHA512Params, + MLDSA65PublicKey, + MLDSA65PrivateKey, + { HashMLDSA65_with_SHA512Params::PH_LEN }, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN, - MLDSA65PublicKey, - MLDSA65PrivateKey, - MLDSA65_TAU, - MLDSA65_LAMBDA, - MLDSA65_GAMMA1, - MLDSA65_GAMMA2, - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, - MLDSA65_BETA, - MLDSA65_OMEGA, - MLDSA65_C_TILDE, - MLDSA65_POLY_Z_PACKED_LEN, - MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MINUS_BETA, - MLDSA65_GAMMA2_MINUS_BETA, - MLDSA65_GAMMA1_MASK_LEN, >; - -impl Algorithm for HashMLDSA65_with_SHA512 { - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} /// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-65-with-sha512 { sigAlgs 33 } impl AlgorithmOID for HashMLDSA65_with_SHA512 { const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 33]; @@ -301,35 +185,14 @@ impl AlgorithmOID for HashMLDSA65_with_SHA512 { /// The HashML-DSA-87_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA87_with_SHA512 = HashMLDSA< - SHA512, - 64, + HashMLDSA87_with_SHA512Params, + MLDSA87PublicKey, + MLDSA87PrivateKey, + { HashMLDSA87_with_SHA512Params::PH_LEN }, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN, - MLDSA87PublicKey, - MLDSA87PrivateKey, - MLDSA87_TAU, - MLDSA87_LAMBDA, - MLDSA87_GAMMA1, - MLDSA87_GAMMA2, - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, - MLDSA87_BETA, - MLDSA87_OMEGA, - MLDSA87_C_TILDE, - MLDSA87_POLY_Z_PACKED_LEN, - MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MINUS_BETA, - MLDSA87_GAMMA2_MINUS_BETA, - MLDSA87_GAMMA1_MASK_LEN, >; - -impl Algorithm for HashMLDSA87_with_SHA512 { - const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} /// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-87-with-sha512 { sigAlgs 34 } impl AlgorithmOID for HashMLDSA87_with_SHA512 { const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 34]; @@ -337,6 +200,21 @@ impl AlgorithmOID for HashMLDSA87_with_SHA512 { &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x22]; } +impl< + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PH_LEN: usize, + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, +> Algorithm for HashMLDSA +{ + const ALG_NAME: &'static str = P::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; +} + /// An instance of the HashML-DSA algorithm. /// /// The code is exposing the HashMLDSA struct this way so that alternative hash functions can be used @@ -344,32 +222,16 @@ impl AlgorithmOID for HashMLDSA87_with_SHA512 { /// by specifying the hash function to use (in the verifier), and specifying the bytes of the OID to /// to use as its domain separator in constructing the message representative M'. pub struct HashMLDSA< - HASH: Hash + AlgorithmOID + Default, - const HASH_LEN: usize, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > { - _phantom: PhantomData<(PK, SK)>, + _phantom: PhantomData<(P, PK, SK)>, signer_rnd: Option<[u8; MLDSA_RND_LEN]>, @@ -383,7 +245,7 @@ pub struct HashMLDSA< pk: Option, /// Hash function instance for streaming message hashing - hash: HASH, + hash: P::PreHash, /// Since HashML-DSA does message buffering in the external pre-hash, not in mu, /// this needs to be saved for later @@ -392,56 +254,15 @@ pub struct HashMLDSA< } impl< - HASH: Hash + AlgorithmOID + Default, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MASK_LEN: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, -> - HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> HashMLDSA { /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. /// @@ -450,60 +271,16 @@ impl< /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. /// Error condition: basically only on RNG failures. pub fn keygen() -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen() + MLDSA::::keygen() } /// Imports a secret key from a seed. pub fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen_internal(seed) + MLDSA::::keygen_internal(seed) } /// Same as [`Signer::sign`], but signs from an [`MLDSAPrivateKeyExpanded`]. pub fn sign_with_expanded_key( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError> { @@ -514,7 +291,7 @@ impl< } /// Same as [`Signer::sign_out`], but signs from an [`MLDSAPrivateKeyExpanded`]. pub fn sign_with_expanded_key_out( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], @@ -522,12 +299,12 @@ impl< output.fill(0); let mut ph_m = [0u8; PH_LEN]; - _ = HASH::default().hash_out(msg, &mut ph_m); + _ = ::default().hash_out(msg, &mut ph_m); Self::sign_ph_with_expanded_key_out(sk, &ph_m, ctx, output) } /// Same as [`PHSigner::sign_ph`], but signs from an [`MLDSAPrivateKeyExpanded`]. pub fn sign_ph_with_expanded_key( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError> { @@ -538,7 +315,7 @@ impl< } /// Same as [`PHSigner::sign_ph_out`], but signs from an [`MLDSAPrivateKeyExpanded`]. pub fn sign_ph_with_expanded_key_out( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], @@ -564,7 +341,7 @@ impl< /// prevent accidental nonce reuse, this function moves `rnd`. pub fn sign_ph_deterministic( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&::MatrixA>, ctx: Option<&[u8]>, ph: &[u8; PH_LEN], rnd: [u8; 32], @@ -587,7 +364,7 @@ impl< /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. pub fn sign_ph_deterministic_out( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&::MatrixA>, ctx: Option<&[u8]>, ph: &[u8; PH_LEN], rnd: [u8; 32], @@ -615,7 +392,8 @@ impl< h.absorb(&[1u8]).expect("absorb before squeeze is infallible"); h.absorb(&[ctx.len() as u8]).expect("absorb before squeeze is infallible"); h.absorb(ctx).expect("absorb before squeeze is infallible"); - h.absorb(HASH::OID_DER).expect("absorb before squeeze is infallible"); + h.absorb(::OID_DER) + .expect("absorb before squeeze is infallible"); h.absorb(ph).expect("absorb before squeeze is infallible"); let mut mu = [0u8; MLDSA_MU_LEN]; let bytes_written = h.squeeze_out(&mut mu); @@ -625,29 +403,10 @@ impl< }; // 24: ๐œŽ โ† ML-DSA.Sign_internal(๐‘ ๐‘˜, ๐‘€', ๐‘Ÿ๐‘›๐‘‘) - let bytes_written = MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::sign_mu_deterministic_out(sk, A_hat, &mu, rnd, output)?; + let bytes_written = + MLDSA::::sign_mu_deterministic_out( + sk, A_hat, &mu, rnd, output, + )?; Ok(bytes_written) } @@ -688,27 +447,27 @@ impl< sk: None, seed: Some(seed.clone()), pk: None, - hash: HASH::default(), + hash: ::default(), ctx, ctx_len, }) } /// Same as [`SignatureVerifier::verify`], but verifies from an [`MLDSAPublicKeyExpanded`]. pub fn verify_with_expanded_key( - pk: &MLDSAPublicKeyExpanded, + pk: &MLDSAPublicKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8], ) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; - _ = HASH::default().hash_out(msg, &mut ph_m); + _ = ::default().hash_out(msg, &mut ph_m); Self::verify_ph_internal(&pk.pk, Some(&pk.A_hat()), &ph_m, ctx, sig) } fn verify_ph_internal( pk: &PK, - A_hat: Option<&Matrix>, + A_hat: Option<&::MatrixA>, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, sig: &[u8], @@ -738,7 +497,8 @@ impl< h.absorb(&[1u8]).expect("absorb before squeeze is infallible"); h.absorb(&[ctx.len() as u8]).expect("absorb before squeeze is infallible"); h.absorb(ctx).expect("absorb before squeeze is infallible"); - h.absorb(HASH::OID_DER).expect("absorb before squeeze is infallible"); + h.absorb(::OID_DER) + .expect("absorb before squeeze is infallible"); h.absorb(ph).expect("absorb before squeeze is infallible"); let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); @@ -747,107 +507,32 @@ impl< }; match A_hat { - Some(A_hat) => MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::verify_mu(pk, Some(A_hat), &mu, sig_sized), - None => MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::verify_mu(pk, Some(&pk.A_hat()), &mu, sig_sized), + Some(A_hat) => MLDSA::::verify_mu( + pk, + Some(A_hat), + &mu, + sig_sized, + ), + None => MLDSA::::verify_mu( + pk, + Some(&pk.A_hat()), + &mu, + sig_sized, + ), } } } impl< - HASH: Hash + AlgorithmOID + Default, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> Signer - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> Signer for HashMLDSA { /// Algorithm 4 HashML-DSA.Sign(๐‘ ๐‘˜, ๐‘€ , ๐‘๐‘ก๐‘ฅ, PH) /// Generate a โ€œpre-hashโ€ ML-DSA signature. @@ -867,7 +552,7 @@ impl< output.fill(0); let mut ph_m = [0u8; PH_LEN]; - _ = HASH::default().hash_out(msg, &mut ph_m); + _ = ::default().hash_out(msg, &mut ph_m); Self::sign_ph_out(sk, &ph_m, ctx, output) } @@ -879,7 +564,7 @@ impl< sk: Some(sk.clone()), seed: None, pk: None, - hash: HASH::default(), + hash: ::default(), ctx, ctx_len, }) @@ -946,60 +631,19 @@ impl< } impl< - HASH: Hash + AlgorithmOID + Default, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> SignatureVerifier - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> SignatureVerifier for HashMLDSA { fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; - _ = HASH::default().hash_out(msg, &mut ph_m); + _ = ::default().hash_out(msg, &mut ph_m); Self::verify_ph(pk, &ph_m, ctx, sig) } @@ -1012,7 +656,7 @@ impl< sk: None, seed: None, pk: Some(pk.clone()), - hash: HASH::default(), + hash: ::default(), ctx, ctx_len, }) @@ -1033,56 +677,16 @@ impl< } impl< - HASH: Hash + AlgorithmOID + Default, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MASK_LEN: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, > PHSigner - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > + for HashMLDSA { fn sign_ph( sk: &SK, @@ -1114,56 +718,16 @@ impl< } impl< - HASH: Hash + AlgorithmOID + Default, + P: HashMLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MASK_LEN: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, > PHSignatureVerifier - for HashMLDSA< - HASH, - PH_LEN, - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > + for HashMLDSA { fn verify_ph( pk: &PK, diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 2bea9873..15d0cb01 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -148,6 +148,7 @@ pub mod hash_mldsa; mod matrix; pub mod mldsa; mod mldsa_keys; +mod params; mod polynomial; /*** Exported types ***/ @@ -172,14 +173,6 @@ pub use mldsa::ML_DSA_44_NAME; pub use mldsa::ML_DSA_65_NAME; pub use mldsa::ML_DSA_87_NAME; -pub use hash_mldsa::HASH_ML_DSA_44_with_SHA256_NAME; -pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA256_NAME; -pub use hash_mldsa::HASH_ML_DSA_87_with_SHA256_NAME; - -pub use hash_mldsa::HASH_ML_DSA_44_with_SHA512_NAME; -pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA512_NAME; -pub use hash_mldsa::HASH_ML_DSA_87_WITH_SHA512_NAME; - pub use mldsa::{MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSA_SEED_LEN, MLDSA_TR_LEN}; pub use mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; pub use mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; @@ -187,4 +180,10 @@ pub use mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; pub use mldsa::SUSPENDED_MU_BUILDER_STATE_LEN; -pub use matrix::Matrix; +pub use hash_mldsa::HASH_ML_DSA_44_with_SHA256_NAME; +pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA256_NAME; +pub use hash_mldsa::HASH_ML_DSA_87_with_SHA256_NAME; + +pub use hash_mldsa::HASH_ML_DSA_44_with_SHA512_NAME; +pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA512_NAME; +pub use hash_mldsa::HASH_ML_DSA_87_WITH_SHA512_NAME; diff --git a/crypto/mldsa/src/matrix.rs b/crypto/mldsa/src/matrix.rs index 916fe47d..e08bb62f 100644 --- a/crypto/mldsa/src/matrix.rs +++ b/crypto/mldsa/src/matrix.rs @@ -3,11 +3,32 @@ use crate::aux_functions::multiply_ntt; use crate::mldsa::H; +use crate::params::MLDSAParams; use crate::polynomial::Polynomial; use bouncycastle_core::traits::XOF; use bouncycastle_utils::secret::ZeroizablePrimitive; use core::ops::{Index, IndexMut}; +/// The operations this crate performs on the public matrix ๐€ฬ‚. +/// +/// [`Matrix`] is the only implementation; see the module docs for why the trait exists. +pub(crate) trait MatrixTrait: Sized + Clone { + /// The vector this matrix can be applied to: an element of ๐‘…^โ„“. + type VecL: VectorTrait; + /// The vector applying this matrix produces: an element of ๐‘…^๐‘˜. + type VecK: VectorTrait; + + /// A matrix with every coefficient set to zero. + fn new() -> Self; + + /// Overwrites the polynomial at `elems[row][col]`. + fn set_elem(&mut self, row: usize, col: usize, p: Polynomial); + + /// Algorithm 48 MatrixVectorNTT(๐Œ, ๐ฏ) + /// Computes the product ๐Œ โˆ˜ฬ‚ ๐ฏ_hat of a matrix ๐Œ_hat and a vector ๐ฏ_hat over ๐‘‡๐‘ž. + fn matrix_vector_ntt(&self, v: &Self::VecL) -> Self::VecK; +} + /// A matrix over the ML-DSA ring. #[derive(Clone)] pub struct Matrix { @@ -47,8 +68,88 @@ impl Matrix { } } +impl MatrixTrait for Matrix { + type VecL = Vector; + type VecK = Vector; + + fn new() -> Self { + Matrix::new() + } + + fn set_elem(&mut self, row: usize, col: usize, p: Polynomial) { + self.elems[row][col] = p; + } + + fn matrix_vector_ntt(&self, v: &Vector) -> Vector { + Matrix::matrix_vector_ntt(self, v) + } +} + +/// The operations this crate performs on a vector of polynomials, i.e. on an element of ๐‘…^LEN. +/// +/// [`Vector`] is the only implementation; the trait exists so that code generic over a parameter +/// set can operate on [`MLDSAParams::VecK`] and [`MLDSAParams::VecL`] without knowing their length. +pub trait VectorTrait: + Sized + Copy + ZeroizablePrimitive + Index + IndexMut +{ + /// The number of polynomial coordinates, i.e. ๐‘˜ or โ„“. + const LEN: usize; + + /// A vector with every coefficient set to zero. + fn new() -> Self; + + /// The coordinates, for iteration and chunking. + fn elems(&self) -> &[Polynomial]; + /// The coordinates, for iteration and chunking. + fn elems_mut(&mut self) -> &mut [Polynomial]; + + /// Algorithm 46 AddVectorNTT(๐ฏ, ๐ฐ)ฬ‚ + /// Computes the sum ๐ฏ_hat + ๐ฐ_hat of two vectors ๐ฏ_hat, ๐ฐ_hat over ๐‘‡๐‘ž. + fn add_vector_ntt(&mut self, s: &Self); + + /// Subtracts another vector from this one, coordinatewise. + fn sub_vector(&self, s: &Self) -> Self; + + /// Algorithm 47 ScalarVectorNTT(๐‘,ฬ‚ ๐ฏ)ฬ‚ + /// Computes the product ๐‘_hat * ๐ฏ_hat of a scalar ๐‘_hat and a vector ๐ฏ_hat over ๐‘‡๐‘ž. + fn scalar_vector_ntt(&self, w: &Polynomial) -> Self; + + /// Adds ๐‘ž to every negative coefficient. + fn conditional_add_q(&mut self); + + /// Montgomery-reduces every coefficient. + fn reduce(&mut self); + + /// Applies Algorithm 41 NTT(๐‘ค) to every coordinate. + fn ntt(&mut self); + + /// Applies Algorithm 42 NTTโˆ’1(๐‘ค_hat) to every coordinate. + fn inv_ntt(&mut self); + + /// Applies Algorithm 37 HighBits(๐‘Ÿ) coefficientwise. + fn high_bits(&self) -> Self; + + /// Applies Algorithm 38 LowBits(๐‘Ÿ) coefficientwise. + fn low_bits(&self) -> Self; + + /// Multiplies every coefficient by 2^๐‘‘. + fn shift_left_d(&self) -> Self; + + /// Tests whether any coefficient of any coordinate has absolute value at least `bound`. + /// See `Polynomial::check_norm` for why `bound` is not a const generic. + fn check_norm(&self, bound: i32) -> bool; + + /// Algorithm 28 w1Encode(๐ฐ1), fed straight into `h` rather than into a buffer. + fn w1_encode_and_hash(&self, h: &mut H); +} + +/// A vector of `LEN` polynomials, i.e. an element of ๐‘…^LEN. +/// +/// Public only because it is the value of [`MLDSAParams::VecK`] and [`MLDSAParams::VecL`]; its +/// fields and operations are crate-private, so from outside it is an opaque handle. Reach it +/// through [`VectorTrait`]. #[derive(Clone, Copy)] -pub(crate) struct Vector { +pub struct Vector { pub(crate) elems: [Polynomial; LEN], } @@ -75,21 +176,37 @@ impl Vector { pub(crate) const fn new() -> Self { Self { elems: [Polynomial::new(); LEN] } } +} + +impl VectorTrait for Vector { + const LEN: usize = LEN; + + fn new() -> Self { + Vector::new() + } + + fn elems(&self) -> &[Polynomial] { + &self.elems + } + + fn elems_mut(&mut self) -> &mut [Polynomial] { + &mut self.elems + } /// Algorithm 46 AddVectorNTT(๐ฏ, ๐ฐ)ฬ‚ /// Computes the sum ๐ฏ_hat + ๐ฐ_hat of two vectors ๐ฏ_hat, ๐ฐ_hat over ๐‘‡๐‘ž. /// Input: โ„“ โˆˆ โ„•, v_hat โˆˆ T^โ„“, w_hat โˆˆ ๐‘‡^โ„“ /// Output: u_hat โˆˆ T^โ„“_๐‘ž. /// Add another vector to this vector - pub(crate) fn add_vector_ntt(&mut self, s: &Self) { + fn add_vector_ntt(&mut self, s: &Self) { for i in 0..LEN { // perform montgomery addition of each polynomial in the vector self[i].add_ntt(&s[i]); } } - pub(crate) fn sub_vector(&self, s: &Self) -> Self { - let mut out = self.clone(); + fn sub_vector(&self, s: &Self) -> Self { + let mut out = *self; for i in 0..LEN { out[i].sub(&s[i]); } @@ -100,8 +217,8 @@ impl Vector { /// Computes the product ๐‘_hat * ๐ฏ_hat of a scalar ๐‘_hat and a vector ๐ฏ_hat over ๐‘‡๐‘ž. /// Input: ๐‘_hat โˆˆ ๐‘‡๐‘ž, โ„“ โˆˆ โ„•, ๐ฏ_hat โˆˆ ๐‘‡^โ„“ /// Output: ๐‘ž . - pub(crate) fn scalar_vector_ntt(&self, w: &Polynomial) -> Self { - let mut s_hat = Self::new(); + fn scalar_vector_ntt(&self, w: &Polynomial) -> Self { + let mut s_hat = Vector::::new(); for i in 0..LEN { s_hat[i] = multiply_ntt(&self[i], &w); } @@ -109,63 +226,63 @@ impl Vector { s_hat } - pub(crate) fn conditional_add_q(&mut self) { + fn conditional_add_q(&mut self) { for i in 0..LEN { self[i].conditional_add_q(); } } - pub(crate) fn reduce(&mut self) { + fn reduce(&mut self) { for i in 0..LEN { self[i].reduce(); } } - pub(crate) fn ntt(&mut self) { + fn ntt(&mut self) { for i in 0..LEN { self[i].ntt(); } } - pub(crate) fn inv_ntt(&mut self) { + fn inv_ntt(&mut self) { for i in 0..LEN { self[i].inv_ntt(); } } - pub(crate) fn high_bits(&self) -> Self { - let mut s = Self::new(); + fn high_bits(&self) -> Self { + let mut s = Vector::::new(); for i in 0..LEN { - s[i] = self[i].high_bits::(); + s[i] = self[i].high_bits::

(); } s } - pub(crate) fn low_bits(&self) -> Self { - let mut s = Self::new(); + fn low_bits(&self) -> Self { + let mut s = Vector::::new(); for i in 0..LEN { - s[i] = self[i].low_bits::(); + s[i] = self[i].low_bits::

(); } s } - pub(crate) fn shift_left(&self) -> Self { - let mut out = self.clone(); + fn shift_left_d(&self) -> Self { + let mut out = *self; for i in 0..LEN { - out[i].shift_left::(); + out[i].shift_left_d(); } out } - pub(crate) fn check_norm(&self) -> bool { + fn check_norm(&self, bound: i32) -> bool { // Fine that this is not constant-time because it is used in a rejection loop -- the early quit leads to rejection. for x in self.elems.iter() { - if x.check_norm::() { + if x.check_norm(bound) { return true; } } @@ -177,7 +294,7 @@ impl Vector { /// Input: ๐ฐ1 โˆˆ ๐‘…๐‘˜ whose polynomial coordinates have coefficients in \[0, (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1]. /// Output: A byte string representation ๐ฐ1_tilde โˆˆ ๐”น32๐‘˜โ‹…bitlen ((๐‘žโˆ’1)/(2๐›พ2)โˆ’1) /// Optimized from FIPS 204 to feed into the hash one row at a time to reduce overall memory footprint. - pub(crate) fn w1_encode_and_hash(&self, h: &mut H) { + fn w1_encode_and_hash(&self, h: &mut H) { // 1: ๐ฐฬƒ1 โ† () // Nothing needs to be allocated since it is being fed into the hash row-wise @@ -185,8 +302,7 @@ impl Vector { // 3: ๐ฐฬƒ1 โ† ๐ฐฬƒ1 || SimpleBitPack (๐ฐ1[๐‘–], (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1) // 4: end for for w in self.elems.iter() { - h.absorb(&w.w1_encode::()) - .expect("absorb before squeeze is infallible"); + h.absorb(w.w1_encode::

().as_ref()).expect("absorb before squeeze is infallible"); } } } diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 842841a4..9e003579 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -479,9 +479,10 @@ use crate::aux_functions::{ expand_mask, expandA, expandS, make_hint_vecs, power_2_round_vec, sample_in_ball, sig_decode, sig_encode, use_hint_vecs, }; -use crate::matrix::{Matrix, Vector}; +use crate::matrix::{MatrixTrait, VectorTrait}; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPrivateKeyTrait}; use crate::mldsa_keys::{MLDSAPublicKeyInternalTrait, MLDSAPublicKeyTrait}; +use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams}; use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPublicKeyExpanded, @@ -493,7 +494,7 @@ use bouncycastle_core::traits::{ }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; -use bouncycastle_utils::secret::Secret; +use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; use core::marker::PhantomData; // imports needed just for docs @@ -511,7 +512,7 @@ pub const ML_DSA_65_NAME: &str = "ML-DSA-65"; /// pub const ML_DSA_87_NAME: &str = "ML-DSA-87"; -// From FIPS 204 Table 1 and Table 2 +/*** From FIPS 204 Table 1 and Table 2 ***/ // Constants that are the same for all parameter sets pub(crate) const N: usize = 256; @@ -529,95 +530,28 @@ pub const MLDSA_MU_LEN: usize = 64; pub(crate) const POLY_T0PACKED_LEN: usize = 416; pub(crate) const POLY_T1PACKED_LEN: usize = 320; -/* ML-DSA-44 params */ - -/// Length of the \[u8] holding a ML-DSA-44 public key. -pub const MLDSA44_PK_LEN: usize = 1312; -/// Length of the \[u8] holding a ML-DSA-44 private key. -pub const MLDSA44_SK_LEN: usize = 2560; -/// Length of the \[u8] holding a ML-DSA-44 signature value. -pub const MLDSA44_SIG_LEN: usize = 2420; -pub(crate) const MLDSA44_TAU: i32 = 39; -pub(crate) const MLDSA44_LAMBDA: i32 = 128; -pub(crate) const MLDSA44_GAMMA1: i32 = 1 << 17; -pub(crate) const MLDSA44_GAMMA2: i32 = (q - 1) / 88; // mutants note: because of the bitshifting, the "- 1" ends up not mattering -pub(crate) const MLDSA44_k: usize = 4; -pub(crate) const MLDSA44_l: usize = 4; -pub(crate) const MLDSA44_ETA: usize = 2; -pub(crate) const MLDSA44_BETA: i32 = 78; -pub(crate) const MLDSA44_OMEGA: i32 = 80; - -// Useful derived values -pub(crate) const MLDSA44_C_TILDE: usize = 32; -pub(crate) const MLDSA44_POLY_Z_PACKED_LEN: usize = 576; -pub(crate) const MLDSA44_POLY_W1_PACKED_LEN: usize = 192; -pub(crate) const MLDSA44_LAMBDA_over_4: usize = 128 / 4; -pub(crate) const MLDSA44_GAMMA1_MINUS_BETA: i32 = MLDSA44_GAMMA1 - MLDSA44_BETA; -pub(crate) const MLDSA44_GAMMA2_MINUS_BETA: i32 = MLDSA44_GAMMA2 - MLDSA44_BETA; - -// Alg 32 -// 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) -pub(crate) const MLDSA44_GAMMA1_MASK_LEN: usize = 576; // 32*(1 + bitlen (๐›พ1 โˆ’ 1) ) - -/* ML-DSA-65 params */ - -/// Length of the \[u8] holding a ML-DSA-65 public key. -pub const MLDSA65_PK_LEN: usize = 1952; -/// Length of the \[u8] holding a ML-DSA-65 private key. -pub const MLDSA65_SK_LEN: usize = 4032; -/// Length of the \[u8] holding a ML-DSA-65 signature value. -pub const MLDSA65_SIG_LEN: usize = 3309; -pub(crate) const MLDSA65_TAU: i32 = 49; -pub(crate) const MLDSA65_LAMBDA: i32 = 192; -pub(crate) const MLDSA65_GAMMA1: i32 = 1 << 19; -pub(crate) const MLDSA65_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering -pub(crate) const MLDSA65_k: usize = 6; -pub(crate) const MLDSA65_l: usize = 5; -pub(crate) const MLDSA65_ETA: usize = 4; -pub(crate) const MLDSA65_BETA: i32 = 196; -pub(crate) const MLDSA65_OMEGA: i32 = 55; - -// Useful derived values -pub(crate) const MLDSA65_C_TILDE: usize = 48; -pub(crate) const MLDSA65_POLY_Z_PACKED_LEN: usize = 640; -pub(crate) const MLDSA65_POLY_W1_PACKED_LEN: usize = 128; -pub(crate) const MLDSA65_LAMBDA_over_4: usize = 192 / 4; -pub(crate) const MLDSA65_GAMMA1_MINUS_BETA: i32 = MLDSA65_GAMMA1 - MLDSA65_BETA; -pub(crate) const MLDSA65_GAMMA2_MINUS_BETA: i32 = MLDSA65_GAMMA2 - MLDSA65_BETA; - -// Alg 32 -// 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) -pub(crate) const MLDSA65_GAMMA1_MASK_LEN: usize = 640; - -/* ML-DSA-87 params */ - -/// Length of the \[u8] holding a ML-DSA-87 public key. -pub const MLDSA87_PK_LEN: usize = 2592; -/// Length of the \[u8] holding a ML-DSA-87 private key. -pub const MLDSA87_SK_LEN: usize = 4896; -/// Length of the \[u8] holding a ML-DSA-87 signature value. -pub const MLDSA87_SIG_LEN: usize = 4627; -pub(crate) const MLDSA87_TAU: i32 = 60; -pub(crate) const MLDSA87_LAMBDA: i32 = 256; -pub(crate) const MLDSA87_GAMMA1: i32 = 1 << 19; -pub(crate) const MLDSA87_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering -pub(crate) const MLDSA87_k: usize = 8; -pub(crate) const MLDSA87_l: usize = 7; -pub(crate) const MLDSA87_ETA: usize = 2; -pub(crate) const MLDSA87_BETA: i32 = 120; -pub(crate) const MLDSA87_OMEGA: i32 = 75; - -// Useful derived values -pub(crate) const MLDSA87_C_TILDE: usize = 64; -pub(crate) const MLDSA87_POLY_Z_PACKED_LEN: usize = 640; -pub(crate) const MLDSA87_POLY_W1_PACKED_LEN: usize = 128; -pub(crate) const MLDSA87_LAMBDA_over_4: usize = 256 / 4; -pub(crate) const MLDSA87_GAMMA1_MINUS_BETA: i32 = MLDSA87_GAMMA1 - MLDSA87_BETA; -pub(crate) const MLDSA87_GAMMA2_MINUS_BETA: i32 = MLDSA87_GAMMA2 - MLDSA87_BETA; - -// Alg 32 -// 1: ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) -pub(crate) const MLDSA87_GAMMA1_MASK_LEN: usize = 640; +/*** Re-exporting length constants that a caller will need instead of the entire Params objects which contains a bunch of internal algorithm detail ***/ + +/// Length of the \[u8] holding an ML-DSA-44 public key. +pub const MLDSA44_PK_LEN: usize = MLDSA44Params::PK_LEN; +/// Length of the \[u8] holding an ML-DSA-44 private key. +pub const MLDSA44_SK_LEN: usize = MLDSA44Params::SK_LEN; +/// Length of the \[u8] holding an ML-DSA-44 signature value. +pub const MLDSA44_SIG_LEN: usize = MLDSA44Params::SIG_LEN; + +/// Length of the \[u8] holding an ML-DSA-65 public key. +pub const MLDSA65_PK_LEN: usize = MLDSA65Params::PK_LEN; +/// Length of the \[u8] holding an ML-DSA-65 private key. +pub const MLDSA65_SK_LEN: usize = MLDSA65Params::SK_LEN; +/// Length of the \[u8] holding an ML-DSA-65 signature value. +pub const MLDSA65_SIG_LEN: usize = MLDSA65Params::SIG_LEN; + +/// Length of the \[u8] holding an ML-DSA-87 public key. +pub const MLDSA87_PK_LEN: usize = MLDSA87Params::PK_LEN; +/// Length of the \[u8] holding an ML-DSA-87 private key. +pub const MLDSA87_SK_LEN: usize = MLDSA87Params::SK_LEN; +/// Length of the \[u8] holding an ML-DSA-87 signature value. +pub const MLDSA87_SIG_LEN: usize = MLDSA87Params::SIG_LEN; // Typedefs just to make the algorithms look more like the FIPS 204 sample code. pub(crate) type H = SHAKE256; @@ -627,110 +561,63 @@ pub(crate) type G = SHAKE128; /// The ML-DSA-44 algorithm. pub type MLDSA44 = MLDSA< + MLDSA44Params, + MLDSA44PublicKey, + MLDSA44PrivateKey, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN, - MLDSA44PublicKey, - MLDSA44PrivateKey, - MLDSA44_TAU, - MLDSA44_LAMBDA, - MLDSA44_GAMMA1, - MLDSA44_GAMMA2, - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, - MLDSA44_BETA, - MLDSA44_OMEGA, - MLDSA44_C_TILDE, - MLDSA44_POLY_Z_PACKED_LEN, - MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MINUS_BETA, - MLDSA44_GAMMA2_MINUS_BETA, - MLDSA44_GAMMA1_MASK_LEN, >; -impl Algorithm for MLDSA44 { - const ALG_NAME: &'static str = ML_DSA_44_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } -impl AlgorithmOID for MLDSA44 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; -} - /// The ML-DSA-65 algorithm. pub type MLDSA65 = MLDSA< + MLDSA65Params, + MLDSA65PublicKey, + MLDSA65PrivateKey, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN, - MLDSA65PublicKey, - MLDSA65PrivateKey, - MLDSA65_TAU, - MLDSA65_LAMBDA, - MLDSA65_GAMMA1, - MLDSA65_GAMMA2, - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, - MLDSA65_BETA, - MLDSA65_OMEGA, - MLDSA65_C_TILDE, - MLDSA65_POLY_Z_PACKED_LEN, - MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MINUS_BETA, - MLDSA65_GAMMA2_MINUS_BETA, - MLDSA65_GAMMA1_MASK_LEN, >; -impl Algorithm for MLDSA65 { - const ALG_NAME: &'static str = ML_DSA_65_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } -impl AlgorithmOID for MLDSA65 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; -} - /// The ML-DSA-87 algorithm. pub type MLDSA87 = MLDSA< + MLDSA87Params, + MLDSA87PublicKey, + MLDSA87PrivateKey, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN, - MLDSA87PublicKey, - MLDSA87PrivateKey, - MLDSA87_TAU, - MLDSA87_LAMBDA, - MLDSA87_GAMMA1, - MLDSA87_GAMMA2, - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, - MLDSA87_BETA, - MLDSA87_OMEGA, - MLDSA87_C_TILDE, - MLDSA87_POLY_Z_PACKED_LEN, - MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MINUS_BETA, - MLDSA87_GAMMA2_MINUS_BETA, - MLDSA87_GAMMA1_MASK_LEN, >; -impl Algorithm for MLDSA87 { - const ALG_NAME: &'static str = ML_DSA_87_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +/// The name and claimed strength of an ML-DSA algorithm are properties of its parameter set, so +/// one impl covers all three; `MLDSAParams` is sealed, so those are the only three that exist. +impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, +> Algorithm for MLDSA +{ + const ALG_NAME: &'static str = P::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } -impl AlgorithmOID for MLDSA87 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; + +/// The OIDs NIST assigned in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 }, +/// id-ml-dsa-65 { sigAlgs 18 } and id-ml-dsa-87 { sigAlgs 19 }. As with [`Algorithm`], the values +/// belong to the parameter set, so one impl covers all three. +impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, +> AlgorithmOID for MLDSA +{ + const OID: &'static [u32] = P::OID; + const OID_DER: &'static [u8] = P::OID_DER; } /// The core internal implementation of the ML-DSA algorithm. @@ -738,30 +625,14 @@ impl AlgorithmOID for MLDSA87 { /// but it shouldn't ever need to be used directly. /// Please use the named public types [`MLDSA44`], [`MLDSA65`], [`MLDSA87`] instead. pub struct MLDSA< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_VEC_H_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, > { - _phantom: PhantomData<(PK, SK)>, + _phantom: PhantomData<(P, PK, SK)>, /// used for streaming the message for both signing and verifying mu_builder: MuBuilder, @@ -779,52 +650,13 @@ pub struct MLDSA< } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> - MLDSA< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> MLDSA { /// Implements Algorithm 6 of FIPS 204 /// Note: NIST has made a special exception in the FIPS 204 FAQ that this _internal function @@ -844,7 +676,7 @@ impl< )); } - if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if seed.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(SignatureError::KeyGenError( "Seed SecurityStrength must match algorithm security strength", )); @@ -859,8 +691,8 @@ impl< // scope for h let mut h = H::default(); h.absorb(seed.ref_to_bytes()).expect("absorb before squeeze is infallible"); - h.absorb(&(k as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); - h.absorb(&(l as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); + h.absorb(&(P::k as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); + h.absorb(&(P::l as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); let bytes_written = h.squeeze_out(&mut rho); debug_assert_eq!(bytes_written, 32); let mut rho_prime: [u8; 64] = [0u8; 64]; @@ -870,7 +702,7 @@ impl< debug_assert_eq!(bytes_written, 32); // 4: (๐ฌ1, ๐ฌ2) โ† ExpandS(๐œŒโ€ฒ) - let (mut s1, s2) = expandS::(&rho_prime); + let (mut s1, s2) = expandS::

(&rho_prime); s1.ntt(); (s1, s2) @@ -879,7 +711,7 @@ impl< let t_hat = { // scope for s1_hat // 3: ๐€_hat โ† ExpandA(๐œŒ) โ–ท ๐€ is generated and stored in NTT representation as ๐€ - let A_hat = expandA::(&rho); + let A_hat = expandA::

(&rho); // 5: ๐ญ โ† NTTโˆ’1(๐€ โˆ˜ NTT(๐ฌ1)) + ๐ฌ2 // โ–ท compute ๐ญ = ๐€๐ฌ1 + ๐ฌ2 @@ -896,7 +728,7 @@ impl< // 6: (๐ญ1, ๐ญ0) โ† Power2Round(๐ญ) // โ–ท compress ๐ญ // โ–ท PowerTwoRound is applied componentwise (see explanatory text in Section 7.4) - power_2_round_vec::(&t) + power_2_round_vec(&t) }; // 8: ๐‘๐‘˜ โ† pkEncode(๐œŒ, ๐ญ1) @@ -928,7 +760,7 @@ impl< /// modified to take an externally-computed mu instead of M', and to take the public matrix A_hat fn sign_internal( sk: &SK, - A_hat: &Matrix, + A_hat: &P::MatrixA, mu: &[u8; 64], rnd: [u8; 32], output: &mut [u8; SIG_LEN], @@ -972,22 +804,22 @@ impl< // โ–ท rejection sampling loop // these need to be outside the loop because they form the encoded signature value - let mut sig_val_c_tilde = [0u8; LAMBDA_over_4]; - let mut sig_val_z: Vector; - let mut sig_val_h: Vector; + let mut sig_val_c_tilde = ::ZEROED; + let mut sig_val_z: P::VecL; + let mut sig_val_h: P::VecK; loop { // FIPS 204 s. 6.2 allows: // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value." // mutants note: there is no test for this because, at this point, // we don't know of a KAT that will exceed this limit. - if kappa > 1000 * k as u16 { + if kappa > 1000 * P::k as u16 { return Err(SignatureError::GenericError( "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.", )); } // 11: ๐ฒ โˆˆ ๐‘…^โ„“ โ† ExpandMask(๐œŒโ€ณ, ๐œ…) - let mut y = expand_mask::(&rho_p_p, kappa); + let mut y = expand_mask::

(&rho_p_p, kappa); let w = { // scope for y_hat @@ -1002,7 +834,7 @@ impl< // 13: ๐ฐ1 โ† HighBits(๐ฐ) // โ–ท signerโ€™s commitment - let w1 = w.high_bits::(); + let w1 = w.high_bits::

(); { // scope for h @@ -1010,15 +842,15 @@ impl< // โ–ท commitment hash let mut hash = H::new(); hash.absorb(mu).expect("absorb before squeeze is infallible"); - w1.w1_encode_and_hash::(&mut hash); - hash.squeeze_out(&mut sig_val_c_tilde); + w1.w1_encode_and_hash::

(&mut hash); + hash.squeeze_out(sig_val_c_tilde.as_mut()); } // 16: ๐‘ โˆˆ ๐‘…๐‘ž โ† SampleInBall(c_tilde) // โ–ท verifierโ€™s challenge let c_hat = { // scope for c - let mut c = sample_in_ball::(&sig_val_c_tilde); + let mut c = sample_in_ball::

(&sig_val_c_tilde); // 17: ๐‘_hat โ† NTT(๐‘) c.ntt(); @@ -1038,8 +870,8 @@ impl< // โ–ท validity checks // This is done out-of-order on purpose for performance reasons: // rejection sampling check is done before any extra heavy computation - if sig_val_z.check_norm::() { - kappa += l as u16; + if sig_val_z.check_norm(P::gamma1_minus_beta) { + kappa += P::l as u16; continue; }; @@ -1048,7 +880,7 @@ impl< cs2.inv_ntt(); // 21: ๐ซ0 โ† LowBits(๐ฐ โˆ’ โŸจโŸจ๐‘๐ฌ2โŸฉโŸฉ) - let mut r0 = w.sub_vector(&cs2).low_bits::(); + let mut r0 = w.sub_vector(&cs2).low_bits::

(); // 23 (second half): if ||๐ณ||โˆž โ‰ฅ ๐›พ1 โˆ’ ๐›ฝ or ||๐ซ0||โˆž โ‰ฅ ๐›พ2 โˆ’ ๐›ฝ then (z, h) โ† โŠฅ // โ–ท validity checks @@ -1058,8 +890,8 @@ impl< // and checking whether โ€–r0โ€–โˆž < ฮณ2 โˆ’ ฮฒ and r1 = w1, it is equivalent to just check that // โ€–w0 โˆ’ cs2โ€–โˆž < ฮณ2 โˆ’ ฮฒ, where w0 is the low part of w. If this check passes, w0 โˆ’ cs2 // is the low part of w โˆ’ cs2." - if r0.check_norm::() { - kappa += l as u16; + if r0.check_norm(P::gamma2_minus_beta) { + kappa += P::l as u16; continue; }; @@ -1071,8 +903,8 @@ impl< // This is done out-of-order on purpose for performance reasons: // rejection sampling check is done before any extra heavy computation // mutants note: there is currently no unit test that triggers this branch - if ct0.check_norm::() { - kappa += l as u16; + if ct0.check_norm(P::gamma2) { + kappa += P::l as u16; continue; }; @@ -1083,15 +915,15 @@ impl< let hint_hamming_weight: i32; sig_val_h = { // scope for hint - let (hint, inner_hint_hamming_weight) = make_hint_vecs::(&r0, &w1); + let (hint, inner_hint_hamming_weight) = make_hint_vecs::

(&r0, &w1); hint_hamming_weight = inner_hint_hamming_weight; hint }; // 28 (second half): if ||โŸจโŸจ๐‘๐ญ0โŸฉโŸฉ||โˆž โ‰ฅ ๐›พ2 or the number of 1โ€™s in ๐ก is greater than ๐œ”, then (z, h) โ† โŠฅ // mutants note: there is no test KAT that triggers this branch - if hint_hamming_weight > OMEGA { - kappa += l as u16; + if hint_hamming_weight > P::omega { + kappa += P::l as u16; continue; }; @@ -1106,9 +938,7 @@ impl< // 33: ๐œŽ โ† sigEncode(๐‘, ๐ณฬƒ modยฑ๐‘ž, ๐ก) let bytes_written = - sig_encode::( - &sig_val_c_tilde, &sig_val_z, &sig_val_h, output, - ); + sig_encode::(&sig_val_c_tilde, &sig_val_z, &sig_val_h, output); Ok(bytes_written) } @@ -1119,7 +949,7 @@ impl< /// Input: Signature ๐œŽ โˆˆ ๐”น๐œ†/4+โ„“โ‹…32โ‹…(1+bitlen (๐›พ1โˆ’1))+๐œ”+๐‘˜. fn verify_internal( pk: &PK, - A_hat: &Matrix, + A_hat: &P::MatrixA, mu: &[u8; 64], sig: &[u8; SIG_LEN], ) -> Result<(), SignatureError> { @@ -1129,12 +959,11 @@ impl< // 2: (๐‘_tilde, ๐ณ, ๐ก) โ† sigDecode(๐œŽ) // โ–ท signerโ€™s commitment hash c_tilde, response ๐ณ, and hint ๐ก // 3: if ๐ก = โŠฅ then return false - let (c_tilde, z, h) = - sig_decode::(&sig) - .map_err(|_| SignatureError::SignatureVerificationFailed)?; + let (c_tilde, z, h) = sig_decode::(&sig) + .map_err(|_| SignatureError::SignatureVerificationFailed)?; // 13 (first half) return [[ ||๐ณ||โˆž < ๐›พ1 โˆ’ ๐›ฝ]] - if z.check_norm::() { + if z.check_norm(P::gamma1_minus_beta) { return Err(SignatureError::SignatureVerificationFailed); } @@ -1151,7 +980,7 @@ impl< // 8: ๐‘ โˆˆ ๐‘…๐‘ž โ† SampleInBall(c_tilde) let c_hat = { - let mut c = sample_in_ball::(&c_tilde); + let mut c = sample_in_ball::

(&c_tilde); c.ntt(); c @@ -1173,7 +1002,7 @@ impl< }; let ct1 = { // potential optimization -- pre-compute this on key load? - let mut t1_shift_hat = pk.t1().shift_left::(); + let mut t1_shift_hat = pk.t1().shift_left_d(); t1_shift_hat.ntt(); t1_shift_hat.scalar_vector_ntt(&c_hat) }; @@ -1183,23 +1012,23 @@ impl< // 10: ๐ฐ1โ€ฒ โ† UseHint(๐ก, ๐ฐ'_approx) // โ–ท reconstruction of signerโ€™s commitment - use_hint_vecs::(&h, &wp_approx) + use_hint_vecs::

(&h, &wp_approx) }; // 12: ๐‘_tilde_p โ† H(๐œ‡||w1Encode(๐ฐ1'), ๐œ†/4) // โ–ท hash it; this should match ๐‘_tilde let c_tilde_p = { - let mut c_tilde_p = [0u8; LAMBDA_over_4]; + let mut c_tilde_p = ::ZEROED; let mut hash = H::new(); hash.absorb(mu).expect("absorb before squeeze is infallible"); - w1p.w1_encode_and_hash::(&mut hash); - hash.squeeze_out(&mut c_tilde_p); + w1p.w1_encode_and_hash::

(&mut hash); + hash.squeeze_out(c_tilde_p.as_mut()); c_tilde_p }; // verification probably doesn't technically need to be constant-time, but why not? // 13 (second half): return [[ ||๐ณ||โˆž < ๐›พ1 โˆ’ ๐›ฝ]] and [[๐‘ ฬƒ = ๐‘โ€ฒ ]] - if bouncycastle_utils::ct::ct_eq_bytes(&c_tilde, &c_tilde_p) { + if bouncycastle_utils::ct::ct_eq_bytes(c_tilde.as_ref(), c_tilde_p.as_ref()) { Ok(()) } else { Err(SignatureError::SignatureVerificationFailed) @@ -1208,52 +1037,13 @@ impl< } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> MLDSATrait - for MLDSA< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> MLDSATrait for MLDSA { fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { Self::keygen_internal(seed) @@ -1290,21 +1080,21 @@ impl< MuBuilder::compute_mu(tr, msg, ctx) } fn compute_mu_from_pk( - pk: &impl MLDSAPublicKeyTrait, + pk: &impl MLDSAPublicKeyTrait, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; 64], SignatureError> { MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx) } fn compute_mu_from_sk( - sk: &impl MLDSAPrivateKeyTrait, + sk: &impl MLDSAPrivateKeyTrait, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; 64], SignatureError> { MuBuilder::compute_mu(&sk.tr(), msg, ctx) } fn sign_with_expanded_key( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError> { @@ -1313,7 +1103,7 @@ impl< } fn sign_with_expanded_key_out( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, out: &mut [u8; SIG_LEN], @@ -1326,7 +1116,7 @@ impl< fn sign_mu( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN]; @@ -1336,7 +1126,7 @@ impl< } fn sign_mu_out( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], output: &mut [u8; SIG_LEN], ) -> Result { @@ -1348,8 +1138,8 @@ impl< Self::sign_mu_deterministic_out(sk, A_hat, mu, rnd, output) } fn sign_mu_with_expanded_key( - sk: &MLDSAPrivateKeyExpanded, - A_hat: Option<&Matrix>, + sk: &MLDSAPrivateKeyExpanded, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN]; @@ -1358,8 +1148,8 @@ impl< Ok(out) } fn sign_mu_with_expanded_key_out( - sk: &MLDSAPrivateKeyExpanded, - A_hat: Option<&Matrix>, + sk: &MLDSAPrivateKeyExpanded, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], out: &mut [u8; SIG_LEN], ) -> Result { @@ -1370,7 +1160,7 @@ impl< fn sign_mu_deterministic( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], rnd: [u8; 32], ) -> Result<[u8; SIG_LEN], SignatureError> { @@ -1381,7 +1171,7 @@ impl< } fn sign_mu_deterministic_out( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], rnd: [u8; 32], output: &mut [u8; SIG_LEN], @@ -1409,7 +1199,7 @@ impl< /// This is a middle ground between keygen_from_seed()+sign_mu() and /// the fully streamed low-memory implementation. // TODO: benchmark peak memory + runtime against - // keygen_from_seed() + sign_mu_deterministic() to confirm the separate path earns being kept. + // keygen_from_seed() + sign_mu_deterministic() to confirm the separate path earns being kept. // Note: this path intentionally avoids the public key entirely // (no pkEncode / tr = H(pk)) since ฮผ is supplied externally. fn sign_mu_deterministic_from_seed_out( @@ -1436,7 +1226,7 @@ impl< )); } - if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if seed.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(SignatureError::KeyGenError( "Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87).", )); @@ -1453,8 +1243,8 @@ impl< let (rho, rho_prime, K) = { let mut h = H::default(); h.absorb(seed.ref_to_bytes()).expect("absorb before squeeze is infallible"); - h.absorb(&(k as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); - h.absorb(&(l as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); + h.absorb(&(P::k as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); + h.absorb(&(P::l as u8).to_le_bytes()).expect("absorb before squeeze is infallible"); let mut rho = [0u8; 32]; let bytes_written = h.squeeze_out(&mut rho); debug_assert_eq!(bytes_written, 32); @@ -1481,7 +1271,7 @@ impl< }; // 4: (๐ฌ1, ๐ฌ2) โ† ExpandS(๐œŒโ€ฒ) - let (s1, s2) = expandS::(&rho_prime); + let (s1, s2) = expandS::

(&rho_prime); (rho, rho_p_p, s1, s2) }; @@ -1494,7 +1284,7 @@ impl< // as 20 or even 80 times. So moving expandA() inside the loop would be a pretty drastic speed-for-memory tradeoff // whose generality falls out of the scope of this implementation. // It is left as an optimization that can be made by users that require further reduction of memory usage - let A_hat = expandA::(&rho); + let A_hat = expandA::

(&rho); // Alg 7; 8: ๐œ… โ† 0 // โ–ท initialize counter ๐œ… @@ -1507,21 +1297,21 @@ impl< // โ–ท rejection sampling loop // these need to be outside the loop because they form the encoded signature value - let mut sig_val_c_tilde = [0u8; LAMBDA_over_4]; - let mut sig_val_z: Vector; - let mut sig_val_h: Vector; + let mut sig_val_c_tilde = ::ZEROED; + let mut sig_val_z: P::VecL; + let mut sig_val_h: P::VecK; loop { // FIPS 204 s. 6.2 allows: // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value." // mutants note: there is no test for this because we don't know of a KAT that will exceed this limit. - if kappa > 1000 * k as u16 { + if kappa > 1000 * P::k as u16 { return Err(SignatureError::GenericError( "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.", )); } // Alg 7; 11: ๐ฒ โˆˆ ๐‘…^โ„“ โ† ExpandMask(๐œŒโ€ณ, ๐œ…) - let mut y = expand_mask::(&rho_p_p, kappa); + let mut y = expand_mask::

(&rho_p_p, kappa); let w = { // scope for y_hat @@ -1536,7 +1326,7 @@ impl< // Alg 7; 13: ๐ฐ1 โ† HighBits(๐ฐ) // โ–ท signerโ€™s commitment - let w1 = w.high_bits::(); + let w1 = w.high_bits::

(); { // scope for h @@ -1544,22 +1334,22 @@ impl< // โ–ท commitment hash let mut hash = H::new(); hash.absorb(mu).expect("absorb before squeeze is infallible"); - w1.w1_encode_and_hash::(&mut hash); - hash.squeeze_out(&mut sig_val_c_tilde); + w1.w1_encode_and_hash::

(&mut hash); + hash.squeeze_out(sig_val_c_tilde.as_mut()); } // Alg 7; 16: ๐‘ โˆˆ ๐‘…๐‘ž โ† SampleInBall(c_tilde) // โ–ท verifierโ€™s challenge let c_hat = { // scope for c - let mut c = sample_in_ball::(&sig_val_c_tilde); + let mut c = sample_in_ball::

(&sig_val_c_tilde); // 17: ๐‘_hat โ† NTT(๐‘) c.ntt(); c }; - let t_hat: Vector; + let t_hat: P::VecK; sig_val_z = { // scope for s1_hat, cs1 // Alg 7; 2: ๐ฌ1ฬ‚_hat โ† NTT(๐ฌ1) @@ -1591,13 +1381,13 @@ impl< // โ–ท validity checks // This is done out-of-order on purpose for performance reasons: // rejection sampling check is done before any extra heavy computation - if sig_val_z.check_norm::() { - kappa += l as u16; + if sig_val_z.check_norm(P::gamma1_minus_beta) { + kappa += P::l as u16; continue; }; - let t0: Vector; - let mut r0: Vector = { + let t0: P::VecK; + let mut r0: P::VecK = { // scope for s2_hat and cs2 // 3: ๐ฌ2ฬ‚_hat โ† NTT(๐ฌ2) let mut s2_hat = s2.clone(); @@ -1608,7 +1398,7 @@ impl< cs2.inv_ntt(); // 21: ๐ซ0 โ† LowBits(๐ฐ โˆ’ โŸจโŸจ๐‘๐ฌ2โŸฉโŸฉ) - let r0 = w.sub_vector(&cs2).low_bits::(); + let r0 = w.sub_vector(&cs2).low_bits::

(); // while s2_hat is in scope, derive t0 let mut t = t_hat; @@ -1619,7 +1409,7 @@ impl< // 6: (๐ญ1, ๐ญ0) โ† Power2Round(๐ญ) // โ–ท compress ๐ญ // โ–ท PowerTwoRound is applied componentwise (see explanatory text in Section 7.4) - let (_t1tmp, t0tmp) = power_2_round_vec::(&t); + let (_t1tmp, t0tmp) = power_2_round_vec(&t); t0 = t0tmp; r0 @@ -1627,14 +1417,14 @@ impl< // Alg 7; 23 (second half): if ||๐ณ||โˆž โ‰ฅ ๐›พ1 โˆ’ ๐›ฝ or ||๐ซ0||โˆž โ‰ฅ ๐›พ2 โˆ’ ๐›ฝ then (z, h) โ† โŠฅ // โ–ท validity checks - if r0.check_norm::() { + if r0.check_norm(P::gamma2_minus_beta) { // mutants note: mutants thinks this can be replaced with -=, but in practice that makes // the rejection sampling loop go forever, so is a false positive. - kappa += l as u16; + kappa += P::l as u16; continue; }; - let ct0: Vector = { + let ct0: P::VecK = { // scope for t0_hat // 4: ๐ญ0ฬ‚_hat โ† NTT(๐ญ0)ฬ‚ let mut t0_hat = t0.clone(); @@ -1650,8 +1440,8 @@ impl< // out-of-order on purpose for performance reasons: // might as well do the rejection sampling check before any extra heavy computation // mutants note: there is currently no unit test that triggers this branch - if ct0.check_norm::() { - kappa += l as u16; + if ct0.check_norm(P::gamma2) { + kappa += P::l as u16; continue; }; @@ -1662,15 +1452,15 @@ impl< let hint_hamming_weight: i32; sig_val_h = { // scope for hint - let (hint, inner_hint_hamming_weight) = make_hint_vecs::(&r0, &w1); + let (hint, inner_hint_hamming_weight) = make_hint_vecs::

(&r0, &w1); hint_hamming_weight = inner_hint_hamming_weight; hint }; // Alg 7; 28 (second half): if ||โŸจโŸจ๐‘๐ญ0โŸฉโŸฉ||โˆž โ‰ฅ ๐›พ2 or the number of 1โ€™s in ๐ก is greater than ๐œ”, then (z, h) โ† โŠฅ // mutants note: there is currently no unit test that triggers this branch - if hint_hamming_weight > OMEGA { - kappa += l as u16; + if hint_hamming_weight > P::omega { + kappa += P::l as u16; continue; }; @@ -1686,9 +1476,7 @@ impl< // Alg 7; 33: ๐œŽ โ† sigEncode(๐‘, ๐ณฬƒ modยฑ๐‘ž, ๐ก) let bytes_written = - sig_encode::( - &sig_val_c_tilde, &sig_val_z, &sig_val_h, output, - ); + sig_encode::(&sig_val_c_tilde, &sig_val_z, &sig_val_h, output); Ok(bytes_written) } @@ -1711,7 +1499,7 @@ impl< } fn verify_with_expanded_key( - pk: &MLDSAPublicKeyExpanded, + pk: &MLDSAPublicKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8], @@ -1725,7 +1513,7 @@ impl< fn verify_mu( pk: &PK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], sig: &[u8; SIG_LEN], ) -> Result<(), SignatureError> { @@ -1738,16 +1526,12 @@ impl< /// Trait for all three of the ML-DSA algorithm variants. pub trait MLDSATrait< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const LAMBDA: i32, - const k: usize, - const l: usize, - const ETA: usize, >: Sized { /// Runs a key generation using the library's default RNG, seeded from the OS. @@ -1762,7 +1546,7 @@ pub trait MLDSATrait< // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { // Source the seed from the provided RNG - if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if rng.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; } let mut seed = KeyMaterial256::new(); @@ -1829,26 +1613,26 @@ pub trait MLDSATrait< ) -> Result<[u8; 64], SignatureError>; /// Same as [`MLDSATrait::compute_mu_from_tr`], but extracts tr from the public key. fn compute_mu_from_pk( - pk: &impl MLDSAPublicKeyTrait, + pk: &impl MLDSAPublicKeyTrait, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; 64], SignatureError>; /// Same as [`MLDSATrait::compute_mu_from_tr`], but extracts tr from the private key. // dev note: defined sk this way so that it accepts either MLDSAPrivateKey or MLDSAPRivateKeyExpanded fn compute_mu_from_sk( - sk: &impl MLDSAPrivateKeyTrait, + sk: &impl MLDSAPrivateKeyTrait, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; 64], SignatureError>; /// Same as [`Signer::sign`], but signs from an [`MLDSAPrivateKeyExpanded`]. fn sign_with_expanded_key( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError>; /// Same as [`MLDSATrait::sign_with_expanded_key`], but takes an output array. fn sign_with_expanded_key_out( - sk: &MLDSAPrivateKeyExpanded, + sk: &MLDSAPrivateKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, out: &mut [u8; SIG_LEN], @@ -1861,7 +1645,7 @@ pub trait MLDSATrait< /// Optionally, takes a pre-expanded public matrix `A_hat`. fn sign_mu( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], ) -> Result<[u8; SIG_LEN], SignatureError>; /// Performs an ML-DSA signature using the provided external message representative `mu`. @@ -1876,20 +1660,20 @@ pub trait MLDSATrait< /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. fn sign_mu_out( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], output: &mut [u8; SIG_LEN], ) -> Result; /// Same as [`MLDSATrait::sign_mu`], but signs from an [`MLDSAPrivateKeyExpanded`]. fn sign_mu_with_expanded_key( - sk: &MLDSAPrivateKeyExpanded, - A_hat: Option<&Matrix>, + sk: &MLDSAPrivateKeyExpanded, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], ) -> Result<[u8; SIG_LEN], SignatureError>; /// Same as [`MLDSATrait::sign_mu_out`], but signs from an [`MLDSAPrivateKeyExpanded`]. fn sign_mu_with_expanded_key_out( - sk: &MLDSAPrivateKeyExpanded, - A_hat: Option<&Matrix>, + sk: &MLDSAPrivateKeyExpanded, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], output: &mut [u8; SIG_LEN], ) -> Result; @@ -1916,7 +1700,7 @@ pub trait MLDSATrait< /// prevent accidental nonce reuse, this function moves `rnd`. fn sign_mu_deterministic( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], rnd: [u8; 32], ) -> Result<[u8; SIG_LEN], SignatureError>; @@ -1945,7 +1729,7 @@ pub trait MLDSATrait< /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. fn sign_mu_deterministic_out( sk: &SK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], rnd: [u8; 32], output: &mut [u8; SIG_LEN], @@ -1978,7 +1762,7 @@ pub trait MLDSATrait< ) -> Result; /// Same as [`SignatureVerifier::verify`], but signs from an expanded key object. fn verify_with_expanded_key( - pk: &MLDSAPublicKeyExpanded, + pk: &MLDSAPublicKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8], @@ -1989,59 +1773,20 @@ pub trait MLDSATrait< /// Optionally, takes a pre-expanded public matrix `A_hat`. fn verify_mu( pk: &PK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, mu: &[u8; 64], sig: &[u8; SIG_LEN], ) -> Result<(), SignatureError>; } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> Signer - for MLDSA< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> Signer for MLDSA { fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; @@ -2125,52 +1870,13 @@ impl< } impl< + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const TAU: i32, - const LAMBDA: i32, - const GAMMA1: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const ETA: usize, - const BETA: i32, - const OMEGA: i32, - const C_TILDE: usize, - const POLY_Z_PACKED_LEN: usize, - const POLY_W1_PACKED_LEN: usize, - const LAMBDA_over_4: usize, - const GAMMA1_MINUS_BETA: i32, - const GAMMA2_MINUS_BETA: i32, - const GAMMA1_MASK_LEN: usize, -> SignatureVerifier - for MLDSA< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - > +> SignatureVerifier for MLDSA { fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; diff --git a/crypto/mldsa/src/mldsa_keys.rs b/crypto/mldsa/src/mldsa_keys.rs index 456a26c8..5d4dee7d 100644 --- a/crypto/mldsa/src/mldsa_keys.rs +++ b/crypto/mldsa/src/mldsa_keys.rs @@ -1,14 +1,11 @@ use crate::aux_functions::{ - bit_pack_eta, bit_pack_t0, bit_unpack_eta, bit_unpack_t0, bitlen_eta, expandA, - power_2_round_vec, simple_bit_pack_t1, simple_bit_unpack_t1, + bit_pack_eta, bit_pack_t0, bit_unpack_eta, bit_unpack_t0, expandA, power_2_round_vec, + simple_bit_pack_t1, simple_bit_unpack_t1, }; -use crate::matrix::{Matrix, Vector}; +use crate::matrix::{MatrixTrait, VectorTrait}; use crate::mldsa::H; -use crate::mldsa::{MLDSA44_ETA, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_k, MLDSA44_l}; -use crate::mldsa::{MLDSA65_ETA, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_k, MLDSA65_l}; -use crate::mldsa::{MLDSA87_ETA, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_k, MLDSA87_l}; use crate::mldsa::{POLY_T0PACKED_LEN, POLY_T1PACKED_LEN}; -use crate::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME}; +use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams}; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{SignaturePrivateKey, SignaturePublicKey, XOF}; @@ -25,71 +22,77 @@ use crate::polynomial::Polynomial; /* Pub Types */ /// ML-DSA-44 Public Key -pub type MLDSA44PublicKey = MLDSAPublicKey; +pub type MLDSA44PublicKey = MLDSAPublicKey; /// ML-DSA-44 Private Key pub type MLDSA44PrivateKey = - MLDSAPrivateKey; + MLDSAPrivateKey; /// ML-DSA-65 Public Key -pub type MLDSA65PublicKey = MLDSAPublicKey; +pub type MLDSA65PublicKey = MLDSAPublicKey; /// ML-DSA-65 Private Key pub type MLDSA65PrivateKey = - MLDSAPrivateKey; + MLDSAPrivateKey; /// ML-DSA-87 Public Key -pub type MLDSA87PublicKey = MLDSAPublicKey; +pub type MLDSA87PublicKey = MLDSAPublicKey; /// ML-DSA-87 Private Key pub type MLDSA87PrivateKey = - MLDSAPrivateKey; + MLDSAPrivateKey; /* Pre-expanded keys for repeated operations */ /// ML-DSA-44 Public Key with a pre-expanded public matrix A for repeated encaps operations. pub type MLDSA44PublicKeyExpanded = - MLDSAPublicKeyExpanded; + MLDSAPublicKeyExpanded; /// ML-DSA-44 Private Key with a pre-expanded public matrix A for repeated decaps operations. pub type MLDSA44PrivateKeyExpanded = MLDSAPrivateKeyExpanded< - MLDSA44_k, - MLDSA44_l, - MLDSA44_ETA, + MLDSA44Params, MLDSA44PublicKey, MLDSA44PrivateKey, - MLDSA44_SK_LEN, - MLDSA44_PK_LEN, + { MLDSA44Params::SK_LEN }, + { MLDSA44Params::PK_LEN }, >; /// ML-DSA-65 Public Key with a pre-expanded public matrix A for repeated encaps operations. pub type MLDSA65PublicKeyExpanded = - MLDSAPublicKeyExpanded; + MLDSAPublicKeyExpanded; /// ML-DSA-65 Private Key with a pre-expanded public matrix A for repeated decaps operations. pub type MLDSA65PrivateKeyExpanded = MLDSAPrivateKeyExpanded< - MLDSA65_k, - MLDSA65_l, - MLDSA65_ETA, + MLDSA65Params, MLDSA65PublicKey, MLDSA65PrivateKey, - MLDSA65_SK_LEN, - MLDSA65_PK_LEN, + { MLDSA65Params::SK_LEN }, + { MLDSA65Params::PK_LEN }, >; /// ML-DSA-87 Public Key with a pre-expanded public matrix A for repeated encaps operations. pub type MLDSA87PublicKeyExpanded = - MLDSAPublicKeyExpanded; + MLDSAPublicKeyExpanded; /// ML-DSA-87 Private Key with a pre-expanded public matrix A for repeated decaps operations. pub type MLDSA87PrivateKeyExpanded = MLDSAPrivateKeyExpanded< - MLDSA87_k, - MLDSA87_l, - MLDSA87_ETA, + MLDSA87Params, MLDSA87PublicKey, MLDSA87PrivateKey, - MLDSA87_SK_LEN, - MLDSA87_PK_LEN, + { MLDSA87Params::SK_LEN }, + { MLDSA87Params::PK_LEN }, >; /// An ML-DSA public key. -#[derive(Clone)] -pub struct MLDSAPublicKey { +/// +/// `PK_LEN` duplicates `MLDSAParams::PK_LEN`; it has to be carried separately because +/// [`SignaturePublicKey`] takes the encoded length as a const generic parameter, and an associated +/// const of a type parameter may not be used as a const generic argument. The type aliases below +/// wire the two together. +pub struct MLDSAPublicKey { rho: [u8; 32], - t1: Vector, + t1: P::VecK, +} + +// Written out rather than derived: `#[derive(Clone)]` would demand `P: Clone`, and `P` is a +// marker for the parameter set that is never stored, only used to name the field types. +impl Clone for MLDSAPublicKey { + fn clone(&self) -> Self { + Self { rho: self.rho, t1: self.t1 } + } } -impl MLDSAPublicKey { +impl MLDSAPublicKey { /// Algorithm 22 pkEncode(๐œŒ, ๐ญ1) /// Encodes a public key for ML-DSA into a byte string. /// Input:๐œŒ โˆˆ ๐”น32, ๐ญ1 โˆˆ ๐‘…๐‘˜ with coefficients in [0, 2bitlen (๐‘žโˆ’1)โˆ’๐‘‘ โˆ’ 1]. @@ -102,11 +105,11 @@ impl MLDSAPublicKey(); // that should divide evenly the remainder of the array - debug_assert_eq!(pk_chunks.len(), k); + debug_assert_eq!(pk_chunks.len(), P::k); debug_assert_eq!(last_chunk.len(), 0); - for (pk_chunk, t1_i) in pk_chunks.into_iter().zip(&self.t1.elems) { - pk_chunk.copy_from_slice(&simple_bit_pack_t1(&t1_i)); + for (pk_chunk, t1_i) in pk_chunks.into_iter().zip(self.t1.elems()) { + pk_chunk.copy_from_slice(&simple_bit_pack_t1(t1_i)); } PK_LEN @@ -114,7 +117,7 @@ impl MLDSAPublicKey: +pub trait MLDSAPublicKeyTrait: SignaturePublicKey { /// Algorithm 23 pkDecode(๐‘๐‘˜) @@ -124,7 +127,7 @@ pub trait MLDSAPublicKeyTrait Self; /// Get a copy of the expanded public matrix A_hat - fn A_hat(&self) -> Matrix; + fn A_hat(&self) -> P::MatrixA; /// Compute the public key hash (tr) from the public key. /// @@ -135,43 +138,43 @@ pub trait MLDSAPublicKeyTrait [u8; 64]; } -pub(crate) trait MLDSAPublicKeyInternalTrait: +pub(crate) trait MLDSAPublicKeyInternalTrait: SignaturePublicKey { /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. - fn new(rho: [u8; 32], t1: Vector) -> Self; + fn new(rho: [u8; 32], t1: P::VecK) -> Self; /// Get a ref to t1 - fn t1(&self) -> &Vector; + fn t1(&self) -> &P::VecK; } -impl MLDSAPublicKeyTrait - for MLDSAPublicKey +impl MLDSAPublicKeyTrait + for MLDSAPublicKey { // todo: block a t1 of all zeros? Maybe add to consistency_check() ? fn pk_decode(pk: &[u8; PK_LEN]) -> Self { let rho = pk[0..32].try_into().unwrap(); - let mut t1 = Vector::::new(); + let mut t1 = P::VecK::new(); let (pk_chunks, last_chunk) = pk[32..].as_chunks::(); // that should divide evenly the remainder of the array - debug_assert_eq!(pk_chunks.len(), k); + debug_assert_eq!(pk_chunks.len(), P::k); debug_assert_eq!(last_chunk.len(), 0); - for (t1_i, pk_chunk) in t1.elems.iter_mut().zip(pk_chunks) { + for (t1_i, pk_chunk) in t1.elems_mut().iter_mut().zip(pk_chunks) { // 3: ๐ญ1[๐‘–] โ† SimpleBitUnpack(๐‘ง๐‘–, 2bitlen (๐‘žโˆ’1)โˆ’๐‘‘ โˆ’ 1) // โ–ท This is always in the correct range // Therefore, we don't need to check that the coeeffs are in range t1_i.coeffs.copy_from_slice(&simple_bit_unpack_t1(pk_chunk).coeffs); } - Self::new(rho, t1) + >::new(rho, t1) } - fn A_hat(&self) -> Matrix { - expandA::(&self.rho) + fn A_hat(&self) -> P::MatrixA { + expandA::

(&self.rho) } fn compute_tr(&self) -> [u8; 64] { @@ -182,21 +185,19 @@ impl MLDSAPublicKeyTrait MLDSAPublicKeyInternalTrait - for MLDSAPublicKey +impl MLDSAPublicKeyInternalTrait + for MLDSAPublicKey { - fn new(rho: [u8; 32], t1: Vector) -> Self { + fn new(rho: [u8; 32], t1: P::VecK) -> Self { Self { rho, t1 } } - fn t1(&self) -> &Vector { + fn t1(&self) -> &P::VecK { &self.t1 } } -impl SignaturePublicKey - for MLDSAPublicKey -{ +impl SignaturePublicKey for MLDSAPublicKey { fn encode(&self) -> [u8; PK_LEN] { let mut pk = [0u8; PK_LEN]; let bytes_written = self.encode_out(&mut pk); @@ -218,15 +219,13 @@ impl SignaturePublicKey>::pk_decode(&bytes_sized)) } } -impl Eq for MLDSAPublicKey {} +impl Eq for MLDSAPublicKey {} -impl PartialEq - for MLDSAPublicKey -{ +impl PartialEq for MLDSAPublicKey { fn eq(&self, other: &Self) -> bool { let self_encoded = self.encode(); let other_encoded = other.encode(); @@ -234,50 +233,54 @@ impl PartialEq } } -impl Debug for MLDSAPublicKey { +impl Debug for MLDSAPublicKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; - write!(f, "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.compute_tr(),) + write!( + f, + "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", + P::ALG_NAME, + >::compute_tr(self), + ) } } -impl Display for MLDSAPublicKey { +impl Display for MLDSAPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; - write!(f, "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.compute_tr(),) + write!( + f, + "MLDSAPublicKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", + P::ALG_NAME, + >::compute_tr(self), + ) } } /// A fully expanded ML-DSA public key that includes the intermediate values needed for performing /// multiple verification operations against the same public key, which causes the public key struct /// to take up more memory, but results in more efficient repeated verify() operations. -#[derive(Clone)] pub struct MLDSAPublicKeyExpanded< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, const PK_LEN: usize, > { pub(crate) pk: PK, - pub(crate) A_hat: Matrix, + pub(crate) A_hat: P::MatrixA, +} + +/// See the note on [`MLDSAPublicKey`]'s `Clone` for why this is not derived. +impl, const PK_LEN: usize> Clone + for MLDSAPublicKeyExpanded +{ + fn clone(&self) -> Self { + Self { pk: self.pk.clone(), A_hat: self.A_hat.clone() } + } } impl< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, const PK_LEN: usize, -> SignaturePublicKey for MLDSAPublicKeyExpanded +> SignaturePublicKey for MLDSAPublicKeyExpanded { fn encode(&self) -> [u8; PK_LEN] { self.pk.encode() @@ -296,16 +299,15 @@ impl< )); } let bytes_sized: [u8; PK_LEN] = bytes[..PK_LEN].try_into().unwrap(); - Ok(Self::pk_decode(&bytes_sized)) + Ok(>::pk_decode(&bytes_sized)) } } impl< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, const PK_LEN: usize, -> PartialEq for MLDSAPublicKeyExpanded +> PartialEq for MLDSAPublicKeyExpanded { fn eq(&self, other: &Self) -> bool { self.pk.eq(&other.pk) @@ -313,66 +315,50 @@ impl< } impl< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, const PK_LEN: usize, -> Eq for MLDSAPublicKeyExpanded +> Eq for MLDSAPublicKeyExpanded { } impl< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, const PK_LEN: usize, -> Debug for MLDSAPublicKeyExpanded +> Debug for MLDSAPublicKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLDSAPublicKeyExpanded {{ alg: {}, pub_key_hash (tr): {:x?} }}", - alg, - self.compute_tr(), + P::ALG_NAME, + self.pk.compute_tr(), ) } } impl< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, const PK_LEN: usize, -> Display for MLDSAPublicKeyExpanded +> Display for MLDSAPublicKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLDSAPublicKeyExpanded {{ alg: {}, pub_key_hash (tr): {:x?} }}", - alg, - self.compute_tr(), + P::ALG_NAME, + self.pk.compute_tr(), ) } } impl< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, const PK_LEN: usize, -> From<&PK> for MLDSAPublicKeyExpanded +> From<&PK> for MLDSAPublicKeyExpanded { /// Fully expands the intermediate values needed for performing multiple encaps operations /// against the same public key, which causes the MLKEMPublicKey struct to take up @@ -384,11 +370,10 @@ impl< } impl< - const k: usize, - const l: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, const PK_LEN: usize, -> MLDSAPublicKeyTrait for MLDSAPublicKeyExpanded +> MLDSAPublicKeyTrait for MLDSAPublicKeyExpanded { fn pk_decode(pk: &[u8; PK_LEN]) -> Self { let pk1 = PK::pk_decode(pk); @@ -396,7 +381,7 @@ impl< Self { pk: pk1, A_hat } } - fn A_hat(&self) -> Matrix { + fn A_hat(&self) -> P::MatrixA { self.A_hat.clone() } @@ -407,15 +392,10 @@ impl< /// An ML-DSA private key. /// +/// See [`MLDSAPublicKey`] for why `SK_LEN` and `PK_LEN` are carried alongside `P`. +// // Dev note: This will automatically inherit the [`Secret`] protections because [`Polynomial`] wraps the underlying data with [`Secret`]. -#[derive(Clone)] -pub struct MLDSAPrivateKey< - const k: usize, - const l: usize, - const eta: usize, - const SK_LEN: usize, - const PK_LEN: usize, -> { +pub struct MLDSAPrivateKey { rho: [u8; 32], K: Secret<[u8; 32]>, tr: [u8; 64], @@ -425,16 +405,31 @@ pub struct MLDSAPrivateKey< // So we are going to hold them as s1_hat, s2_hat, and t0_hat. // Note: these are not necessarily in their reduced form; so you'll need to reduce them before // inv_ntt()'ing them or hashing them. - s1_hat: Secret>, - s2_hat: Secret>, - t0_hat: Vector, + s1_hat: Secret, + s2_hat: Secret, + t0_hat: P::VecK, // note: KeyMaterial is inherently Secret seed: Option>, } -impl - MLDSAPrivateKey +/// See the note on [`MLDSAPublicKey`]'s `Clone` for why this is not derived. +impl Clone + for MLDSAPrivateKey { + fn clone(&self) -> Self { + Self { + rho: self.rho, + K: self.K.clone(), + tr: self.tr, + s1_hat: self.s1_hat.clone(), + s2_hat: self.s2_hat.clone(), + t0_hat: self.t0_hat, + seed: self.seed.clone(), + } + } +} + +impl MLDSAPrivateKey { /// Algorithm 24 skEncode(๐œŒ, ๐พ, ๐‘ก๐‘Ÿ, ๐ฌ1, ๐ฌ2, ๐ญ0) /// Encodes a secret key for ML-DSA into a byte string. /// Input: ๐œŒ โˆˆ ๐”น32, ๐พ โˆˆ ๐”น32, ๐‘ก๐‘Ÿ โˆˆ ๐”น64 , ๐ฌ1 โˆˆ ๐‘…โ„“ with coefficients in [โˆ’๐œ‚, ๐œ‚], ๐ฌ2 โˆˆ ๐‘…๐‘˜ with @@ -452,47 +447,44 @@ impl(&s1_i, &mut buf); + bit_pack_eta::

(&s1_i, &mut buf); sk_chunk.copy_from_slice(&buf[..eta_pack_len]); } - off += l * bitlen_eta(eta); + off += P::l * eta_pack_len; - let sk_chunks = out[off..off + k * bitlen_eta(eta)].chunks_mut(bitlen_eta(eta)); - debug_assert_eq!(sk_chunks.len(), k); - for (sk_chunk, s2_hat_i) in sk_chunks.into_iter().zip(&self.s2_hat.elems) { + let sk_chunks = out[off..off + P::k * eta_pack_len].chunks_mut(eta_pack_len); + debug_assert_eq!(sk_chunks.len(), P::k); + for (sk_chunk, s2_hat_i) in sk_chunks.into_iter().zip(self.s2_hat.elems()) { // Deviation from the FIPS: // We are holding these in ntt form, so need to convert back to standard form - let mut s2_hat_i = s2_hat_i.clone(); - s2_hat_i.reduce(); - s2_hat_i.inv_ntt(); - let s2_i = s2_hat_i; + let mut s2_i = *s2_hat_i; + s2_i.reduce(); + s2_i.inv_ntt(); - bit_pack_eta::(&s2_i, &mut buf); + bit_pack_eta::

(&s2_i, &mut buf); sk_chunk.copy_from_slice(&buf[..eta_pack_len]); } - off += k * bitlen_eta(eta); + off += P::k * eta_pack_len; - let sk_chunks = out[off..off + k * POLY_T0PACKED_LEN].chunks_mut(POLY_T0PACKED_LEN); - debug_assert_eq!(sk_chunks.len(), k); - for (sk_chunk, t0_hat_i) in sk_chunks.into_iter().zip(&self.t0_hat.elems) { + let sk_chunks = out[off..off + P::k * POLY_T0PACKED_LEN].chunks_mut(POLY_T0PACKED_LEN); + debug_assert_eq!(sk_chunks.len(), P::k); + for (sk_chunk, t0_hat_i) in sk_chunks.into_iter().zip(self.t0_hat.elems()) { // Deviation from the FIPS: // We are holding these in ntt form, so need to convert back to standard form - let mut t0_hat_i = t0_hat_i.clone(); - t0_hat_i.reduce(); - t0_hat_i.inv_ntt(); - let t0_i = t0_hat_i; + let mut t0_i = *t0_hat_i; + t0_i.reduce(); + t0_i.inv_ntt(); sk_chunk.copy_from_slice(&bit_pack_t0(&t0_i)); } @@ -502,13 +494,8 @@ impl: SignaturePrivateKey +pub trait MLDSAPrivateKeyTrait: + SignaturePrivateKey { /// Get a ref to the seed, if there is one stored with this private key fn seed(&self) -> Option<&KeyMaterial<32>>; @@ -517,10 +504,10 @@ pub trait MLDSAPrivateKeyTrait< fn tr(&self) -> &[u8; 64]; /// Get the public matrix A_hat. - fn A_hat(&self) -> Matrix; + fn A_hat(&self) -> P::MatrixA; /// This is a partial implementation of keygen_internal(), and probably not allowed in FIPS mode. - fn derive_pk(&self) -> MLDSAPublicKey; + fn derive_pk(&self) -> MLDSAPublicKey; /// Algorithm 25 skDecode(๐‘ ๐‘˜) /// Reverses the procedure skEncode. /// Input: Private key ๐‘ ๐‘˜ โˆˆ ๐”น32+32+64+32โ‹…((โ„“+๐‘˜)โ‹…bitlen (2๐œ‚)+๐‘‘๐‘˜). @@ -533,9 +520,7 @@ pub trait MLDSAPrivateKeyTrait< } pub(crate) trait MLDSAPrivateKeyInternalTrait< - const k: usize, - const l: usize, - const eta: usize, + P: MLDSAParams, const SK_LEN: usize, const PK_LEN: usize, > @@ -546,23 +531,23 @@ pub(crate) trait MLDSAPrivateKeyInternalTrait< rho: [u8; 32], K: Secret<[u8; 32]>, tr: [u8; 64], - s1_hat: Secret>, - s2_hat: Secret>, - t0_hat: Vector, + s1_hat: Secret, + s2_hat: Secret, + t0_hat: P::VecK, seed: Option>, ) -> Self; /// Get a ref to K fn K(&self) -> &Secret<[u8; 32]>; /// Get a ref to s1 - fn s1_hat(&self) -> &Vector; + fn s1_hat(&self) -> &P::VecL; /// Get a ref to s2 - fn s2_hat(&self) -> &Vector; + fn s2_hat(&self) -> &P::VecK; /// Get a ref to t0 - fn t0_hat(&self) -> &Vector; + fn t0_hat(&self) -> &P::VecK; } -impl - MLDSAPrivateKeyTrait for MLDSAPrivateKey +impl + MLDSAPrivateKeyTrait for MLDSAPrivateKey { fn seed(&self) -> Option<&KeyMaterial<32>> { match self.seed { @@ -575,18 +560,18 @@ impl Matrix { - expandA::(&self.rho) + fn A_hat(&self) -> P::MatrixA { + expandA::

(&self.rho) } - fn derive_pk(&self) -> MLDSAPublicKey { + fn derive_pk(&self) -> MLDSAPublicKey { // 5: ๐ญ โ† NTTโˆ’1(๐€ โˆ˜ NTT(๐ฌ1)) + ๐ฌ2 // โ–ท compute ๐ญ = ๐€๐ฌ1 + ๐ฌ2 let mut t = { // scope for A_hat // 3: ๐€ โ† ExpandA(๐œŒ) // โ–ท ๐€ is generated and stored in NTT representation as ๐€ - let A_hat = expandA::(&self.rho); + let A_hat = expandA::

(&self.rho); let mut t_ntt = A_hat.matrix_vector_ntt(&self.s1_hat); t_ntt.inv_ntt(); @@ -596,7 +581,7 @@ impl = self.s2_hat.clone(); s2.reduce(); s2.inv_ntt(); @@ -606,9 +591,9 @@ impl(&t); + let (t1, _) = power_2_round_vec(&t); - MLDSAPublicKey::::new(self.rho.clone(), t1) + as MLDSAPublicKeyInternalTrait>::new(self.rho, t1) } fn sk_decode(sk: &[u8; SK_LEN]) -> Result { // Construct the (Secret-protected) key up front and unpack each field directly into it, @@ -621,23 +606,25 @@ impl::new(), + t0_hat: P::VecK::new(), seed: None, }; key.K.copy_from_slice(&sk[32..64]); let mut off = 128; + let eta_pack_len = P::POLY_ETA_PACKED_LEN; + let eta = P::eta as i32; // unpack s1 directly into key.s1_hat so that we don't make additional non-Secret copies. - let sk_chunks = sk[off..off + (l * bitlen_eta(eta))].chunks(bitlen_eta(eta)); - debug_assert_eq!(sk_chunks.len(), l); - for (s1_i, sk_chunk) in key.s1_hat.elems.iter_mut().zip(sk_chunks) { + let sk_chunks = sk[off..off + (P::l * eta_pack_len)].chunks(eta_pack_len); + debug_assert_eq!(sk_chunks.len(), P::l); + for (s1_i, sk_chunk) in key.s1_hat.elems_mut().iter_mut().zip(sk_chunks) { // 3: ๐ฌ1[๐‘–] โ† BitUnpack(๐‘ฆ๐‘–, ๐œ‚, ๐œ‚) // โ–ท this may lie outside [โˆ’๐œ‚, ๐œ‚] if input is malformed - s1_i.coeffs.copy_from_slice(&bit_unpack_eta::(&sk_chunk).coeffs); + s1_i.coeffs.copy_from_slice(&bit_unpack_eta::

(sk_chunk).coeffs); // check that the coefficients are within the expected range for coeff in s1_i.coeffs.iter() { - if *coeff < -(eta as i32) || *coeff > (eta as i32) { + if *coeff < -eta || *coeff > eta { return Err(SignatureError::DecodingError("Invalid or corrupted key")); } } @@ -645,19 +632,19 @@ impl(&sk_chunk).coeffs); + s2_i.coeffs.copy_from_slice(&bit_unpack_eta::

(sk_chunk).coeffs); // check that the coefficients are within the expected range for coeff in s2_i.coeffs.iter() { - if *coeff < -(eta as i32) || *coeff > (eta as i32) { + if *coeff < -eta || *coeff > eta { return Err(SignatureError::DecodingError("Invalid or corrupted key")); } } @@ -665,17 +652,17 @@ impl(); + sk[off..off + (P::k * POLY_T0PACKED_LEN)].as_chunks::(); // that should divide evenly the remainder of the array - debug_assert_eq!(sk_chunks.len(), k); + debug_assert_eq!(sk_chunks.len(), P::k); debug_assert_eq!(last_chunk.len(), 0); - for (t0_i, sk_chunk) in key.t0_hat.elems.iter_mut().zip(sk_chunks) { + for (t0_i, sk_chunk) in key.t0_hat.elems_mut().iter_mut().zip(sk_chunks) { t0_i.coeffs.copy_from_slice(&bit_unpack_t0(sk_chunk).coeffs); } // Deviation from the FIPS: @@ -686,49 +673,40 @@ impl - MLDSAPrivateKeyInternalTrait - for MLDSAPrivateKey +impl + MLDSAPrivateKeyInternalTrait for MLDSAPrivateKey { fn new( rho: [u8; 32], K: Secret<[u8; 32]>, tr: [u8; 64], - s1_hat: Secret>, - s2_hat: Secret>, - t0_hat: Vector, + s1_hat: Secret, + s2_hat: Secret, + t0_hat: P::VecK, seed: Option>, ) -> Self { - Self { - rho: rho.clone(), - K: K.clone(), - tr: tr.clone(), - s1_hat: s1_hat.clone(), - s2_hat: s2_hat.clone(), - t0_hat: t0_hat.clone(), - seed: seed.clone(), - } + Self { rho, K, tr, s1_hat, s2_hat, t0_hat, seed } } fn K(&self) -> &Secret<[u8; 32]> { &self.K } - fn s1_hat(&self) -> &Vector { + fn s1_hat(&self) -> &P::VecL { &self.s1_hat } - fn s2_hat(&self) -> &Vector { + fn s2_hat(&self) -> &P::VecK { &self.s2_hat } - fn t0_hat(&self) -> &Vector { + fn t0_hat(&self) -> &P::VecK { &self.t0_hat } } -impl - SignaturePrivateKey for MLDSAPrivateKey +impl SignaturePrivateKey + for MLDSAPrivateKey { fn encode(&self) -> [u8; SK_LEN] { let mut out = [0u8; SK_LEN]; @@ -752,17 +730,17 @@ impl>::sk_decode(&bytes_sized) } } -impl Eq - for MLDSAPrivateKey +impl Eq + for MLDSAPrivateKey { } -impl - PartialEq for MLDSAPrivateKey +impl PartialEq + for MLDSAPrivateKey { fn eq(&self, other: &Self) -> bool { let self_encoded = self.encode(); @@ -772,20 +750,14 @@ impl - fmt::Debug for MLDSAPrivateKey +impl fmt::Debug + for MLDSAPrivateKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLDSAPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.tr, self.seed.is_some(), ) @@ -793,20 +765,14 @@ impl - Display for MLDSAPrivateKey +impl Display + for MLDSAPrivateKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLDSAPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.tr, self.seed.is_some(), ) @@ -816,32 +782,39 @@ impl, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, > { _phantom: core::marker::PhantomData, pub(crate) sk: SK, - pub(crate) A_hat: Matrix, + pub(crate) A_hat: P::MatrixA, +} + +/// See the note on [`MLDSAPublicKey`]'s `Clone` for why this is not derived. +impl< + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> Clone for MLDSAPrivateKeyExpanded +{ + fn clone(&self) -> Self { + Self { _phantom: core::marker::PhantomData, sk: self.sk.clone(), A_hat: self.A_hat.clone() } + } } impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> PartialEq for MLDSAPrivateKeyExpanded +> PartialEq for MLDSAPrivateKeyExpanded { fn eq(&self, other: &Self) -> bool { self.sk.eq(&other.sk) @@ -849,40 +822,28 @@ impl< } impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Eq for MLDSAPrivateKeyExpanded +> Eq for MLDSAPrivateKeyExpanded { } impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Debug for MLDSAPrivateKeyExpanded +> Debug for MLDSAPrivateKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLDSAPrivateKeyExpanded {{ alg: {}, pub_key_hash (tr): {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.sk.tr(), self.sk.seed().is_some(), ) @@ -890,27 +851,18 @@ impl< } impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Display for MLDSAPrivateKeyExpanded +> Display for MLDSAPrivateKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 4 => ML_DSA_44_NAME, - 6 => ML_DSA_65_NAME, - 8 => ML_DSA_87_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLDSAPrivateKeyExpanded {{ alg: {}, pub_key_hash (tr): {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.sk.tr(), self.sk.seed().is_some(), ) @@ -918,35 +870,30 @@ impl< } impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> From<&SK> for MLDSAPrivateKeyExpanded +> From<&SK> for MLDSAPrivateKeyExpanded { /// Fully expands the intermediate values needed for performing multiple encaps operations /// against the same public key, which causes the MLKEMPublicKey struct to take up fn from(sk: &SK) -> Self { - let A_hat = sk.derive_pk().A_hat(); + let A_hat = + as MLDSAPublicKeyTrait>::A_hat(&sk.derive_pk()); Self { _phantom: core::marker::PhantomData, sk: sk.clone(), A_hat } } } impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> SignaturePrivateKey for MLDSAPrivateKeyExpanded +> SignaturePrivateKey for MLDSAPrivateKeyExpanded { fn encode(&self) -> [u8; SK_LEN] { self.sk.encode() @@ -965,16 +912,12 @@ impl< } impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + P: MLDSAParams, + PK: MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> MLDSAPrivateKeyTrait - for MLDSAPrivateKeyExpanded +> MLDSAPrivateKeyTrait for MLDSAPrivateKeyExpanded { fn seed(&self) -> Option<&KeyMaterial<32>> { self.sk.seed() @@ -984,17 +927,18 @@ impl< self.sk.tr() } - fn A_hat(&self) -> Matrix { + fn A_hat(&self) -> P::MatrixA { self.sk.A_hat() } - fn derive_pk(&self) -> MLDSAPublicKey { + fn derive_pk(&self) -> MLDSAPublicKey { self.sk.derive_pk() } fn sk_decode(sk: &[u8; SK_LEN]) -> Result { let sk1 = SK::sk_decode(sk)?; - let A_hat = sk1.derive_pk().A_hat(); + let A_hat = + as MLDSAPublicKeyTrait>::A_hat(&sk1.derive_pk()); Ok(Self { _phantom: core::marker::PhantomData, sk: sk1, A_hat }) } diff --git a/crypto/mldsa/src/params.rs b/crypto/mldsa/src/params.rs new file mode 100644 index 00000000..67c2ab76 --- /dev/null +++ b/crypto/mldsa/src/params.rs @@ -0,0 +1,510 @@ +//! The three ML-DSA parameter sets of FIPS 204, Section 4, and the six HashML-DSA pairings built on +//! them, each as a sealed trait with one type per set. +//! +//! # Derived parameters +//! +//! FIPS 204, Table 1 assigns eight independent values per set (๐œ, ๐œ†, ๐›พ1, ๐›พ2, (๐‘˜, โ„“), ๐œ‚, ๐œ”) plus the +//! three sizes of Table 2. Everything else this implementation needs is a function of those, so it +//! is written once as a defaulted associated const rather than three times as a hand-computed +//! number. `params::tests` checks every derivation against the values tabulated in FIPS 204. + +use crate::hash_mldsa::{ + HASH_ML_DSA_44_with_SHA256_NAME, HASH_ML_DSA_44_with_SHA512_NAME, + HASH_ML_DSA_65_WITH_SHA256_NAME, HASH_ML_DSA_65_WITH_SHA512_NAME, + HASH_ML_DSA_87_WITH_SHA512_NAME, HASH_ML_DSA_87_with_SHA256_NAME, +}; +use crate::matrix::{Matrix, MatrixTrait, Vector, VectorTrait}; +use crate::mldsa::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME, q}; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, Hash, HashAlgParams, SecurityStrength}; +use bouncycastle_sha2::{SHA256, SHA512}; +use bouncycastle_utils::secret::ZeroizablePrimitive; + +/// `bitlen ๐‘ฅ`, the length of the binary expansion of ๐‘ฅ (FIPS 204, Section 2.3). +/// +/// `bitlen 0` is 0; every use below has a positive argument. +pub(crate) const fn bitlen(x: u32) -> usize { + if x == 0 { 0 } else { x.ilog2() as usize + 1 } +} + +/// A fixed-size byte buffer whose length depends on the parameter set. +/// +/// [`ZeroizablePrimitive`] rather than [`Default`] supplies the all-zero value, because `Default` +/// for arrays stops at 32 elements and every buffer here is longer than that. +trait ByteBuffer: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]> {} +impl ByteBuffer for [u8; N] {} + +/// A crate-private (aka "sealed") trait that prevents a new ML-DSA parameter set from being defined +/// outside this crate. +trait MLDSAParamsInternalTrait {} + +/// One ML-DSA parameter set: the values of FIPS 204, Table 1 and Table 2, and the types whose size +/// they determine. +/// +/// Sealed via a private supertrait, so [`MLDSA44Params`], [`MLDSA65Params`] and [`MLDSA87Params`] +/// are the only implementations. +pub trait MLDSAParams: MLDSAParamsInternalTrait { + /* FIPS 204, Table 1: the values assigned by each parameter set. */ + + /// ๐œ, the number of ยฑ1's in the polynomial ๐‘. + const tau: i32; + /// ๐œ†, the collision strength of ๐‘ฬƒ, in bits. + const lambda: i32; + /// ๐›พ1, the coefficient range of ๐ฒ. Always a power of two. + const gamma1: i32; + /// ๐›พ2, the low-order rounding range. + const gamma2: i32; + /// ๐‘˜, the number of rows of ๐€. + const k: usize; + /// โ„“, the number of columns of ๐€. + const l: usize; + /// ๐œ‚, the private key range. + const eta: usize; + /// ๐œ”, the maximum number of 1's in the hint ๐ก. + const omega: i32; + + /* FIPS 204, Table 2: sizes in bytes of keys and signatures. */ + + /// The length of an encoded public key. + const PK_LEN: usize; + /// The length of an encoded private key. + const SK_LEN: usize; + /// The length of a signature. + const SIG_LEN: usize; + + /* Algorithm meta-data */ + + /// The algorithm name, as reported by `Algorithm::ALG_NAME`. + const ALG_NAME: &'static str; + /// The strength claimed for this parameter set, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. + const MAX_SECURITY_STRENGTH: SecurityStrength; + /// The OID in component form, as reported by `AlgorithmOID::OID`. + const OID: &'static [u32]; + /// The DER encoding of [`MLDSAParams::OID`], as reported by `AlgorithmOID::OID_DER`. + const OID_DER: &'static [u8]; + + /* Derived. Never written out per parameter set -- see the module docs. */ + + /// ๐›ฝ, which FIPS 204, Table 1 defines as "๐›ฝ = ๐œ โ‹… ๐œ‚". + const beta: i32 = Self::tau * Self::eta as i32; + + /// The length of the commitment hash ๐‘ฬƒ, which FIPS 204, Algorithm 26 (sigEncode) gives as + /// ๐‘ฬƒ โˆˆ ๐”น^(๐œ†/4). + const C_TILDE_LEN: usize = Self::lambda as usize / 4; + + /// The packed length of one coordinate of ๐ณ: FIPS 204, Algorithm 26 (sigEncode) writes each of + /// the โ„“ coordinates as ๐”น^(32โ‹…(1+bitlen (๐›พ1โˆ’1))). + /// + /// This is also the number of bytes ExpandMask squeezes per coordinate: FIPS 204, + /// Algorithm 34, line 1 sets ๐‘ โ† 1 + bitlen (๐›พ1 โˆ’ 1) and line 4 squeezes 32๐‘ bytes. + const POLY_Z_PACKED_LEN: usize = 32 * (1 + bitlen(Self::gamma1 as u32 - 1)); + + /// The packed length of one coordinate of ๐ฌ1 or ๐ฌ2: FIPS 204, Algorithm 24 (skEncode), line 3 + /// packs each with BitPack(๐ฌ1[๐‘–], ๐œ‚, ๐œ‚), and Algorithm 17 (BitPack) outputs + /// ๐”น^(32โ‹…bitlen (๐‘Ž+๐‘)), so 32โ‹…bitlen (2๐œ‚). + const POLY_ETA_PACKED_LEN: usize = 32 * bitlen(2 * Self::eta as u32); + + /// The packed length of one coordinate of ๐ฐ1: FIPS 204, Algorithm 28 (w1Encode) outputs + /// ๐”น^(32๐‘˜โ‹…bitlen ((๐‘žโˆ’1)/(2๐›พ2)โˆ’1)) for all ๐‘˜ coordinates together. + const POLY_W1_PACKED_LEN: usize = 32 * bitlen(((q - 1) / (2 * Self::gamma2)) as u32 - 1); + + /// ๐›พ1 โˆ’ ๐›ฝ, the rejection bound on โ€–๐ณโ€–โˆž (FIPS 204, Algorithm 7, line 23). + const gamma1_minus_beta: i32 = Self::gamma1 - Self::beta; + + /// ๐›พ2 โˆ’ ๐›ฝ, the rejection bound on โ€–๐ซ0โ€–โˆž (FIPS 204, Algorithm 7, line 23). + const gamma2_minus_beta: i32 = Self::gamma2 - Self::beta; + + /* Types whose size depends on the parameter set. */ + + /// A vector of ๐‘˜ polynomials, i.e. an element of ๐‘…^๐‘˜. + type VecK: VectorTrait; + /// A vector of โ„“ polynomials, i.e. an element of ๐‘…^โ„“. + type VecL: VectorTrait; + /// The ๐‘˜ ร— โ„“ public matrix ๐€ฬ‚. + type MatrixA: MatrixTrait; + + /// The commitment hash ๐‘ฬƒ, of [`MLDSAParams::C_TILDE_LEN`] bytes. + type SigCTilde: ByteBuffer; + /// One packed coordinate of ๐ณ, of [`MLDSAParams::POLY_Z_PACKED_LEN`] bytes. + /// + /// ExpandMask squeezes into a buffer of this same length; see + /// [`MLDSAParams::POLY_Z_PACKED_LEN`]. + type PolyZPacked: ByteBuffer; + /// One packed coordinate of ๐ฐ1, of [`MLDSAParams::POLY_W1_PACKED_LEN`] bytes. + type PolyW1Packed: ByteBuffer; +} + +/// The ML-DSA-44 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA44Params; +/// The ML-DSA-65 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA65Params; +/// The ML-DSA-87 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA87Params; + +impl MLDSAParamsInternalTrait for MLDSA44Params {} +impl MLDSAParamsInternalTrait for MLDSA65Params {} +impl MLDSAParamsInternalTrait for MLDSA87Params {} + +impl MLDSAParams for MLDSA44Params { + const tau: i32 = 39; + const lambda: i32 = 128; + const gamma1: i32 = 1 << 17; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 88; + const k: usize = 4; + const l: usize = 4; + const eta: usize = 2; + const omega: i32 = 80; + + const PK_LEN: usize = 1312; + const SK_LEN: usize = 2560; + const SIG_LEN: usize = 2420; + + const ALG_NAME: &'static str = ML_DSA_44_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; + + type VecK = Vector<4>; + type VecL = Vector<4>; + type MatrixA = Matrix<4, 4>; + + type SigCTilde = [u8; 32]; // ๐œ†/4 = 128/4 + type PolyZPacked = [u8; 576]; // 32 * (1 + bitlen(2^17 - 1)) = 32 * 18 + type PolyW1Packed = [u8; 192]; // 32 * bitlen(44 - 1) = 32 * 6 +} + +impl MLDSAParams for MLDSA65Params { + const tau: i32 = 49; + const lambda: i32 = 192; + const gamma1: i32 = 1 << 19; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 32; + const k: usize = 6; + const l: usize = 5; + const eta: usize = 4; + const omega: i32 = 55; + + const PK_LEN: usize = 1952; + const SK_LEN: usize = 4032; + const SIG_LEN: usize = 3309; + + const ALG_NAME: &'static str = ML_DSA_65_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; + + type VecK = Vector<6>; + type VecL = Vector<5>; + type MatrixA = Matrix<6, 5>; + + type SigCTilde = [u8; 48]; // ๐œ†/4 = 192/4 + type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 + type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 +} + +impl MLDSAParams for MLDSA87Params { + const tau: i32 = 60; + const lambda: i32 = 256; + const gamma1: i32 = 1 << 19; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 32; + const k: usize = 8; + const l: usize = 7; + const eta: usize = 2; + const omega: i32 = 75; + + const PK_LEN: usize = 2592; + const SK_LEN: usize = 4896; + const SIG_LEN: usize = 4627; + + const ALG_NAME: &'static str = ML_DSA_87_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; + + type VecK = Vector<8>; + type VecL = Vector<7>; + type MatrixA = Matrix<8, 7>; + + type SigCTilde = [u8; 64]; // ๐œ†/4 = 256/4 + type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 + type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 +} + +/// The two distinct values ๐›พ1 takes across the three parameter sets (FIPS 204, Table 1). +/// +/// The bit-packing routines have one layout per distinct ๐›พ1, so they dispatch on these rather than +/// on the parameter set. ML-DSA-65 and ML-DSA-87 share the second value. +pub(crate) const GAMMA1_2_POW_17: i32 = MLDSA44Params::gamma1; +/// See [`GAMMA1_2_POW_17`]. +pub(crate) const GAMMA1_2_POW_19: i32 = MLDSA65Params::gamma1; + +/// The two distinct values ๐›พ2 takes across the three parameter sets (FIPS 204, Table 1). +/// +/// As with ๐›พ1, the routines that depend on ๐›พ2 have one form per distinct value rather than one per +/// parameter set. ML-DSA-65 and ML-DSA-87 share the second value. +pub(crate) const GAMMA2_Q_MINUS_1_OVER_88: i32 = MLDSA44Params::gamma2; +/// See [`GAMMA2_Q_MINUS_1_OVER_88`]. +pub(crate) const GAMMA2_Q_MINUS_1_OVER_32: i32 = MLDSA65Params::gamma2; + +/// The weaker of two security strengths. +/// +/// [`SecurityStrength`]'s discriminants are assigned in increasing order of strength, so comparing +/// them as integers orders them. A `const fn` because the strength of a HashML-DSA pairing is a +/// defaulted associated const. +const fn weaker_of(a: SecurityStrength, b: SecurityStrength) -> SecurityStrength { + if (a as u8) <= (b as u8) { a } else { b } +} + +/// A crate-private (aka "sealed") trait that prevents a new HashML-DSA pairing from being defined +/// outside this crate. +trait HashMLDSAParamsInternalTrait {} + +/// One HashML-DSA algorithm: an ML-DSA parameter set paired with a pre-hash function. +/// +/// FIPS 204, Algorithm 4 (HashML-DSA.Sign) leaves the choice of PH open, so an instantiation is a +/// pairing rather than a single parameter set. Everything that varies across the pairings lives +/// here, so [`crate::hash_mldsa::HashMLDSA`] takes one type rather than a parameter set plus a +/// hash function plus a digest length. +/// +/// Sealed via a private supertrait, so the six types below are the only implementations. +pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { + /// The ML-DSA parameter set underneath. + type MLDSA: MLDSAParams; + /// PH, the pre-hash function. + type PreHash: Hash + HashAlgParams + AlgorithmOID + Default; + + /// The algorithm name, as reported by `Algorithm::ALG_NAME`. + /// + /// Written out per pairing rather than derived: it is the two component names spliced + /// together, and `&'static str` cannot be concatenated in a const context. + const ALG_NAME: &'static str; + + /* Derived. Never written out per pairing. */ + + /// The length of the pre-hash `ph`, which is just PH's output length. + const PH_LEN: usize = ::OUTPUT_LEN; + + /// The strength claimed for the pairing, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. + /// + /// A HashML-DSA signature is no stronger than either of its two components, so this is the + /// weaker of the two. That is what caps, for example, HashML-DSA-87_with_SHA256 at 128 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = weaker_of( + ::MAX_SECURITY_STRENGTH, + ::MAX_SECURITY_STRENGTH, + ); +} + +/// The HashML-DSA-44_with_SHA256 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA44_with_SHA256Params; +/// The HashML-DSA-65_with_SHA256 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA65_with_SHA256Params; +/// The HashML-DSA-87_with_SHA256 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA87_with_SHA256Params; +/// The HashML-DSA-44_with_SHA512 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA44_with_SHA512Params; +/// The HashML-DSA-65_with_SHA512 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA65_with_SHA512Params; +/// The HashML-DSA-87_with_SHA512 pairing. +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HashMLDSA87_with_SHA512Params; + +impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA256Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA256Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA256Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA512Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA512Params {} +impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA512Params {} + +impl HashMLDSAParams for HashMLDSA44_with_SHA256Params { + type MLDSA = MLDSA44Params; + type PreHash = SHA256; + const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA256_NAME; +} +impl HashMLDSAParams for HashMLDSA65_with_SHA256Params { + type MLDSA = MLDSA65Params; + type PreHash = SHA256; + const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA256_NAME; +} +impl HashMLDSAParams for HashMLDSA87_with_SHA256Params { + type MLDSA = MLDSA87Params; + type PreHash = SHA256; + const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA256_NAME; +} +impl HashMLDSAParams for HashMLDSA44_with_SHA512Params { + type MLDSA = MLDSA44Params; + type PreHash = SHA512; + const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA512_NAME; +} +impl HashMLDSAParams for HashMLDSA65_with_SHA512Params { + type MLDSA = MLDSA65Params; + type PreHash = SHA512; + const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; +} +impl HashMLDSAParams for HashMLDSA87_with_SHA512Params { + type MLDSA = MLDSA87Params; + type PreHash = SHA512; + const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mldsa::d; + + /// FIPS 204, Table 1, transcribed column by column: the eight values each parameter set + /// assigns. `(tau, lambda, gamma1, gamma2, k, l, eta, omega)`. + const TABLE_1: [(i32, i32, i32, i32, usize, usize, usize, i32); 3] = [ + (39, 128, 131072, (q - 1) / 88, 4, 4, 2, 80), + (49, 192, 524288, (q - 1) / 32, 6, 5, 4, 55), + (60, 256, 524288, (q - 1) / 32, 8, 7, 2, 75), + ]; + + /// FIPS 204, Table 2, transcribed row by row: `(private key, public key, signature)` in bytes. + const TABLE_2: [(usize, usize, usize); 3] = + [(2560, 1312, 2420), (4032, 1952, 3309), (4896, 2592, 4627)]; + + /// FIPS 204, Table 1 also tabulates ๐›ฝ, which it labels "๐›ฝ = ๐œ โ‹… ๐œ‚". + const TABLE_1_BETA: [i32; 3] = [78, 196, 120]; + + fn check_table_1(i: usize) { + let (tau, lambda, gamma1, gamma2, k, l, eta, omega) = TABLE_1[i]; + assert_eq!(P::tau, tau, "{}: ๐œ", P::ALG_NAME); + assert_eq!(P::lambda, lambda, "{}: ๐œ†", P::ALG_NAME); + assert_eq!(P::gamma1, gamma1, "{}: ๐›พ1", P::ALG_NAME); + assert_eq!(P::gamma2, gamma2, "{}: ๐›พ2", P::ALG_NAME); + assert_eq!(P::k, k, "{}: ๐‘˜", P::ALG_NAME); + assert_eq!(P::l, l, "{}: โ„“", P::ALG_NAME); + assert_eq!(P::eta, eta, "{}: ๐œ‚", P::ALG_NAME); + assert_eq!(P::omega, omega, "{}: ๐œ”", P::ALG_NAME); + assert_eq!(P::beta, TABLE_1_BETA[i], "{}: ๐›ฝ = ๐œ โ‹… ๐œ‚", P::ALG_NAME); + } + + fn check_table_2(i: usize) { + let (sk_len, pk_len, sig_len) = TABLE_2[i]; + assert_eq!(P::SK_LEN, sk_len, "{}: private key size", P::ALG_NAME); + assert_eq!(P::PK_LEN, pk_len, "{}: public key size", P::ALG_NAME); + assert_eq!(P::SIG_LEN, sig_len, "{}: signature size", P::ALG_NAME); + } + + /// Each of the three sizes of Table 2 also has a formula in FIPS 204, and the two must agree. + /// Table 2 is what is written down above; this is what re-derives it. + fn check_table_2_formulas() { + // Algorithm 22 (pkEncode): ๐‘๐‘˜ โˆˆ ๐”น^(32+32๐‘˜(bitlen (๐‘žโˆ’1)โˆ’๐‘‘)). + let pk_len = 32 + 32 * P::k * (bitlen((q - 1) as u32) - d as usize); + assert_eq!(P::PK_LEN, pk_len, "{}: Algorithm 22 output size", P::ALG_NAME); + + // Algorithm 24 (skEncode): ๐‘ ๐‘˜ โˆˆ ๐”น^(32+32+64+32โ‹…((๐‘˜+โ„“)โ‹…bitlen (2๐œ‚)+๐‘‘๐‘˜)). + let sk_len = + 32 + 32 + 64 + 32 * ((P::k + P::l) * bitlen(2 * P::eta as u32) + d as usize * P::k); + assert_eq!(P::SK_LEN, sk_len, "{}: Algorithm 24 output size", P::ALG_NAME); + + // Algorithm 26 (sigEncode): ๐œŽ โˆˆ ๐”น^(๐œ†/4+โ„“โ‹…32โ‹…(1+bitlen (๐›พ1โˆ’1))+๐œ”+๐‘˜). + let sig_len = P::lambda as usize / 4 + + P::l * 32 * (1 + bitlen(P::gamma1 as u32 - 1)) + + P::omega as usize + + P::k; + assert_eq!(P::SIG_LEN, sig_len, "{}: Algorithm 26 output size", P::ALG_NAME); + } + + /// The associated types must be exactly as long as the consts that describe them; they are + /// written out by hand per parameter set, so this guards against a typo in one of them. + fn check_associated_type_sizes() { + assert_eq!( + size_of::(), + P::C_TILDE_LEN, + "{}: SigCTilde vs C_TILDE_LEN", + P::ALG_NAME + ); + assert_eq!( + size_of::(), + P::POLY_Z_PACKED_LEN, + "{}: PolyZPacked vs POLY_Z_PACKED_LEN", + P::ALG_NAME + ); + assert_eq!( + size_of::(), + P::POLY_W1_PACKED_LEN, + "{}: PolyW1Packed vs POLY_W1_PACKED_LEN", + P::ALG_NAME + ); + assert_eq!(::LEN, P::k, "{}: VecK vs ๐‘˜", P::ALG_NAME); + assert_eq!(::LEN, P::l, "{}: VecL vs โ„“", P::ALG_NAME); + } + + #[test] + fn test_parameter_sets_match_fips204_table_1() { + check_table_1::(0); + check_table_1::(1); + check_table_1::(2); + } + + #[test] + fn test_sizes_match_fips204_table_2() { + check_table_2::(0); + check_table_2::(1); + check_table_2::(2); + } + + #[test] + fn test_table_2_sizes_agree_with_the_encoding_formulas() { + check_table_2_formulas::(); + check_table_2_formulas::(); + check_table_2_formulas::(); + } + + #[test] + fn test_associated_types_are_the_length_their_consts_claim() { + check_associated_type_sizes::(); + check_associated_type_sizes::(); + check_associated_type_sizes::(); + } + + #[test] + fn test_bitlen_matches_its_definition() { + // FIPS 204 Section 2.3 defines bitlen ๐‘ฅ as the length of the binary expansion of ๐‘ฅ. + assert_eq!(bitlen(0), 0); + assert_eq!(bitlen(1), 1); + assert_eq!(bitlen(2), 2); + assert_eq!(bitlen(3), 2); + assert_eq!(bitlen(4), 3); + // The two arguments the derivations above actually use, plus bitlen(๐‘ž โˆ’ 1) = 23. + assert_eq!(bitlen((1 << 17) - 1), 17); + assert_eq!(bitlen((1 << 19) - 1), 19); + assert_eq!(bitlen((q - 1) as u32), 23); + } + + #[test] + fn test_gamma_dispatch_constants_cover_every_parameter_set() { + // The packing routines dispatch on these; a parameter set whose ๐›พ is neither value would + // fall through to a panic at runtime rather than fail to compile, so pin them here. + for gamma1 in [MLDSA44Params::gamma1, MLDSA65Params::gamma1, MLDSA87Params::gamma1] { + assert!(gamma1 == GAMMA1_2_POW_17 || gamma1 == GAMMA1_2_POW_19); + } + for gamma2 in [MLDSA44Params::gamma2, MLDSA65Params::gamma2, MLDSA87Params::gamma2] { + assert!(gamma2 == GAMMA2_Q_MINUS_1_OVER_88 || gamma2 == GAMMA2_Q_MINUS_1_OVER_32); + } + assert_ne!(GAMMA1_2_POW_17, GAMMA1_2_POW_19); + assert_ne!(GAMMA2_Q_MINUS_1_OVER_88, GAMMA2_Q_MINUS_1_OVER_32); + } +} diff --git a/crypto/mldsa/src/polynomial.rs b/crypto/mldsa/src/polynomial.rs index 98d47125..73b33a0c 100644 --- a/crypto/mldsa/src/polynomial.rs +++ b/crypto/mldsa/src/polynomial.rs @@ -3,7 +3,9 @@ use crate::aux_functions::{ ZETAS, conditional_add_q, high_bits, low_bits, make_hint, montgomery_reduce, }; -use crate::mldsa::{MLDSA44_POLY_W1_PACKED_LEN, MLDSA65_POLY_W1_PACKED_LEN, N, q}; +use crate::mldsa::{N, d, q}; +use crate::params::{GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, MLDSAParams}; +use bouncycastle_utils::secret::ZeroizablePrimitive; use core::ops::{Index, IndexMut}; /// A polynomial over the ML-DSA ring. @@ -12,8 +14,11 @@ use core::ops::{Index, IndexMut}; /// Polynomials themselves are not inherently secret since sometimes they are part of public keys /// and sometimes private keys. /// It is the responsibility of the caller to wrap sensitive instances in `Secret`. +/// +/// Public only because it appears in [`crate::VectorTrait`]'s signatures; its fields and +/// operations are crate-private, so from outside it is an opaque handle. #[derive(Clone, Copy)] -pub(crate) struct Polynomial { +pub struct Polynomial { pub(crate) coeffs: [i32; N], } @@ -34,7 +39,7 @@ impl IndexMut for Polynomial { impl Polynomial { /// Create a new polynomial with all coefficients set to zero. - pub const fn new() -> Self { + pub(crate) const fn new() -> Self { Self { coeffs: [0i32; N] } } @@ -65,25 +70,31 @@ impl Polynomial { } } - pub(crate) fn high_bits(&self) -> Self { + pub(crate) fn high_bits(&self) -> Self { let mut w = Self::new(); for i in 0..N { - w[i] = high_bits::(self[i]); + w[i] = high_bits::

(self[i]); } w } - pub(crate) fn low_bits(&self) -> Self { + pub(crate) fn low_bits(&self) -> Self { let mut w = Self::new(); for i in 0..N { - w[i] = low_bits::(self[i]); + w[i] = low_bits::

(self[i]); } w } - pub(crate) fn check_norm(&self) -> bool { + /// Tests whether any coefficient has absolute value at least `bound`. + /// + /// `bound` is a runtime argument rather than a const generic because every call site passes a + /// value derived from the parameter set (๐›พ1 โˆ’ ๐›ฝ, ๐›พ2 โˆ’ ๐›ฝ, or ๐›พ2), and an associated const of a + /// type parameter cannot be used as a const generic argument. It is still a constant after + /// monomorphization, so this costs nothing. + pub(crate) fn check_norm(&self, bound: i32) -> bool { // It is acceptable that this function is not constant-time (returns true early) // The reason being because it is used in a rejection loop. // That is, the early quit here leads to rejection, dropping the secret values and @@ -95,33 +106,38 @@ impl Polynomial { // if bound > (q - 1) / 8 { // return true; // } - // but since BOUND is a constant here, a debug_assert is performed to make sure the value is what we expect. - debug_assert!(BOUND <= (q - 1) / 8); + // but since every caller passes a parameter-set constant, a debug_assert is performed + // instead to make sure the value is what we expect. + debug_assert!(bound <= (q - 1) / 8); let mut t: i32; for x in self.coeffs.iter() { t = *x >> 31; t = *x - (t & (2 * *x)); - if t >= BOUND { + if t >= bound { return true; } } false } - pub(crate) fn shift_left(&mut self) { + /// Multiplies every coefficient by 2^๐‘‘. + /// + /// ๐‘‘ is 13 for all three parameter sets (FIPS 204, Table 1), so it is read from the global + /// constant rather than being passed in. + pub(crate) fn shift_left_d(&mut self) { for x in self.coeffs.iter_mut() { *x <<= d; } } /// Creates the hint vector, and also returns its hamming weight (i.e. the number of 1's). - pub(crate) fn make_hint(&self, r: &Self) -> (Self, i32) { + pub(crate) fn make_hint(&self, r: &Self) -> (Self, i32) { let mut out = Polynomial::new(); let mut count = 0i32; for i in 0..N { - let x = make_hint::(self[i], r[i]); + let x = make_hint::

(self[i], r[i]); out[i] = x; // mutants note: this chains up to hint_hamming_weight > OMEGA and there is no test KAT that triggers this branch @@ -131,19 +147,24 @@ impl Polynomial { (out, count) } - pub(crate) fn w1_encode(&self) -> [u8; POLY_W1_PACKED_LEN] { - let mut r = [0u8; POLY_W1_PACKED_LEN]; + /// SimpleBitPack(๐ฐ1[๐‘–], (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1), the per-coordinate body of + /// FIPS 204, Algorithm 28 (w1Encode), line 3. + pub(crate) fn w1_encode(&self) -> P::PolyW1Packed { + let mut out = ::ZEROED; + let r = out.as_mut(); - match POLY_W1_PACKED_LEN { - MLDSA44_POLY_W1_PACKED_LEN => { + match P::gamma2 { + // ML-DSA-44: (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1 = 43, so four 6-bit coefficients pack into three bytes. + GAMMA2_Q_MINUS_1_OVER_88 => { for i in 0..N / 4 { r[3 * i] = ((self[4 * i]) as u8) | ((self[4 * i + 1] << 6) as u8); r[3 * i + 1] = ((self[4 * i + 1] >> 2) as u8) | ((self[4 * i + 2] << 4) as u8); r[3 * i + 2] = ((self[4 * i + 2] >> 4) as u8) | ((self[4 * i + 3] << 2) as u8); } } - // ML-DSA65 and 87 share a POLY_W1_PACKED_LEN value - MLDSA65_POLY_W1_PACKED_LEN => { + // ML-DSA-65 and ML-DSA-87 share this ๐›พ2: (๐‘ž โˆ’ 1)/(2๐›พ2) โˆ’ 1 = 15, so two 4-bit + // coefficients pack into one byte. + GAMMA2_Q_MINUS_1_OVER_32 => { for i in 0..N / 2 { r[i] = ((self[2 * i]) | (self[2 * i + 1] << 4)) as u8; } @@ -153,7 +174,7 @@ impl Polynomial { } } - r + out } /// Algorithm 41 NTT(๐‘ค) diff --git a/crypto/mldsa/tests/hash_mldsa_tests.rs b/crypto/mldsa/tests/hash_mldsa_tests.rs index ecdfb7c3..1ff7081e 100644 --- a/crypto/mldsa/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa/tests/hash_mldsa_tests.rs @@ -3,7 +3,7 @@ mod hash_mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; use bouncycastle_core::traits::{ - Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, + Hash, PHSignatureVerifier, PHSigner, SecurityStrength, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_hex as hex; @@ -365,4 +365,75 @@ mod hash_mldsa_tests { let pk_expanded = MLDSA44PublicKeyExpanded::from(&pk); HashMLDSA44_with_SHA256::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); } + + #[test] + fn algorithm_names_strengths_and_oids() { + use bouncycastle_core::traits::{Algorithm, AlgorithmOID}; + + // `Algorithm` is implemented once, generically over the pairing, so nothing else states + // these per algorithm. Pinned here so a wrong wiring of the blanket impl, or a typo in a + // pairing, is a test failure rather than a mislabelled algorithm. + assert_eq!(HashMLDSA44_with_SHA256::ALG_NAME, "HashML-DSA-44_with_SHA256"); + assert_eq!(HashMLDSA65_with_SHA256::ALG_NAME, "HashML-DSA-65_with_SHA256"); + assert_eq!(HashMLDSA87_with_SHA256::ALG_NAME, "HashML-DSA-87_with_SHA256"); + assert_eq!(HashMLDSA44_with_SHA512::ALG_NAME, "HashML-DSA-44_with_SHA512"); + assert_eq!(HashMLDSA65_with_SHA512::ALG_NAME, "HashML-DSA-65_with_SHA512"); + assert_eq!(HashMLDSA87_with_SHA512::ALG_NAME, "HashML-DSA-87_with_SHA512"); + + // The claimed strength is derived as the weaker of the two components, so these pin the + // derivation rather than six hand-written values. SHA-256 caps every pairing it appears in + // at 128 bits; with SHA-512 the ML-DSA parameter set is what binds. + assert_eq!(HashMLDSA44_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA65_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA87_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA44_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HashMLDSA65_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(HashMLDSA87_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + + // NIST's Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 }, + // -65- { sigAlgs 33 }, -87- { sigAlgs 34 }. The three SHA-256 pairings carry no OID in + // this implementation, which is why `AlgorithmOID` is still written out per alias rather + // than derived from the pairing like the name and strength above. + assert_eq!(HashMLDSA44_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 32]); + assert_eq!(HashMLDSA65_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 33]); + assert_eq!(HashMLDSA87_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 34]); + + for (oid, der) in [ + (HashMLDSA44_with_SHA512::OID, HashMLDSA44_with_SHA512::OID_DER), + (HashMLDSA65_with_SHA512::OID, HashMLDSA65_with_SHA512::OID_DER), + (HashMLDSA87_with_SHA512::OID, HashMLDSA87_with_SHA512::OID_DER), + ] { + assert_eq!(der[0], 0x06, "DER tag must be OBJECT IDENTIFIER"); + assert_eq!(der[1] as usize, der.len() - 2, "DER length must match the content"); + assert_eq!( + &der[2..], + &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, *oid.last().unwrap() as u8] + ); + } + } + + #[test] + fn prehash_lengths_match_the_hash_functions() { + // `PH_LEN` is still a const generic on `HashMLDSA` -- `PHSigner` takes it as one -- but + // no alias hard-codes it: each passes `{ ...Params::PH_LEN }`, which is the pre-hash's own + // `HashAlgParams::OUTPUT_LEN`. So there is only one value, and nothing in the chain can + // disagree with itself. What is left to check is whether that value matches the digest + // the hash actually produces, which is what this test does. + assert_eq!(SHA256::new().hash(b"").len(), 32); + assert_eq!(SHA512::new().hash(b"").len(), 64); + + // A signature is produced from a `ph` of exactly that length, so a mismatch would fail + // here rather than silently truncating. + let msg = b"The quick brown fox"; + let ph256: [u8; 32] = SHA256::new().hash(msg).try_into().unwrap(); + let ph512: [u8; 64] = SHA512::new().hash(msg).try_into().unwrap(); + + let (pk, sk) = HashMLDSA65_with_SHA256::keygen().unwrap(); + let sig = HashMLDSA65_with_SHA256::sign_ph(&sk, &ph256, None).unwrap(); + HashMLDSA65_with_SHA256::verify_ph(&pk, &ph256, None, &sig).unwrap(); + + let (pk, sk) = HashMLDSA65_with_SHA512::keygen().unwrap(); + let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph512, None).unwrap(); + HashMLDSA65_with_SHA512::verify_ph(&pk, &ph512, None, &sig).unwrap(); + } } diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index 33aa8f9f..aebd3a06 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -1071,6 +1071,46 @@ mod mldsa_tests { _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } } + + #[test] + fn algorithm_names_and_oids() { + use bouncycastle_core::traits::{Algorithm, AlgorithmOID}; + + // `Algorithm` and `AlgorithmOID` are implemented once, generically over the parameter set, + // so nothing else states these per algorithm. Pinned here so that a wrong wiring of the + // blanket impls, or a typo in a parameter set, is a test failure rather than a silently + // mislabelled algorithm or an unparseable OID. + assert_eq!(MLDSA44::ALG_NAME, "ML-DSA-44"); + assert_eq!(MLDSA65::ALG_NAME, "ML-DSA-65"); + assert_eq!(MLDSA87::ALG_NAME, "ML-DSA-87"); + + assert_eq!(MLDSA44::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(MLDSA65::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(MLDSA87::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + + // NIST's Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 }, + // id-ml-dsa-65 { sigAlgs 18 }, id-ml-dsa-87 { sigAlgs 19 }, under + // joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) nistAlgorithm(4) + // sigAlgs(3). + assert_eq!(MLDSA44::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 17]); + assert_eq!(MLDSA65::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 18]); + assert_eq!(MLDSA87::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 19]); + + // The DER encodings must be the OBJECT IDENTIFIER (tag 0x06) encodings of those arcs: + // 9 content bytes, the first being 40*2 + 16 = 0x60, then 840 as the two-byte 0x86 0x48. + for (oid, der) in [ + (MLDSA44::OID, MLDSA44::OID_DER), + (MLDSA65::OID, MLDSA65::OID_DER), + (MLDSA87::OID, MLDSA87::OID_DER), + ] { + assert_eq!(der[0], 0x06, "DER tag must be OBJECT IDENTIFIER"); + assert_eq!(der[1] as usize, der.len() - 2, "DER length must match the content"); + assert_eq!( + &der[2..], + &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, *oid.last().unwrap() as u8] + ); + } + } } struct Kat { diff --git a/crypto/mlkem-lowmemory/src/aux_functions.rs b/crypto/mlkem-lowmemory/src/aux_functions.rs index b548f440..406ef47a 100644 --- a/crypto/mlkem-lowmemory/src/aux_functions.rs +++ b/crypto/mlkem-lowmemory/src/aux_functions.rs @@ -146,7 +146,7 @@ pub(crate) fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { /// Takes a seed as input and outputs a pseudorandom sample from the distribution D๐œ‚(๐‘…๐‘ž). /// Input: byte array ๐ต โˆˆ ๐”น64๐œ‚ . /// Output: array ๐‘“ โˆˆ โ„ค256 โ–ท the coefficients of the sampled polynomial -pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { +pub(crate) fn sample_poly_cbd(bytes: &[u8], eta: i16) -> Polynomial { debug_assert_eq!(bytes.len(), 64 * eta as usize); let mut f = Polynomial::new(); @@ -193,7 +193,7 @@ pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { /// SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘ )) /// Performs both the PRF and SamplePolyCBD steps -pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial { +pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8, eta: i16) -> Polynomial { // Alg 13: 9: ๐ฌ[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘ )) // โ–ท ๐ฌ[๐‘–] โˆˆ โ„ค256 sampled from CBD match eta { @@ -208,7 +208,7 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial buf }; - sample_poly_cbd::(&buf) + sample_poly_cbd(&buf, eta) } 3 => { let buf = { @@ -220,7 +220,7 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial buf }; - sample_poly_cbd::(&buf) + sample_poly_cbd(&buf, eta) } _ => unreachable!(), } diff --git a/crypto/mlkem-lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs index 250c9ea6..7a15b31e 100644 --- a/crypto/mlkem-lowmemory/src/lib.rs +++ b/crypto/mlkem-lowmemory/src/lib.rs @@ -244,6 +244,7 @@ mod aux_functions; mod low_memory_helpers; pub mod mlkem; mod mlkem_keys; +mod params; mod polynomial; /*** Exported types ***/ @@ -264,3 +265,5 @@ pub use mlkem::{MLKEM_RND_LEN, MLKEM_SEED_LEN, MLKEM_SS_LEN}; pub use mlkem::{MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN}; pub use mlkem::{MLKEM768_CT_LEN, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; pub use mlkem::{MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN}; + +/*** Parameter sets ***/ diff --git a/crypto/mlkem-lowmemory/src/low_memory_helpers.rs b/crypto/mlkem-lowmemory/src/low_memory_helpers.rs index f7773e54..8c16f34c 100644 --- a/crypto/mlkem-lowmemory/src/low_memory_helpers.rs +++ b/crypto/mlkem-lowmemory/src/low_memory_helpers.rs @@ -4,6 +4,7 @@ use crate::aux_functions::{byte_decode, byte_encode, sample_ntt, sample_poly_CBD}; use crate::mlkem::{N, POLY_BYTES, q}; +use crate::params::MLKEMParams; use crate::polynomial::Polynomial; /// Computes the element [i,j] of the A_hat public matrix @@ -13,23 +14,23 @@ pub(crate) fn expandA_elem(rho: &[u8; 32], i: usize, j: usize) -> Polynomial { /// Computes a single row of the core keygen operation /// Alg 13: line 18: ๐€_hat โˆ˜ ๐ฌ_hat -pub(crate) fn compute_A_hat_dot_s_hat( +pub(crate) fn compute_A_hat_dot_s_hat( rho: &[u8; 32], sigma: &[u8; 32], row: usize, ) -> Polynomial { let mut t_hat_i: Polynomial = { let mut A_i0 = expandA_elem(rho, row, 0); - let mut s_0 = sample_poly_CBD::(sigma, 0 as u8); + let mut s_0 = sample_poly_CBD(sigma, 0 as u8, P::eta1); s_0.ntt(); // now s_hat_0 A_i0.base_mult_montgomery(&s_0); A_i0 }; - for j in 1..k { + for j in 1..P::k { let mut A_ij = expandA_elem(rho, row, j); - let mut s_j = sample_poly_CBD::(sigma, j as u8); + let mut s_j = sample_poly_CBD(sigma, j as u8, P::eta1); s_j.ntt(); // now s_hat_j A_ij.base_mult_montgomery(&s_j); t_hat_i.add(&A_ij); @@ -43,7 +44,7 @@ pub(crate) fn compute_A_hat_dot_s_hat( /// Compute a single row of the core encaps operation /// Alg 14: line 19: NTTโˆ’1(๐€_hat_T โˆ˜ ๐ฒ_hat) -pub(crate) fn compute_A_hat_dot_y_hat( +pub(crate) fn compute_A_hat_dot_y_hat( rho: &[u8; 32], r: &[u8; 32], row: usize, @@ -52,7 +53,7 @@ pub(crate) fn compute_A_hat_dot_y_hat( // โ–ท re-generate matrix ๐€ โˆˆ (โ„ค256_๐‘ž )๐‘˜ร—๐‘˜ sampled in Alg. 13 // 9: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++) - // โ–ท generate ๐ฒ โˆˆ (โ„ค256_๐‘ž)k + // โ–ท generate ๐ฒ โˆˆ (โ„ค256_๐‘ž)^๐‘˜ // 10: ๐ฒ[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐‘Ÿ, ๐‘)) // โ–ท ๐ฒ[๐‘–] โˆˆ โ„ค256 sampled from CBD // 11: ๐‘ โ† ๐‘ + 1 @@ -61,16 +62,16 @@ pub(crate) fn compute_A_hat_dot_y_hat( let mut u_i: Polynomial = { let mut A_0i = expandA_elem(rho, 0, row); - let mut y_0 = sample_poly_CBD::(r, /*N*/ 0); + let mut y_0 = sample_poly_CBD(r, /*N*/ 0, P::eta1); y_0.ntt(); A_0i.base_mult_montgomery(&y_0); A_0i }; - for j in 1..k { + for j in 1..P::k { let mut A_ji = expandA_elem(&rho, j, row); - let mut y_j = sample_poly_CBD::(r, /*N*/ j as u8); + let mut y_j = sample_poly_CBD(r, /*N*/ j as u8, P::eta1); y_j.ntt(); A_ji.base_mult_montgomery(&y_j); u_i.add(&A_ji); @@ -82,12 +83,12 @@ pub(crate) fn compute_A_hat_dot_y_hat( /// Compute a term of the output polynomial v of the core encaps operation based on a single row of t_hat_i and y_hat. /// Alg 14: line 21: ๐‘ฃ โ† NTTโˆ’1(๐ญ_hat_T โˆ˜ ๐ฒ_hat) -pub(crate) fn compute_t_hat_dot_y_hat_row( +pub(crate) fn compute_t_hat_dot_y_hat_row( r: &[u8; 32], t_hat_i: &Polynomial, row: usize, ) -> Polynomial { - let mut y_i = sample_poly_CBD::(r, /*N*/ row as u8); + let mut y_i = sample_poly_CBD(r, /*N*/ row as u8, P::eta1); y_i.ntt(); y_i.base_mult_montgomery(&t_hat_i); y_i.inv_ntt(); @@ -95,32 +96,29 @@ pub(crate) fn compute_t_hat_dot_y_hat_row( y_i } -pub(crate) fn pack_t_hat_row( +pub(crate) fn pack_t_hat_row( t_hat_i: &Polynomial, row: usize, - t_hat_packed: &mut [u8; T_PACKED_LEN], + t_hat_packed: &mut P::TPacked, ) { byte_encode::<12, POLY_BYTES>( &t_hat_i, - t_hat_packed[row * POLY_BYTES..(row + 1) * POLY_BYTES].as_mut().try_into().unwrap(), + (&mut t_hat_packed.as_mut()[row * POLY_BYTES..(row + 1) * POLY_BYTES]).try_into().unwrap(), ); } -pub(crate) fn unpack_t_hat_row( - t_hat_packed: &[u8; T_PACKED_LEN], - row: usize, -) -> Polynomial { +pub(crate) fn unpack_t_hat_row(t_hat_packed: &[u8], row: usize) -> Polynomial { byte_decode::<12, POLY_BYTES>( t_hat_packed[row * POLY_BYTES..(row + 1) * POLY_BYTES].try_into().unwrap(), ) } -pub(crate) fn pack_s_hat_row( +pub(crate) fn pack_s_hat_row( s_hat_i: &Polynomial, row: usize, s_hat_packed: &mut [u8], ) { - debug_assert!(s_hat_packed.len() >= k * POLY_BYTES); + debug_assert!(s_hat_packed.len() >= P::k * POLY_BYTES); byte_encode::<12, POLY_BYTES>( s_hat_i, @@ -130,28 +128,28 @@ pub(crate) fn pack_s_hat_row( /// This is an optimized version of /// ByteEncode_๐‘‘๐‘ข( Compress_๐‘‘๐‘ข(๐ฎ) ) -/// which packs a single row of the polynomial vector u according to the packing coefficient dv +/// which packs a single row of the polynomial vector u according to the packing coefficient ๐‘‘๐‘ข /// into the correct location within the ciphertext -pub(crate) fn compress_u_row( +pub(crate) fn compress_u_row( u_i: Polynomial, row: usize, ct: &mut [u8; CT_LEN], ) { - // make sure we have received a dv - assert!(du == 10 || du == 11); + // make sure we received a supported ๐‘‘๐‘ข + assert!(P::du == 10 || P::du == 11); // bc-java has a conditional_sub_q() here, but I pass all unit tests without it, so I'm taking it out for performance. // let mut u_i = u_i.clone(); // u_i.conditional_sub_q(); // figure out where in the ct array we're going to write to - // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let start: usize = row * (N * (du as usize) / 8); - let end: usize = (row + 1) * (N * (du as usize) / 8); + // each of the N i16's will take ๐‘‘๐‘ข bits, so a polynomial takes N * ๐‘‘๐‘ข bits, then we have ๐‘˜ of them + let start: usize = row * (N * (P::du as usize) / 8); + let end: usize = (row + 1) * (N * (P::du as usize) / 8); let out = &mut ct[start..end]; let mut idx = 0; - match du { + match P::du { 10 => { // MLKEM512 and MLKEM 768 let mut t = [0i16; 4]; @@ -196,24 +194,24 @@ pub(crate) fn compress_u_row( } } -pub(crate) fn unpack_ciphertext_u_row( +pub(crate) fn unpack_ciphertext_u_row( row: usize, ct: &[u8; CT_LEN], ) -> Polynomial { let mut u_i = Polynomial::new(); - // make sure to received a dv - assert!(du == 10 || du == 11); + // make sure we received a supported ๐‘‘๐‘ข + assert!(P::du == 10 || P::du == 11); // figure out where in the ct array we're going to write to - // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let start: usize = row * (N * (du as usize) / 8); - let end: usize = (row + 1) * (N * (du as usize) / 8); + // each of the N i16's will take ๐‘‘๐‘ข bits, so a polynomial takes N * ๐‘‘๐‘ข bits, then we have ๐‘˜ of them + let start: usize = row * (N * (P::du as usize) / 8); + let end: usize = (row + 1) * (N * (P::du as usize) / 8); let compressed_u_i = &ct[start..end]; let mut idx = 0; - match du { + match P::du { 10 => { // MLKEM512 and MLKEM768 let mut t = [0i16; 4]; @@ -269,18 +267,13 @@ pub(crate) fn unpack_ciphertext_u_row( u_i } -pub(crate) fn unpack_ciphertext_v< - const k: usize, - const CT_LEN: usize, - const du: i16, - const dv: i16, ->( +pub(crate) fn unpack_ciphertext_v( c: &[u8; CT_LEN], ) -> Polynomial { - // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k * (N * (du as usize) / 8); + // each of the N i16's will take ๐‘‘๐‘ข bits, so a polynomial takes N * ๐‘‘๐‘ข bits, then we have ๐‘˜ of them + let lim: usize = P::k * (N * (P::du as usize) / 8); - let v = Polynomial::decompress_poly::(&c[lim..]); + let v = Polynomial::decompress_poly::

(&c[lim..]); v } diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index dfb7ce51..da61c593 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -11,6 +11,7 @@ use crate::mlkem_keys::{ }; use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait}; use crate::mlkem_keys::{MLKEMPublicKeyInternalTrait, MLKEMPublicKeyTrait}; +use crate::params::{MLKEM512Params, MLKEM768Params, MLKEM1024Params, MLKEMParams}; use crate::polynomial::Polynomial; use bouncycastle_core::errors::{KEMError, RNGError}; use bouncycastle_core::key_material::{ @@ -45,70 +46,44 @@ pub const MLKEM_SS_LEN: usize = 32; pub(crate) const N: usize = 256; pub(crate) const q: i16 = 3329; pub(crate) const q_inv: i32 = 62209; -pub(crate) const ETA2: i16 = 2; pub(crate) const POLY_BYTES: usize = 384; /* ML-KEM-512 params */ -/// Length of the \[u8] holding a ML-KEM-512 public key. -pub const MLKEM512_PK_LEN: usize = 800; -/// Length of the \[u8] holding a ML-KEM-512 seed-based private key. +/// Length of the \[u8] holding an ML-KEM-512 public key. +pub const MLKEM512_PK_LEN: usize = MLKEM512Params::PK_LEN; +/// Length of the \[u8] holding an ML-KEM-512 seed-based private key. pub const MLKEM512_SK_LEN: usize = MLKEM_SEED_LEN; /// Length of the \[u8] holding a full ML-KEM-512 private key in the NIST encoding. -pub const MLKEM512_FULL_SK_LEN: usize = 1632; -/// Length of the \[u8] holding a ML-KEM-512 ciphertext. -pub const MLKEM512_CT_LEN: usize = 768; -pub(crate) const MLKEM512_k: usize = 2; -pub(crate) const MLKEM512_ETA1: i16 = 3; -pub(crate) const MLKEM512_DU: i16 = 10; -pub(crate) const MLKEM512_DV: i16 = 4; -/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2 -pub(crate) const MLKEM512_LAMBDA: i16 = 128; - -// internal derived values -pub(crate) const MLKEM512_T_PACKED_LEN: usize = 12 * MLKEM512_k * 32; +pub const MLKEM512_FULL_SK_LEN: usize = MLKEM512Params::FULL_SK_LEN; +/// Length of the \[u8] holding an ML-KEM-512 ciphertext. +pub const MLKEM512_CT_LEN: usize = MLKEM512Params::CT_LEN; + +/*** internal derived values ***/ /* ML-KEM-768 params */ -/// Length of the \[u8] holding a ML-KEM-768 public key. -pub const MLKEM768_PK_LEN: usize = 1184; -/// Length of the \[u8] holding a ML-KEM-768 seed-based private key. +/// Length of the \[u8] holding an ML-KEM-768 public key. +pub const MLKEM768_PK_LEN: usize = MLKEM768Params::PK_LEN; +/// Length of the \[u8] holding an ML-KEM-768 seed-based private key. pub const MLKEM768_SK_LEN: usize = MLKEM_SEED_LEN; /// Length of the \[u8] holding a full ML-KEM-768 private key in the NIST encoding. -pub const MLKEM768_FULL_SK_LEN: usize = 2400; -/// Length of the \[u8] holding a ML-KEM-768 ciphertext. -pub const MLKEM768_CT_LEN: usize = 1088; -pub(crate) const MLKEM768_k: usize = 3; -pub(crate) const MLKEM768_ETA1: i16 = 2; -pub(crate) const MLKEM768_DU: i16 = 10; -pub(crate) const MLKEM768_DV: i16 = 4; -/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2 -pub(crate) const MLKEM768_LAMBDA: i16 = 192; - -// internal derived values -pub(crate) const MLKEM768_T_PACKED_LEN: usize = 12 * MLKEM768_k * 32; +pub const MLKEM768_FULL_SK_LEN: usize = MLKEM768Params::FULL_SK_LEN; +/// Length of the \[u8] holding an ML-KEM-768 ciphertext. +pub const MLKEM768_CT_LEN: usize = MLKEM768Params::CT_LEN; /* ML-KEM-1024 params */ -/// Length of the \[u8] holding a ML-KEM-1024 public key. -pub const MLKEM1024_PK_LEN: usize = 1568; -/// Length of the \[u8] holding a ML-KEM-512 seed-based private key. +/// Length of the \[u8] holding an ML-KEM-1024 public key. +pub const MLKEM1024_PK_LEN: usize = MLKEM1024Params::PK_LEN; +/// Length of the \[u8] holding an ML-KEM-1024 seed-based private key. pub const MLKEM1024_SK_LEN: usize = MLKEM_SEED_LEN; -/// Length of the \[u8] holding a full ML-KEM-512 private key in the NIST encoding. -pub const MLKEM1024_FULL_SK_LEN: usize = 3168; -/// Length of the \[u8] holding a ML-KEM-1024 ciphertext. -pub const MLKEM1024_CT_LEN: usize = 1568; -pub(crate) const MLKEM1024_k: usize = 4; -pub(crate) const MLKEM1024_ETA1: i16 = 2; -pub(crate) const MLKEM1024_DU: i16 = 11; -pub(crate) const MLKEM1024_DV: i16 = 5; -/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2 -pub(crate) const MLKEM1024_LAMBDA: i16 = 256; - -// internal derived values -pub(crate) const MLKEM1024_T_PACKED_LEN: usize = 12 * MLKEM1024_k * 32; - -// Typedefs just to make the algorithms look more like the FIPS 204 sample code. +/// Length of the \[u8] holding a full ML-KEM-1024 private key in the NIST encoding. +pub const MLKEM1024_FULL_SK_LEN: usize = MLKEM1024Params::FULL_SK_LEN; +/// Length of the \[u8] holding an ML-KEM-1024 ciphertext. +pub const MLKEM1024_CT_LEN: usize = MLKEM1024Params::CT_LEN; + +/*** Typedefs just to make the algorithms look more like the FIPS 204 sample code. ***/ pub(crate) type G = SHA3_512; pub(crate) type H = SHA3_256; pub(crate) type J = SHAKE256; @@ -117,86 +92,73 @@ pub(crate) type J = SHAKE256; /// The ML-KEM-512 algorithm. pub type MLKEM512 = MLKEM< + MLKEM512Params, + MLKEM512PublicKey, + MLKEM512PrivateKey, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_FULL_SK_LEN, MLKEM512_CT_LEN, MLKEM_SS_LEN, - MLKEM512PublicKey, - MLKEM512PrivateKey, - MLKEM512_k, - MLKEM512_ETA1, - MLKEM512_DU, - MLKEM512_DV, - MLKEM512_LAMBDA, - MLKEM512_T_PACKED_LEN, >; -impl Algorithm for MLKEM512 { - const ALG_NAME: &'static str = ML_KEM_512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 } -impl AlgorithmOID for MLKEM512 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01]; -} - /// The ML-KEM-768 algorithm. pub type MLKEM768 = MLKEM< + MLKEM768Params, + MLKEM768PublicKey, + MLKEM768PrivateKey, MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_FULL_SK_LEN, MLKEM768_CT_LEN, MLKEM_SS_LEN, - MLKEM768PublicKey, - MLKEM768PrivateKey, - MLKEM768_k, - MLKEM768_ETA1, - MLKEM768_DU, - MLKEM768_DV, - MLKEM768_LAMBDA, - MLKEM768_T_PACKED_LEN, >; -impl Algorithm for MLKEM768 { - const ALG_NAME: &'static str = ML_KEM_768_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 } -impl AlgorithmOID for MLKEM768 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02]; -} - /// The ML-KEM-1024 algorithm. pub type MLKEM1024 = MLKEM< + MLKEM1024Params, + MLKEM1024PublicKey, + MLKEM1024PrivateKey, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_FULL_SK_LEN, MLKEM1024_CT_LEN, MLKEM_SS_LEN, - MLKEM1024PublicKey, - MLKEM1024PrivateKey, - MLKEM1024_k, - MLKEM1024_ETA1, - MLKEM1024_DU, - MLKEM1024_DV, - MLKEM1024_LAMBDA, - MLKEM1024_T_PACKED_LEN, >; -impl Algorithm for MLKEM1024 { - const ALG_NAME: &'static str = ML_KEM_1024_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, +> Algorithm for MLKEM +{ + const ALG_NAME: &'static str = P::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 } -impl AlgorithmOID for MLKEM1024 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03]; + +/// The OIDs NIST assigned in the Computer Security Objects Register: id-alg-ml-kem-512 +/// { kems 1 }, id-alg-ml-kem-768 { kems 2 } and id-alg-ml-kem-1024 { kems 3 }. As with +/// [`Algorithm`], the values belong to the parameter set, so one impl covers all three. +impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, +> AlgorithmOID for MLKEM +{ + const OID: &'static [u32] = P::OID; + const OID_DER: &'static [u8] = P::OID_DER; } /// The core internal implementation of the ML-KEM algorithm. @@ -204,57 +166,30 @@ impl AlgorithmOID for MLKEM1024 { /// but is shouldn't ever need to be used directly. /// Please use the named public types. pub struct MLKEM< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait - + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta1: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, - const T_PACKED_LEN: usize, > { - _phantom: PhantomData<(PK, SK)>, + _phantom: PhantomData<(P, PK, SK)>, } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait - + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta1: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, - const T_PACKED_LEN: usize, -> - MLKEM< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - CT_LEN, - SS_LEN, - PK, - SK, - k, - eta1, - du, - dv, - LAMBDA, - T_PACKED_LEN, - > +> MLKEM { /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds. /// @@ -278,7 +213,7 @@ impl< /// Input: randomness ๐‘Ÿ โˆˆ ๐”น32 . /// Output: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ). fn pke_encrypt( - t_hat_packed: &[u8; T_PACKED_LEN], + t_hat_packed: &P::TPacked, rho: &[u8; 32], m: [u8; 32], r: &[u8; 32], @@ -297,14 +232,14 @@ impl< // Note: y_hat is needed twice: once here at line 19, and again at line 21. // Here it is generated each time it is needed in order to save memory. - for i in 0..k { - let mut u_i = compute_A_hat_dot_y_hat::(rho, &r, i); + for i in 0..P::k { + let mut u_i = compute_A_hat_dot_y_hat::

(rho, &r, i); - let e1_i = sample_poly_CBD::(&r, (k + i) as u8); + let e1_i = sample_poly_CBD(&r, (P::k + i) as u8, P::eta2); u_i.add(&e1_i); u_i.poly_reduce(); - compress_u_row::(u_i, i, &mut ct); + compress_u_row::(u_i, i, &mut ct); } // 17: ๐‘’2 โ† SamplePolyCBD_๐œ‚2(PRF๐œ‚2 (๐‘Ÿ, ๐‘)) @@ -313,23 +248,23 @@ impl< // 23: ๐‘2 โ† ByteEncode_๐‘‘๐‘ฃ(Compress_๐‘‘๐‘ฃ(๐‘ฃ)) { // compute v, which is a single polynomial, but requires iterating over the vectors t_hat and y_hat - let mut v = compute_t_hat_dot_y_hat_row::( + let mut v = compute_t_hat_dot_y_hat_row::

( &r, - &unpack_t_hat_row(t_hat_packed, 0), + &unpack_t_hat_row(t_hat_packed.as_ref(), 0), /*row*/ 0, ); - for i in 1..k { - let v_i = compute_t_hat_dot_y_hat_row::( + for i in 1..P::k { + let v_i = compute_t_hat_dot_y_hat_row::

( &r, - &unpack_t_hat_row(t_hat_packed, i), + &unpack_t_hat_row(t_hat_packed.as_ref(), i), /*row*/ i, ); v.add(&v_i); } // perform polynomial addition - let e2 = sample_poly_CBD::(&r, 2 * k as u8); + let e2 = sample_poly_CBD(&r, 2 * P::k as u8, P::eta2); v.add(&e2); let mu = Polynomial::from_msg(m); @@ -337,7 +272,7 @@ impl< v.poly_reduce(); - v.compress_poly::(&mut ct[CT_LEN - (N * (dv as usize) / 8)..]); + v.compress_poly::

(&mut ct[CT_LEN - (N * (P::dv as usize) / 8)..]); } ct @@ -367,8 +302,6 @@ impl< /// Failing to use this properly will result in catastrophic vulnerabilities. /// Please don't do it. pub fn encaps_internal(ek: &PK, m: [u8; 32]) -> ([u8; 32], [u8; CT_LEN]) { - debug_assert_eq!(CT_LEN, 32 * ((du as usize) * k + (dv as usize))); - // 1: (๐พ, ๐‘Ÿ) โ† G(๐‘šโ€–H(ek)) // โ–ท derive shared secret key ๐พ and randomness ๐‘Ÿ let K: [u8; MLKEM_SS_LEN]; @@ -411,7 +344,7 @@ impl< let mut v1 = { let mut s_hat_i = dk.compute_s_hat_row(0); { - let mut u_prime_i = unpack_ciphertext_u_row::(0, &ct); + let mut u_prime_i = unpack_ciphertext_u_row::(0, &ct); u_prime_i.ntt(); s_hat_i.base_mult_montgomery(&u_prime_i); } @@ -420,10 +353,10 @@ impl< s_hat_i }; - for i in 1..k { + for i in 1..P::k { let mut s_hat_i = dk.compute_s_hat_row(i); { - let mut u_prime_i = unpack_ciphertext_u_row::(i, &ct); + let mut u_prime_i = unpack_ciphertext_u_row::(i, &ct); u_prime_i.ntt(); s_hat_i.base_mult_montgomery(&u_prime_i); } @@ -439,7 +372,7 @@ impl< let w = { // second half of // 6: ๐‘ค โ† ๐‘ฃโ€ฒ โˆ’ NTTโˆ’1(๐ฌ_hat^T โˆ˜ NTT(๐ฎโ€ฒ)) - let mut v_prime = unpack_ciphertext_v::(&ct); + let mut v_prime = unpack_ciphertext_v::(&ct); v_prime.sub(&v1); v_prime.poly_reduce(); @@ -532,52 +465,17 @@ impl< } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait - + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta1: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, - const T_PACKED_LEN: usize, -> - MLKEMTrait< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - CT_LEN, - SS_LEN, - PK, - SK, - k, - eta1, - du, - dv, - LAMBDA, - T_PACKED_LEN, - > - for MLKEM< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - CT_LEN, - SS_LEN, - PK, - SK, - k, - eta1, - du, - dv, - LAMBDA, - T_PACKED_LEN, - > +> MLKEMTrait + for MLKEM { /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError> { @@ -624,21 +522,15 @@ impl< /// Trait for all three of the ML-DSA algorithm variants. pub trait MLKEMTrait< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait - + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, - const T_PACKED_LEN: usize, >: Sized { /// Generates a fresh key pair. @@ -650,7 +542,7 @@ pub trait MLKEMTrait< // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> { // Source the seed from the provided RNG - if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if rng.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; } let mut seed = KeyMaterial::<64>::new(); @@ -681,37 +573,17 @@ pub trait MLKEMTrait< } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait - + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, - const T_PACKED_LEN: usize, > KEMEncapsulator - for MLKEM< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - CT_LEN, - SS_LEN, - PK, - SK, - k, - eta, - du, - dv, - LAMBDA, - T_PACKED_LEN, - > + for MLKEM { fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { let mut os_rng = HashDRBG_SHA512::new_from_os(); @@ -723,7 +595,7 @@ impl< rng: &mut dyn RNG, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { // Source the random message m from the provided RNG - if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if rng.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; } let mut m = [0u8; 32]; @@ -734,7 +606,7 @@ impl< let mut ss_keymaterial = KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| { - ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + ss_keymaterial.set_security_strength(P::MAX_SECURITY_STRENGTH) })?; Ok((ss_keymaterial, ct)) @@ -742,37 +614,17 @@ impl< } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait - + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, - const T_PACKED_LEN: usize, > KEMDecapsulator - for MLKEM< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - CT_LEN, - SS_LEN, - PK, - SK, - k, - eta, - du, - dv, - LAMBDA, - T_PACKED_LEN, - > + for MLKEM { /// Performs a decapsulation of the given ciphertext. /// Returns the shared secret key. @@ -789,7 +641,7 @@ impl< let mut ss_keymaterial = KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| { - ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + ss_keymaterial.set_security_strength(P::MAX_SECURITY_STRENGTH) })?; Ok(ss_keymaterial) diff --git a/crypto/mlkem-lowmemory/src/mlkem_keys.rs b/crypto/mlkem-lowmemory/src/mlkem_keys.rs index df479a0b..c5f62e6e 100644 --- a/crypto/mlkem-lowmemory/src/mlkem_keys.rs +++ b/crypto/mlkem-lowmemory/src/mlkem_keys.rs @@ -3,27 +3,18 @@ use crate::low_memory_helpers::{ compute_A_hat_dot_s_hat, pack_s_hat_row, pack_t_hat_row, unpack_t_hat_row, }; use crate::mlkem::{G, H, POLY_BYTES, q}; -use crate::mlkem::{ - MLKEM512_ETA1, MLKEM512_FULL_SK_LEN, MLKEM512_LAMBDA, MLKEM512_PK_LEN, MLKEM512_SK_LEN, - MLKEM512_T_PACKED_LEN, MLKEM512_k, -}; -use crate::mlkem::{ - MLKEM768_ETA1, MLKEM768_FULL_SK_LEN, MLKEM768_LAMBDA, MLKEM768_PK_LEN, MLKEM768_SK_LEN, - MLKEM768_T_PACKED_LEN, MLKEM768_k, -}; -use crate::mlkem::{ - MLKEM1024_ETA1, MLKEM1024_FULL_SK_LEN, MLKEM1024_LAMBDA, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, - MLKEM1024_T_PACKED_LEN, MLKEM1024_k, -}; +use crate::mlkem::{MLKEM512_FULL_SK_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN}; +use crate::mlkem::{MLKEM768_FULL_SK_LEN, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; +use crate::mlkem::{MLKEM1024_FULL_SK_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN}; +use crate::params::{MLKEM512Params, MLKEM768Params, MLKEM1024Params, MLKEMParams}; use crate::polynomial::Polynomial; -use crate::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME}; use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_sha3::SHA3_256; -use bouncycastle_utils::secret::Secret; +use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; use core::fmt; use core::fmt::{Debug, Display, Formatter}; // imports just for docs @@ -31,53 +22,37 @@ use core::fmt::{Debug, Display, Formatter}; /* Pub Types */ /// ML-KEM-512 Public Key -pub type MLKEM512PublicKey = MLKEMPublicKey; +pub type MLKEM512PublicKey = MLKEMPublicKey; /// ML-KEM-512 Private Key -pub type MLKEM512PrivateKey = MLKEMSeedPrivateKey< - MLKEM512_k, - MLKEM512_ETA1, - MLKEM512_LAMBDA, - MLKEM512_SK_LEN, - MLKEM512_FULL_SK_LEN, - MLKEM512_PK_LEN, - MLKEM512_T_PACKED_LEN, ->; +pub type MLKEM512PrivateKey = + MLKEMSeedPrivateKey; /// ML-KEM-768 Public Key -pub type MLKEM768PublicKey = MLKEMPublicKey; +pub type MLKEM768PublicKey = MLKEMPublicKey; /// ML-KEM-768 Private Key -pub type MLKEM768PrivateKey = MLKEMSeedPrivateKey< - MLKEM768_k, - MLKEM768_ETA1, - MLKEM768_LAMBDA, - MLKEM768_SK_LEN, - MLKEM768_FULL_SK_LEN, - MLKEM768_PK_LEN, - MLKEM768_T_PACKED_LEN, ->; +pub type MLKEM768PrivateKey = + MLKEMSeedPrivateKey; /// ML-KEM-1024 Public Key -pub type MLKEM1024PublicKey = MLKEMPublicKey; +pub type MLKEM1024PublicKey = MLKEMPublicKey; /// ML-KEM-1024 Private Key -pub type MLKEM1024PrivateKey = MLKEMSeedPrivateKey< - MLKEM1024_k, - MLKEM1024_ETA1, - MLKEM1024_LAMBDA, - MLKEM1024_SK_LEN, - MLKEM1024_FULL_SK_LEN, - MLKEM1024_PK_LEN, - MLKEM1024_T_PACKED_LEN, ->; +pub type MLKEM1024PrivateKey = + MLKEMSeedPrivateKey; /// An ML-KEM public key. -#[derive(Clone)] -pub struct MLKEMPublicKey { - pub(crate) t_hat_packed: [u8; T_PACKED_LEN], +pub struct MLKEMPublicKey { + pub(crate) t_hat_packed: P::TPacked, pub(crate) rho: [u8; 32], } +// Written out rather than derived: `#[derive(Clone)]` would demand `P: Clone`, and `P` is a +// marker for the parameter set that is never stored, only used to name the field types. +impl Clone for MLKEMPublicKey { + fn clone(&self) -> Self { + Self { t_hat_packed: self.t_hat_packed, rho: self.rho } + } +} + /// General trait for all ML-KEM public keys types. -pub trait MLKEMPublicKeyTrait: - KEMPublicKey -{ +pub trait MLKEMPublicKeyTrait: KEMPublicKey { /// Algorithm 23 pkDecode(๐‘๐‘˜) /// Reverses the procedure pkEncode. /// Input: Public key ๐‘๐‘˜ โˆˆ ๐”น32+32๐‘˜(bitlen (๐‘žโˆ’1)โˆ’๐‘‘). @@ -86,7 +61,7 @@ pub trait MLKEMPublicKeyTrait Result; /// Get a ref to t_hat_packed byte array - fn t_hat_packed(&self) -> &[u8; T_PACKED_LEN]; + fn t_hat_packed(&self) -> &P::TPacked; /// Get a ref to rho fn rho(&self) -> &[u8; 32]; @@ -95,29 +70,30 @@ pub trait MLKEMPublicKeyTrait [u8; 32]; } -pub(crate) trait MLKEMPublicKeyInternalTrait< - const k: usize, - const T_PACKED_LEN: usize, - const PK_LEN: usize, ->: MLKEMPublicKeyTrait +pub(crate) trait MLKEMPublicKeyInternalTrait: + MLKEMPublicKeyTrait { /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. - fn new(t_hat: [u8; T_PACKED_LEN], rho: [u8; 32]) -> Self; + fn new(t_hat: P::TPacked, rho: [u8; 32]) -> Self; } -impl - MLKEMPublicKeyTrait for MLKEMPublicKey +impl MLKEMPublicKeyTrait + for MLKEMPublicKey { fn pk_decode(pk: &[u8; PK_LEN]) -> Result { let pk = Self::new( - pk[..T_PACKED_LEN].try_into().unwrap(), - pk[T_PACKED_LEN..].try_into().unwrap(), + { + let mut t = ::ZEROED; + t.as_mut().copy_from_slice(&pk[..P::T_PACKED_LEN]); + t + }, + pk[P::T_PACKED_LEN..].try_into().unwrap(), ); // check that all entries are in range - for i in 0..k { - let p = unpack_t_hat_row(&pk.t_hat_packed, i); + for i in 0..P::k { + let p = unpack_t_hat_row(pk.t_hat_packed.as_ref(), i); for w in p.coeffs.iter() { if *w >= q { return Err(KEMError::DecodingError("Invalid public key")); @@ -128,7 +104,7 @@ impl Ok(pk) } - fn t_hat_packed(&self) -> &[u8; T_PACKED_LEN] { + fn t_hat_packed(&self) -> &P::TPacked { &self.t_hat_packed } @@ -141,7 +117,7 @@ impl let mut out = [0u8; 32]; let mut h = H::default(); - h.do_update(&self.t_hat_packed); + h.do_update(self.t_hat_packed.as_ref()); h.do_update(&self.rho); let bytes_written = h.do_final_out(&mut out); debug_assert_eq!(bytes_written, 32); @@ -149,24 +125,20 @@ impl } } -impl - MLKEMPublicKeyInternalTrait - for MLKEMPublicKey +impl MLKEMPublicKeyInternalTrait + for MLKEMPublicKey { - fn new(t_hat_packed: [u8; T_PACKED_LEN], rho: [u8; 32]) -> Self { + fn new(t_hat_packed: P::TPacked, rho: [u8; 32]) -> Self { Self { rho, t_hat_packed } } } -impl KEMPublicKey - for MLKEMPublicKey -{ +impl KEMPublicKey for MLKEMPublicKey { /// Algorithm 22 pkEncode(๐œŒ, ๐ญ1) /// Encodes a public key for ML-DSA into a byte string. /// Input:๐œŒ โˆˆ ๐”น32, ๐ญ1 โˆˆ ๐‘…๐‘˜ with coefficients in [0, 2bitlen (๐‘žโˆ’1)โˆ’๐‘‘ โˆ’ 1]. /// Output: Public key ๐‘๐‘˜ โˆˆ ๐”น32+32๐‘˜(bitlen (๐‘žโˆ’1)โˆ’๐‘‘). fn encode(&self) -> [u8; PK_LEN] { - debug_assert_eq!(PK_LEN, 32 + 12 * k * 32); let mut pk = [0u8; PK_LEN]; self.encode_out(&mut pk); @@ -174,13 +146,14 @@ impl KEMPublicKe } fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize { - debug_assert_eq!(self.t_hat_packed.len(), T_PACKED_LEN); + // Check length + debug_assert_eq!(self.t_hat_packed.as_ref().len(), P::T_PACKED_LEN); out.fill(0); - out[..T_PACKED_LEN].copy_from_slice(&self.t_hat_packed); - debug_assert_eq!(out[T_PACKED_LEN..].len(), 32); - out[T_PACKED_LEN..].copy_from_slice(&self.rho); + out[..P::T_PACKED_LEN].copy_from_slice(self.t_hat_packed.as_ref()); + debug_assert_eq!(out[P::T_PACKED_LEN..].len(), 32); + out[P::T_PACKED_LEN..].copy_from_slice(&self.rho); PK_LEN } @@ -194,60 +167,36 @@ impl KEMPublicKe } } -impl Eq - for MLKEMPublicKey -{ -} +impl Eq for MLKEMPublicKey {} -impl PartialEq - for MLKEMPublicKey -{ +impl PartialEq for MLKEMPublicKey { fn eq(&self, other: &Self) -> bool { bouncycastle_utils::ct::ct_eq_bytes(&self.encode(), &other.encode()) } } -impl Debug - for MLKEMPublicKey -{ +impl Debug for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let hash = SHA3_256::new().hash(&self.encode()); - write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, hash) + write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } -impl Display - for MLKEMPublicKey -{ +impl Display for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let hash = SHA3_256::new().hash(&self.encode()); - write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, hash) + write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } /// An ML-KEM private key. -#[derive(Clone)] pub struct MLKEMSeedPrivateKey< - const k: usize, - const eta1: i16, - const LAMBDA: i16, + P: MLKEMParams, const SK_LEN: usize, const FULL_SK_LEN: usize, const PK_LEN: usize, - const T_PACKED_LEN: usize, > { + _phantom: core::marker::PhantomData

, rho: [u8; 32], sigma: Secret<[u8; 32]>, pk_hash: Option<[u8; 32]>, @@ -255,15 +204,24 @@ pub struct MLKEMSeedPrivateKey< seed_d: Secret<[u8; 32]>, } -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> MLKEMSeedPrivateKey +/// See the note on [`MLKEMPublicKey`]'s `Clone` for why this is not derived. +impl Clone + for MLKEMSeedPrivateKey +{ + fn clone(&self) -> Self { + Self { + _phantom: core::marker::PhantomData, + rho: self.rho, + sigma: self.sigma.clone(), + pk_hash: self.pk_hash, + z: self.z.clone(), + seed_d: self.seed_d.clone(), + } + } +} + +impl + MLKEMSeedPrivateKey { /// Create a new MLKEMSeedPrivateKey from a 64-byte KeyMaterial. /// Seed SecurityStrength must match algorithm security strength: 128-bit (ML-KEM-512), 192-bit (ML-KEM-768), or 256-bit (ML-KEM-1024). @@ -276,7 +234,7 @@ impl< )); } - if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if seed.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(KEMError::KeyGenError("SecurityStrength")); } @@ -291,7 +249,7 @@ impl< // Deviation from the FIPS: The implementation does not persist the hash of the public key H(ek) in the // in-memory representation because it can be re-computed as needed. - Ok(Self { rho, sigma, pk_hash: None, z, seed_d }) + Ok(Self { _phantom: core::marker::PhantomData, rho, sigma, pk_hash: None, z, seed_d }) } /// Algorithm 13 K-PKE.KeyGen(๐‘‘) /// 1: (๐œŒ, ๐œŽ) โ† G(๐‘‘โ€–๐‘˜) @@ -305,7 +263,7 @@ impl< let mut g = G::new(); g.do_update(seed_d); - g.do_update(&[k as u8]); + g.do_update(&[P::k as u8]); let bytes_written = g.do_final_out(buf.as_mut()); debug_assert_eq!(bytes_written, 64); @@ -318,11 +276,10 @@ impl< /// General trait for all ML-KEM private keys types. pub trait MLKEMPrivateKeyTrait< - const k: usize, + P: MLKEMParams, const SK_LEN: usize, const FULL_SK_LEN: usize, const PK_LEN: usize, - const T_PACKED_LEN: usize, >: KEMPrivateKey { /// New from KeyMaterial. Can throw a KEMError if the KeyMaterial does not contain sufficient entropy. @@ -332,7 +289,7 @@ pub trait MLKEMPrivateKeyTrait< fn seed(&self) -> Option>; /// Runs essentially a full keygen according to Algorithm 13. // Dev note: This is a partial implementation of keygen_internal(), and probably not allowed in FIPS mode. - fn pk(&self) -> MLKEMPublicKey; + fn pk(&self) -> MLKEMPublicKey; /// Get a ref to the stored public key hash. /// Since in this implementation, this requires running the full keygen, this is a lazy evaluation and /// will only be computationally heavy the first time it is called for a given key. @@ -362,10 +319,9 @@ pub trait MLKEMPrivateKeyTrait< } pub(crate) trait MLKEMPrivateKeyInternalTrait< - const k: usize, + P: MLKEMParams, const SK_LEN: usize, const PK_LEN: usize, - const T_PACKED_LEN: usize, > { fn z(&self) -> &[u8; 32]; @@ -375,19 +331,12 @@ pub(crate) trait MLKEMPrivateKeyInternalTrait< fn rho(&self) -> &[u8; 32]; /// Note: this one is not a ref because the data does not exist in the private key. - fn t_hat_packed(&self) -> [u8; T_PACKED_LEN]; + fn t_hat_packed(&self) -> P::TPacked; } -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> MLKEMPrivateKeyTrait - for MLKEMSeedPrivateKey +impl + MLKEMPrivateKeyTrait + for MLKEMSeedPrivateKey { fn from_keymaterial(seed: &KeyMaterial<64>) -> Result { Self::new(seed) @@ -398,19 +347,14 @@ impl< tmp[32..].as_mut().copy_from_slice(&*self.z); let mut seed = KeyMaterial::<64>::from_bytes_as_type(&*tmp, KeyType::Seed).unwrap(); do_hazardous_operations(&mut seed, |seed| { - seed.set_security_strength(match k { - 2 => SecurityStrength::_128bit, - 3 => SecurityStrength::_192bit, - 4 => SecurityStrength::_256bit, - _ => unreachable!("Invalid mlkem param set"), - }) + seed.set_security_strength(P::MAX_SECURITY_STRENGTH) }) .unwrap(); Some(seed) } - fn pk(&self) -> MLKEMPublicKey { - MLKEMPublicKey::::new(self.t_hat_packed(), self.rho) + fn pk(&self) -> MLKEMPublicKey { + MLKEMPublicKey::::new(self.t_hat_packed(), self.rho) } fn pk_hash(&mut self) -> &[u8; 32] { if self.pk_hash.is_none() { @@ -447,10 +391,10 @@ impl< /* dk_pke */ // Alg 13; line 20: dkPKE โ† ByteEncode12(๐ฌ) - for i in 0..k { - pack_s_hat_row::(&self.compute_s_hat_row(i), i, out); + for i in 0..P::k { + pack_s_hat_row::

(&self.compute_s_hat_row(i), i, out); } - pos += k * POLY_BYTES; + pos += P::k * POLY_BYTES; /* ek */ // Alg 13; line 19: ekPKE โ† ByteEncode12(๐ญ)โ€–๐œŒ @@ -468,37 +412,29 @@ impl< FULL_SK_LEN } fn sk_decode(sk: &[u8; SK_LEN]) -> Self { - debug_assert_eq!(SK_LEN, /* seed*/ 64); Self::from_bytes(sk).unwrap() } } -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> MLKEMPrivateKeyInternalTrait - for MLKEMSeedPrivateKey +impl + MLKEMPrivateKeyInternalTrait + for MLKEMSeedPrivateKey { fn z(&self) -> &[u8; 32] { &self.z } fn compute_s_hat_row(&self, idx: usize) -> Polynomial { - debug_assert!(idx < k); + debug_assert!(idx < P::k); // We're doing just one row of this: // 8: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++) - // โ–ท generate ๐ฌ โˆˆ (โ„ค256)^k + // โ–ท generate ๐ฌ โˆˆ (โ„ค256)^P::k // 9: ๐ฌ[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘ )) // โ–ท ๐ฌ[๐‘–] โˆˆ โ„ค256 sampled from CBD // 10: ๐‘ โ† ๐‘ + 1 // Note: here n = 0 - let mut s_i = sample_poly_CBD::(&self.sigma, idx as u8); + let mut s_i = sample_poly_CBD(&self.sigma, idx as u8, P::eta1); // 16: ๐ฌ_hat โ† NTT(๐ฌ)ฬ‚ s_i.ntt(); @@ -510,47 +446,39 @@ impl< } /// Runs essentially a full keygen according to Algorithm 13 /// Outputs t_hat in the packed encoding specified in FIPS 203 - fn t_hat_packed(&self) -> [u8; T_PACKED_LEN] { - let mut t_hat_packed = [0u8; T_PACKED_LEN]; + fn t_hat_packed(&self) -> P::TPacked { + let mut t_hat_packed = ::ZEROED; - for i in 0..k { + for i in 0..P::k { // first half of // 18: ๐ญ_hat โ† ๐€_hat โˆ˜ ๐ฌ_hat + ๐ž_hat - let mut t_hat_i = compute_A_hat_dot_s_hat::(&self.rho, &self.sigma, i); + let mut t_hat_i = compute_A_hat_dot_s_hat::

(&self.rho, &self.sigma, i); // second half of // 18: ๐ญ_hat โ† ๐€_hat โˆ˜ ๐ฌ_hat + ๐ž_hat { // 12: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++) - // โ–ท generate ๐ž โˆˆ (โ„ค256)^k + // โ–ท generate ๐ž โˆˆ (โ„ค256)^P::k // 13: ๐ž[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘)) // โ–ท ๐ž[๐‘–] โˆˆ โ„ค256 sampled from CBD // 14: ๐‘ โ† ๐‘ + 1 - // Note: here n = k - let mut e_i = sample_poly_CBD::(&self.sigma, (k + i) as u8); + // Note: here n = P::k + let mut e_i = sample_poly_CBD(&self.sigma, (P::k + i) as u8, P::eta1); e_i.ntt(); // technically now e_hat_i t_hat_i.add(&e_i); } t_hat_i.poly_reduce(); - pack_t_hat_row::(&t_hat_i, i, &mut t_hat_packed); + pack_t_hat_row::

(&t_hat_i, i, &mut t_hat_packed); } t_hat_packed } } -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> KEMPrivateKey - for MLKEMSeedPrivateKey +impl + KEMPrivateKey for MLKEMSeedPrivateKey { /// Encode the private key as a 64-byte seed (d || z) fn encode(&self) -> [u8; SK_LEN] { @@ -561,8 +489,6 @@ impl< } fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { - debug_assert_eq!(SK_LEN, 64); - out.fill(0); out[..32].copy_from_slice(&*self.seed_d); @@ -585,27 +511,13 @@ impl< } } -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> Eq for MLKEMSeedPrivateKey +impl Eq + for MLKEMSeedPrivateKey { } -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> PartialEq for MLKEMSeedPrivateKey +impl PartialEq + for MLKEMSeedPrivateKey { fn eq(&self, other: &Self) -> bool { let self_encoded = self.encode(); @@ -615,47 +527,21 @@ impl< } /// Debug impl mainly to prevent the secret key from being printed in logs. -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> fmt::Debug for MLKEMSeedPrivateKey +impl fmt::Debug + for MLKEMSeedPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let pk_hash = self.pk().compute_hash(); - write!(f, "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, &pk_hash,) + write!(f, "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, &pk_hash,) } } /// Display impl mainly to prevent the secret key from being printed in logs. -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> Display for MLKEMSeedPrivateKey +impl Display + for MLKEMSeedPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let pk_hash = self.pk().compute_hash(); - write!(f, "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, &pk_hash,) + write!(f, "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, &pk_hash,) } } diff --git a/crypto/mlkem-lowmemory/src/params.rs b/crypto/mlkem-lowmemory/src/params.rs new file mode 100644 index 00000000..447fb533 --- /dev/null +++ b/crypto/mlkem-lowmemory/src/params.rs @@ -0,0 +1,234 @@ +//! The three ML-KEM parameter sets of FIPS 203, Section 8, as a sealed trait with one type per set. +//! +//! This mirrors `bouncycastle_mlkem::params`, minus the vector and matrix types: this crate never +//! materializes ๐€ฬ‚ or a whole polynomial vector, so the only parameter-sized type it needs is a +//! byte buffer for the packed ๐ญฬ‚. +//! +//! # Derived parameters +//! +//! FIPS 203, Table 2 assigns five values per set (๐‘˜, ๐œ‚1, ๐œ‚2, ๐‘‘๐‘ข, ๐‘‘๐‘ฃ); its last column, the +//! required RBG strength, is carried by `MAX_SECURITY_STRENGTH`. +//! The sizes of Table 3 are each a function of those, so they are written once as defaulted +//! associated consts rather than three times as a hand-computed number. `params::tests` checks every +//! derivation against the values tabulated in FIPS 203. + +use crate::mlkem::{ + ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME, MLKEM_SEED_LEN, MLKEM_SS_LEN, +}; +use bouncycastle_core::traits::SecurityStrength; +use bouncycastle_utils::secret::ZeroizablePrimitive; + +/// A fixed-size byte buffer whose length depends on the parameter set. +/// +/// [`ZeroizablePrimitive`] rather than [`Default`] supplies the all-zero value, because `Default` +/// for arrays stops at 32 elements and every buffer here is longer than that. +trait ByteBuffer: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]> {} +impl ByteBuffer for [u8; N] {} + +/// A crate-private (aka "sealed") trait that prevents a new ML-KEM parameter set from being defined +/// outside this crate. +trait MLKEMParamsInternalTrait {} + +/// One ML-KEM parameter set: the values of FIPS 203, Table 2 and Table 3, and the types whose size +/// they determine. +/// +/// Sealed via a private supertrait, so [`MLKEM512Params`], [`MLKEM768Params`] and +/// [`MLKEM1024Params`] are the only implementations. +pub trait MLKEMParams: MLKEMParamsInternalTrait { + /* FIPS 203, Table 2: the values assigned by each parameter set. */ + + /// ๐‘˜, the rank of the module. + const k: usize; + /// ๐œ‚1, the CBD parameter used for the secret vector ๐ฌ and the keygen error vector ๐ž. + const eta1: i16; + /// ๐œ‚2, the CBD parameter used for the encaps error terms ๐ž1 and ๐‘’2. + /// + /// FIPS 203, Table 2 lists this per parameter set even though all three assign it 2. + const eta2: i16; + /// ๐‘‘๐‘ข, the compression parameter for ๐ฎ. + const du: i16; + /// ๐‘‘๐‘ฃ, the compression parameter for ๐‘ฃ. + const dv: i16; + + /* Algorithm meta-data */ + + /// The algorithm name, as reported by `Algorithm::ALG_NAME`. + const ALG_NAME: &'static str; + /// The strength claimed for this parameter set, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. + const MAX_SECURITY_STRENGTH: SecurityStrength; + /// The OID in component form, as reported by `AlgorithmOID::OID`. + const OID: &'static [u32]; + /// The DER encoding of [`MLKEMParams::OID`], as reported by `AlgorithmOID::OID_DER`. + const OID_DER: &'static [u8]; + + /* Derived. Never written out per parameter set -- see the module docs. */ + + /// The length of an encapsulation key: FIPS 203, Algorithm 16 (ML-KEM.KeyGen_internal) gives + /// ek โˆˆ ๐”น^(384๐‘˜+32). + const PK_LEN: usize = 384 * Self::k + 32; + + /// The length of the FIPS 203 encoding of a decapsulation key: Algorithm 16 gives + /// dk โˆˆ ๐”น^(768๐‘˜+96). + /// + /// Named `FULL_SK_LEN` rather than `SK_LEN` because this crate's private keys are held as the + /// 64-byte seed and expanded on demand; see [`MLKEMParams::SK_LEN`]. + const FULL_SK_LEN: usize = 768 * Self::k + 96; + + /// The length of a ciphertext: FIPS 203, Algorithm 17 (ML-KEM.Encaps_internal) gives + /// ๐‘ โˆˆ ๐”น^(32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ)). + const CT_LEN: usize = 32 * (Self::du as usize * Self::k + Self::dv as usize); + + /// The length of a private key as this crate stores it: the 64-byte seed (๐‘‘, ๐‘ง), for every + /// parameter set. + const SK_LEN: usize = MLKEM_SEED_LEN; + + /// The length of a shared secret. 32 bytes for every parameter set (FIPS 203, Table 3). + const SS_LEN: usize = MLKEM_SS_LEN; + + /// The packed length of ๐ญฬ‚: ๐‘˜ polynomials of 12-bit coefficients, i.e. 384๐‘˜ bytes. This is the + /// encapsulation key without its trailing 32-byte ๐œŒ. + const T_PACKED_LEN: usize = 12 * Self::k * 32; + + /* Types whose size depends on the parameter set. */ + + /// The packed ๐ญฬ‚, of [`MLKEMParams::T_PACKED_LEN`] bytes. + type TPacked: ByteBuffer; +} + +/// The ML-KEM-512 parameter set (FIPS 203, Table 2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLKEM512Params; +/// The ML-KEM-768 parameter set (FIPS 203, Table 2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLKEM768Params; +/// The ML-KEM-1024 parameter set (FIPS 203, Table 2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLKEM1024Params; + +impl MLKEMParamsInternalTrait for MLKEM512Params {} +impl MLKEMParamsInternalTrait for MLKEM768Params {} +impl MLKEMParamsInternalTrait for MLKEM1024Params {} + +impl MLKEMParams for MLKEM512Params { + const k: usize = 2; + const eta1: i16 = 3; + const eta2: i16 = 2; + const du: i16 = 10; + const dv: i16 = 4; + + const ALG_NAME: &'static str = ML_KEM_512_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + /// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01]; + + type TPacked = [u8; 768]; // 384 * 2 +} + +impl MLKEMParams for MLKEM768Params { + const k: usize = 3; + const eta1: i16 = 2; + const eta2: i16 = 2; + const du: i16 = 10; + const dv: i16 = 4; + + const ALG_NAME: &'static str = ML_KEM_768_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + /// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02]; + + type TPacked = [u8; 1152]; // 384 * 3 +} + +impl MLKEMParams for MLKEM1024Params { + const k: usize = 4; + const eta1: i16 = 2; + const eta2: i16 = 2; + const du: i16 = 11; + const dv: i16 = 5; + + const ALG_NAME: &'static str = ML_KEM_1024_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; + /// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03]; + + type TPacked = [u8; 1536]; // 384 * 4 +} + +#[cfg(test)] +mod tests { + use super::*; + + /// FIPS 203, Table 2, transcribed row by row: the five values each parameter set + /// assigns, plus its last column. `(k, eta1, eta2, du, dv, rbg_strength)`. + const TABLE_2: [(usize, i16, i16, i16, i16, i16); 3] = + [(2, 3, 2, 10, 4, 128), (3, 2, 2, 10, 4, 192), (4, 2, 2, 11, 5, 256)]; + + /// FIPS 203, Table 3, transcribed row by row, in bytes: + /// `(encapsulation key, decapsulation key, ciphertext, shared secret key)`. The decapsulation + /// key column is the full FIPS encoding, which this crate calls `FULL_SK_LEN`. + const TABLE_3: [(usize, usize, usize, usize); 3] = + [(800, 1632, 768, 32), (1184, 2400, 1088, 32), (1568, 3168, 1568, 32)]; + + fn check_table_2(i: usize) { + let (k, eta1, eta2, du, dv, rbg_strength) = TABLE_2[i]; + assert_eq!(P::k, k, "{}: ๐‘˜", P::ALG_NAME); + assert_eq!(P::eta1, eta1, "{}: ๐œ‚1", P::ALG_NAME); + assert_eq!(P::eta2, eta2, "{}: ๐œ‚2", P::ALG_NAME); + assert_eq!(P::du, du, "{}: ๐‘‘๐‘ข", P::ALG_NAME); + assert_eq!(P::dv, dv, "{}: ๐‘‘๐‘ฃ", P::ALG_NAME); + assert_eq!( + P::MAX_SECURITY_STRENGTH, + SecurityStrength::from_bits(rbg_strength as usize), + "{}: required RBG strength", + P::ALG_NAME + ); + } + + fn check_table_3(i: usize) { + let (pk_len, full_sk_len, ct_len, ss_len) = TABLE_3[i]; + assert_eq!(P::PK_LEN, pk_len, "{}: encapsulation key size", P::ALG_NAME); + assert_eq!(P::FULL_SK_LEN, full_sk_len, "{}: decapsulation key size", P::ALG_NAME); + assert_eq!(P::CT_LEN, ct_len, "{}: ciphertext size", P::ALG_NAME); + assert_eq!(P::SS_LEN, ss_len, "{}: shared secret size", P::ALG_NAME); + // This crate stores the seed, not the expanded key, for every parameter set. + assert_eq!(P::SK_LEN, 64, "{}: stored private key size", P::ALG_NAME); + } + + fn check_associated_type_sizes() { + assert_eq!( + size_of::(), + P::T_PACKED_LEN, + "{}: TPacked vs T_PACKED_LEN", + P::ALG_NAME + ); + // The encapsulation key is the packed ๐ญฬ‚ followed by the 32-byte ๐œŒ. + assert_eq!(P::T_PACKED_LEN + 32, P::PK_LEN, "{}: 384๐‘˜ + 32 = PK_LEN", P::ALG_NAME); + } + + #[test] + fn test_parameter_sets_match_fips203_table_2() { + check_table_2::(0); + check_table_2::(1); + check_table_2::(2); + } + + #[test] + fn test_sizes_match_fips203_table_3() { + check_table_3::(0); + check_table_3::(1); + check_table_3::(2); + } + + #[test] + fn test_associated_types_are_the_length_their_consts_claim() { + check_associated_type_sizes::(); + check_associated_type_sizes::(); + check_associated_type_sizes::(); + } +} diff --git a/crypto/mlkem-lowmemory/src/polynomial.rs b/crypto/mlkem-lowmemory/src/polynomial.rs index 20684c02..cc0480f1 100644 --- a/crypto/mlkem-lowmemory/src/polynomial.rs +++ b/crypto/mlkem-lowmemory/src/polynomial.rs @@ -4,6 +4,7 @@ use crate::aux_functions::{ ZETAS, ZETAS_INV, barrett_reduce, montgomery_reduce, mul_mont, ntt_base_mult, }; use crate::mlkem::{N, q}; +use crate::params::MLKEMParams; use core::ops::{Index, IndexMut}; /// A polynomial over the ML-KEM ring. @@ -168,13 +169,13 @@ impl Polynomial { /// This is an optimized version of /// ByteEncode_๐‘‘๐‘ฃ( Compress_๐‘‘๐‘ฃ(๐‘ฃ) ) /// which packs a single polynomial according to the packing coefficient dv - pub(crate) fn compress_poly(&self, out: &mut [u8]) { - // make sure to received a dv - debug_assert!(dv == 4 || dv == 5); + pub(crate) fn compress_poly(&self, out: &mut [u8]) { + // make sure to received a P::dv + debug_assert!(P::dv == 4 || P::dv == 5); // make sure the right size output buffer is given - // each of the N i16's will take dv bits - debug_assert_eq!(out.len(), N * (dv as usize) / 8); + // each of the N i16's will take P::dv bits + debug_assert_eq!(out.len(), N * (P::dv as usize) / 8); let mut t = [0u8; 8]; let mut idx = 0; @@ -186,7 +187,7 @@ impl Polynomial { // let mut s = self.clone(); // s.cond_sub_q(); - match dv { + match P::dv { 4 => { // MLKEM512 and MLKEM768 for i in 0..N / 8 { @@ -227,20 +228,20 @@ impl Polynomial { /// This is an optimized version of /// Decompress_๐‘‘๐‘ฃ( ByteDecode_๐‘‘๐‘ฃ(๐‘2) ) /// which unpacks a single polynomial according to the packing coefficient dv - pub(crate) fn decompress_poly(compressed_v: &[u8]) -> Polynomial { - // make sure we have received a dv - debug_assert!(dv == 4 || dv == 5); + pub(crate) fn decompress_poly(compressed_v: &[u8]) -> Polynomial { + // make sure we have received a P::dv + debug_assert!(P::dv == 4 || P::dv == 5); // make sure we were given the right size output buffer - // each of the N i16's will take dv bits - debug_assert_eq!(compressed_v.len(), N * (dv as usize) / 8); + // each of the N i16's will take P::dv bits + debug_assert_eq!(compressed_v.len(), N * (P::dv as usize) / 8); let mut v = Polynomial::new(); let mut idx = 0usize; // if self.m_engine.poly_compressed_bytes() == 128 { - match dv { + match P::dv { 4 => { // MLKEM512 and MLKEM768 for i in 0..N / 2 { diff --git a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs index 858bd200..74cd7c17 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs @@ -722,6 +722,42 @@ mod mlkem_tests { fake_rng.set_security_strength(SecurityStrength::_256bit); _ = MLKEM1024::encaps_rng(&pk1024, &mut fake_rng).unwrap(); } + + #[test] + fn algorithm_names_and_oids() { + use bouncycastle_core::traits::{Algorithm, AlgorithmOID, SecurityStrength}; + + // `Algorithm` and `AlgorithmOID` are implemented once, generically over the parameter set, + // so nothing else states these per algorithm. Pinned here so that a wrong wiring of the + // blanket impls, or a typo in a parameter set, is a test failure rather than a silently + // mislabelled algorithm or an unparseable OID. + assert_eq!(MLKEM512::ALG_NAME, "ML-KEM-512"); + assert_eq!(MLKEM768::ALG_NAME, "ML-KEM-768"); + assert_eq!(MLKEM1024::ALG_NAME, "ML-KEM-1024"); + + assert_eq!(MLKEM512::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(MLKEM768::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(MLKEM1024::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + + // NIST's Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 }, + // id-alg-ml-kem-768 { kems 2 }, id-alg-ml-kem-1024 { kems 3 }. + assert_eq!(MLKEM512::OID, &[2, 16, 840, 1, 101, 3, 4, 4, 1]); + assert_eq!(MLKEM768::OID, &[2, 16, 840, 1, 101, 3, 4, 4, 2]); + assert_eq!(MLKEM1024::OID, &[2, 16, 840, 1, 101, 3, 4, 4, 3]); + + for (oid, der) in [ + (MLKEM512::OID, MLKEM512::OID_DER), + (MLKEM768::OID, MLKEM768::OID_DER), + (MLKEM1024::OID, MLKEM1024::OID_DER), + ] { + assert_eq!(der[0], 0x06, "DER tag must be OBJECT IDENTIFIER"); + assert_eq!(der[1] as usize, der.len() - 2, "DER length must match the content"); + assert_eq!( + &der[2..], + &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, *oid.last().unwrap() as u8] + ); + } + } } // struct Kat { diff --git a/crypto/mlkem/src/aux_functions.rs b/crypto/mlkem/src/aux_functions.rs index dbd71e0f..3dbb8683 100644 --- a/crypto/mlkem/src/aux_functions.rs +++ b/crypto/mlkem/src/aux_functions.rs @@ -1,19 +1,20 @@ //! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. -use crate::matrix::{Matrix, Vector}; +use crate::matrix::{MatrixTrait, VectorTrait}; use crate::mlkem::{N, q, q_inv}; +use crate::params::MLKEMParams; use crate::polynomial::Polynomial; use bouncycastle_core::traits::XOF; use bouncycastle_sha3::{SHAKE128, SHAKE256}; -pub(crate) fn expandA(rho: &[u8; 32]) -> Matrix { - let mut A_hat = Matrix::::new(); - for i in 0..k { +pub(crate) fn expandA(rho: &[u8; 32]) -> P::MatrixA { + let mut A_hat = P::MatrixA::new(); + for i in 0..P::k { // 5: for (๐‘— โ† 0; ๐‘— < ๐‘˜; ๐‘—++) - for j in 0..k { + for j in 0..P::k { // 6: ๐€[๐‘–, ๐‘—] โ† SampleNTT(๐œŒโ€–๐‘—โ€–๐‘–) // โ–ท ๐‘— and ๐‘– are bytes 33 and 34 of the input - A_hat.elems[i][j] = sample_ntt(rho, &[j as u8, i as u8]); + A_hat.set_elem(i, j, sample_ntt(rho, &[j as u8, i as u8])); } } @@ -24,7 +25,6 @@ pub(crate) fn expandA(rho: &[u8; 32]) -> Matrix { /// Encodes an array of ๐‘‘-bit integers into a byte array for 1 โ‰ค ๐‘‘ โ‰ค 12. /// Input: integer array ๐น โˆˆ โ„ค_M^256, where ๐‘š = 2^๐‘‘ if ๐‘‘ < 12, and ๐‘š = ๐‘ž if ๐‘‘ = 12. /// Output: byte array ๐ต โˆˆ ๐”น32๐‘‘. -/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. pub fn byte_encode(F: &Polynomial) -> [u8; PACK_LEN] { debug_assert_eq!(PACK_LEN, 32 * d); @@ -66,7 +66,6 @@ pub fn byte_encode(F: &Polynomial) -> [u8 /// Decodes a byte array into an array of ๐‘‘-bit integers for 1 โ‰ค ๐‘‘ โ‰ค 12. /// Input: byte array ๐ต โˆˆ ๐”น32๐‘‘ . /// Output: integer array ๐น โˆˆ โ„ค256 , where ๐‘š = 2๐‘‘ if ๐‘‘ < 12 and ๐‘š = ๐‘ž if ๐‘‘ = 12. -/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. pub fn byte_decode(B: &[u8; PACK_LEN]) -> Polynomial { debug_assert_eq!(PACK_LEN, 32 * d); @@ -87,7 +86,6 @@ pub fn byte_decode(B: &[u8; PACK_LEN]) -> /// Takes a 32-byte seed and two indices as input and outputs a pseudorandom element of ๐‘‡๐‘ž. /// Input: byte array ๐ต โˆˆ ๐”น34 . โ–ท a 32-byte seed along with two indices /// Output: array ๐‘Ž_hat โˆˆ โ„ค256 โ–ท the coefficients of the NTT of a polynomial -/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. pub fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { let mut a_hat = Polynomial::new(); @@ -157,8 +155,7 @@ pub fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { /// Takes a seed as input and outputs a pseudorandom sample from the distribution D๐œ‚(๐‘…๐‘ž). /// Input: byte array ๐ต โˆˆ ๐”น64๐œ‚ . /// Output: array ๐‘“ โˆˆ โ„ค256 โ–ท the coefficients of the sampled polynomial -/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. -pub fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { +pub(crate) fn sample_poly_cbd(bytes: &[u8], eta: i16) -> Polynomial { debug_assert_eq!(bytes.len(), 64 * eta as usize); let mut f = Polynomial::new(); @@ -205,7 +202,7 @@ pub fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { /// SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘ )) /// Performs both the PRF and SamplePolyCBD steps -pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial { +pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8, eta: i16) -> Polynomial { // Alg 13: 9: ๐ฌ[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘ )) // โ–ท ๐ฌ[๐‘–] โˆˆ โ„ค256 sampled from CBD match eta { @@ -220,7 +217,7 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial buf }; - sample_poly_cbd::(&buf) + sample_poly_cbd(&buf, eta) } 3 => { let buf = { @@ -232,21 +229,18 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial buf }; - sample_poly_cbd::(&buf) + sample_poly_cbd(&buf, eta) } _ => unreachable!(), } } /// Internal helper for keygen since both s_hat and e_hat have identical sampling code -pub(crate) fn sample_vector_CBD( - b: &[u8; 32], - mut n: u8, -) -> Vector { - let mut v = Vector::::new(); +pub(crate) fn sample_vector_CBD(b: &[u8; 32], mut n: u8, eta: i16) -> P::VecK { + let mut v = P::VecK::new(); - for i in 0..k { - v[i] = sample_poly_CBD::(b, n); + for i in 0..P::k { + v[i] = sample_poly_CBD(b, n, eta); // Alg 13: 10: ๐‘ โ† ๐‘ + 1 n += 1; @@ -333,48 +327,36 @@ pub(crate) fn ntt_base_mult( r[off + 1] = out_val1; } -pub(crate) fn pack_ciphertext( - u: &Vector, +pub(crate) fn pack_ciphertext( + u: &P::VecK, v: &Polynomial, ) -> [u8; CT_LEN] { let mut out = [0u8; CT_LEN]; // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k * (N * (du as usize) / 8); + let lim: usize = P::k * (N * (P::du as usize) / 8); - u.compress_pol_vec::(&mut out[..lim]); - v.compress_poly::(&mut out[lim..]); + u.compress_pol_vec::

(&mut out[..lim]); + v.compress_poly::

(&mut out[lim..]); out } -pub(crate) fn unpack_ciphertext_u< - const k: usize, - const CT_LEN: usize, - const du: i16, - const dv: i16, ->( +pub(crate) fn unpack_ciphertext_u( c: &[u8; CT_LEN], -) -> Vector { +) -> P::VecK { // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k * (N * (du as usize) / 8); + let lim: usize = P::k * (N * (P::du as usize) / 8); - let u = Vector::::decompress_pol_vec::(&c[..lim]); - - u + P::VecK::decompress_pol_vec::

(&c[..lim]) } -pub(crate) fn unpack_ciphertext_v< - const k: usize, - const CT_LEN: usize, - const du: i16, - const dv: i16, ->( +pub(crate) fn unpack_ciphertext_v( c: &[u8; CT_LEN], ) -> Polynomial { // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k * (N * (du as usize) / 8); + let lim: usize = P::k * (N * (P::du as usize) / 8); - let v = Polynomial::decompress_poly::(&c[lim..]); + let v = Polynomial::decompress_poly::

(&c[lim..]); v } diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index cfd91c3f..5c48828b 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -157,6 +157,7 @@ mod aux_functions; mod matrix; pub mod mlkem; mod mlkem_keys; +mod params; mod polynomial; /*** Exported types ***/ @@ -181,9 +182,6 @@ pub use mlkem::ML_KEM_768_NAME; pub use mlkem::ML_KEM_1024_NAME; pub use mlkem::{MLKEM_RND_LEN, MLKEM_SEED_LEN, MLKEM_SS_LEN}; - pub use mlkem::{MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN}; pub use mlkem::{MLKEM768_CT_LEN, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; pub use mlkem::{MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN}; - -pub use matrix::Matrix; diff --git a/crypto/mlkem/src/matrix.rs b/crypto/mlkem/src/matrix.rs index 93356585..2b04eb23 100644 --- a/crypto/mlkem/src/matrix.rs +++ b/crypto/mlkem/src/matrix.rs @@ -4,10 +4,59 @@ use core::ops::{Index, IndexMut}; use crate::mlkem::{N, q}; +use crate::params::MLKEMParams; use crate::polynomial; use crate::polynomial::Polynomial; use bouncycastle_utils::secret::ZeroizablePrimitive; +/// The operations this crate performs on a vector of polynomials, i.e. on an element of ๐‘…^LEN. +/// +/// [`Vector`] is the only implementation; the trait exists so that code generic over a parameter +/// set can operate on `MLKEMParams::VecK` without knowing its length. +pub trait VectorTrait: + Sized + Copy + ZeroizablePrimitive + Index + IndexMut +{ + /// A vector with every coefficient set to zero. + fn new() -> Self; + + /// The coordinates, for iteration and chunking. + fn elems(&self) -> &[Polynomial]; + /// The coordinates, for iteration and chunking. + fn elems_mut(&mut self) -> &mut [Polynomial]; + + /// Adds another vector to this one, coordinatewise, in the NTT domain. + fn add_vector_ntt(&mut self, s: &Self); + /// The dot product of two vectors in the NTT domain. + fn dot_product(&self, v: &Self) -> Polynomial; + /// Barrett-reduces every coefficient. + fn reduce(&mut self); + /// Applies the NTT to every coordinate. + fn ntt(&mut self); + /// Applies the inverse NTT to every coordinate. + fn inv_ntt(&mut self); + /// Converts every coefficient into the Montgomery domain. + fn convert_to_mont(&mut self); + /// FIPS 203, Algorithm 5 (ByteEncode) applied to the compressed vector. + fn compress_pol_vec(&self, out: &mut [u8]); + /// The inverse of [`VectorTrait::compress_pol_vec`]. + fn decompress_pol_vec(compressed_u: &[u8]) -> Self; +} + +/// The operations this crate performs on the public matrix ๐€ฬ‚. +/// +/// [`Matrix`] is the only implementation; see [`VectorTrait`] for why the trait exists. +pub trait MatrixTrait: Sized + Clone { + /// The vector this matrix maps between: an element of ๐‘…^๐‘˜. + type Vec: VectorTrait; + + /// A matrix with every coefficient set to zero. + fn new() -> Self; + /// Overwrites the polynomial at `elems[row][col]`. + fn set_elem(&mut self, row: usize, col: usize, p: Polynomial); + /// Computes ๐€ฬ‚ โˆ˜ ๐ฏฬ‚, transposing ๐€ฬ‚ first when `transpose` is set. + fn matrix_vector_ntt(&self, v: &Self::Vec) -> Self::Vec; +} + #[derive(Clone)] /// A matrix over the ML-KEM ring. pub struct Matrix { @@ -64,8 +113,30 @@ impl Matrix { } } +/// ML-KEM's ๐€ฬ‚ is always ๐‘˜ ร— ๐‘˜, so the trait is implemented only for the square case; that is what +/// lets [`MatrixTrait::Vec`] be one vector type rather than an input and an output type. +impl MatrixTrait for Matrix { + type Vec = Vector; + + fn new() -> Self { + Matrix::new() + } + + fn set_elem(&mut self, row: usize, col: usize, p: Polynomial) { + self.elems[row][col] = p; + } + + fn matrix_vector_ntt(&self, v: &Vector) -> Vector { + Matrix::matrix_vector_ntt::(self, v) + } +} + #[derive(Clone, Copy)] -pub(crate) struct Vector { +/// A vector of `k` polynomials, i.e. an element of ๐‘…^๐‘˜. +/// +/// Public only because it is the value of `MLKEMParams::VecK`; its fields and operations are +/// crate-private, so from outside it is an opaque handle. Reach it through [`VectorTrait`]. +pub struct Vector { pub(crate) elems: [Polynomial; k], } @@ -92,20 +163,28 @@ impl Vector { pub(crate) const fn new() -> Self { Self { elems: [Polynomial::new(); k] } } +} - /// Algorithm 46 AddVectorNTT(๐ฏ, ๐ฐ)ฬ‚ - /// Computes the sum ๐ฏ_hat + ๐ฐ_hat of two vectors ๐ฏ_hat, ๐ฐ_hat over ๐‘‡๐‘ž. - /// Input: โ„“ โˆˆ โ„•, v_hat โˆˆ T^โ„“, w_hat โˆˆ ๐‘‡^โ„“ - /// Output: u_hat โˆˆ T^โ„“_๐‘ž. - /// Add another vector to this vector - pub(crate) fn add_vector_ntt(&mut self, s: &Self) { +impl VectorTrait for Vector { + fn new() -> Self { + Vector::new() + } + + fn elems(&self) -> &[Polynomial] { + &self.elems + } + + fn elems_mut(&mut self) -> &mut [Polynomial] { + &mut self.elems + } + fn add_vector_ntt(&mut self, s: &Self) { for i in 0..k { // perform Montgomery addition of each polynomial in the vector self[i].add(&s[i]); } } - pub(crate) fn dot_product(&self, v: &Self) -> Polynomial { + fn dot_product(&self, v: &Self) -> Polynomial { // split out the 0 case to skip a no-op add_ntt() let mut w = polynomial::base_mult_montgomery(&self[0], &v[0]); @@ -120,25 +199,25 @@ impl Vector { w } - pub(crate) fn reduce(&mut self) { + fn reduce(&mut self) { for i in 0..k { self[i].poly_reduce(); } } - pub(crate) fn ntt(&mut self) { + fn ntt(&mut self) { for i in 0..k { self[i].ntt(); } } - pub(crate) fn inv_ntt(&mut self) { + fn inv_ntt(&mut self) { for i in 0..k { self[i].inv_ntt(); } } - pub(crate) fn convert_to_mont(&mut self) { + fn convert_to_mont(&mut self) { for i in 0..k { self[i].convert_to_mont(); } @@ -147,13 +226,13 @@ impl Vector { /// This is an optimized version of /// ByteEncode_๐‘‘๐‘ข( Compress_๐‘‘๐‘ข(๐ฎ) ) /// which packs a polynomial vector according to the packing coefficient dv - pub(crate) fn compress_pol_vec(&self, out: &mut [u8]) { + fn compress_pol_vec(&self, out: &mut [u8]) { // make sure we have received a dv - assert!(du == 10 || du == 11); + assert!(P::du == 10 || P::du == 11); // make sure we were given the right size output buffer // each of the N i16's will take dv bits - debug_assert_eq!(out.len(), k * (N * (du as usize) / 8)); + debug_assert_eq!(out.len(), k * (N * (P::du as usize) / 8)); // No conditional_sub_q needed (as done in bc-java): callers must reduce() first, // so coefficients are in [0, q) (barrett_reduce, floor variant). The Compress mask `& (2^du - 1)` folds @@ -165,7 +244,7 @@ impl Vector { // s.conditional_sub_q(); let mut idx = 0; - match du { + match P::du { 10 => { // MLKEM512 and MLKEM 768 let mut t = [0i16; 4]; @@ -218,19 +297,19 @@ impl Vector { } } - pub(crate) fn decompress_pol_vec(compressed_u: &[u8]) -> Vector { + fn decompress_pol_vec(compressed_u: &[u8]) -> Self { let mut u = Vector::::new(); // make sure we have received a dv - assert!(du == 10 || du == 11); + assert!(P::du == 10 || P::du == 11); // make sure we were given the right size output buffer // each of the N i16's will take dv bits - debug_assert_eq!(compressed_u.len(), k * (N * (du as usize) / 8)); + debug_assert_eq!(compressed_u.len(), k * (N * (P::du as usize) / 8)); let mut idx = 0; - match du { + match P::du { 10 => { // MLKEM512 and MLKEM768 let mut t = [0i16; 4]; diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index b0979b69..6490a521 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -90,6 +90,7 @@ //! private key encoding (which is often called the "semi-expanded format" since the in-memory representation //! is still larger). //! Contact us if you need such a thing implemented. +//! //! ## Deterministic encapsulation //! //! This section pertains to [`MLKEM::encaps_internal`] which allows to pass in the encapsulation randomness @@ -132,7 +133,7 @@ use crate::aux_functions::{ expandA, pack_ciphertext, sample_poly_CBD, sample_vector_CBD, unpack_ciphertext_u, unpack_ciphertext_v, }; -use crate::matrix::{Matrix, Vector}; +use crate::matrix::{MatrixTrait, VectorTrait}; use crate::mlkem_keys::{ MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEM1024PrivateKey, MLKEM1024PublicKey, @@ -141,6 +142,7 @@ use crate::mlkem_keys::{ MLKEMPrivateKeyExpanded, MLKEMPublicKeyInternalTrait, MLKEMPublicKeyTrait, }; use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait}; +use crate::params::{MLKEM512Params, MLKEM768Params, MLKEM1024Params, MLKEMParams}; use crate::polynomial::Polynomial; use bouncycastle_core::errors::KEMError; use bouncycastle_core::errors::RNGError; @@ -175,53 +177,34 @@ pub const MLKEM_SS_LEN: usize = 32; pub(crate) const N: usize = 256; pub(crate) const q: i16 = 3329; pub(crate) const q_inv: i32 = 62209; -pub(crate) const ETA2: i16 = 2; pub(crate) const POLY_BYTES: usize = 384; -/* ML-KEM-512 params */ - -/// Length of the \[u8] holding a ML-KEM-512 public key. -pub const MLKEM512_PK_LEN: usize = 800; -/// Length of the \[u8] holding a ML-KEM-512 private key. -pub const MLKEM512_SK_LEN: usize = 1632; -/// Length of the \[u8] holding a ML-KEM-512 ciphertext. -pub const MLKEM512_CT_LEN: usize = 768; -pub(crate) const MLKEM512_k: usize = 2; -pub(crate) const MLKEM512_ETA1: i16 = 3; -pub(crate) const MLKEM512_DU: i16 = 10; -pub(crate) const MLKEM512_DV: i16 = 4; -/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2 -pub(crate) const MLKEM512_LAMBDA: i16 = 128; - -/* ML-KEM-768 params */ - -/// Length of the \[u8] holding a ML-KEM-768 public key. -pub const MLKEM768_PK_LEN: usize = 1184; -/// Length of the \[u8] holding a ML-KEM-768 private key. -pub const MLKEM768_SK_LEN: usize = 2400; -/// Length of the \[u8] holding a ML-KEM-768 ciphertext. -pub const MLKEM768_CT_LEN: usize = 1088; -pub(crate) const MLKEM768_k: usize = 3; -pub(crate) const MLKEM768_ETA1: i16 = 2; -pub(crate) const MLKEM768_DU: i16 = 10; -pub(crate) const MLKEM768_DV: i16 = 4; -/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2 -pub(crate) const MLKEM768_LAMBDA: i16 = 192; - -/* ML-KEM-1024 params */ - -/// Length of the \[u8] holding a ML-KEM-1024 public key. -pub const MLKEM1024_PK_LEN: usize = 1568; -/// Length of the \[u8] holding a ML-KEM-1024 private key. -pub const MLKEM1024_SK_LEN: usize = 3168; -/// Length of the \[u8] holding a ML-KEM-1024 ciphertext. -pub const MLKEM1024_CT_LEN: usize = 1568; -pub(crate) const MLKEM1024_k: usize = 4; -pub(crate) const MLKEM1024_ETA1: i16 = 2; -pub(crate) const MLKEM1024_DU: i16 = 11; -pub(crate) const MLKEM1024_DV: i16 = 5; -/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2 -pub(crate) const MLKEM1024_LAMBDA: i16 = 256; +/* ML-KEM-512 sizes (FIPS 203, Table 3) */ + +/// Length of the \[u8] holding an ML-KEM-512 public key. +pub const MLKEM512_PK_LEN: usize = MLKEM512Params::PK_LEN; +/// Length of the \[u8] holding an ML-KEM-512 private key. +pub const MLKEM512_SK_LEN: usize = MLKEM512Params::SK_LEN; +/// Length of the \[u8] holding an ML-KEM-512 ciphertext. +pub const MLKEM512_CT_LEN: usize = MLKEM512Params::CT_LEN; + +/* ML-KEM-768 sizes (FIPS 203, Table 3) */ + +/// Length of the \[u8] holding an ML-KEM-768 public key. +pub const MLKEM768_PK_LEN: usize = MLKEM768Params::PK_LEN; +/// Length of the \[u8] holding an ML-KEM-768 private key. +pub const MLKEM768_SK_LEN: usize = MLKEM768Params::SK_LEN; +/// Length of the \[u8] holding an ML-KEM-768 ciphertext. +pub const MLKEM768_CT_LEN: usize = MLKEM768Params::CT_LEN; + +/* ML-KEM-1024 sizes (FIPS 203, Table 3) */ + +/// Length of the \[u8] holding an ML-KEM-1024 public key. +pub const MLKEM1024_PK_LEN: usize = MLKEM1024Params::PK_LEN; +/// Length of the \[u8] holding an ML-KEM-1024 private key. +pub const MLKEM1024_SK_LEN: usize = MLKEM1024Params::SK_LEN; +/// Length of the \[u8] holding an ML-KEM-1024 ciphertext. +pub const MLKEM1024_CT_LEN: usize = MLKEM1024Params::CT_LEN; // Typedefs just to make the algorithms look more like the FIPS 204 sample code. pub(crate) type G = SHA3_512; @@ -232,116 +215,96 @@ pub(crate) type J = SHAKE256; /// The ML-KEM-512 algorithm. pub type MLKEM512 = MLKEM< + MLKEM512Params, + MLKEM512PublicKey, + MLKEM512PrivateKey, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_CT_LEN, MLKEM_SS_LEN, - MLKEM512PublicKey, - MLKEM512PrivateKey, - MLKEM512_k, - MLKEM512_ETA1, - MLKEM512_DU, - MLKEM512_DV, - MLKEM512_LAMBDA, >; -impl Algorithm for MLKEM512 { - const ALG_NAME: &'static str = ML_KEM_512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 } -impl AlgorithmOID for MLKEM512 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01]; -} - /// The ML-KEM-768 algorithm. pub type MLKEM768 = MLKEM< + MLKEM768Params, + MLKEM768PublicKey, + MLKEM768PrivateKey, MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_CT_LEN, MLKEM_SS_LEN, - MLKEM768PublicKey, - MLKEM768PrivateKey, - MLKEM768_k, - MLKEM768_ETA1, - MLKEM768_DU, - MLKEM768_DV, - MLKEM768_LAMBDA, >; -impl Algorithm for MLKEM768 { - const ALG_NAME: &'static str = ML_KEM_768_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 } -impl AlgorithmOID for MLKEM768 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02]; -} - /// The ML-KEM-1024 algorithm. pub type MLKEM1024 = MLKEM< + MLKEM1024Params, + MLKEM1024PublicKey, + MLKEM1024PrivateKey, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_CT_LEN, MLKEM_SS_LEN, - MLKEM1024PublicKey, - MLKEM1024PrivateKey, - MLKEM1024_k, - MLKEM1024_ETA1, - MLKEM1024_DU, - MLKEM1024_DV, - MLKEM1024_LAMBDA, >; -impl Algorithm for MLKEM1024 { - const ALG_NAME: &'static str = ML_KEM_1024_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, +> Algorithm for MLKEM +{ + const ALG_NAME: &'static str = P::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 } -impl AlgorithmOID for MLKEM1024 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03]; + +/// The OIDs NIST assigned in the Computer Security Objects Register: id-alg-ml-kem-512 +/// { kems 1 }, id-alg-ml-kem-768 { kems 2 } and id-alg-ml-kem-1024 { kems 3 }. As with +/// [`Algorithm`], the values belong to the parameter set, so one impl covers all three. +impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, + const PK_LEN: usize, + const SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, +> AlgorithmOID for MLKEM +{ + const OID: &'static [u32] = P::OID; + const OID_DER: &'static [u8] = P::OID_DER; } /// The core internal implementation of the ML-KEM algorithm. /// This needs to be public for the compiler to be able to find it, but you shouldn't ever /// need to use this directly. Please use the named public types. pub struct MLKEM< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, > { - _phantom: PhantomData<(PK, SK)>, + _phantom: PhantomData<(P, PK, SK)>, } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta1: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, -> MLKEM +> MLKEM { /// Algorithm 16 ML-KEM.KeyGen_internal(๐‘‘, ๐‘ง) /// Uses randomness to generate an encapsulation key and a corresponding decapsulation key. @@ -358,7 +321,7 @@ impl< )); } - if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if seed.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(KEMError::KeyGenError( "Seed SecurityStrength must match algorithm security strength", )); @@ -385,7 +348,7 @@ impl< /// Input: randomness ๐‘‘ โˆˆ ๐”น32 . /// Output: encryption key ek_PKE โˆˆ ๐”น384๐‘˜+32. /// Output: decryption key dk_PKE โˆˆ ๐”น384๐‘˜. - fn pke_keygen(d: &[u8; 32]) -> (PK, Secret>) { + fn pke_keygen(d: &[u8; 32]) -> (PK, Secret) { // 1: (๐œŒ, ๐œŽ) โ† G(๐‘‘โ€–๐‘˜) // โ–ท expand 32+1 bytes to two pseudorandom 32-byte seeds1 // rho: public seed @@ -393,7 +356,7 @@ impl< let (rho, mut sigma) = { let mut g = G::new(); g.do_update(d); - g.do_update(&[k as u8]); + g.do_update(&[P::k as u8]); let mut buf = [0u8; 64]; let bytes_written = g.do_final_out(&mut buf); debug_assert_eq!(bytes_written, 64); @@ -412,9 +375,9 @@ impl< // โ–ท ๐ฌ[๐‘–] โˆˆ โ„ค256 sampled from CBD // 10: ๐‘ โ† ๐‘ + 1 // Note: here n = 0 - let s_hat: Secret> = { - let mut s: Secret> = Secret::new(); - *s = sample_vector_CBD::(&sigma, 0); + let s_hat: Secret = { + let mut s: Secret = Secret::new(); + *s = sample_vector_CBD::

(&sigma, 0, P::eta1); // 16: ๐ฌ_hat โ† NTT(๐ฌ)ฬ‚ s.ntt(); @@ -427,7 +390,7 @@ impl< let mut t_hat = { // 3: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++) // โ–ท generate matrix A_hat โˆˆ (โ„ค256)^k x k - let A_hat = expandA(&rho); + let A_hat = expandA::

(&rho); A_hat.matrix_vector_ntt::(&s_hat) }; @@ -441,7 +404,7 @@ impl< // โ–ท ๐ž[๐‘–] โˆˆ โ„ค256 sampled from CBD // 14: ๐‘ โ† ๐‘ + 1 // Note: here n = k - let mut e = sample_vector_CBD::(&sigma, k as u8); + let mut e = sample_vector_CBD::

(&sigma, P::k as u8, P::eta1); e.ntt(); // technically now e_hat e.reduce(); @@ -464,7 +427,7 @@ impl< /// Input: message ๐‘š โˆˆ ๐”น32 . /// Input: randomness ๐‘Ÿ โˆˆ ๐”น32 . /// Output: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ). - fn pke_encrypt(ek: &PK, A_hat: &Matrix, m: [u8; 32], r: &[u8; 32]) -> [u8; CT_LEN] { + fn pke_encrypt(ek: &PK, A_hat: &P::MatrixA, m: [u8; 32], r: &[u8; 32]) -> [u8; CT_LEN] { // 1: ๐‘ โ† 0 // since the number of loops here is static, the N values can be hard-coded rather than using a counter @@ -484,7 +447,7 @@ impl< // 11: ๐‘ โ† ๐‘ + 1 // Note: here n = 0 let y_hat = { - let mut y = sample_vector_CBD::(&r, 0); + let mut y = sample_vector_CBD::

(&r, 0, P::eta1); // 18: ๐ฒ_hat โ† NTT(๐ฒ) y.ntt(); @@ -502,7 +465,7 @@ impl< // โ–ท ๐ž[๐‘–] โˆˆ โ„ค256 sampled from CBD๐‘ž // 14: ๐‘ โ† ๐‘ + 1 // note: here n = k - let e1 = sample_vector_CBD::(&r, k as u8); + let e1 = sample_vector_CBD::

(&r, P::k as u8, P::eta2); u.add_vector_ntt(&e1); } @@ -517,7 +480,7 @@ impl< // 17: ๐‘’2 โ† SamplePolyCBD๐œ‚2(PRF๐œ‚2 (๐‘Ÿ, ๐‘)) // โ–ท sample ๐‘’2 โˆˆ โ„ค256 from CBD // note: here n = 2k - let e2 = sample_poly_CBD::(&r, 2 * k as u8); + let e2 = sample_poly_CBD(&r, 2 * P::k as u8, P::eta2); v.add(&e2); let mu = Polynomial::from_msg(m); @@ -525,7 +488,7 @@ impl< v.poly_reduce(); - pack_ciphertext::(&u, &v) + pack_ciphertext::(&u, &v) } /// Algorithm 17 ML-KEM.Encaps_internal(ek, ๐‘š) @@ -562,11 +525,9 @@ impl< /// Please don't do it. pub fn encaps_internal( ek: &PK, - A_hat: Option<&Matrix>, + A_hat: Option<&P::MatrixA>, m: [u8; 32], ) -> ([u8; 32], [u8; CT_LEN]) { - debug_assert_eq!(CT_LEN, 32 * ((du as usize) * k + (dv as usize))); - // 1: (๐พ, ๐‘Ÿ) โ† G(๐‘šโ€–H(ek)) // โ–ท derive shared secret key ๐พ and randomness ๐‘Ÿ let K: [u8; MLKEM_SS_LEN]; @@ -606,7 +567,7 @@ impl< // 3: ๐ฎโ€ฒ โ† Decompress_๐‘‘๐‘ข(ByteDecode_๐‘‘๐‘ข(๐‘1)) // 4: ๐‘ฃโ€ฒ โ† Decompress_๐‘‘๐‘ฃ(ByteDecode_๐‘‘๐‘ฃ(๐‘2)) let v1 = { - let mut u_prime = unpack_ciphertext_u::(&ct); + let mut u_prime = unpack_ciphertext_u::(&ct); // 5: ๐ฌ_hat โ† ByteDecode12(dkPKE) // Unnecessary here because dk is already decoded @@ -620,7 +581,7 @@ impl< }; let w = { - let mut v_prime = unpack_ciphertext_v::(&ct); + let mut v_prime = unpack_ciphertext_v::(&ct); v_prime.sub(&v1); v_prime.poly_reduce(); @@ -637,11 +598,7 @@ impl< /// Input: decapsulation key dk โˆˆ ๐”น768๐‘˜+96 . /// Input: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ). /// Output: shared secret key ๐พ โˆˆ ๐”น32 . - fn decaps_internal( - dk: &SK, - A_hat: Option<&Matrix>, - c: [u8; CT_LEN], - ) -> [u8; MLKEM_SS_LEN] { + fn decaps_internal(dk: &SK, A_hat: Option<&P::MatrixA>, c: [u8; CT_LEN]) -> [u8; MLKEM_SS_LEN] { // Structured to mirror the FIPS as closely as possible, with unnamed scopes // used to limit the number of live stack variables at any given time. @@ -720,20 +677,16 @@ impl< } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta1: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, -> MLKEMTrait - for MLKEM +> MLKEMTrait + for MLKEM { /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError> { @@ -778,18 +731,18 @@ impl< } fn encaps_for_expanded_key( - pk: &MLKEMPublicKeyExpanded, + pk: &MLKEMPublicKeyExpanded, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { let mut os_rng = HashDRBG_SHA512::new_from_os(); Self::encaps_for_expanded_key_rng(pk, &mut os_rng) } fn encaps_for_expanded_key_rng( - pk: &MLKEMPublicKeyExpanded, + pk: &MLKEMPublicKeyExpanded, rng: &mut dyn RNG, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { // Source the random message m from the provided RNG - if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if rng.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; } let mut m = [0u8; 32]; @@ -799,20 +752,19 @@ impl< let mut key = KeyMaterial::::from_bytes_as_type(&ss, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut key, |key| { - key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + key.set_security_strength(P::MAX_SECURITY_STRENGTH) })?; Ok((key, ct)) } fn decaps_with_expanded_key( - sk: &MLKEMPrivateKeyExpanded, + sk: &MLKEMPrivateKeyExpanded, ct: &[u8], ) -> Result, KEMError> { /* decapsulation inputs checks described on FIPS 203 section 7.3 */ // 1. (Ciphertext type check) If ๐‘ is not a byte array of length 32(๐‘‘๐‘ข ๐‘˜ + ๐‘‘๐‘ฃ) for the values of ๐‘‘๐‘ข, // ๐‘‘๐‘ฃ, and ๐‘˜ specified by the relevant parameter set, then input checking has failed. - debug_assert_eq!(CT_LEN, 32 * ((du as usize) * k + (dv as usize))); if ct.len() != CT_LEN { return Err(KEMError::LengthError("Ciphertext has the incorrect length")); @@ -830,7 +782,7 @@ impl< let mut key = KeyMaterial::::from_bytes_as_type(&K, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut key, |key| { - key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + key.set_security_strength(P::MAX_SECURITY_STRENGTH) })?; Ok(key) @@ -839,18 +791,14 @@ impl< /// Trait for all three of the ML-DSA algorithm variants. pub trait MLKEMTrait< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, >: Sized { /// Generates a fresh key pair. @@ -862,7 +810,7 @@ pub trait MLKEMTrait< // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> { // Source the seed from the provided RNG - if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + if rng.security_strength() < P::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; } let mut seed = KeyMaterial::<64>::new(); @@ -893,37 +841,32 @@ pub trait MLKEMTrait< /// Same as [`KEMEncapsulator::encaps`], but acts on an [`MLKEMPublicKeyExpanded`]. fn encaps_for_expanded_key( - pk: &MLKEMPublicKeyExpanded, + pk: &MLKEMPublicKeyExpanded, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; /// Same as [`KEMEncapsulator::encaps`], but acts on an [`MLKEMPublicKeyExpanded`] and uses a provided RNG. fn encaps_for_expanded_key_rng( - pk: &MLKEMPublicKeyExpanded, + pk: &MLKEMPublicKeyExpanded, rng: &mut dyn RNG, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; /// Same as [`KEMDecapsulator::decaps`], but acts on an [`MLKEMPrivateKeyExpanded`]. fn decaps_with_expanded_key( - sk: &MLKEMPrivateKeyExpanded, + sk: &MLKEMPrivateKeyExpanded, ct: &[u8], ) -> Result, KEMError>; } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, -> KEMEncapsulator - for MLKEM +> KEMEncapsulator for MLKEM { /// Performs an encapsulation against the given public key, using the library's default internal RNG. /// Returns (shared_secret_key, ciphertext) @@ -944,25 +887,20 @@ impl< pk: &PK, rng: &mut dyn RNG, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { - Self::encaps_for_expanded_key_rng(&MLKEMPublicKeyExpanded::::from(pk), rng) + Self::encaps_for_expanded_key_rng(&MLKEMPublicKeyExpanded::::from(pk), rng) } } impl< + P: MLKEMParams, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, - PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const k: usize, - const eta: i16, - const du: i16, - const dv: i16, - const LAMBDA: i16, -> KEMDecapsulator - for MLKEM +> KEMDecapsulator for MLKEM { /// Performs a decapsulation of the given ciphertext. /// Returns the shared secret key. @@ -971,7 +909,7 @@ impl< /// As ML-KEM is an implicitly-rejecting KEM, this returns an error only if the ciphertext is invalid (ie the wrong length).. fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError> { Self::decaps_with_expanded_key( - &MLKEMPrivateKeyExpanded::::from(sk), + &MLKEMPrivateKeyExpanded::::from(sk), ct, ) } diff --git a/crypto/mlkem/src/mlkem_keys.rs b/crypto/mlkem/src/mlkem_keys.rs index 8fd2bb8a..2df7f861 100644 --- a/crypto/mlkem/src/mlkem_keys.rs +++ b/crypto/mlkem/src/mlkem_keys.rs @@ -1,14 +1,14 @@ use crate::aux_functions::{byte_decode, byte_encode, expandA}; -use crate::matrix::{Matrix, Vector}; +use crate::matrix::VectorTrait; use crate::mlkem::{H, POLY_BYTES, q}; -use crate::mlkem::{MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_k}; -use crate::mlkem::{MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_k}; -use crate::mlkem::{MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_k}; -use crate::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME}; +use crate::mlkem::{MLKEM512_PK_LEN, MLKEM512_SK_LEN}; +use crate::mlkem::{MLKEM768_PK_LEN, MLKEM768_SK_LEN}; +use crate::mlkem::{MLKEM1024_PK_LEN, MLKEM1024_SK_LEN}; +use crate::params::{MLKEM512Params, MLKEM768Params, MLKEM1024Params, MLKEMParams}; use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey, SecurityStrength}; +use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey}; use bouncycastle_sha3::SHA3_256; use bouncycastle_utils::secret::Secret; use core::fmt; @@ -23,29 +23,29 @@ use crate::polynomial::Polynomial; /* Pub Types */ /// ML-KEM-512 Public Key -pub type MLKEM512PublicKey = MLKEMPublicKey; +pub type MLKEM512PublicKey = MLKEMPublicKey; /// ML-KEM-512 Private Key pub type MLKEM512PrivateKey = - MLKEMPrivateKey; + MLKEMPrivateKey; /// ML-KEM-768 Public Key -pub type MLKEM768PublicKey = MLKEMPublicKey; +pub type MLKEM768PublicKey = MLKEMPublicKey; /// ML-KEM-768 Private Key pub type MLKEM768PrivateKey = - MLKEMPrivateKey; + MLKEMPrivateKey; /// ML-KEM-1024 Public Key -pub type MLKEM1024PublicKey = MLKEMPublicKey; +pub type MLKEM1024PublicKey = MLKEMPublicKey; /// ML-KEM-1024 Private Key pub type MLKEM1024PrivateKey = - MLKEMPrivateKey; + MLKEMPrivateKey; /* Pre-expanded keys for repeated operations */ /// ML-KEM-512 Public Key with a pre-expanded public matrix A for repeated encaps operations. pub type MLKEM512PublicKeyExpanded = - MLKEMPublicKeyExpanded; + MLKEMPublicKeyExpanded; /// ML-KEM-512 Private Key with a pre-expanded public matrix A for repeated decaps operations. pub type MLKEM512PrivateKeyExpanded = MLKEMPrivateKeyExpanded< - MLKEM512_k, + MLKEM512Params, MLKEM512PublicKey, MLKEM512PrivateKey, MLKEM512_SK_LEN, @@ -53,10 +53,10 @@ pub type MLKEM512PrivateKeyExpanded = MLKEMPrivateKeyExpanded< >; /// ML-KEM-768 Public Key with a pre-expanded public matrix A for repeated encaps operations. pub type MLKEM768PublicKeyExpanded = - MLKEMPublicKeyExpanded; + MLKEMPublicKeyExpanded; /// ML-KEM-768 Private Key with a pre-expanded public matrix A for repeated decaps operations. pub type MLKEM768PrivateKeyExpanded = MLKEMPrivateKeyExpanded< - MLKEM768_k, + MLKEM768Params, MLKEM768PublicKey, MLKEM768PrivateKey, MLKEM768_SK_LEN, @@ -64,10 +64,10 @@ pub type MLKEM768PrivateKeyExpanded = MLKEMPrivateKeyExpanded< >; /// ML-KEM-1024 Public Key with a pre-expanded public matrix A for repeated encaps operations. pub type MLKEM1024PublicKeyExpanded = - MLKEMPublicKeyExpanded; + MLKEMPublicKeyExpanded; /// ML-KEM-1024 Private Key with a pre-expanded public matrix A for repeated decaps operations. pub type MLKEM1024PrivateKeyExpanded = MLKEMPrivateKeyExpanded< - MLKEM1024_k, + MLKEM1024Params, MLKEM1024PublicKey, MLKEM1024PrivateKey, MLKEM1024_SK_LEN, @@ -75,50 +75,57 @@ pub type MLKEM1024PrivateKeyExpanded = MLKEMPrivateKeyExpanded< >; /// An ML-KEM public key. -#[derive(Clone)] -pub struct MLKEMPublicKey { - t_hat: Vector, +pub struct MLKEMPublicKey { + t_hat: P::VecK, rho: [u8; 32], } +// Written out rather than derived: `#[derive(Clone)]` would demand `P: Clone`, and `P` is a +// marker for the parameter set that is never stored, only used to name the field types. +impl Clone for MLKEMPublicKey { + fn clone(&self) -> Self { + Self { t_hat: self.t_hat, rho: self.rho } + } +} + /// General trait for all ML-KEM public keys types. -pub trait MLKEMPublicKeyTrait: KEMPublicKey { +pub trait MLKEMPublicKeyTrait: KEMPublicKey { /// Algorithm 23 pkDecode(๐‘๐‘˜) /// Reverses the procedure pkEncode. /// Input: Public key ๐‘๐‘˜ โˆˆ ๐”น32+32๐‘˜(bitlen (๐‘žโˆ’1)โˆ’๐‘‘). /// Output: ๐œŒ โˆˆ ๐”น32, ๐ญ1 โˆˆ ๐‘…๐‘˜ with coefficients in [0, 2bitlen (๐‘žโˆ’1)โˆ’๐‘‘ โˆ’ 1]. fn pk_decode(pk: &[u8; PK_LEN]) -> Result; /// Get a copy of the expanded public matrix A_hat - fn A_hat(&self) -> Matrix; + fn A_hat(&self) -> P::MatrixA; /// Get the hash of the public key fn compute_hash(&self) -> [u8; 32]; } -pub(crate) trait MLKEMPublicKeyInternalTrait: - MLKEMPublicKeyTrait +pub(crate) trait MLKEMPublicKeyInternalTrait: + MLKEMPublicKeyTrait { /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. - fn new(t_hat: Vector, rho: [u8; 32]) -> Self; + fn new(t_hat: P::VecK, rho: [u8; 32]) -> Self; /// Get a ref to t1 - fn t_hat(&self) -> &Vector; + fn t_hat(&self) -> &P::VecK; } -impl MLKEMPublicKeyTrait - for MLKEMPublicKey +impl MLKEMPublicKeyTrait + for MLKEMPublicKey { fn pk_decode(pk: &[u8; PK_LEN]) -> Result { let (pk_chunks, last_chunk) = pk.as_chunks::(); // that should divide evenly the remainder of the array, leaving space for rho at the end - debug_assert_eq!(pk_chunks.len(), k); + debug_assert_eq!(pk_chunks.len(), P::k); debug_assert_eq!(last_chunk.len(), 32); let t_hat = { - let mut t_hat = Vector::::new(); + let mut t_hat = P::VecK::new(); - for (t_i, pk_chunk) in t_hat.elems.iter_mut().zip(pk_chunks) { + for (t_i, pk_chunk) in t_hat.elems_mut().iter_mut().zip(pk_chunks) { t_i.coeffs.copy_from_slice(&byte_decode::<12, POLY_BYTES>(pk_chunk).coeffs); // FIPS 203 says: @@ -141,8 +148,8 @@ impl MLKEMPublicKeyTrait Ok(Self::new(t_hat, rho)) } - fn A_hat(&self) -> Matrix { - expandA(&self.rho) + fn A_hat(&self) -> P::MatrixA { + expandA::

(&self.rho) } fn compute_hash(&self) -> [u8; 32] { @@ -153,19 +160,19 @@ impl MLKEMPublicKeyTrait } } -impl MLKEMPublicKeyInternalTrait - for MLKEMPublicKey +impl MLKEMPublicKeyInternalTrait + for MLKEMPublicKey { - fn new(t_hat: Vector, rho: [u8; 32]) -> Self { + fn new(t_hat: P::VecK, rho: [u8; 32]) -> Self { Self { rho, t_hat } } - fn t_hat(&self) -> &Vector { + fn t_hat(&self) -> &P::VecK { &self.t_hat } } -impl KEMPublicKey for MLKEMPublicKey { +impl KEMPublicKey for MLKEMPublicKey { /// Encodes the public key as per FIPS 203 Algorithm 13 /// 19: ekPKE โ† ByteEncode12(๐ญ)โ€–๐œŒ fn encode(&self) -> [u8; PK_LEN] { @@ -177,7 +184,6 @@ impl KEMPublicKey for MLKEMPublicKe /// Encodes the public key as per FIPS 203 Algorithm 13 /// 19: ekPKE โ† ByteEncode12(๐ญ)โ€–๐œŒ fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize { - debug_assert_eq!(PK_LEN, 12 * k * 32 + 32); debug_assert_eq!(POLY_BYTES, 12 * 32); out.fill(0); @@ -185,10 +191,10 @@ impl KEMPublicKey for MLKEMPublicKe let (pk_chunks, last_chunk) = out.as_chunks_mut::(); // that should divide evenly the remainder of the array, leaving space for rho at the end - debug_assert_eq!(pk_chunks.len(), k); + debug_assert_eq!(pk_chunks.len(), P::k); debug_assert_eq!(last_chunk.len(), 32); - for (pk_chunk, t_i) in pk_chunks.into_iter().zip(&self.t_hat.elems) { + for (pk_chunk, t_i) in pk_chunks.into_iter().zip(self.t_hat.elems()) { pk_chunk.copy_from_slice(&byte_encode::<12, POLY_BYTES>(t_i)); } last_chunk.copy_from_slice(&self.rho); @@ -205,70 +211,66 @@ impl KEMPublicKey for MLKEMPublicKe } } -impl Eq for MLKEMPublicKey {} +impl Eq for MLKEMPublicKey {} -impl PartialEq for MLKEMPublicKey { +impl PartialEq for MLKEMPublicKey { fn eq(&self, other: &Self) -> bool { bouncycastle_utils::ct::ct_eq_bytes(&self.encode(), &other.encode()) } } -impl Debug for MLKEMPublicKey { +impl Debug for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let hash = SHA3_256::new().hash(&self.encode()); - write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, hash) + write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } -impl Display for MLKEMPublicKey { +impl Display for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let hash = SHA3_256::new().hash(&self.encode()); - write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, hash) + write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } /// A fully expanded ML-KEM public key that includes the intermediate values needed for performing multiple encaps operations /// against the same public key, which causes the MLKEMPublicKey struct to take up more memory, but results /// in more efficient repeated encaps() operations. -#[derive(Clone)] pub struct MLKEMPublicKeyExpanded< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const PK_LEN: usize, > { pub(crate) ek: PK, - pub(crate) A_hat: Matrix, + pub(crate) A_hat: P::MatrixA, } -impl, const PK_LEN: usize> - MLKEMPublicKeyInternalTrait for MLKEMPublicKeyExpanded +/// See the note on [`MLKEMPublicKey`]'s `Clone` for why this is not derived. +impl, const PK_LEN: usize> Clone + for MLKEMPublicKeyExpanded { - fn new(t_hat: Vector, rho: [u8; 32]) -> Self { + fn clone(&self) -> Self { + Self { ek: self.ek.clone(), A_hat: self.A_hat.clone() } + } +} + +impl, const PK_LEN: usize> + MLKEMPublicKeyInternalTrait for MLKEMPublicKeyExpanded +{ + fn new(t_hat: P::VecK, rho: [u8; 32]) -> Self { let ek = PK::new(t_hat, rho); let A_hat = ek.A_hat(); Self { ek, A_hat } } - fn t_hat(&self) -> &Vector { + fn t_hat(&self) -> &P::VecK { self.ek.t_hat() } } -impl, const PK_LEN: usize> - KEMPublicKey for MLKEMPublicKeyExpanded +impl, const PK_LEN: usize> + KEMPublicKey for MLKEMPublicKeyExpanded { fn encode(&self) -> [u8; PK_LEN] { let mut pk = [0u8; PK_LEN]; @@ -292,51 +294,39 @@ impl, const PK_LEN: u } } -impl, const PK_LEN: usize> PartialEq - for MLKEMPublicKeyExpanded +impl, const PK_LEN: usize> PartialEq + for MLKEMPublicKeyExpanded { fn eq(&self, other: &Self) -> bool { self.encode() == other.encode() } } -impl, const PK_LEN: usize> Eq - for MLKEMPublicKeyExpanded +impl, const PK_LEN: usize> Eq + for MLKEMPublicKeyExpanded { } -impl, const PK_LEN: usize> Debug - for MLKEMPublicKeyExpanded +impl, const PK_LEN: usize> Debug + for MLKEMPublicKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let hash = SHA3_256::new().hash(&self.encode()); - write!(f, "MLKEMPublicKeyExpanded {{ alg: {}, pub_key_hash: {:x?} }}", alg, hash) + write!(f, "MLKEMPublicKeyExpanded {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } -impl, const PK_LEN: usize> Display - for MLKEMPublicKeyExpanded +impl, const PK_LEN: usize> Display + for MLKEMPublicKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; let hash = SHA3_256::new().hash(&self.encode()); - write!(f, "MLKEMPublicKeyExpanded {{ alg: {}, pub_key_hash: {:x?} }}", alg, hash) + write!(f, "MLKEMPublicKeyExpanded {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } -impl, const PK_LEN: usize> - MLKEMPublicKeyTrait for MLKEMPublicKeyExpanded +impl, const PK_LEN: usize> + MLKEMPublicKeyTrait for MLKEMPublicKeyExpanded { fn pk_decode(pk: &[u8; PK_LEN]) -> Result { let ek = PK::pk_decode(pk)?; @@ -344,7 +334,7 @@ impl, const PK_LEN: u Ok(Self { ek, A_hat }) } - fn A_hat(&self) -> Matrix { + fn A_hat(&self) -> P::MatrixA { self.A_hat.clone() } @@ -353,8 +343,8 @@ impl, const PK_LEN: u } } -impl, const PK_LEN: usize> From<&PK> - for MLKEMPublicKeyExpanded +impl, const PK_LEN: usize> From<&PK> + for MLKEMPublicKeyExpanded { /// Fully expands the intermediate values needed for performing multiple encaps operations /// against the same public key, which causes the MLKEMPublicKey struct to take up @@ -368,43 +358,64 @@ impl, const PK_LEN: u /// An ML-KEM private key. /// // Dev note: This will automatically inherit the [`Secret`] protections because [`Polynomial`] wraps the underlying data with [`Secret`]. -#[derive(Clone)] pub struct MLKEMPrivateKey< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, > { - s_hat: Secret>, + s_hat: Secret, ek: PK, pk_hash: [u8; 32], z: Secret<[u8; 32]>, seed_d: Option>, } +/// See the note on [`MLKEMPublicKey`]'s `Clone` for why this is not derived. +impl< + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> Clone for MLKEMPrivateKey +{ + fn clone(&self) -> Self { + Self { + s_hat: self.s_hat.clone(), + ek: self.ek.clone(), + pk_hash: self.pk_hash, + z: self.z.clone(), + seed_d: self.seed_d.clone(), + } + } +} + impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> MLKEMPrivateKey +> MLKEMPrivateKey { /// As described on Algorithm 16 line /// 3: dk โ† (dkPKE โ€– ek โ€– H(ek) โ€– ๐‘ง) fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { out.fill(0); - debug_assert_eq!(SK_LEN, /* dk_pke*/ 12*k*32 + /*ek*/PK_LEN + /*H(ek)*/32 + /*z*/32); + debug_assert_eq!( + SK_LEN, + /* dk_pke*/ 12*P::k*32 + /*ek*/PK_LEN + /*H(ek)*/32 + /*z*/32 + ); let mut pos = 0usize; /* dk_pke */ // Alg 13; line 20: dkPKE โ† ByteEncode12(๐ฌ) - for i in 0..k { + for i in 0..P::k { out[i * POLY_BYTES..(i + 1) * POLY_BYTES] .copy_from_slice(&byte_encode::<12, POLY_BYTES>(&self.s_hat[i])); } - pos += k * POLY_BYTES; + pos += P::k * POLY_BYTES; /* ek */ // Alg 13; line 19: ekPKE โ† ByteEncode12(๐ญ)โ€–๐œŒ @@ -426,8 +437,8 @@ impl< /// General trait for all ML-KEM private keys types. pub trait MLKEMPrivateKeyTrait< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, >: KEMPrivateKey @@ -444,8 +455,8 @@ pub trait MLKEMPrivateKeyTrait< } pub(crate) trait MLKEMPrivateKeyInternalTrait< - const k: usize, - PK: MLKEMPublicKeyTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyTrait, const SK_LEN: usize, const PK_LEN: usize, > @@ -453,7 +464,7 @@ pub(crate) trait MLKEMPrivateKeyInternalTrait< /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. fn new( - s_hat: Secret>, + s_hat: Secret, ek: PK, h: [u8; 32], z: Secret<[u8; 32]>, @@ -461,17 +472,17 @@ pub(crate) trait MLKEMPrivateKeyInternalTrait< ) -> Self; /// Get a ref to s_hat - fn s_hat(&self) -> &Vector; + fn s_hat(&self) -> &P::VecK; fn z(&self) -> &Secret<[u8; 32]>; } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> MLKEMPrivateKeyTrait for MLKEMPrivateKey +> MLKEMPrivateKeyTrait for MLKEMPrivateKey { fn seed(&self) -> Option> { if self.seed_d.is_none() { @@ -483,12 +494,7 @@ impl< let mut seed = KeyMaterial::<64>::from_bytes_as_type(&*tmp, KeyType::Seed).unwrap(); key_material::do_hazardous_operations(&mut seed, |seed| { - seed.set_security_strength(match k { - 2 => SecurityStrength::_128bit, - 3 => SecurityStrength::_192bit, - 4 => SecurityStrength::_256bit, - _ => unreachable!("Invalid mlkem param set"), - }) + seed.set_security_strength(P::MAX_SECURITY_STRENGTH) }) .unwrap(); @@ -505,14 +511,17 @@ impl< } fn sk_decode(sk: &[u8; SK_LEN]) -> Result { - debug_assert_eq!(SK_LEN, /* dk_pke*/ 12*k*32 + /*ek*/PK_LEN + /*H(ek)*/32 + /*z*/32); + debug_assert_eq!( + SK_LEN, + /* dk_pke*/ 12*P::k*32 + /*ek*/PK_LEN + /*H(ek)*/32 + /*z*/32 + ); let mut pos = 0usize; /* dk_pke */ - let mut s_hat: Secret> = Secret::new(); + let mut s_hat: Secret = Secret::new(); // for (s_i, sk_chunk) in s_hat.0.iter_mut().zip(sk_chunks) { - for i in 0..k { + for i in 0..P::k { s_hat[i] = byte_decode::<12, POLY_BYTES>( sk[i * POLY_BYTES..(i + 1) * POLY_BYTES].try_into().unwrap(), ); @@ -529,7 +538,7 @@ impl< } } } - pos += k * POLY_BYTES; + pos += P::k * POLY_BYTES; /* ek */ let ek = PK::pk_decode(sk[pos..pos + PK_LEN].try_into().unwrap())?; @@ -557,15 +566,15 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> MLKEMPrivateKeyInternalTrait for MLKEMPrivateKey +> MLKEMPrivateKeyInternalTrait for MLKEMPrivateKey { /// Note to future maintainers: FIPS 203 section 7.3 requires that ek be hashed and compared to pk_hash. fn new( - s_hat: Secret>, + s_hat: Secret, ek: PK, pk_hash: [u8; 32], z: Secret<[u8; 32]>, @@ -574,7 +583,7 @@ impl< Self { s_hat, ek, pk_hash, z, seed_d: seed_d.clone() } } - fn s_hat(&self) -> &Vector { + fn s_hat(&self) -> &P::VecK { &self.s_hat } @@ -584,11 +593,11 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> KEMPrivateKey for MLKEMPrivateKey +> KEMPrivateKey for MLKEMPrivateKey { fn encode(&self) -> [u8; SK_LEN] { let mut out = [0u8; SK_LEN]; @@ -617,20 +626,20 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Eq for MLKEMPrivateKey +> Eq for MLKEMPrivateKey { } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> PartialEq for MLKEMPrivateKey +> PartialEq for MLKEMPrivateKey { fn eq(&self, other: &Self) -> bool { let self_encoded = self.encode(); @@ -641,23 +650,17 @@ impl< /// Debug impl mainly to prevent the secret key from being printed in logs. impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> fmt::Debug for MLKEMPrivateKey +> fmt::Debug for MLKEMPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLKEMPrivateKey {{ alg: {}, pub_key_hash: {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.pk_hash, self.seed_d.is_some(), ) @@ -666,23 +669,17 @@ impl< /// Display impl mainly to prevent the secret key from being printed in logs. impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Display for MLKEMPrivateKey +> Display for MLKEMPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLKEMPrivateKey {{ alg: {}, pub_key_hash: {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.pk_hash, self.seed_d.is_some(), ) @@ -692,28 +689,42 @@ impl< /// A fully expanded ML-KEM private key that includes the intermediate values needed for performing /// multiple decaps operations with the same private key, which causes the private key struct to /// take up more memory, but results in more efficient repeated decaps() operations. -#[derive(Clone)] pub struct MLKEMPrivateKeyExpanded< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, > { _phantom: core::marker::PhantomData, pub(crate) dk: SK, - pub(crate) A_hat: Matrix, + pub(crate) A_hat: P::MatrixA, } +/// See the note on [`MLKEMPublicKey`]'s `Clone` for why this is not derived. impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> From<&SK> for MLKEMPrivateKeyExpanded +> Clone for MLKEMPrivateKeyExpanded +{ + fn clone(&self) -> Self { + Self { _phantom: core::marker::PhantomData, dk: self.dk.clone(), A_hat: self.A_hat.clone() } + } +} + +impl< + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> From<&SK> for MLKEMPrivateKeyExpanded { /// Fully expands the intermediate values needed for performing multiple encaps operations /// against the same public key, which causes the MLKEMPublicKey struct to take up @@ -725,13 +736,13 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> KEMPrivateKey for MLKEMPrivateKeyExpanded +> KEMPrivateKey for MLKEMPrivateKeyExpanded { fn encode(&self) -> [u8; SK_LEN] { self.dk.encode() @@ -749,13 +760,13 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> PartialEq for MLKEMPrivateKeyExpanded +> PartialEq for MLKEMPrivateKeyExpanded { fn eq(&self, other: &Self) -> bool { self.dk.eq(&other.dk) @@ -763,36 +774,30 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Eq for MLKEMPrivateKeyExpanded +> Eq for MLKEMPrivateKeyExpanded { } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Debug for MLKEMPrivateKeyExpanded +> Debug for MLKEMPrivateKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLKEMPrivateKeyExpanded {{ alg: {}, pub_key_hash: {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.dk.pk().compute_hash(), self.dk.seed().is_some(), ) @@ -800,25 +805,19 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> Display for MLKEMPrivateKeyExpanded +> Display for MLKEMPrivateKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; write!( f, "MLKEMPrivateKeyExpanded {{ alg: {}, pub_key_hash: {:x?}, has_seed: {} }}", - alg, + P::ALG_NAME, self.dk.pk().compute_hash(), self.dk.seed().is_some(), ) @@ -826,14 +825,14 @@ impl< } impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, + P: MLKEMParams, + PK: MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize, -> MLKEMPrivateKeyTrait - for MLKEMPrivateKeyExpanded +> MLKEMPrivateKeyTrait + for MLKEMPrivateKeyExpanded { fn seed(&self) -> Option> { self.dk.seed() diff --git a/crypto/mlkem/src/params.rs b/crypto/mlkem/src/params.rs new file mode 100644 index 00000000..2e0df283 --- /dev/null +++ b/crypto/mlkem/src/params.rs @@ -0,0 +1,216 @@ +//! The three ML-KEM parameter sets of FIPS 203, Section 8, as a sealed trait with one type per set. +//! +//! # Derived parameters +//! +//! FIPS 203, Table 2 assigns five values per set (๐‘˜, ๐œ‚1, ๐œ‚2, ๐‘‘๐‘ข, ๐‘‘๐‘ฃ); its last column, the +//! required RBG strength, is carried by `MAX_SECURITY_STRENGTH`. +//! The three sizes of Table 3 are each a function of those, so they are written once as defaulted +//! associated consts rather than three times as a hand-computed number. `params::tests` checks every +//! derivation against the values tabulated in FIPS 203. + +use crate::matrix::{Matrix, MatrixTrait, Vector, VectorTrait}; +use crate::mlkem::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME, MLKEM_SS_LEN}; +use bouncycastle_core::traits::SecurityStrength; + +/// A crate-private (aka "sealed") trait that prevents a new ML-KEM parameter set from being defined +/// outside this crate. +trait MLKEMParamsInternalTrait {} + +/// One ML-KEM parameter set: the values of FIPS 203, Table 2 and Table 3, and the types whose size +/// they determine. +/// +/// Sealed via a private supertrait, so [`MLKEM512Params`], [`MLKEM768Params`] and +/// [`MLKEM1024Params`] are the only implementations. +pub trait MLKEMParams: MLKEMParamsInternalTrait { + /* FIPS 203, Table 2: the values assigned by each parameter set. */ + + /// ๐‘˜, the rank of the module. + const k: usize; + /// ๐œ‚1, the CBD parameter used for the secret vector ๐ฌ and the keygen error vector ๐ž. + const eta1: i16; + /// ๐œ‚2, the CBD parameter used for the encaps error terms ๐ž1 and ๐‘’2. + /// FIPS 203, Table 2 lists this per parameter set even though all three assign it 2. + const eta2: i16; + /// ๐‘‘๐‘ข, the compression parameter for ๐ฎ. + const du: i16; + /// ๐‘‘๐‘ฃ, the compression parameter for ๐‘ฃ. + const dv: i16; + + /* Algorithm meta-data */ + + /// The algorithm name, as reported by `Algorithm::ALG_NAME`. + const ALG_NAME: &'static str; + /// The strength claimed for this parameter set, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. + const MAX_SECURITY_STRENGTH: SecurityStrength; + /// The OID in component form, as reported by `AlgorithmOID::OID`. + const OID: &'static [u32]; + /// The DER encoding of [`MLKEMParams::OID`], as reported by `AlgorithmOID::OID_DER`. + const OID_DER: &'static [u8]; + + /* Derived. Never written out per parameter set -- see the module docs. */ + + /// The length of an encapsulation key: FIPS 203, Algorithm 16 (ML-KEM.KeyGen_internal) gives + /// ek โˆˆ ๐”น^(384๐‘˜+32). + const PK_LEN: usize = 384 * Self::k + 32; + + /// The length of a decapsulation key: FIPS 203, Algorithm 16 (ML-KEM.KeyGen_internal) gives + /// dk โˆˆ ๐”น^(768๐‘˜+96). + const SK_LEN: usize = 768 * Self::k + 96; + + /// The length of a ciphertext: FIPS 203, Algorithm 17 (ML-KEM.Encaps_internal) gives + /// ๐‘ โˆˆ ๐”น^(32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ)). + const CT_LEN: usize = 32 * (Self::du as usize * Self::k + Self::dv as usize); + + /// The length of a shared secret. 32 bytes for every parameter set (FIPS 203, Table 3). + const SS_LEN: usize = MLKEM_SS_LEN; + + /* Types whose size depends on the parameter set. */ + + /// A vector of ๐‘˜ polynomials, i.e. an element of ๐‘…^๐‘˜. + type VecK: VectorTrait; + /// The ๐‘˜ ร— ๐‘˜ public matrix ๐€ฬ‚. + type MatrixA: MatrixTrait; +} + +/// The ML-KEM-512 parameter set (FIPS 203, Table 2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLKEM512Params; +/// The ML-KEM-768 parameter set (FIPS 203, Table 2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLKEM768Params; +/// The ML-KEM-1024 parameter set (FIPS 203, Table 2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLKEM1024Params; + +impl MLKEMParamsInternalTrait for MLKEM512Params {} +impl MLKEMParamsInternalTrait for MLKEM768Params {} +impl MLKEMParamsInternalTrait for MLKEM1024Params {} + +impl MLKEMParams for MLKEM512Params { + const k: usize = 2; + const eta1: i16 = 3; + const eta2: i16 = 2; + const du: i16 = 10; + const dv: i16 = 4; + + const ALG_NAME: &'static str = ML_KEM_512_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + /// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01]; + + type VecK = Vector<2>; + type MatrixA = Matrix<2, 2>; +} + +impl MLKEMParams for MLKEM768Params { + const k: usize = 3; + const eta1: i16 = 2; + const eta2: i16 = 2; + const du: i16 = 10; + const dv: i16 = 4; + + const ALG_NAME: &'static str = ML_KEM_768_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + /// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02]; + + type VecK = Vector<3>; + type MatrixA = Matrix<3, 3>; +} + +impl MLKEMParams for MLKEM1024Params { + const k: usize = 4; + const eta1: i16 = 2; + const eta2: i16 = 2; + const du: i16 = 11; + const dv: i16 = 5; + + const ALG_NAME: &'static str = ML_KEM_1024_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; + /// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03]; + + type VecK = Vector<4>; + type MatrixA = Matrix<4, 4>; +} + +#[cfg(test)] +mod tests { + use super::*; + + /// FIPS 203, Table 2, transcribed row by row: the five values each parameter set + /// assigns, plus its last column. `(k, eta1, eta2, du, dv, rbg_strength)`. + const TABLE_2: [(usize, i16, i16, i16, i16, i16); 3] = + [(2, 3, 2, 10, 4, 128), (3, 2, 2, 10, 4, 192), (4, 2, 2, 11, 5, 256)]; + + /// FIPS 203, Table 3, transcribed row by row, in bytes: + /// `(encapsulation key, decapsulation key, ciphertext, shared secret key)`. + const TABLE_3: [(usize, usize, usize, usize); 3] = + [(800, 1632, 768, 32), (1184, 2400, 1088, 32), (1568, 3168, 1568, 32)]; + + fn check_table_2(i: usize) { + let (k, eta1, eta2, du, dv, rbg_strength) = TABLE_2[i]; + assert_eq!(P::k, k, "{}: ๐‘˜", P::ALG_NAME); + assert_eq!(P::eta1, eta1, "{}: ๐œ‚1", P::ALG_NAME); + assert_eq!(P::eta2, eta2, "{}: ๐œ‚2", P::ALG_NAME); + assert_eq!(P::du, du, "{}: ๐‘‘๐‘ข", P::ALG_NAME); + assert_eq!(P::dv, dv, "{}: ๐‘‘๐‘ฃ", P::ALG_NAME); + assert_eq!( + P::MAX_SECURITY_STRENGTH, + SecurityStrength::from_bits(rbg_strength as usize), + "{}: required RBG strength", + P::ALG_NAME + ); + } + + fn check_table_3(i: usize) { + let (pk_len, sk_len, ct_len, ss_len) = TABLE_3[i]; + assert_eq!(P::PK_LEN, pk_len, "{}: encapsulation key size", P::ALG_NAME); + assert_eq!(P::SK_LEN, sk_len, "{}: decapsulation key size", P::ALG_NAME); + assert_eq!(P::CT_LEN, ct_len, "{}: ciphertext size", P::ALG_NAME); + assert_eq!(P::SS_LEN, ss_len, "{}: shared secret size", P::ALG_NAME); + } + + /// The associated types must be as long as `k` says; they are written out by hand per + /// parameter set, so this guards against a typo in one of them. + fn check_associated_type_sizes() { + // Measured rather than read off a const: `MatrixTrait` already ties the + // matrix to the vector at the type level, so the only thing left to check is that both + // are ๐‘˜ polynomials long. + let poly = size_of::(); + assert_eq!(size_of::(), P::k * poly, "{}: VecK vs ๐‘˜", P::ALG_NAME); + assert_eq!( + size_of::(), + P::k * P::k * poly, + "{}: MatrixA vs ๐‘˜ ร— ๐‘˜", + P::ALG_NAME + ); + } + + #[test] + fn test_parameter_sets_match_fips203_table_2() { + check_table_2::(0); + check_table_2::(1); + check_table_2::(2); + } + + #[test] + fn test_sizes_match_fips203_table_3() { + check_table_3::(0); + check_table_3::(1); + check_table_3::(2); + } + + #[test] + fn test_associated_types_are_the_length_their_consts_claim() { + check_associated_type_sizes::(); + check_associated_type_sizes::(); + check_associated_type_sizes::(); + } +} diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index 9d913db0..5fdd6672 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -6,6 +6,7 @@ use crate::aux_functions::{ ZETAS, ZETAS_INV, barrett_reduce, montgomery_reduce, mul_mont, ntt_base_mult, }; use crate::mlkem::{N, q}; +use crate::params::MLKEMParams; /// A polynomial over the ML-KEM ring. /// @@ -14,7 +15,10 @@ use crate::mlkem::{N, q}; /// and sometimes private keys. /// It is the responsibility of the caller to wrap sensitive instances in `Secret`. #[derive(Clone, Copy)] -pub(crate) struct Polynomial { +/// +/// Public only because it appears in [`crate::VectorTrait`]'s signatures; its fields and +/// operations are crate-private, so from outside it is an opaque handle. +pub struct Polynomial { pub(crate) coeffs: [i16; N], } @@ -136,13 +140,13 @@ impl Polynomial { /// This is an optimized version of /// ByteEncode_๐‘‘๐‘ฃ( Compress_๐‘‘๐‘ฃ(๐‘ฃ) ) /// which packs a single polynomial according to the packing coefficient dv - pub(crate) fn compress_poly(&self, out: &mut [u8]) { - // make sure we have received a dv - debug_assert!(dv == 4 || dv == 5); + pub(crate) fn compress_poly(&self, out: &mut [u8]) { + // make sure we have received a P::dv + debug_assert!(P::dv == 4 || P::dv == 5); // make sure the right size output buffer is given - // each of the N i16's will take dv bits - debug_assert_eq!(out.len(), N * (dv as usize) / 8); + // each of the N i16's will take P::dv bits + debug_assert_eq!(out.len(), N * (P::dv as usize) / 8); let mut t = [0u8; 8]; let mut idx = 0; @@ -154,7 +158,7 @@ impl Polynomial { // let mut s = self.clone(); // s.cond_sub_q(); - match dv { + match P::dv { 4 => { // MLKEM512 and MLKEM768 for i in 0..N / 8 { @@ -195,22 +199,18 @@ impl Polynomial { /// This is an optimized version of /// Decompress_๐‘‘๐‘ฃ( ByteDecode_๐‘‘๐‘ฃ(๐‘2) ) /// which unpacks a single polynomial according to the packing coefficient dv - pub(crate) fn decompress_poly(compressed_v: &[u8]) -> Polynomial { - // make sure to received a dv - debug_assert!(dv == 4 || dv == 5); - + pub(crate) fn decompress_poly(compressed_v: &[u8]) -> Polynomial { // make sure the right size output buffer is given - // each of the N i16's will take dv bits - debug_assert_eq!(compressed_v.len(), N * (dv as usize) / 8); + // each of the N i16's will take P::dv bits + debug_assert_eq!(compressed_v.len(), N * (P::dv as usize) / 8); let mut v = Polynomial::new(); let mut idx = 0usize; - // if self.m_engine.poly_compressed_bytes() == 128 { - match dv { + match P::dv { + // MLKEM512 and MLKEM768 4 => { - // MLKEM512 and MLKEM768 for i in 0..N / 2 { v[2 * i] = (((((compressed_v[idx] & 15) as i16) as i32 * (q as i32)) + 8) >> 4) as i16; @@ -219,8 +219,8 @@ impl Polynomial { idx += 1; } } + // MLKEM1024 5 => { - // MLKEM1024 let mut t = [0u8; 8]; for i in 0..N / 8 { t[0] = compressed_v[idx]; @@ -320,7 +320,6 @@ impl Polynomial { /// /// Borrowed from: /// -/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. pub(crate) fn base_mult_montgomery(a: &Polynomial, b: &Polynomial) -> Polynomial { let mut r = Polynomial::new(); diff --git a/crypto/mlkem/tests/mlkem_tests.rs b/crypto/mlkem/tests/mlkem_tests.rs index 9ade7bcf..4faf8498 100644 --- a/crypto/mlkem/tests/mlkem_tests.rs +++ b/crypto/mlkem/tests/mlkem_tests.rs @@ -813,6 +813,42 @@ mod mlkem_tests { fake_rng.set_security_strength(SecurityStrength::_256bit); _ = MLKEM1024::encaps_rng(&pk1024, &mut fake_rng).unwrap(); } + + #[test] + fn algorithm_names_and_oids() { + use bouncycastle_core::traits::{Algorithm, AlgorithmOID, SecurityStrength}; + + // `Algorithm` and `AlgorithmOID` are implemented once, generically over the parameter set, + // so nothing else states these per algorithm. Pinned here so that a wrong wiring of the + // blanket impls, or a typo in a parameter set, is a test failure rather than a silently + // mislabelled algorithm or an unparseable OID. + assert_eq!(MLKEM512::ALG_NAME, "ML-KEM-512"); + assert_eq!(MLKEM768::ALG_NAME, "ML-KEM-768"); + assert_eq!(MLKEM1024::ALG_NAME, "ML-KEM-1024"); + + assert_eq!(MLKEM512::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(MLKEM768::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(MLKEM1024::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + + // NIST's Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 }, + // id-alg-ml-kem-768 { kems 2 }, id-alg-ml-kem-1024 { kems 3 }. + assert_eq!(MLKEM512::OID, &[2, 16, 840, 1, 101, 3, 4, 4, 1]); + assert_eq!(MLKEM768::OID, &[2, 16, 840, 1, 101, 3, 4, 4, 2]); + assert_eq!(MLKEM1024::OID, &[2, 16, 840, 1, 101, 3, 4, 4, 3]); + + for (oid, der) in [ + (MLKEM512::OID, MLKEM512::OID_DER), + (MLKEM768::OID, MLKEM768::OID_DER), + (MLKEM1024::OID, MLKEM1024::OID_DER), + ] { + assert_eq!(der[0], 0x06, "DER tag must be OBJECT IDENTIFIER"); + assert_eq!(der[1] as usize, der.len() - 2, "DER length must match the content"); + assert_eq!( + &der[2..], + &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, *oid.last().unwrap() as u8] + ); + } + } } // struct Kat {