use ed25519_dalek::SigningKey;
use hkdf::Hkdf;
use libcrux_ml_dsa::ml_dsa_65::{self, MLDSA65KeyPair};
use sha2::Sha256;
pub use libcrux_ml_dsa::ml_dsa_65::{MLDSA65KeyPair as AuthKeyPair, MLDSA65SigningKey};
pub const SEED_BYTES: usize = 32;
const LABEL_IDENTITY_ED25519: &[u8] = b"parley/identity/ed25519/v1";
const LABEL_AUTH_MLDSA65: &[u8] = b"parley/auth/ml-dsa-65/v1";
fn derive_bytes<const N: usize>(seed: &[u8; SEED_BYTES], label: &[u8]) -> [u8; N] {
let hk = Hkdf::<Sha256>::new(None, seed);
let mut out = [0u8; N];
let Ok(()) = hk.expand(label, &mut out) else {
unreachable!("HKDF output length {N} is within the 255·HashLen ceiling");
};
out
}
#[must_use]
pub fn derive_identity_ed25519(seed: &[u8; SEED_BYTES]) -> SigningKey {
let sk: [u8; 32] = derive_bytes(seed, LABEL_IDENTITY_ED25519);
SigningKey::from_bytes(&sk)
}
#[must_use]
pub fn derive_auth_mldsa(seed: &[u8; SEED_BYTES]) -> MLDSA65KeyPair {
let randomness: [u8; 32] = derive_bytes(seed, LABEL_AUTH_MLDSA65);
ml_dsa_65::generate_key_pair(randomness)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use libcrux_ml_dsa::ml_dsa_65::{MLDSA65Signature, MLDSA65VerificationKey};
#[test]
fn derivation_is_deterministic() {
let seed = [7u8; SEED_BYTES];
let a = derive_identity_ed25519(&seed);
let b = derive_identity_ed25519(&seed);
assert_eq!(a.to_bytes(), b.to_bytes());
let ka = derive_auth_mldsa(&seed);
let kb = derive_auth_mldsa(&seed);
assert_eq!(
ka.verification_key.as_slice(),
kb.verification_key.as_slice()
);
}
#[test]
fn domain_separation_yields_distinct_key_material() {
let seed = [9u8; SEED_BYTES];
let id = derive_identity_ed25519(&seed);
let auth = derive_auth_mldsa(&seed);
assert_ne!(
&id.verifying_key().to_bytes()[..],
&auth.verification_key.as_slice()[..32]
);
}
#[test]
fn different_seeds_diverge() {
let a = derive_identity_ed25519(&[1u8; SEED_BYTES]);
let b = derive_identity_ed25519(&[2u8; SEED_BYTES]);
assert_ne!(a.to_bytes(), b.to_bytes());
}
#[test]
fn ml_dsa_65_sizes_match_fips204_constants() {
assert_eq!(MLDSA65VerificationKey::len(), 1952);
assert_eq!(MLDSA65Signature::len(), 3309);
}
}