use ed25519_dalek::{
Signature as EdSignature, Signer as _, SigningKey as EdSigningKey, VerifyingKey as EdVerifyingKey,
};
use ml_dsa::signature::{Keypair as _, Signer as _, Verifier as _};
use ml_dsa::{
EncodedSignature, EncodedVerifyingKey, MlDsa87, Seed, Signature as MlSignature,
SigningKey as MlSigningKey, VerifyingKey as MlVerifyingKey,
};
use zeroize::Zeroizing;
pub const ED25519_PUB_LEN: usize = 32;
pub const ED25519_SIG_LEN: usize = 64;
pub const ED25519_SEED_LEN: usize = 32;
pub const MLDSA_VK_LEN: usize = 2592;
pub const MLDSA_SIG_LEN: usize = 4627;
pub const MLDSA_SEED_LEN: usize = 32;
pub const VERIFYING_KEY_LEN: usize = ED25519_PUB_LEN + MLDSA_VK_LEN; pub const SIGNING_KEY_LEN: usize = ED25519_SEED_LEN + MLDSA_SEED_LEN; pub const SIGNATURE_LEN: usize = ED25519_SIG_LEN + MLDSA_SIG_LEN;
const SIGN_CONTEXT: &[u8] = b"quipu/v3/sign";
pub struct VerifyingKey {
ed: EdVerifyingKey,
ml: MlVerifyingKey<MlDsa87>,
}
pub struct SigningKey {
ed_seed: Zeroizing<[u8; ED25519_SEED_LEN]>,
ml_seed: Zeroizing<[u8; MLDSA_SEED_LEN]>,
}
pub fn generate_keypair() -> (VerifyingKey, SigningKey) {
let mut ed_seed = [0u8; ED25519_SEED_LEN];
let mut ml_seed = [0u8; MLDSA_SEED_LEN];
getrandom::getrandom(&mut ed_seed).expect("RNG del sistema");
getrandom::getrandom(&mut ml_seed).expect("RNG del sistema");
let sk = SigningKey {
ed_seed: Zeroizing::new(ed_seed),
ml_seed: Zeroizing::new(ml_seed),
};
let vk = sk.verifying_key();
(vk, sk)
}
fn ml_signing_key(seed: &[u8; MLDSA_SEED_LEN]) -> MlSigningKey<MlDsa87> {
let s = Seed::try_from(&seed[..]).expect("semilla ML-DSA de 32 bytes");
MlSigningKey::<MlDsa87>::from_seed(&s)
}
impl SigningKey {
pub fn verifying_key(&self) -> VerifyingKey {
let ed = EdSigningKey::from_bytes(&self.ed_seed).verifying_key();
let ml = ml_signing_key(&self.ml_seed).verifying_key();
VerifyingKey { ed, ml }
}
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
let vk = self.verifying_key();
let preimage = build_preimage(&vk.to_bytes(), message);
let ed_sig = EdSigningKey::from_bytes(&self.ed_seed).sign(&preimage);
let ml_sig = ml_signing_key(&self.ml_seed).sign(&preimage);
let mut out = Vec::with_capacity(SIGNATURE_LEN);
out.extend_from_slice(&ed_sig.to_bytes());
out.extend_from_slice(ml_sig.encode().as_slice());
out
}
pub fn to_bytes(&self) -> Zeroizing<Vec<u8>> {
let mut v = Vec::with_capacity(SIGNING_KEY_LEN);
v.extend_from_slice(self.ed_seed.as_ref());
v.extend_from_slice(self.ml_seed.as_ref());
Zeroizing::new(v)
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() != SIGNING_KEY_LEN {
return None;
}
let ed_seed: [u8; ED25519_SEED_LEN] = b[0..ED25519_SEED_LEN].try_into().ok()?;
let ml_seed: [u8; MLDSA_SEED_LEN] = b[ED25519_SEED_LEN..].try_into().ok()?;
Some(SigningKey {
ed_seed: Zeroizing::new(ed_seed),
ml_seed: Zeroizing::new(ml_seed),
})
}
}
impl VerifyingKey {
pub fn verify(&self, message: &[u8], signature: &[u8]) -> bool {
if signature.len() != SIGNATURE_LEN {
return false;
}
let preimage = build_preimage(&self.to_bytes(), message);
let ed_sig_bytes: [u8; ED25519_SIG_LEN] = match signature[0..ED25519_SIG_LEN].try_into() {
Ok(b) => b,
Err(_) => return false,
};
let ed_sig = EdSignature::from_bytes(&ed_sig_bytes);
let ed_ok = self.ed.verify_strict(&preimage, &ed_sig).is_ok();
let ml_sig_bytes = &signature[ED25519_SIG_LEN..];
let ml_ok = match EncodedSignature::<MlDsa87>::try_from(ml_sig_bytes) {
Ok(enc) => match MlSignature::<MlDsa87>::decode(&enc) {
Some(sig) => self.ml.verify(&preimage, &sig).is_ok(),
None => false,
},
Err(_) => false,
};
ed_ok && ml_ok
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut v = Vec::with_capacity(VERIFYING_KEY_LEN);
v.extend_from_slice(self.ed.as_bytes());
v.extend_from_slice(self.ml.encode().as_slice());
v
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() != VERIFYING_KEY_LEN {
return None;
}
let ed_bytes: [u8; ED25519_PUB_LEN] = b[0..ED25519_PUB_LEN].try_into().ok()?;
let ed = EdVerifyingKey::from_bytes(&ed_bytes).ok()?;
let ml_enc = EncodedVerifyingKey::<MlDsa87>::try_from(&b[ED25519_PUB_LEN..]).ok()?;
let ml = MlVerifyingKey::<MlDsa87>::decode(&ml_enc);
Some(VerifyingKey { ed, ml })
}
}
fn build_preimage(vk_bytes: &[u8], message: &[u8]) -> Vec<u8> {
let mut p = Vec::with_capacity(SIGN_CONTEXT.len() + vk_bytes.len() + message.len());
p.extend_from_slice(SIGN_CONTEXT);
p.extend_from_slice(vk_bytes);
p.extend_from_slice(message);
p
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parameters_are_cnsa_level5() {
assert_eq!(MLDSA_VK_LEN, 2592, "vk ML-DSA-87");
assert_eq!(MLDSA_SIG_LEN, 4627, "firma ML-DSA-87");
}
#[test]
fn sign_verify_round_trips() {
let (vk, sk) = generate_keypair();
let msg = b"documento firmado";
let sig = sk.sign(msg);
assert_eq!(sig.len(), SIGNATURE_LEN);
assert!(vk.verify(msg, &sig));
}
#[test]
fn tampered_message_fails() {
let (vk, sk) = generate_keypair();
let sig = sk.sign(b"pagar 100");
assert!(!vk.verify(b"pagar 900", &sig));
}
#[test]
fn tampered_signature_fails() {
let (vk, sk) = generate_keypair();
let msg = b"mensaje";
let mut sig = sk.sign(msg);
sig[0] ^= 0x01;
assert!(!vk.verify(msg, &sig));
let mut sig2 = sk.sign(msg);
sig2[ED25519_SIG_LEN + 10] ^= 0x01;
assert!(!vk.verify(msg, &sig2));
}
#[test]
fn wrong_key_fails() {
let (_vk, sk) = generate_keypair();
let (vk2, _sk2) = generate_keypair();
let msg = b"mensaje";
let sig = sk.sign(msg);
assert!(!vk2.verify(msg, &sig));
}
#[test]
fn and_combiner_rejects_swapped_component() {
let (vk, sk) = generate_keypair();
let (_vk2, sk2) = generate_keypair();
let msg = b"mensaje";
let sig = sk.sign(msg);
let other = sk2.sign(msg);
let mut frankensig = Vec::with_capacity(SIGNATURE_LEN);
frankensig.extend_from_slice(&sig[0..ED25519_SIG_LEN]); frankensig.extend_from_slice(&other[ED25519_SIG_LEN..]); assert!(!vk.verify(msg, &frankensig));
}
#[test]
fn signing_key_serialization_round_trips() {
let (vk, sk) = generate_keypair();
let bytes = sk.to_bytes();
assert_eq!(bytes.len(), SIGNING_KEY_LEN);
let sk2 = SigningKey::from_bytes(&bytes).unwrap();
let msg = b"mensaje";
assert!(vk.verify(msg, &sk2.sign(msg)));
}
#[test]
fn verifying_key_serialization_round_trips() {
let (vk, sk) = generate_keypair();
let bytes = vk.to_bytes();
assert_eq!(bytes.len(), VERIFYING_KEY_LEN);
let vk2 = VerifyingKey::from_bytes(&bytes).unwrap();
let msg = b"mensaje";
assert!(vk2.verify(msg, &sk.sign(msg)));
}
#[test]
fn signatures_are_deterministic_but_bind_message() {
let (_vk, sk) = generate_keypair();
assert_eq!(sk.sign(b"a"), sk.sign(b"a"));
assert_ne!(sk.sign(b"a"), sk.sign(b"b"));
}
#[test]
fn wrong_length_signature_rejected() {
let (vk, sk) = generate_keypair();
let mut sig = sk.sign(b"m");
sig.truncate(SIGNATURE_LEN - 1);
assert!(!vk.verify(b"m", &sig));
}
}
#[cfg(feature = "slh")]
use fips205::slh_dsa_sha2_256s;
#[cfg(feature = "slh")]
use fips205::traits::{SerDes as _, Signer as _, Verifier as _};
#[cfg(feature = "slh")]
pub const SLH_PUB_LEN: usize = slh_dsa_sha2_256s::PK_LEN; #[cfg(feature = "slh")]
pub const SLH_SECRET_LEN: usize = slh_dsa_sha2_256s::SK_LEN; #[cfg(feature = "slh")]
pub const SLH_SIG_LEN: usize = slh_dsa_sha2_256s::SIG_LEN;
#[cfg(feature = "slh")]
const SLH_CTX: &[u8] = b"";
#[cfg(feature = "slh")]
pub const TRIPLE_VERIFYING_KEY_LEN: usize = ED25519_PUB_LEN + MLDSA_VK_LEN + SLH_PUB_LEN; #[cfg(feature = "slh")]
pub const TRIPLE_SIGNING_KEY_LEN: usize = ED25519_SEED_LEN + MLDSA_SEED_LEN + SLH_SECRET_LEN; #[cfg(feature = "slh")]
pub const TRIPLE_SIGNATURE_LEN: usize = ED25519_SIG_LEN + MLDSA_SIG_LEN + SLH_SIG_LEN;
#[cfg(feature = "slh")]
const SIGN_TRIPLE_CONTEXT: &[u8] = b"quipu/v4/sign-triple";
#[cfg(feature = "slh")]
pub struct TripleVerifyingKey {
ed: EdVerifyingKey,
ml: MlVerifyingKey<MlDsa87>,
slh: slh_dsa_sha2_256s::PublicKey,
}
#[cfg(feature = "slh")]
pub struct TripleSigningKey {
ed_seed: Zeroizing<[u8; ED25519_SEED_LEN]>,
ml_seed: Zeroizing<[u8; MLDSA_SEED_LEN]>,
slh_sk: Zeroizing<[u8; SLH_SECRET_LEN]>,
}
#[cfg(feature = "slh")]
pub fn generate_triple_keypair() -> (TripleVerifyingKey, TripleSigningKey) {
let mut ed_seed = [0u8; ED25519_SEED_LEN];
let mut ml_seed = [0u8; MLDSA_SEED_LEN];
getrandom::getrandom(&mut ed_seed).expect("RNG del sistema");
getrandom::getrandom(&mut ml_seed).expect("RNG del sistema");
let (_slh_pk, slh_priv) = slh_dsa_sha2_256s::try_keygen().expect("keygen SLH-DSA");
let slh_sk = slh_priv.into_bytes();
let sk = TripleSigningKey {
ed_seed: Zeroizing::new(ed_seed),
ml_seed: Zeroizing::new(ml_seed),
slh_sk: Zeroizing::new(slh_sk),
};
let vk = sk.verifying_key();
(vk, sk)
}
#[cfg(feature = "slh")]
fn slh_signing_key(sk_bytes: &[u8; SLH_SECRET_LEN]) -> slh_dsa_sha2_256s::PrivateKey {
slh_dsa_sha2_256s::PrivateKey::try_from_bytes(sk_bytes).expect("clave secreta SLH de 128 bytes")
}
#[cfg(feature = "slh")]
impl TripleSigningKey {
pub fn verifying_key(&self) -> TripleVerifyingKey {
let ed = EdSigningKey::from_bytes(&self.ed_seed).verifying_key();
let ml = ml_signing_key(&self.ml_seed).verifying_key();
let slh = slh_signing_key(&self.slh_sk).get_public_key();
TripleVerifyingKey { ed, ml, slh }
}
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
let vk = self.verifying_key();
let preimage = build_triple_preimage(&vk.to_bytes(), message);
let ed_sig = EdSigningKey::from_bytes(&self.ed_seed).sign(&preimage);
let ml_sig = ml_signing_key(&self.ml_seed).sign(&preimage);
let slh_sig = slh_signing_key(&self.slh_sk)
.try_sign(&preimage, SLH_CTX, false)
.expect("firma SLH-DSA determinista");
let mut out = Vec::with_capacity(TRIPLE_SIGNATURE_LEN);
out.extend_from_slice(&ed_sig.to_bytes());
out.extend_from_slice(ml_sig.encode().as_slice());
out.extend_from_slice(&slh_sig);
out
}
pub fn to_bytes(&self) -> Zeroizing<Vec<u8>> {
let mut v = Vec::with_capacity(TRIPLE_SIGNING_KEY_LEN);
v.extend_from_slice(self.ed_seed.as_ref());
v.extend_from_slice(self.ml_seed.as_ref());
v.extend_from_slice(self.slh_sk.as_ref());
Zeroizing::new(v)
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() != TRIPLE_SIGNING_KEY_LEN {
return None;
}
let ed_seed: [u8; ED25519_SEED_LEN] = b[0..ED25519_SEED_LEN].try_into().ok()?;
let ml_start = ED25519_SEED_LEN;
let ml_seed: [u8; MLDSA_SEED_LEN] =
b[ml_start..ml_start + MLDSA_SEED_LEN].try_into().ok()?;
let slh_start = ml_start + MLDSA_SEED_LEN;
let slh_sk: [u8; SLH_SECRET_LEN] = b[slh_start..].try_into().ok()?;
Some(TripleSigningKey {
ed_seed: Zeroizing::new(ed_seed),
ml_seed: Zeroizing::new(ml_seed),
slh_sk: Zeroizing::new(slh_sk),
})
}
}
#[cfg(feature = "slh")]
impl TripleVerifyingKey {
pub fn verify(&self, message: &[u8], signature: &[u8]) -> bool {
if signature.len() != TRIPLE_SIGNATURE_LEN {
return false;
}
let preimage = build_triple_preimage(&self.to_bytes(), message);
let (ed_sig_bytes, rest) = signature.split_at(ED25519_SIG_LEN);
let (ml_sig_bytes, slh_sig_bytes) = rest.split_at(MLDSA_SIG_LEN);
let ed_arr: [u8; ED25519_SIG_LEN] = match ed_sig_bytes.try_into() {
Ok(b) => b,
Err(_) => return false,
};
let ed_ok = self
.ed
.verify_strict(&preimage, &EdSignature::from_bytes(&ed_arr))
.is_ok();
let ml_ok = match EncodedSignature::<MlDsa87>::try_from(ml_sig_bytes) {
Ok(enc) => match MlSignature::<MlDsa87>::decode(&enc) {
Some(sig) => self.ml.verify(&preimage, &sig).is_ok(),
None => false,
},
Err(_) => false,
};
let slh_arr: &[u8; SLH_SIG_LEN] = match slh_sig_bytes.try_into() {
Ok(a) => a,
Err(_) => return false,
};
let slh_ok = self.slh.verify(&preimage, slh_arr, SLH_CTX);
ed_ok && ml_ok && slh_ok
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut v = Vec::with_capacity(TRIPLE_VERIFYING_KEY_LEN);
v.extend_from_slice(self.ed.as_bytes());
v.extend_from_slice(self.ml.encode().as_slice());
v.extend_from_slice(&self.slh.clone().into_bytes());
v
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() != TRIPLE_VERIFYING_KEY_LEN {
return None;
}
let ed_bytes: [u8; ED25519_PUB_LEN] = b[0..ED25519_PUB_LEN].try_into().ok()?;
let ed = EdVerifyingKey::from_bytes(&ed_bytes).ok()?;
let ml_start = ED25519_PUB_LEN;
let ml_enc =
EncodedVerifyingKey::<MlDsa87>::try_from(&b[ml_start..ml_start + MLDSA_VK_LEN]).ok()?;
let ml = MlVerifyingKey::<MlDsa87>::decode(&ml_enc);
let slh_start = ml_start + MLDSA_VK_LEN;
let slh_bytes: [u8; SLH_PUB_LEN] = b[slh_start..].try_into().ok()?;
let slh = slh_dsa_sha2_256s::PublicKey::try_from_bytes(&slh_bytes).ok()?;
Some(TripleVerifyingKey { ed, ml, slh })
}
}
#[cfg(feature = "slh")]
fn build_triple_preimage(vk_bytes: &[u8], message: &[u8]) -> Vec<u8> {
let mut p = Vec::with_capacity(SIGN_TRIPLE_CONTEXT.len() + vk_bytes.len() + message.len());
p.extend_from_slice(SIGN_TRIPLE_CONTEXT);
p.extend_from_slice(vk_bytes);
p.extend_from_slice(message);
p
}
#[cfg(all(test, feature = "slh"))]
mod triple_spike {
use super::*;
#[test]
fn slh_dsa_256s_sizes_and_roundtrip() {
assert_eq!(SLH_PUB_LEN, 64, "SLH pk len");
assert_eq!(SLH_SECRET_LEN, 128, "SLH sk len");
assert_eq!(SLH_SIG_LEN, 29_792, "SLH sig len");
let (pk, sk) = slh_dsa_sha2_256s::try_keygen().unwrap();
let sig = sk.try_sign(b"spike", SLH_CTX, false).unwrap();
let pk_bytes = pk.into_bytes();
assert_eq!(pk_bytes.len(), SLH_PUB_LEN);
let pk2 = slh_dsa_sha2_256s::PublicKey::try_from_bytes(&pk_bytes).unwrap();
assert!(pk2.verify(b"spike", &sig, SLH_CTX));
assert!(!pk2.verify(b"otro", &sig, SLH_CTX));
let sk_bytes = sk.into_bytes();
assert_eq!(sk_bytes.len(), SLH_SECRET_LEN);
}
}
#[cfg(all(test, feature = "slh"))]
mod triple_tests {
use super::*;
#[test]
fn triple_sign_verify_round_trips() {
let (vk, sk) = generate_triple_keypair();
let msg = b"documento de altisimo valor";
let sig = sk.sign(msg);
assert_eq!(sig.len(), TRIPLE_SIGNATURE_LEN);
assert!(vk.verify(msg, &sig));
}
#[test]
fn triple_parameters_are_level5() {
assert_eq!(SLH_SIG_LEN, 29_792);
assert_eq!(TRIPLE_VERIFYING_KEY_LEN, 2_688);
assert_eq!(TRIPLE_SIGNING_KEY_LEN, 192);
assert_eq!(TRIPLE_SIGNATURE_LEN, 34_483);
}
#[test]
fn triple_tampered_message_fails() {
let (vk, sk) = generate_triple_keypair();
let sig = sk.sign(b"pagar 100");
assert!(!vk.verify(b"pagar 900", &sig));
}
#[test]
fn triple_tampered_signature_fails() {
let (vk, sk) = generate_triple_keypair();
let msg = b"mensaje";
for pos in [0, ED25519_SIG_LEN + 10, ED25519_SIG_LEN + MLDSA_SIG_LEN + 10] {
let mut sig = sk.sign(msg);
sig[pos] ^= 0x01;
assert!(!vk.verify(msg, &sig), "flip en offset {pos} debio fallar");
}
}
#[test]
fn triple_wrong_key_fails() {
let (_vk, sk) = generate_triple_keypair();
let (vk2, _sk2) = generate_triple_keypair();
let sig = sk.sign(b"mensaje");
assert!(!vk2.verify(b"mensaje", &sig));
}
#[test]
fn triple_and_combiner_rejects_swapped_component() {
let (vk, sk) = generate_triple_keypair();
let (_vk2, sk2) = generate_triple_keypair();
let msg = b"mensaje";
let sig = sk.sign(msg);
let other = sk2.sign(msg);
let ed_end = ED25519_SIG_LEN;
let ml_end = ED25519_SIG_LEN + MLDSA_SIG_LEN;
let mut a = other[..ed_end].to_vec();
a.extend_from_slice(&sig[ed_end..]);
assert!(!vk.verify(msg, &a), "swap Ed25519");
let mut b = sig[..ed_end].to_vec();
b.extend_from_slice(&other[ed_end..ml_end]);
b.extend_from_slice(&sig[ml_end..]);
assert!(!vk.verify(msg, &b), "swap ML-DSA");
let mut c = sig[..ml_end].to_vec();
c.extend_from_slice(&other[ml_end..]);
assert!(!vk.verify(msg, &c), "swap SLH-DSA");
}
#[test]
fn triple_signing_key_serialization_round_trips() {
let (vk, sk) = generate_triple_keypair();
let bytes = sk.to_bytes();
assert_eq!(bytes.len(), TRIPLE_SIGNING_KEY_LEN);
let sk2 = TripleSigningKey::from_bytes(&bytes).unwrap();
assert!(vk.verify(b"m", &sk2.sign(b"m")));
}
#[test]
fn triple_verifying_key_serialization_round_trips() {
let (vk, sk) = generate_triple_keypair();
let bytes = vk.to_bytes();
assert_eq!(bytes.len(), TRIPLE_VERIFYING_KEY_LEN);
let vk2 = TripleVerifyingKey::from_bytes(&bytes).unwrap();
assert!(vk2.verify(b"m", &sk.sign(b"m")));
}
#[test]
fn triple_wrong_length_signature_rejected() {
let (vk, sk) = generate_triple_keypair();
let mut sig = sk.sign(b"m");
sig.truncate(TRIPLE_SIGNATURE_LEN - 1);
assert!(!vk.verify(b"m", &sig));
}
#[test]
fn triple_signatures_are_deterministic_but_bind_message() {
let (_vk, sk) = generate_triple_keypair();
assert_eq!(sk.sign(b"a"), sk.sign(b"a"));
assert_ne!(sk.sign(b"a"), sk.sign(b"b"));
}
}