ml-dsa: rename KeyGen::key_gen_internal to from_seed (#1054)

Despite NIST's somewhat scary "internal" naming, seeds are now the
preferred format for ML-DSA keys.

Closes #1045
This commit is contained in:
Liam Gray
2025-09-12 15:54:21 +01:00
committed by GitHub
parent b439a650d5
commit ecc8d2768a
4 changed files with 50 additions and 19 deletions
+3 -3
View File
@@ -15,7 +15,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let m: B32 = rand(&mut rng);
let ctx: B32 = rand(&mut rng);
let kp = MlDsa65::key_gen_internal(&xi);
let kp = MlDsa65::from_seed(&xi);
let sk = kp.signing_key();
let vk = kp.verifying_key();
let sig = sk.sign_deterministic(&m, &ctx).unwrap();
@@ -27,7 +27,7 @@ fn criterion_benchmark(c: &mut Criterion) {
// Key generation
c.bench_function("keygen", |b| {
b.iter(|| {
let kp = MlDsa65::key_gen_internal(&xi);
let kp = MlDsa65::from_seed(&xi);
let _sk_bytes = kp.signing_key().encode();
let _vk_bytes = kp.verifying_key().encode();
})
@@ -53,7 +53,7 @@ fn criterion_benchmark(c: &mut Criterion) {
// Round trip
c.bench_function("round_trip", |b| {
b.iter(|| {
let kp = MlDsa65::key_gen_internal(&xi);
let kp = MlDsa65::from_seed(&xi);
let sig = kp.signing_key().sign_deterministic(&m, &ctx).unwrap();
let _ver = kp.verifying_key().verify_with_context(&m, &ctx, &sig);
})
+43 -12
View File
@@ -237,7 +237,7 @@ where
let seed = Array::try_from(private_key_info.private_key.as_bytes())
.map_err(|_| pkcs8::Error::KeyMalformed)?;
Ok(P::key_gen_internal(&seed))
Ok(P::from_seed(&seed))
}
}
@@ -352,6 +352,16 @@ impl<P: MlDsaParams> SigningKey<P> {
}
}
/// Deterministically generate a signing key from the specified seed.
///
/// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204, but only returns a
/// signing key.
#[must_use]
pub fn from_seed(seed: &B32) -> Self {
let kp = P::from_seed(seed);
kp.signing_key
}
/// This method reflects the ML-DSA.Sign_internal algorithm from FIPS 204. It does not
/// include the domain separator that distinguishes between the normal and pre-hashed cases,
/// and it does not separate the context string from the rest of the message.
@@ -913,8 +923,9 @@ pub trait KeyGen: MlDsaParams {
fn key_gen<R: CryptoRng + ?Sized>(rng: &mut R) -> Self::KeyPair;
/// Deterministically generate a signing key pair from the specified seed
// TODO(RLB): Only expose this based on a feature.
fn key_gen_internal(xi: &B32) -> Self::KeyPair;
///
/// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204.
fn from_seed(xi: &B32) -> Self::KeyPair;
}
impl<P> KeyGen for P
@@ -929,12 +940,14 @@ where
fn key_gen<R: CryptoRng + ?Sized>(rng: &mut R) -> KeyPair<P> {
let mut xi = B32::default();
rng.fill_bytes(&mut xi);
Self::key_gen_internal(&xi)
Self::from_seed(&xi)
}
/// 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 key_gen_internal(xi: &B32) -> KeyPair<P>
fn from_seed(xi: &B32) -> KeyPair<P>
where
P: MlDsaParams,
{
@@ -1001,7 +1014,7 @@ mod test {
where
P: MlDsaParams + PartialEq,
{
let kp = P::key_gen_internal(&Array::default());
let kp = P::from_seed(&Array::default());
let sk = kp.signing_key;
let vk = kp.verifying_key;
@@ -1032,7 +1045,7 @@ mod test {
where
P: MlDsaParams + PartialEq,
{
let kp = P::key_gen_internal(&Array::default());
let kp = P::from_seed(&Array::default());
let sk = kp.signing_key;
let vk = kp.verifying_key;
let vk_derived = sk.verifying_key();
@@ -1051,7 +1064,7 @@ mod test {
where
P: MlDsaParams,
{
let kp = P::key_gen_internal(&Array::default());
let kp = P::from_seed(&Array::default());
let sk = kp.signing_key;
let vk = kp.verifying_key;
@@ -1084,7 +1097,7 @@ mod test {
let seed_data: &mut [u8] = seed.as_mut();
rng.fill(seed_data);
let kp = P::key_gen_internal(&seed);
let kp = P::from_seed(&seed);
let sk = kp.signing_key;
let vk = kp.verifying_key;
@@ -1113,7 +1126,7 @@ mod test {
where
P: MlDsaParams,
{
let kp = P::key_gen_internal(&Array::default());
let kp = P::from_seed(&Array::default());
let sk = kp.signing_key;
let vk = kp.verifying_key;
@@ -1135,7 +1148,7 @@ mod test {
where
P: MlDsaParams,
{
let kp = P::key_gen_internal(&Array::default());
let kp = P::from_seed(&Array::default());
let sk = kp.signing_key;
let vk = kp.verifying_key;
@@ -1157,7 +1170,7 @@ mod test {
where
P: MlDsaParams,
{
let kp = P::key_gen_internal(&Array::default());
let kp = P::from_seed(&Array::default());
let sk = kp.signing_key;
let vk = kp.verifying_key;
@@ -1172,4 +1185,22 @@ mod test {
sign_internal_verify_mu::<MlDsa65>();
sign_internal_verify_mu::<MlDsa87>();
}
#[test]
fn from_seed_implementations_match() {
fn assert_from_seed_equality<P>()
where
P: MlDsaParams,
{
let seed = Array([0u8; 32]);
let kp1 = P::from_seed(&seed);
let sk1 = SigningKey::<P>::from_seed(&seed);
let vk1 = sk1.verifying_key();
assert_eq!(kp1.signing_key, sk1);
assert_eq!(kp1.verifying_key, vk1);
}
assert_from_seed_equality::<MlDsa44>();
assert_from_seed_equality::<MlDsa65>();
assert_from_seed_equality::<MlDsa87>();
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ fn verify<P: MlDsaParams>(tc: &acvp::TestCase) {
let vk_bytes = EncodedVerifyingKey::<P>::try_from(tc.pk.as_slice()).unwrap();
let sk_bytes = EncodedSigningKey::<P>::try_from(tc.sk.as_slice()).unwrap();
let kp = P::key_gen_internal(&seed);
let kp = P::from_seed(&seed);
let sk = kp.signing_key().clone();
let vk = kp.verifying_key().clone();
+3 -3
View File
@@ -11,17 +11,17 @@ const MSG: &[u8] = b"Hello world";
// Keypairs
prop_compose! {
fn mldsa44_keypair()(seed_bytes in any::<[u8; 32]>()) -> KeyPair<MlDsa44> {
MlDsa44::key_gen_internal(seed_bytes.as_array_ref())
MlDsa44::from_seed(seed_bytes.as_array_ref())
}
}
prop_compose! {
fn mldsa65_keypair()(seed_bytes in any::<[u8; 32]>()) -> KeyPair<MlDsa65> {
MlDsa65::key_gen_internal(seed_bytes.as_array_ref())
MlDsa65::from_seed(seed_bytes.as_array_ref())
}
}
prop_compose! {
fn mldsa87_keypair()(seed_bytes in any::<[u8; 32]>()) -> KeyPair<MlDsa87> {
MlDsa87::key_gen_internal(seed_bytes.as_array_ref())
MlDsa87::from_seed(seed_bytes.as_array_ref())
}
}