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, MlDsa65, 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 = 1952;
pub const MLDSA_SIG_LEN: usize = 3309;
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<MlDsa65>,
}
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<MlDsa65> {
let s = Seed::try_from(&seed[..]).expect("semilla ML-DSA de 32 bytes");
MlSigningKey::<MlDsa65>::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::<MlDsa65>::try_from(ml_sig_bytes) {
Ok(enc) => match MlSignature::<MlDsa65>::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::<MlDsa65>::try_from(&b[ED25519_PUB_LEN..]).ok()?;
let ml = MlVerifyingKey::<MlDsa65>::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 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));
}
}