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.
This commit is contained in:
Tony Arcieri
2026-05-08 10:12:42 -06:00
committed by GitHub
parent f9f4c60d78
commit 20b0095bd9
3 changed files with 69 additions and 69 deletions
+23 -58
View File
@@ -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<P>
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::<P::K, P::L>(&rho);
let s1 = expand_s::<P::L>(&rhop, P::Eta::ETA, 0);
let s2 = expand_s::<P::K>(&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::<P>::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<P> {
SigningKey::from_seed(seed)
}
}
@@ -356,6 +320,7 @@ impl<T> DerefMut for MaybeBox<T> {
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::<P>::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::<P>::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::<P>::from_seed(&seed);
assert_eq!(ssk.expanded_key, sk1);
assert_eq!(ssk.expanded_key(), &sk1);
}
assert_from_seed_equality::<MlDsa44>();
assert_from_seed_equality::<MlDsa65>();
+3 -5
View File
@@ -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<Self> {
let keypair = SigningKey::try_from(private_key_info)?;
Ok(keypair.expanded_key)
fn try_from(private_key_info: PrivateKeyInfoRef<'_>) -> Result<Self> {
SigningKey::try_from(private_key_info).map(|sk| sk.expanded_key().clone())
}
}
+43 -6
View File
@@ -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<P: MlDsaParams> {
/// The expanded form of the signing key.
pub(crate) expanded_key: ExpandedSigningKey<P>,
expanded_key: ExpandedSigningKey<P>,
/// The seed this signing key was derived from
pub(crate) seed: B32,
seed: Seed,
}
impl<P: MlDsaParams> SigningKey<P> {
/// 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::<P::K, P::L>(&rho);
let s1 = expand_s::<P::L>(&rhop, P::Eta::ETA, 0);
let s2 = expand_s::<P::K>(&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::<P>::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`].
///