From 20b0095bd9ee57e3d1962a8d8c93c41c993cd2ed Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 8 May 2026 10:12:42 -0600 Subject: [PATCH] ml-dsa: factor `from_seed` method onto `SigningKey` (#1341) Extracts a `SigningKey::from_seed` that was previously defined on `MlDsaParams` via a trait impl. This changes it to an inherent method, and has the trait method call the inherent method. This makes it possible to restrict the visibility of `SigningKey` fields, addressing a TODO. --- ml-dsa/src/lib.rs | 81 ++++++++++++------------------------------- ml-dsa/src/pkcs8.rs | 8 ++--- ml-dsa/src/signing.rs | 49 ++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index 82b5f88..f378652 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -57,9 +57,7 @@ pub use signature::{self, Error}; use crate::algebra::{AlgebraExt, Vector}; use crate::crypto::H; use crate::hint::Hint; -use crate::ntt::{Ntt, NttInverse}; -use crate::param::{ParameterSet, QMinus1, SamplingSize}; -use crate::sampling::{expand_a, expand_s}; +use crate::param::{ParameterSet, QMinus1}; use core::{ convert::{TryFrom, TryInto}, ops::{Deref, DerefMut}, @@ -68,7 +66,7 @@ use hybrid_array::{ Array, typenum::{ Diff, Length, Prod, Quot, Shleft, U1, U2, U4, U5, U6, U7, U8, U17, U19, U32, U48, U55, U64, - U75, U80, U88, Unsigned, + U75, U80, U88, }, }; use module_lattice::Truncate; @@ -273,43 +271,9 @@ where } /// Deterministically generate a signing key pair from the specified seed - /// - /// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204. // Algorithm 6 ML-DSA.KeyGen_internal - fn from_seed(xi: &Seed) -> SigningKey

- where - P: MlDsaParams, - { - // Derive seeds - let mut h = H::default() - .absorb(xi) - .absorb(&[P::K::U8]) - .absorb(&[P::L::U8]); - - let rho: B32 = h.squeeze_new(); - let rhop: B64 = h.squeeze_new(); - let K: B32 = h.squeeze_new(); - - // Sample private key components - let A_hat = expand_a::(&rho); - let s1 = expand_s::(&rhop, P::Eta::ETA, 0); - let s2 = expand_s::(&rhop, P::Eta::ETA, P::L::USIZE); - - // Compute derived values - let As1_hat = &A_hat * &s1.ntt(); - let t = &As1_hat.ntt_inverse() + &s2; - - // Compress and encode - let (t1, t0) = t.power2round(); - - let enc = VerifyingKey::

::encode_internal(&rho, &t1); - let tr: B64 = H::default().absorb(&enc).squeeze_new(); - let signing_key = ExpandedSigningKey::new(rho, K, tr, s1, s2, t0, A_hat); - - SigningKey { - expanded_key: signing_key, - seed: xi.clone(), - } + fn from_seed(seed: &Seed) -> SigningKey

{ + SigningKey::from_seed(seed) } } @@ -356,6 +320,7 @@ impl DerefMut for MaybeBox { mod test { use super::*; use crate::param::*; + use hybrid_array::typenum::Unsigned; use signature::Keypair; #[test] @@ -385,7 +350,7 @@ mod test { let ssk = P::from_seed(&seed); assert_eq!(ssk.to_seed(), seed); - let sk = &ssk.expanded_key; + let esk = ssk.expanded_key(); let vk = ssk.verifying_key(); let vk_bytes = vk.encode(); @@ -394,13 +359,13 @@ mod test { #[allow(deprecated)] { - let sk_bytes = sk.to_expanded(); + let sk_bytes = esk.to_expanded(); let sk2 = ExpandedSigningKey::

::from_expanded(&sk_bytes); - assert!(sk == &sk2); + assert!(esk == &sk2); let M = b"Hello world"; let rnd = Array([0u8; 32]); - let sig = sk.sign_internal(&[M], &rnd); + let sig = esk.sign_internal(&[M], &rnd); let sig_bytes = sig.encode(); let sig2 = Signature::

::decode(&sig_bytes).unwrap(); assert!(sig == sig2); @@ -419,9 +384,9 @@ mod test { P: MlDsaParams + PartialEq, { let ssk = P::from_seed(&Array::default()); - let sk = &ssk.expanded_key; + let esk = ssk.expanded_key(); let vk = ssk.verifying_key(); - let vk_derived = sk.verifying_key(); + let vk_derived = esk.verifying_key(); assert!(vk == vk_derived); } @@ -438,12 +403,12 @@ mod test { P: MlDsaParams, { let ssk = P::from_seed(&Array::default()); - let sk = &ssk.expanded_key; + let esk = ssk.expanded_key(); let vk = ssk.verifying_key(); let M = b"Hello world"; let rnd = Array([0u8; 32]); - let sig = sk.sign_internal(&[M], &rnd); + let sig = esk.sign_internal(&[M], &rnd); assert!(vk.verify_internal(M, &sig)); } @@ -462,13 +427,13 @@ mod test { P: MlDsaParams, { let ssk = P::from_seed(&Array::default()); - let sk = &ssk.expanded_key; + let esk = ssk.expanded_key(); let vk = ssk.verifying_key(); let M = b"Hello world"; let rnd = Array([0u8; 32]); - let mu = MuBuilder::internal(&sk.tr, &[M]); - let sig = sk.raw_sign_mu(&mu, &rnd); + let mu = MuBuilder::internal(&esk.tr, &[M]); + let sig = esk.raw_sign_mu(&mu, &rnd); assert!(vk.raw_verify_mu(&mu, &sig)); } @@ -484,13 +449,13 @@ mod test { P: MlDsaParams, { let ssk = P::from_seed(&Array::default()); - let sk = &ssk.expanded_key; + let esk = ssk.expanded_key(); let vk = ssk.verifying_key(); let M = b"Hello world"; let rnd = Array([0u8; 32]); - let mu = MuBuilder::internal(&sk.tr, &[M]); - let sig = sk.raw_sign_mu(&mu, &rnd); + let mu = MuBuilder::internal(&esk.tr, &[M]); + let sig = esk.raw_sign_mu(&mu, &rnd); assert!(vk.verify_internal(M, &sig)); } @@ -506,13 +471,13 @@ mod test { P: MlDsaParams, { let ssk = P::from_seed(&Array::default()); - let sk = &ssk.expanded_key; + let esk = ssk.expanded_key(); let vk = ssk.verifying_key(); let M = b"Hello world"; let rnd = Array([0u8; 32]); - let mu = MuBuilder::internal(&sk.tr, &[M]); - let sig = sk.sign_internal(&[M], &rnd); + let mu = MuBuilder::internal(&esk.tr, &[M]); + let sig = esk.sign_internal(&[M], &rnd); assert!(vk.raw_verify_mu(&mu, &sig)); } @@ -530,7 +495,7 @@ mod test { let seed = Seed::default(); let ssk = P::from_seed(&seed); let sk1 = ExpandedSigningKey::

::from_seed(&seed); - assert_eq!(ssk.expanded_key, sk1); + assert_eq!(ssk.expanded_key(), &sk1); } assert_from_seed_equality::(); assert_from_seed_equality::(); diff --git a/ml-dsa/src/pkcs8.rs b/ml-dsa/src/pkcs8.rs index 0effadd..957e08a 100644 --- a/ml-dsa/src/pkcs8.rs +++ b/ml-dsa/src/pkcs8.rs @@ -120,7 +120,7 @@ where let seed_der = SeedString { tag_mode: TagMode::Implicit, tag_number: SEED_TAG_NUMBER, - value: OctetStringRef::new(&self.seed)?, + value: OctetStringRef::new(&self.to_seed())?, } .to_der()?; @@ -148,10 +148,8 @@ where { type Error = ::pkcs8::Error; - fn try_from(private_key_info: ::pkcs8::PrivateKeyInfoRef<'_>) -> ::pkcs8::Result { - let keypair = SigningKey::try_from(private_key_info)?; - - Ok(keypair.expanded_key) + fn try_from(private_key_info: PrivateKeyInfoRef<'_>) -> Result { + SigningKey::try_from(private_key_info).map(|sk| sk.expanded_key().clone()) } } diff --git a/ml-dsa/src/signing.rs b/ml-dsa/src/signing.rs index 44a4b15..bf9fd60 100644 --- a/ml-dsa/src/signing.rs +++ b/ml-dsa/src/signing.rs @@ -9,7 +9,7 @@ use crate::{ crypto::H, hint::Hint, ntt::{Ntt, NttInverse}, - param::SpecQ, + param::{SamplingSize, SpecQ}, sampling::{expand_a, expand_mask, sample_in_ball}, }; use core::fmt; @@ -24,23 +24,60 @@ use { signature::{RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner}, }; +use crate::sampling::expand_s; #[cfg(feature = "zeroize")] use zeroize::{Zeroize, ZeroizeOnDrop}; -/// An ML-DSA signing key. +/// ML-DSA signing key (i.e. private/secret key). /// -/// This type is initialized through a [`Seed`]. -// TODO(tarcieri): reduce field-level visibility. +/// This type is initialized through a [`Seed`], and can be used to generate ML-DSA signatures. #[derive(Clone)] pub struct SigningKey { /// The expanded form of the signing key. - pub(crate) expanded_key: ExpandedSigningKey

, + expanded_key: ExpandedSigningKey

, /// The seed this signing key was derived from - pub(crate) seed: B32, + seed: Seed, } impl SigningKey

{ + /// Deterministically generate a signing key pair from the specified seed + /// + /// This method reflects the `ML-DSA.KeyGen_internal` algorithm from FIPS 204 (Algorithm 6). + #[must_use] + pub fn from_seed(xi: &Seed) -> Self { + // Derive seeds + let mut h = H::default() + .absorb(xi) + .absorb(&[P::K::U8]) + .absorb(&[P::L::U8]); + + let rho: B32 = h.squeeze_new(); + let rhop: B64 = h.squeeze_new(); + let K: B32 = h.squeeze_new(); + + // Sample private key components + let A_hat = expand_a::(&rho); + let s1 = expand_s::(&rhop, P::Eta::ETA, 0); + let s2 = expand_s::(&rhop, P::Eta::ETA, P::L::USIZE); + + // Compute derived values + let As1_hat = &A_hat * &s1.ntt(); + let t = &As1_hat.ntt_inverse() + &s2; + + // Compress and encode + let (t1, t0) = t.power2round(); + + let enc = VerifyingKey::

::encode_internal(&rho, &t1); + let tr: B64 = H::default().absorb(&enc).squeeze_new(); + let signing_key = ExpandedSigningKey::new(rho, K, tr, s1, s2, t0, A_hat); + + SigningKey { + expanded_key: signing_key, + seed: xi.clone(), + } + } + /// Serialize the [`Seed`] value: 32-bytes which can be used to reconstruct the /// [`SigningKey`]. ///