From 346b6f893d65d7a934b616f0e55ce238ba077cfe Mon Sep 17 00:00:00 2001 From: eschorn1 Date: Mon, 28 Oct 2024 14:46:58 -0500 Subject: [PATCH] refactor code comments to align with spec better --- ct_cm4/Cargo.toml | 2 +- dudect/Cargo.toml | 2 +- fuzz/Cargo.toml | 2 +- src/lib.rs | 307 ++++++++++++++++----------- src/ml_dsa.rs | 413 +++++++++++++++++++++---------------- src/types.rs | 2 - tests/nist_vectors/mod.rs | 6 +- wasm/Cargo.toml | 2 +- wasm/www/package-lock.json | 8 +- 9 files changed, 434 insertions(+), 310 deletions(-) diff --git a/ct_cm4/Cargo.toml b/ct_cm4/Cargo.toml index 68fd9f1..05bab30 100644 --- a/ct_cm4/Cargo.toml +++ b/ct_cm4/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fips204-ct_cm4" -version = "0.4.3" +version = "0.4.4" authors = ["Eric Schorn "] description = "Cortex-M4 testbench for FIPS 204 (draft) ML-DSA" edition = "2021" diff --git a/dudect/Cargo.toml b/dudect/Cargo.toml index b5485f5..1bb0f48 100644 --- a/dudect/Cargo.toml +++ b/dudect/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fips204-dudect" -version = "0.4.3" +version = "0.4.4" authors = ["Eric Schorn "] description = "Dudect testbench for FIPS 204 (draft) ML-DSA" edition = "2021" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 80ec447..6be29ae 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fips204-fuzz" -version = "0.4.3" +version = "0.4.4" authors = ["Eric Schorn "] description = "Fuzz harness for FIPS 204 (draft) ML-DSA" edition = "2021" diff --git a/src/lib.rs b/src/lib.rs index a99ef58..cb8780d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ // Functionality map per FIPS 204 // -// Algorithm 1 ML-DSA.KeyGen() on page 17 --> lib.rs +// Algorithm 1 ML-DSA.KeyGen() on page 17 --> ml_dsa.rs (from lib.rs) // Algorithm 2 ML-DSA.Sign(sk,M,ctx) on page 18 --> lib.rs // Algorithm 3 ML-DSA.Verify(pk,M,s,ctx) on page 18 --> lib.rs // Algorithm 4 HashML-DSA.Sign(sk,M,ctx,PH) on page 20 --> lib.rs @@ -116,16 +116,18 @@ const D: u32 = 13; // See table 1 page 15 third row // largely a lightweight wrapper into the ml_dsa functions. macro_rules! functionality { () => { - use crate::hashing::hash_message; - use crate::helpers::{bit_length, ensure}; + use crate::encodings; + use crate::hashing; + use crate::helpers; use crate::ml_dsa; + use crate::ntt; use crate::traits::{KeyGen, SerDes, Signer, Verifier}; - use crate::types::Ph; + use crate::types; use rand_core::CryptoRngCore; use zeroize::{Zeroize, ZeroizeOnDrop}; const LAMBDA_DIV4: usize = LAMBDA / 4; - const W1_LEN: usize = 32 * K * bit_length((Q - 1) / (2 * GAMMA2) - 1); + const W1_LEN: usize = 32 * K * helpers::bit_length((Q - 1) / (2 * GAMMA2) - 1); const CTEST: bool = false; // When true, the logic goes into CT test mode @@ -229,7 +231,7 @@ macro_rules! functionality { Ok((pk, sk)) } - + // Algorithm 1 in KeyGen trait fn keygen_from_seed(xi: &[u8; 32]) -> (Self::PublicKey, Self::PrivateKey) { let (pk, sk) = ml_dsa::key_gen_internal::(ETA, xi); (pk, sk) @@ -241,29 +243,89 @@ macro_rules! functionality { type Signature = [u8; SIG_LEN]; type PublicKey = PublicKey; - - // Algorithm 2 in Signer trait. + /// Algorithm 2: ML-DSA.Sign(π‘ π‘˜, 𝑀 , 𝑐𝑑π‘₯) + /// Generates an ML-DSA signature. + /// + /// **Input**: Private key `π‘ π‘˜ ∈ 𝔹^{32+32+64+32β‹…((β„“+π‘˜)β‹…bitlen(2πœ‚)+π‘‘π‘˜)}`, + /// message `𝑀 ∈ {0, 1}βˆ—`, + /// context string ctx (a byte string of 255 or fewer bytes).
+ /// **Output**: Signature `𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1 βˆ’1))+πœ”+π‘˜`. + /// + /// # Errors + /// Returns an error when the random number generator fails or context too long. fn try_sign_with_rng( &self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ) -> Result { - ensure!(ctx.len() < 256, "ML-DSA.Sign: ctx too long"); - let sig = ml_dsa::sign::( - rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &[], &[], false - )?; + // 1: if |ctx| > 255 then + // 2: return βŠ₯ β–· return an error indication if the context string is too long + // 3: end if + helpers::ensure!(ctx.len() < 256, "ML-DSA.Sign: ctx too long"); + + // 4: (blank line in spec) + + // 5: rnd ← 𝔹^{32} β–· for the optional deterministic variant, substitute rnd ← {0}^32 + // 6: if rnd = NULL then + // 7: return βŠ₯ β–· return an error indication if random bit generation failed + // 8: end if + let mut rnd = [0u8; 32]; + rng.try_fill_bytes(&mut rnd).map_err(|_| "ML-DSA.Sign: random number generator failed")?; + + // 9: (blank line in spec) + + // Note: step 10 is done within sign_internal() and 'below' + // 10: 𝑀 β€² ← BytesToBits(IntegerToBytes(0, 1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|, 1) βˆ₯ 𝑐𝑑π‘₯) βˆ₯ 𝑀 + // 11: 𝜎 ← ML-DSA.Sign_internal(π‘ π‘˜, 𝑀 β€² , π‘Ÿπ‘›π‘‘) + let sig = ml_dsa::sign_internal::( + BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &[], &[], rnd, false + ); + + // 12: return 𝜎 Ok(sig) } - // Algorithm 4 in Signer trait. + /// Algorithm 4: HashML-DSA.Sign(π‘ π‘˜, 𝑀 , 𝑐𝑑π‘₯, PH) on page 20. + /// Generate a β€œpre-hash” ML-DSA signature. + /// + /// **Input**: Private key `sk ∈ 𝔹^{32+32+64+32β‹…((β„“+π‘˜)β‹…bitlen(2πœ‚)+π‘‘π‘˜)}`, + /// message `𝑀 ∈ {0, 1}βˆ—`, + /// context string ctx (a byte string of 255 or fewer bytes), + /// pre-hash function PH.
+ /// **Output**: ML-DSA signature `𝜎 ∈ 𝔹^{πœ†/4+β„“β‹…32β‹…(1+bitlen(𝛾1 βˆ’1))+πœ”+π‘˜}`. + /// + /// # Errors + /// Returns an error when the random number generator fails or context too long. fn try_hash_sign_with_rng( - &self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &Ph, + &self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &types::Ph, ) -> Result { - ensure!(ctx.len() < 256, "HashML-DSA.Sign: ctx too long"); + // 1: if |ctx| > 255 then + // 2: return βŠ₯ β–· return an error indication if the context string is too long + // 3: end if + helpers::ensure!(ctx.len() < 256, "HashML-DSA.Sign: ctx too long"); + + // 4: (blank line in spec) + + // 5: rnd ← 𝔹^{32} β–· for the optional deterministic variant, substitute rnd ← {0}^32 + // 6: if rnd = NULL then + // 7: return βŠ₯ β–· return an error indication if random bit generation failed + // 8: end if + let mut rnd = [0u8; 32]; + rng.try_fill_bytes(&mut rnd).map_err(|_| "HashML-DSA.Sign: random number generator failed")?; + + // 9: (blank line in spec) + + // Note: steps 10-22 are performed within `hash_message()` below let mut phm = [0u8; 64]; // hashers don't all play well with each other - let (oid, phm_len) = hash_message(message, ph, &mut phm); - let sig = ml_dsa::sign::( - rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &oid, &phm[0..phm_len], false - )?; + let (oid, phm_len) = hashing::hash_message(message, ph, &mut phm); + + // Note: step 23 is performed within `sign_internal()` and below. + // 23: 𝑀 β€² ← BytesToBits(IntegerToBytes(1, 1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|, 1) βˆ₯ 𝑐𝑑π‘₯ βˆ₯ OID βˆ₯ PH𝑀 ) + // 24: 𝜎 ← ML-DSA.Sign_internal(π‘ π‘˜, 𝑀 β€² , π‘Ÿπ‘›π‘‘) + let sig = ml_dsa::sign_internal::( + BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &oid, &phm[0..phm_len], rnd, false + ); + + // 25: return 𝜎 Ok(sig) } @@ -271,49 +333,7 @@ macro_rules! functionality { // Documented in traits.rs #[allow(clippy::cast_lossless)] fn get_public_key(&self) -> Self::PublicKey { - use crate::types::{R, T}; - use crate::ntt::{inv_ntt, ntt}; - use crate::helpers::{add_vector_ntt, mat_vec_mul, full_reduce32, mont_reduce}; - use crate::high_low::power2round; - use crate::helpers::to_mont; - use crate::D; - use crate::hashing::expand_a; - - // TODO: refactor - let PrivateKey {rho, cap_k: _, tr, s_hat_1_mont, s_hat_2_mont, t_hat_0_mont} = &self; - let cap_a_hat: [[T; L]; K] = expand_a::(&rho); - let s_1: [R; L] = inv_ntt(&core::array::from_fn(|l| T(core::array::from_fn(|n| full_reduce32(mont_reduce(s_hat_1_mont[l].0[n] as i64)))))); - let s_1: [R; L] = core::array::from_fn(|l| R(core::array::from_fn(|n| if s_1[l].0[n] > (Q >> 2) {s_1[l].0[n] - Q} else {s_1[l].0[n]}))); - let s_2: [R; K] = inv_ntt(&core::array::from_fn(|k| T(core::array::from_fn(|n| full_reduce32(mont_reduce(s_hat_2_mont[k].0[n] as i64)))))); - let s_2: [R; K] = core::array::from_fn(|k| R(core::array::from_fn(|n| if s_2[k].0[n] > (Q >> 2) {s_2[k].0[n] - Q} else {s_2[k].0[n]}))); - let t_0: [R; K] = inv_ntt(&core::array::from_fn(|k| T(core::array::from_fn(|n| full_reduce32(mont_reduce(t_hat_0_mont[k].0[n] as i64)))))); - let sk_t_0: [R; K] = core::array::from_fn(|k| R(core::array::from_fn(|n| if t_0[k].0[n] > (Q / 2) {t_0[k].0[n] - Q} else {t_0[k].0[n]}))); - - // 5: t ← NTTβˆ’1(cap_a_hat β—¦ NTT(s_1)) + s_2 β–· Compute t = As1 + s2 - let t: [R; K] = { - let s_1_hat: [T; L] = ntt(&s_1); - let as1_hat: [T; K] = mat_vec_mul(&cap_a_hat, &s_1_hat); - let t_not_reduced: [R; K] = add_vector_ntt(&inv_ntt(&as1_hat), &s_2); - core::array::from_fn(|k| R(core::array::from_fn(|n| full_reduce32(t_not_reduced[k].0[n])))) - }; - - // 6: (t_1, t_0) ← Power2Round(t, d) β–· Compress t - let (t_1, pk_t_0): ([R; K], [R; K]) = power2round(&t); - debug_assert_eq!(sk_t_0, pk_t_0); // fuzz target - - // 7: pk ← pkEncode(ρ, t_1) - //let pk: [u8; PK_LEN] = pk_encode(rho, &t_1); - // the last term of: - // 9: 𝐰Approx ← NTT (𝐀 ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2𝑑 )) β–· 𝐰Approx = 𝐀𝐳 βˆ’ 𝑐𝐭1 β‹… 2𝑑 - let t1_hat_mont: [T; K] = to_mont(&ntt(&t_1)); - let t1_d2_hat_mont: [T; K] = to_mont(&core::array::from_fn(|k| { - T(core::array::from_fn(|n| mont_reduce(i64::from(t1_hat_mont[k].0[n]) << D))) - })); - //let pk = PublicKey { rho: *rho, cap_a_hat: cap_a_hat.clone(), tr: *tr, t1_d2_hat_mont}; - let pk = PublicKey { rho: *rho, tr: *tr, t1_d2_hat_mont}; - - // 10: return pk - pk + ml_dsa::private_to_public_key(&self) } } @@ -321,29 +341,63 @@ macro_rules! functionality { impl Verifier for PublicKey { type Signature = [u8; SIG_LEN]; - // Algorithm 3 in Verifier trait. + /// Algorithm 3: ML-DSA.Verify(pk, 𝑀, 𝜎, ctx) on page 18. + /// Verifies a signature 𝜎 for a message 𝑀. + /// + /// **Input**: Public key π‘π‘˜ ∈ 𝔹^{32+32π‘˜(bitlen(π‘žβˆ’1)βˆ’π‘‘)}, + /// message 𝑀 ∈ {0, 1}βˆ—, + /// signature 𝜎 ∈ 𝔹^{πœ†/4+β„“β‹…32β‹…(1+bitlen(𝛾1βˆ’1))+πœ”+π‘˜}, + /// context string ctx (a byte string of 255 or fewer bytes).
+ /// **Output**: Boolean. fn verify(&self, message: &[u8], sig: &Self::Signature, ctx: &[u8]) -> bool { - let Ok(res) = ml_dsa::verify::( - BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &message, &sig, ctx, &[], &[], false - ) else { + // 1: if |ctx| > 255 then + // 2: return βŠ₯ β–· return an error indication if the context string is too long + // 3: end if + if ctx.len() > 255 { return false; }; - res + + // 4: (blank line in spec) + + // Note: step 5 is performed within `verify_internal()` and below. + // 5: 𝑀′ ← BytesToBits(IntegerToBytes(0, 1) βˆ₯ IntegerToBytes(|ctx|, 1) βˆ₯ ctx) βˆ₯ 𝑀 + // 6: return ML-DSA.Verify_internal(pk, 𝑀′, 𝜎) + ml_dsa::verify_internal::( + BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &message, &sig, ctx, &[], &[], false + ) } - // Algorithm 5 in Verifier trait. - fn hash_verify(&self, message: &[u8], sig: &Self::Signature, ctx: &[u8], ph: &Ph) -> bool { + /// Algorithm 5: HashML-DSA.Verify(pk, 𝑀 , 𝜎, ctx, PH) on page 21. + /// Verifies a pre-hash HashML-DSA signature. + /// + /// **Input**: Public key π‘π‘˜ ∈ 𝔹^{32+32π‘˜(bitlen(π‘žβˆ’1)βˆ’π‘‘)}, + /// message 𝑀 ∈ {0, 1}βˆ—, + /// signature 𝜎 ∈ 𝔹^{πœ†/4+β„“β‹…32β‹…(1+bitlen(𝛾1 βˆ’1))+πœ”+π‘˜}, + /// context string ctx (a byte string of 255 or fewer bytes), + /// pre-hash function PH.
+ /// **Output**: Boolean. + fn hash_verify(&self, message: &[u8], sig: &Self::Signature, ctx: &[u8], ph: &types::Ph) -> bool { + // 1: if |ctx| > 255 then + // 2: return βŠ₯ β–· return an error indication if the context string is too long + // 3: end if if ctx.len() > 255 { return false; }; + + // 4: (blank line in spec) + + // Note: steps 5-17 are performed within `hash_message()` below let mut phm = [0u8; 64]; // hashers don't all play well with each other - let (oid, phm_len) = hash_message(message, ph, &mut phm); - let Ok(res) = ml_dsa::verify::( + let (oid, phm_len) = hashing::hash_message(message, ph, &mut phm); + + // Note: step 18 is pe + // + // performed within `verify_internal()` and below. + // 18: 𝑀′ ← BytesToBits(IntegerToBytes(1, 1) βˆ₯ IntegerToBytes(|ctx|, 1) βˆ₯ ctx βˆ₯ OID βˆ₯ PH𝑀 ) + // 19: return ML-DSA.Verify_internal(π‘π‘˜, 𝑀′ , 𝜎) + ml_dsa::verify_internal::( BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &message, &sig, ctx, &oid, &phm[0..phm_len], false - ) else { - return false; - }; - res + ) } } @@ -360,23 +414,42 @@ macro_rules! functionality { } - #[allow(clippy::cast_lossless)] fn into_bytes(self) -> Self::ByteArray { - use crate::types::{R, T}; - use crate::helpers::mont_reduce; - use crate::helpers::full_reduce32; - use crate::ntt::inv_ntt; - - // TODO: refactor + // Extract the pre-computes let PrivateKey {rho, cap_k, tr, s_hat_1_mont, s_hat_2_mont, t_hat_0_mont, ..} = &self; - let s_1: [R; L] = inv_ntt(&core::array::from_fn(|l| T(core::array::from_fn(|n| full_reduce32(mont_reduce(s_hat_1_mont[l].0[n] as i64)))))); - let s_1: [R; L] = core::array::from_fn(|l| R(core::array::from_fn(|n| if s_1[l].0[n] > (Q >> 2) {s_1[l].0[n] - Q} else {s_1[l].0[n]}))); - let s_2: [R; K] = inv_ntt(&core::array::from_fn(|k| T(core::array::from_fn(|n| full_reduce32(mont_reduce(s_hat_2_mont[k].0[n] as i64)))))); - let s_2: [R; K] = core::array::from_fn(|k| R(core::array::from_fn(|n| if s_2[k].0[n] > (Q >> 2) {s_2[k].0[n] - Q} else {s_2[k].0[n]}))); - let t_0: [R; K] = inv_ntt(&core::array::from_fn(|k| T(core::array::from_fn(|n| full_reduce32(mont_reduce(t_hat_0_mont[k].0[n] as i64)))))); - let t_0: [R; K] = core::array::from_fn(|k| R(core::array::from_fn(|n| if t_0[k].0[n] > (Q / 2) {t_0[k].0[n] - Q} else {t_0[k].0[n]}))); - let sk = crate::encodings::sk_encode::(ETA, rho, cap_k, tr, &s_1, &s_2, &t_0); - sk + + // mont->norm each n coeff, of L entries of T, then inverse NTT + let s_1: [types::R; L] = ntt::inv_ntt( + &core::array::from_fn(|l| + types::T(core::array::from_fn(|n| + helpers::mont_reduce(i64::from(s_hat_1_mont[l].0[n])))))); + // correct each coeff such that they are centered around 0 + let s_1: [types::R; L] = + core::array::from_fn(|l| + types::R(core::array::from_fn(|n| + if s_1[l].0[n] > (Q / 2) {s_1[l].0[n] - Q} else {s_1[l].0[n]}))); + + let s_2: [types::R; K] = ntt::inv_ntt( + &core::array::from_fn(|k| + types::T(core::array::from_fn(|n| + helpers::mont_reduce(i64::from(s_hat_2_mont[k].0[n])))))); + let s_2: [types::R; K] = + core::array::from_fn(|k| + types::R(core::array::from_fn(|n| + if s_2[k].0[n] > (Q / 2) {s_2[k].0[n] - Q} else {s_2[k].0[n]}))); + + + let t_0: [types::R; K] = ntt::inv_ntt( + &core::array::from_fn(|k| + types::T(core::array::from_fn(|n| + helpers::mont_reduce(i64::from(t_hat_0_mont[k].0[n])))))); + let t_0: [types::R; K] = + core::array::from_fn(|k| + types::R(core::array::from_fn(|n| + if t_0[k].0[n] > (Q / 2) {t_0[k].0[n] - Q} else {t_0[k].0[n]}))); + + // Encode and return + encodings::sk_encode::(ETA, rho, cap_k, tr, &s_1, &s_2, &t_0) } } @@ -392,23 +465,20 @@ macro_rules! functionality { } - #[allow(clippy::cast_lossless)] fn into_bytes(self) -> Self::ByteArray { - use crate::types::{R, T}; - use crate::helpers::mont_reduce; - use crate::helpers::full_reduce32; - use crate::ntt::inv_ntt; - use crate::D; - use crate::hashing::expand_a; - - // TODO: refactor - let PublicKey {rho, tr, t1_d2_hat_mont} = &self; - let cap_a_hat: [[T; L]; K] = expand_a::(&rho); - let (_, _, _, _) = (rho, cap_a_hat, tr, t1_d2_hat_mont); - let t1_d2: [R; K] = inv_ntt(&core::array::from_fn(|k| T(core::array::from_fn(|n| full_reduce32(mont_reduce(t1_d2_hat_mont[k].0[n] as i64)))))); - let t1: [R; K] = core::array::from_fn(|k| R(core::array::from_fn(|n| t1_d2[k].0[n] >> D))); - let pk = crate::encodings::pk_encode(rho, &t1); - pk + // Extract the pre-computes + let PublicKey {rho, tr: _tr, t1_d2_hat_mont} = &self; + + let t1_d2: [types::R; K] = ntt::inv_ntt( + &core::array::from_fn(|k| + types::T(core::array::from_fn(|n| + helpers::mont_reduce(i64::from(t1_d2_hat_mont[k].0[n])))))); + + let t1: [types::R; K] = core::array::from_fn(|k| + types::R(core::array::from_fn(|n| + t1_d2[k].0[n] >> crate::D))); + + encodings::pk_encode(rho, &t1) } } @@ -452,9 +522,11 @@ macro_rules! functionality { rng: &mut impl CryptoRngCore, message: &[u8], ) -> Result<[u8; SIG_LEN], &'static str> { let (_pk, sk) = ml_dsa::key_gen::(rng, ETA)?; - let sig = ml_dsa::sign::( - rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &sk, message, &[1], &[2], &[3], true - )?; + let mut rnd = [0u8; 32]; + rng.try_fill_bytes(&mut rnd).map_err(|_| "Random number generator failed")?; + let sig = ml_dsa::sign_internal::( + BETA, GAMMA1, GAMMA2, OMEGA, TAU, &sk, message, &[1], &[2], &[3], rnd, true + ); Ok(sig) } @@ -469,12 +541,12 @@ macro_rules! functionality { /// # Errors /// Propagate errors from the `sign_finish()` function (for failing RNG). pub fn _internal_sign( - sk: &PrivateKey, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], + sk: &PrivateKey, message: &[u8], ctx: &[u8], rnd: [u8; 32] ) -> Result<[u8; SIG_LEN], &'static str> { - ensure!(ctx.len() < 256, "ML-DSA.Sign: ctx too long"); - let sig = ml_dsa::sign::( - rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, sk, message, ctx, &[], &[], true - )?; + helpers::ensure!(ctx.len() < 256, "ML-DSA.Sign: ctx too long"); + let sig = ml_dsa::sign_internal::( + BETA, GAMMA1, GAMMA2, OMEGA, TAU, sk, message, ctx, &[], &[], rnd, true + ); Ok(sig) } @@ -491,12 +563,9 @@ macro_rules! functionality { if ctx.len() > 255 { return false; }; - let Ok(res) = ml_dsa::verify::( + ml_dsa::verify_internal::( BETA, GAMMA1, GAMMA2, OMEGA, TAU, pk, &message, &sig, ctx, &[], &[], true - ) else { - return false; - }; - res + ) } }; } diff --git a/src/ml_dsa.rs b/src/ml_dsa.rs index 49b42cb..8e7a778 100644 --- a/src/ml_dsa.rs +++ b/src/ml_dsa.rs @@ -14,7 +14,7 @@ use rand_core::CryptoRngCore; use sha3::digest::XofReader; -/// Algorithm: 1 `ML-DSA.KeyGen()` on page 15. +/// Algorithm: 1 `ML-DSA.KeyGen()` on page 17. /// Generates a public-private key pair. /// /// **Input**: `rng` a cryptographically-secure random number generator.
@@ -33,28 +33,127 @@ pub(crate) fn key_gen< rng: &mut impl CryptoRngCore, eta: i32, ) -> Result<(PublicKey, PrivateKey), &'static str> { // - // 1: ΞΎ ← {0,1}^{256} β–· Choose random seed + // 1: ΞΎ ← B^{32} β–· Choose random seed + // 2: if ΞΎ = NULL then + // 3: return βŠ₯ β–· return an error indication if random bit generation failed + // 4: end if let mut xi = [0u8; 32]; rng.try_fill_bytes(&mut xi).map_err(|_| "Random number generator failed")?; + // 5: return ML-DSA.KeyGen_internal (πœ‰) Ok(key_gen_internal::(eta, &xi)) } -// The following two functions effectively implement A) Algorithm 2 ML-DSA.Sign(sk, M) on -// page 17 and B) Algorithm 7 ML-DSA.Sign_internal(sk, M', rnd) on page 25 in conjunction -// with the top level try_sign* API in lib.rs. This is due to the support for a precomputed -// private key that is able to sign with higher performance. +/// Algorithm: 6 `ML-DSA.KeyGen_internal()` on page 15. +/// Generates a public-private key pair. +/// +/// **Input**: `rng` a cryptographically-secure random number generator.
+/// **Output**: Public key, `pk ∈ B^{32+32Β·kΒ·(bitlen(qβˆ’1)βˆ’d)}`, and +/// private key, `sk ∈ B^{32+32+64+32Β·((β„“+k)Β·bitlen(2Β·Ξ·)+dΒ·k)}` +/// +/// # Errors +/// Returns an error when the random number generator fails. +pub(crate) fn key_gen_internal< + const CTEST: bool, + const K: usize, + const L: usize, + const PK_LEN: usize, + const SK_LEN: usize, +>( + eta: i32, xi: &[u8; 32], +) -> (PublicKey, PrivateKey) { + // + // 1: (rho, rhoβ€², 𝐾) ∈ 𝔹32 Γ— 𝔹64 Γ— 𝔹32 ← H(πœ‰||IntegerToBytes(π‘˜, 1)||IntegerToBytes(β„“, 1), 128) + let mut h2 = h256_xof(&[xi, &[K.to_le_bytes()[0]], &[L.to_le_bytes()[0]]]); + let mut rho = [0u8; 32]; + h2.read(&mut rho); + let mut rho_prime = [0u8; 64]; + h2.read(&mut rho_prime); + let mut cap_k = [0u8; 32]; + h2.read(&mut cap_k); + + // There is effectively no step 2 due to formatting error in spec + + // 4: (s_1, s_2) ← ExpandS(ρ′) + let (s_1, s_2): ([R; L], [R; K]) = expand_s::(eta, &rho_prime); + + // 3: cap_a_hat ← ExpandA(ρ) β–· A is generated and stored in NTT representation as Γ‚ + // 5: t ← NTTβˆ’1(cap_a_hat β—¦ NTT(s_1)) + s_2 β–· Compute t = As1 + s2 + // 6: (t_1, t_0) ← Power2Round(t, d) β–· Compress t + + let (t_1, t_0): ([R; K], [R; K]) = { + let cap_a_hat: [[T; L]; K] = expand_a::(&rho); + let s_1_hat: [T; L] = ntt(&s_1); + let as1_hat: [T; K] = mat_vec_mul(&cap_a_hat, &s_1_hat); + let t_not_reduced: [R; K] = add_vector_ntt(&inv_ntt(&as1_hat), &s_2); + let t: [R; K] = core::array::from_fn(|k| { + R(core::array::from_fn(|n| full_reduce32(t_not_reduced[k].0[n]))) + }); + power2round(&t) + }; + + // There is effectively no step 7 due to formatting error in spec + + // 8: pk ← pkEncode(ρ, t_1) + let pk: [u8; PK_LEN] = pk_encode(&rho, &t_1); + + // 9: tr ← H(BytesToBits(pk), 512) + let mut tr = [0u8; 64]; + let mut h8 = h256_xof(&[&pk]); + h8.read(&mut tr); + // 10: sk ← skEncode(ρ, K, tr, s_1, s_2, t_0) β–· K and tr are for use in signing + //let sk: [u8; SK_LEN] = sk_encode(eta, &rho, &cap_k, &tr, &s_1, &s_2, &t_0); + + // the last term of: + // 9: 𝐰Approx ← NTT (𝐀 ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2𝑑 )) β–· 𝐰Approx = 𝐀𝐳 βˆ’ 𝑐𝐭1 β‹… 2𝑑 + let t1_hat_mont: [T; K] = to_mont(&ntt(&t_1)); + let t1_d2_hat_mont: [T; K] = to_mont(&core::array::from_fn(|k| { + T(core::array::from_fn(|n| mont_reduce(i64::from(t1_hat_mont[k].0[n]) << D))) + })); + //let pk = PublicKey { rho, cap_a_hat: cap_a_hat.clone(), tr, t1_d2_hat_mont }; + let pk = PublicKey { rho, tr, t1_d2_hat_mont }; + + // 2: s_hat_1 ← NTT(s_1) + //let s_hat_1_mont: [T; L] = to_mont(&s_1_hat); //ntt(&s_1)); + let s_hat_1_mont: [T; L] = to_mont(&ntt(&s_1)); + // 3: s_hat_2 ← NTT(s_2) + let s_hat_2_mont: [T; K] = to_mont(&ntt(&s_2)); + // 4: t_hat_0 ← NTT(t_0) + let t_hat_0_mont: [T; K] = to_mont(&ntt(&t_0)); + let sk = PrivateKey { + rho, + cap_k, + tr, + s_hat_1_mont, + s_hat_2_mont, + t_hat_0_mont, + // cap_a_hat, + }; -/// Continuation of `sign_start()` + // 11: return (pk, sk) + (pk, sk) +} + + +/// Algorithm 7: ML-DSA.Sign_internal(π‘ π‘˜, 𝑀 β€² , π‘Ÿπ‘›π‘‘) on page 25. +/// Deterministic algorithm to generate a signature for a formatted message 𝑀 β€². +/// +/// **Input**: Private key π‘ π‘˜ ∈ 𝔹^{32+32+64+32β‹…((β„“+π‘˜)β‹…bitlen(2πœ‚)+π‘‘π‘˜)}, +/// formatted message 𝑀′ ∈ {0, 1}βˆ—, and +/// per message randomness or dummy variable rnd ∈ 𝔹^{32}.
+/// **Output**: Signature 𝜎 ∈ 𝔹^{πœ†/4+β„“β‹…32β‹…(1+bitlen(𝛾1βˆ’1))+πœ”+π‘˜}. +// Note the M' is assembled here from provided elements, rather than by caller. +// Further, a deserialized private key struct has a variety of pre-computed +// elements ready-to-go. #[allow( clippy::similar_names, clippy::many_single_char_names, clippy::too_many_arguments, clippy::too_many_lines )] -pub(crate) fn sign< +pub(crate) fn sign_internal< const CTEST: bool, const K: usize, const L: usize, @@ -63,61 +162,46 @@ pub(crate) fn sign< const SK_LEN: usize, const W1_LEN: usize, >( - rand_gen: &mut impl CryptoRngCore, beta: i32, gamma1: i32, gamma2: i32, omega: i32, tau: i32, - esk: &PrivateKey, message: &[u8], ctx: &[u8], oid: &[u8], phm: &[u8], nist: bool, -) -> Result<[u8; SIG_LEN], &'static str> { + beta: i32, gamma1: i32, gamma2: i32, omega: i32, tau: i32, esk: &PrivateKey, + message: &[u8], ctx: &[u8], oid: &[u8], phm: &[u8], rnd: [u8; 32], nist: bool, +) -> [u8; SIG_LEN] { // // 1: (ρ, K, tr, s_1, s_2, t_0) ← skDecode(sk) - // --> calculated in expand_private() + // --> calculated in `expand_private()` near the bottom of this file + // Extract elements from private key + let PrivateKey { rho, cap_k, tr, s_hat_1_mont, s_hat_2_mont, t_hat_0_mont } = esk; // // 2: s_hat_1 ← NTT(s_1) - // --> calculated in expand_private() + // --> the montgomery form is extracted from the private key struct above // // 3: s_hat_2 ← NTT(s_2) - // --> calculated in expand_private() + // --> the montgomery form is extracted from the private key struct above // // 4: t_hat_0 ← NTT(t_0) - // --> calculated in expand_private() + // --> the montgomery form is extracted from the private key struct above // // 5: cap_a_hat ← ExpandA(ρ) β–· A is generated and stored in NTT representation as Γ‚ - // --> calculated in expand_private() - - // Extract from expand_private() - let PrivateKey { - rho, - cap_k, - tr, - s_hat_1_mont, - s_hat_2_mont, - t_hat_0_mont, - //cap_a_hat, - } = esk; - let cap_a_hat: [[T; L]; K] = expand_a::(rho); // 6: πœ‡ ← H(BytesToBits(π‘‘π‘Ÿ)||𝑀 , 64) β–· Compute message representative Β΅ - // We may have arrived from 3 different paths + // Calculate mu based on which of the three different paths led us here let mut h6 = if nist { - // 1. NIST vectors are being applied to "internal" functions + // 6a. NIST vectors are being applied to "internal" functions h256_xof(&[tr, message]) } else if oid.is_empty() { - // 2. From ML-DSA.Sign(): 𝑀′ ← BytesToBits(IntegerToBytes(0,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯) βˆ₯ 𝑀 + // 6b. From ML-DSA.Sign(): 𝑀′ ← BytesToBits(IntegerToBytes(0,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯) βˆ₯ 𝑀 h256_xof(&[tr, &[0u8], &[ctx.len().to_le_bytes()[0]], ctx, message]) } else { - // 3. From HashML-DSA.Sign(): 𝑀′ ← BytesToBits(IntegerToBytes(1,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯ βˆ₯ OID βˆ₯ PH𝑀 ) + // 6c. From HashML-DSA.Sign(): 𝑀′ ← BytesToBits(IntegerToBytes(1,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯ βˆ₯ OID βˆ₯ PH𝑀 ) h256_xof(&[tr, &[1u8], &[oid.len().to_le_bytes()[0]], ctx, oid, phm]) }; let mut mu = [0u8; 64]; h6.read(&mut mu); - // rnd ← {0,1}^256 β–· For the optional deterministic variant, substitute rnd ← {0}^256 - let mut rnd = [0u8; 32]; - rand_gen.try_fill_bytes(&mut rnd).map_err(|_| "Alg 2: rng fail")?; - - // 7: ρ′ ← H(K || rnd || Β΅, 512) β–· Compute private random seed - let mut h8 = h256_xof(&[cap_k, &rnd, &mu]); + // 7: ρ′' ← H(K || rnd || Β΅, 64) β–· Compute private random seed + let mut h7 = h256_xof(&[cap_k, &rnd, &mu]); let mut rho_prime = [0u8; 64]; - h8.read(&mut rho_prime); + h7.read(&mut rho_prime); // 8: ΞΊ ← 0 β–· Initialize counter ΞΊ let mut kappa_ctr = 0u16; @@ -125,12 +209,12 @@ pub(crate) fn sign< // 9: (z, h) ← βŠ₯ β–· we will handle βŠ₯ inline with 'continue' let mut z: [R; L]; let mut h: [R; K]; - let mut c_tilde = [0u8; LAMBDA_DIV4]; // size could be fixed at 32; but spec will fix flaw + let mut c_tilde = [0u8; LAMBDA_DIV4]; // 10: while (z, h) = βŠ₯ do β–· Rejection sampling loop (with continue for βŠ₯) loop { // - // 11: y ← ExpandMask(ρ′, ΞΊ) + // 11: y ← ExpandMask(ρ′', ΞΊ) let y: [R; L] = expand_mask(gamma1, &rho_prime, kappa_ctr); // 12: w ← NTTβˆ’1(cap_a_hat β—¦ NTT(y)) @@ -144,15 +228,15 @@ pub(crate) fn sign< let w_1: [R; K] = core::array::from_fn(|k| R(core::array::from_fn(|n| high_bits(gamma2, w[k].0[n])))); - // There is effectively no step 14 due to formatting error in spec + // There is effectively no step 14 due to formatting oddity in spec - // 15: c_tilde ∈ {0,1}^{2Β·Lambda} ← H(Β΅ || w1Encode(w_1), 2Β·Lambda) β–· Commitment hash + // 15: c_tildeΜƒ ← H(mu||w1Encode(w_1), πœ†/4) β–· commitment hash let mut w1_tilde = [0u8; W1_LEN]; w1_encode::(gamma2, &w_1, &mut w1_tilde); let mut h15 = h256_xof(&[&mu, &w1_tilde]); h15.read(&mut c_tilde); - // 16: c ← SampleInBall(c_tilde_1) β–· Verifier’s challenge + // 16: c ∈ π‘…π‘ž ← SampleInBall(c_tilde_1) β–· Verifier’s challenge let c: R = sample_in_ball::(tau, &c_tilde); // 17: c_hat ← NTT(c) @@ -190,14 +274,14 @@ pub(crate) fn sign< })) }); - // There is effectively no step 22 due to formatting error in spec + // There is effectively no step 22 due to formatting oddity in spec // 23: if ||z||∞ β‰₯ Gamma1 βˆ’ Ξ² or ||r0||∞ β‰₯ Gamma2 βˆ’ Ξ² then (z, h) ← βŠ₯ β–· Validity checks let z_norm = infinity_norm(&z); let r0_norm = infinity_norm(&r0); // CTEST is used only for constant-time measurements via `dudect` if !CTEST && ((z_norm >= (gamma1 - beta)) || (r0_norm >= (gamma2 - beta))) { - kappa_ctr += u16::try_from(L).expect("cannot fail"); + kappa_ctr += u16::try_from(L).expect("cannot fail; K is static parameter"); continue; // // 24: else ... not needed with 'continue' @@ -232,7 +316,7 @@ pub(crate) fn sign< && ((infinity_norm(&c_t_0) >= gamma2) || (h.iter().map(|h_i| h_i.0.iter().sum::()).sum::() > omega)) { - kappa_ctr += u16::try_from(L).expect("cannot fail"); + kappa_ctr += u16::try_from(L).expect("cannot fail; K is static parameter"); continue; // 29: end if } @@ -249,18 +333,25 @@ pub(crate) fn sign< } // 33: Οƒ ← sigEncode(c_tilde, z modΒ± q, h) + // 34: return Οƒ let zmodq: [R; L] = core::array::from_fn(|l| R(core::array::from_fn(|n| center_mod(z[l].0[n])))); - let sig = sig_encode::(gamma1, omega, &c_tilde, &zmodq, &h); - - // 34: return Οƒ - Ok(sig) + sig_encode::(gamma1, omega, &c_tilde, &zmodq, &h) } -/// Continuation of `verify_start()`. The `lib.rs` wrapper around this will convert `Error()` to false. -#[allow(clippy::too_many_arguments, clippy::similar_names)] -pub(crate) fn verify< +/// Algorithm 8: ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) on page 27. +/// Internal function to verify a signature 𝜎 for a formatted message 𝑀′. +/// +/// **Input**: Public key π‘π‘˜ ∈ 𝔹^{32+32π‘˜(bitlen(π‘žβˆ’1)βˆ’π‘‘), +/// message 𝑀′ ∈ {0, 1}βˆ—, +/// Signature 𝜎 ∈ 𝔹^{πœ†/4+β„“β‹…32β‹…(1+bitlen(𝛾1 βˆ’1))+πœ”+π‘˜}.
+/// **Output**: Boolean +// Note the M' is assembled here from provided elements, rather than by caller. +// Further, a deserialized public key struct has a variety of pre-computed +// elements ready-to-go. +#[allow(clippy::too_many_arguments, clippy::similar_names, clippy::type_complexity)] +pub(crate) fn verify_internal< const CTEST: bool, const K: usize, const L: usize, @@ -271,58 +362,55 @@ pub(crate) fn verify< >( beta: i32, gamma1: i32, gamma2: i32, omega: i32, tau: i32, epk: &PublicKey, m: &[u8], sig: &[u8; SIG_LEN], ctx: &[u8], oid: &[u8], phm: &[u8], nist: bool, -) -> Result { +) -> bool { // - //let PublicKey { rho: _, cap_a_hat, tr, t1_d2_hat_mont } = epk; + // 1: (ro, t_1) ← pkDecode(pk) pull out pre-computed elements let PublicKey { rho, tr, t1_d2_hat_mont } = epk; - // 1: (ρ, t_1) ← pkDecode(pk) - // --> calculated in expand_public() - // 2: (c_tilde, z, h) ← sigDecode(Οƒ) β–· Signer’s commitment hash c_tilde, response z and hint h - let (c_tilde, z, h): ([u8; LAMBDA_DIV4], [R; L], Option<[R; K]>) = - sig_decode(gamma1, omega, sig)?; + let Ok((c_tilde, z, h)): Result<([u8; LAMBDA_DIV4], [R; L], Option<[R; K]>), &'static str> = + sig_decode(gamma1, omega, sig) + else { + return false; + }; - // 3: if h = βŠ₯ then return false β–· Hint was not properly encoded - if h.is_none() { - return Ok(false); + // 3: if h = βŠ₯ then return false β–· Hint was not properly encoded + // 4: end if + let Some(h) = h else { return false }; - // 4: end if - }; - let h = h.unwrap(); - debug_assert!(infinity_norm(&z) <= gamma1, "Alg 3: i_norm out of range"); // TODO: consider revising + debug_assert!(infinity_norm(&z) <= gamma1, "Alg 3: i_norm out of range"); // Fuzz target // 5: cap_a_hat ← ExpandA(ρ) β–· A is generated and stored in NTT representation as cap_A_hat - // --> calculated in expand_public() + // stashed in step 9 below to minimize stack consumption when dev has opt-level = 0 // 6: tr ← H(pk, 64) - // --> calculated in expand_public() + // --> extracted from public key pre-computes in step 1 above // 7: πœ‡ ← (H(BytesToBits(tr)||𝑀′, 64)) β–· Compute message representative Β΅ - // We may have arrived from 3 different paths + // Calculate mu based on which of the three different paths led us here let mut h7 = if nist { - // 1. NIST vectors are being applied to "internal" functions + // 7a. NIST vectors are being applied to "internal" functions h256_xof(&[tr, m]) } else if oid.is_empty() { - // 2. From ML-DSA.Verify(): 5: 𝑀′ ← BytesToBits(IntegerToBytes(0,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯) βˆ₯ 𝑀 + // 7b. From ML-DSA.Verify(): 5: 𝑀′ ← BytesToBits(IntegerToBytes(0,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯) βˆ₯ 𝑀 h256_xof(&[tr, &[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m]) } else { - // 3. From HashML-DSA.Verify(): 18: 𝑀′ ← BytesToBits(IntegerToBytes(1,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯ βˆ₯ OID βˆ₯ PH𝑀 ) + // 7c. From HashML-DSA.Verify(): 18: 𝑀′ ← BytesToBits(IntegerToBytes(1,1) βˆ₯ IntegerToBytes(|𝑐𝑑π‘₯|,1) βˆ₯ 𝑐𝑑π‘₯ βˆ₯ OID βˆ₯ PH𝑀 ) h256_xof(&[tr, &[1u8], &[oid.len().to_le_bytes()[0]], ctx, oid, phm]) }; let mut mu = [0u8; 64]; h7.read(&mut mu); - // 8: c ← SampleInBall(c_tilde_1) β–· Compute verifier’s challenge from c_tilde - let c: R = sample_in_ball::(tau, &c_tilde); // false, as this instance isn't pertinent to CT + // 8: c ∈ π‘…π‘ž ← SampleInBall(c_tilde_1) β–· Compute verifier’s challenge from c_tilde + let c: R = sample_in_ball::(tau, &c_tilde); // CTEST is always false (as no CT guarantees) // 9: wβ€²_Approx ← invNTT(cap_A_hat β—¦ NTT(z) - NTT(c) β—¦ NTT(t_1 Β· 2^d) β–· wβ€²_Approx = Az βˆ’ ct1Β·2^d let wp_approx: [R; K] = { - // hardcode CTEST as false since everything is public here - let cap_a_hat: [[T; L]; K] = expand_a::(rho); + // CTEST is always false (as no CT guarantees); from step 5 above + let cap_a_hat: [[T; L]; K] = expand_a::(rho); let z_hat: [T; L] = ntt(&z); let az_hat: [T; K] = mat_vec_mul(&cap_a_hat, &z_hat); - // NTT(t_1 Β· 2^d) --> calculated in expand_public() + // NTT(t_1 Β· 2^d) --> extracted from public key struct let c_hat: &T = &ntt(&[c])[0]; inv_ntt(&core::array::from_fn(|k| { T(core::array::from_fn(|n| { @@ -337,111 +425,19 @@ pub(crate) fn verify< R(core::array::from_fn(|n| use_hint(gamma2, h[k].0[n], wp_approx[k].0[n]))) }); - // There is effectively no step 11 due to formatting error in spec + // There is effectively no step 11 due to formatting oddity in spec - // 12: c_tilde_β€² ← H(Β΅ || w1Encode(wβ€²_1), 2Ξ») β–· Hash it; this should match c_tilde + // 12: c_tilde_β€² ← H(Β΅ || w1Encode(wβ€²_1), Ξ»/4) β–· Hash it; this should match c_tilde let mut tmp = [0u8; W1_LEN]; w1_encode::(gamma2, &wp_1, &mut tmp); let mut h12 = h256_xof(&[&mu, &tmp]); let mut c_tilde_p = [0u8; LAMBDA_DIV4]; - h12.read(&mut c_tilde_p); // leftover to be ignored + h12.read(&mut c_tilde_p); - // 13: return [[ ||z||∞ < Ξ³1 βˆ’Ξ²]] and [[c_tilde = c_tilde_β€²]] and [[number of 1’s in h is ≀ Ο‰]] + // 13: return [[ ||z||∞ < Ξ³1 βˆ’Ξ²]] and [[c_tilde = c_tilde_β€²]] let left = infinity_norm(&z) < (gamma1 - beta); - let center = c_tilde == c_tilde_p; // verify not CT - let right = h.iter().all(|r| r.0.iter().filter(|&&e| e == 1).sum::() <= omega); - Ok(left && center && right) -} - - -/// Algorithm: 6 `ML-DSA.KeyGen_internal()` on page 15. -/// Generates a public-private key pair. -/// -/// **Input**: `rng` a cryptographically-secure random number generator.
-/// **Output**: Public key, `pk ∈ B^{32+32Β·kΒ·(bitlen(qβˆ’1)βˆ’d)}`, and -/// private key, `sk ∈ B^{32+32+64+32Β·((β„“+k)Β·bitlen(2Β·Ξ·)+dΒ·k)}` -/// -/// # Errors -/// Returns an error when the random number generator fails. -pub(crate) fn key_gen_internal< - const CTEST: bool, - const K: usize, - const L: usize, - const PK_LEN: usize, - const SK_LEN: usize, ->( - eta: i32, xi: &[u8; 32], -) -> (PublicKey, PrivateKey) { - // - // 1: (rho, rhoβ€², 𝐾) ∈ 𝔹32 Γ— 𝔹64 Γ— 𝔹32 ← H(πœ‰||IntegerToBytes(π‘˜, 1)||IntegerToBytes(β„“, 1), 128) - let mut h2 = h256_xof(&[xi, &[K.to_le_bytes()[0]], &[L.to_le_bytes()[0]]]); - let mut rho = [0u8; 32]; - h2.read(&mut rho); - let mut rho_prime = [0u8; 64]; - h2.read(&mut rho_prime); - let mut cap_k = [0u8; 32]; - h2.read(&mut cap_k); - - // There is effectively no step 2 due to formatting error in spec - - // 4: (s_1, s_2) ← ExpandS(ρ′) - let (s_1, s_2): ([R; L], [R; K]) = expand_s::(eta, &rho_prime); - - // 3: cap_a_hat ← ExpandA(ρ) β–· A is generated and stored in NTT representation as Γ‚ - // 5: t ← NTTβˆ’1(cap_a_hat β—¦ NTT(s_1)) + s_2 β–· Compute t = As1 + s2 - // 6: (t_1, t_0) ← Power2Round(t, d) β–· Compress t - - let (t_1, t_0): ([R; K], [R; K]) = { - let cap_a_hat: [[T; L]; K] = expand_a::(&rho); - let s_1_hat: [T; L] = ntt(&s_1); - let as1_hat: [T; K] = mat_vec_mul(&cap_a_hat, &s_1_hat); - let t_not_reduced: [R; K] = add_vector_ntt(&inv_ntt(&as1_hat), &s_2); - let t: [R; K] = - core::array::from_fn(|k| R(core::array::from_fn(|n| full_reduce32(t_not_reduced[k].0[n])))); - power2round(&t) - }; - - // There is effectively no step 7 due to formatting error in spec - - // 8: pk ← pkEncode(ρ, t_1) - let pk: [u8; PK_LEN] = pk_encode(&rho, &t_1); - - // 9: tr ← H(BytesToBits(pk), 512) - let mut tr = [0u8; 64]; - let mut h8 = h256_xof(&[&pk]); - h8.read(&mut tr); - - // 10: sk ← skEncode(ρ, K, tr, s_1, s_2, t_0) β–· K and tr are for use in signing - //let sk: [u8; SK_LEN] = sk_encode(eta, &rho, &cap_k, &tr, &s_1, &s_2, &t_0); - - // the last term of: - // 9: 𝐰Approx ← NTT (𝐀 ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2𝑑 )) β–· 𝐰Approx = 𝐀𝐳 βˆ’ 𝑐𝐭1 β‹… 2𝑑 - let t1_hat_mont: [T; K] = to_mont(&ntt(&t_1)); - let t1_d2_hat_mont: [T; K] = to_mont(&core::array::from_fn(|k| { - T(core::array::from_fn(|n| mont_reduce(i64::from(t1_hat_mont[k].0[n]) << D))) - })); - //let pk = PublicKey { rho, cap_a_hat: cap_a_hat.clone(), tr, t1_d2_hat_mont }; - let pk = PublicKey { rho, tr, t1_d2_hat_mont }; - - // 2: s_hat_1 ← NTT(s_1) - //let s_hat_1_mont: [T; L] = to_mont(&s_1_hat); //ntt(&s_1)); - let s_hat_1_mont: [T; L] = to_mont(&ntt(&s_1)); - // 3: s_hat_2 ← NTT(s_2) - let s_hat_2_mont: [T; K] = to_mont(&ntt(&s_2)); - // 4: t_hat_0 ← NTT(t_0) - let t_hat_0_mont: [T; K] = to_mont(&ntt(&t_0)); - let sk = PrivateKey { - rho, - cap_k, - tr, - s_hat_1_mont, - s_hat_2_mont, - t_hat_0_mont, - // cap_a_hat, - }; - - // 11: return (pk, sk) - (pk, sk) + let right = c_tilde == c_tilde_p; // verify() is not CT + left && right } @@ -511,6 +507,67 @@ pub(crate) fn expand_public T(core::array::from_fn(|n| mont_reduce(i64::from(t1_hat_mont[k].0[n]) << D))) })); - //Ok(PublicKey { rho: *rho, cap_a_hat, tr, t1_d2_hat_mont }) Ok(PublicKey { rho: *rho, tr, t1_d2_hat_mont }) } + + +pub(crate) fn private_to_public_key( + sk: &PrivateKey, +) -> PublicKey { + // Extract the pre-computes + let PrivateKey { rho, cap_k: _, tr, s_hat_1_mont, s_hat_2_mont, t_hat_0_mont } = sk; + let cap_a_hat: [[T; L]; K] = expand_a::(rho); + + // mont->norm elements to recover s_1_hat + let s_1_hat: [T; L] = core::array::from_fn(|l| + T(core::array::from_fn(|n| mont_reduce(i64::from(s_hat_1_mont[l].0[n]))))); + + // mont->norm each n coeff, of L entries of T, then inverse NTT into R + let s_2: [R; K] = inv_ntt(&core::array::from_fn(|k| { + T(core::array::from_fn(|n| mont_reduce(i64::from(s_hat_2_mont[k].0[n])))) + })); + // correct each coeff such that they are centered around 0 + let s_2: [R; K] = core::array::from_fn(|k| { + R(core::array::from_fn(|n| { + if s_2[k].0[n] > (Q / 2) { + s_2[k].0[n] - Q + } else { + s_2[k].0[n] + } + })) + }); + + let t_0: [R; K] = inv_ntt(&core::array::from_fn(|k| { + T(core::array::from_fn(|n| mont_reduce(i64::from(t_hat_0_mont[k].0[n])))) + })); + let sk_t_0: [R; K] = core::array::from_fn(|k| { + R(core::array::from_fn(|n| { + if t_0[k].0[n] > (Q / 2) { + t_0[k].0[n] - Q + } else { + t_0[k].0[n] + } + })) + }); + + // 5: t ← NTTβˆ’1(cap_a_hat β—¦ NTT(s_1)) + s_2 β–· Compute t = As1 + s2 + let t: [R; K] = { + //let s_1_hat: [T; L] = ntt(&s_1); + let as1_hat: [T; K] = mat_vec_mul(&cap_a_hat, &s_1_hat); + let t_not_reduced: [R; K] = add_vector_ntt(&inv_ntt(&as1_hat), &s_2); + core::array::from_fn(|k| R(core::array::from_fn(|n| full_reduce32(t_not_reduced[k].0[n])))) + }; + + // 6: (t_1, t_0) ← Power2Round(t, d) β–· Compress t + let (t_1, pk_t_0): ([R; K], [R; K]) = power2round(&t); + debug_assert_eq!(sk_t_0, pk_t_0); // fuzz target + + // 7: pk ← pkEncode(ρ, t_1) + // 9: 𝐰Approx ← NTT (𝐀 ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2𝑑 )) β–· 𝐰Approx = 𝐀𝐳 βˆ’ 𝑐𝐭1 β‹… 2𝑑 + let t1_hat_mont: [T; K] = to_mont(&ntt(&t_1)); + let t1_d2_hat_mont: [T; K] = to_mont(&core::array::from_fn(|k| { + T(core::array::from_fn(|n| mont_reduce(i64::from(t1_hat_mont[k].0[n]) << D))) + })); + // 10: return pk + PublicKey { rho: *rho, tr: *tr, t1_d2_hat_mont } +} diff --git a/src/types.rs b/src/types.rs index 0bcebbc..1c7b533 100644 --- a/src/types.rs +++ b/src/types.rs @@ -25,7 +25,6 @@ pub struct PrivateKey { pub(crate) s_hat_1_mont: [T; L], pub(crate) s_hat_2_mont: [T; K], pub(crate) t_hat_0_mont: [T; K], -// pub(crate) cap_a_hat: [[T; L]; K], } @@ -37,7 +36,6 @@ pub struct PrivateKey { #[repr(align(8))] pub struct PublicKey { pub(crate) rho: [u8; 32], -// pub(crate) cap_a_hat: [[T; L]; K], pub(crate) tr: [u8; 64], pub(crate) t1_d2_hat_mont: [T; K], } diff --git a/tests/nist_vectors/mod.rs b/tests/nist_vectors/mod.rs index 3700a00..d18808c 100644 --- a/tests/nist_vectors/mod.rs +++ b/tests/nist_vectors/mod.rs @@ -119,7 +119,7 @@ fn test_siggen() { ml_dsa_44::PrivateKey::try_from_bytes(sk_bytes.clone().try_into().unwrap()) .unwrap(); //let sig_act = sk.try_sign_with_rng(&mut rnd, &message, &[]).unwrap(); - let sig_act = ml_dsa_44::_internal_sign(&sk, &mut rnd, &message, &[]).unwrap(); + let sig_act = ml_dsa_44::_internal_sign(&sk, &message, &[], seed).unwrap(); assert_eq!(sig_exp, sig_act); } @@ -129,7 +129,7 @@ fn test_siggen() { ml_dsa_65::PrivateKey::try_from_bytes(sk_bytes.clone().try_into().unwrap()) .unwrap(); //let sig_act = sk.try_sign_with_rng(&mut rnd, &message, &[]).unwrap(); - let sig_act = ml_dsa_65::_internal_sign(&sk, &mut rnd, &message, &[]).unwrap(); + let sig_act = ml_dsa_65::_internal_sign(&sk, &message, &[], seed).unwrap(); assert_eq!(sig_exp, sig_act); } @@ -138,7 +138,7 @@ fn test_siggen() { let sk = ml_dsa_87::PrivateKey::try_from_bytes(sk_bytes.try_into().unwrap()).unwrap(); //let sig_act = sk.try_sign_with_rng(&mut rnd, &message, &[]).unwrap(); - let sig_act = ml_dsa_87::_internal_sign(&sk, &mut rnd, &message, &[]).unwrap(); + let sig_act = ml_dsa_87::_internal_sign(&sk, &message, &[], seed).unwrap(); assert_eq!(sig_exp, sig_act); } } diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 867ef96..73c260c 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fips204-wasm" -version = "0.4.3" +version = "0.4.4" authors = ["Eric Schorn "] description = "Sample web page utilizing FIPS 204 code" repository = "" diff --git a/wasm/www/package-lock.json b/wasm/www/package-lock.json index 602e9b3..3072199 100644 --- a/wasm/www/package-lock.json +++ b/wasm/www/package-lock.json @@ -24,7 +24,7 @@ }, "../pkg": { "name": "fips204-wasm", - "version": "0.4.3", + "version": "0.4.4", "license": "MIT OR Apache-2.0" }, "node_modules/@discoveryjs/json-ext": { @@ -1791,9 +1791,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, "dependencies": { "@types/http-proxy": "^1.17.8",