use ed25519_dalek::{
Signer as Ed25519SignerTrait, SigningKey, Verifier as Ed25519VerifierTrait, VerifyingKey,
};
use ml_dsa::signature::{
Keypair as MlDsaKeypairTrait, Signer as MlDsaSignerTrait, Verifier as MlDsaVerifierTrait,
};
use ml_dsa::{EncodedSignature, EncodedVerifyingKey, KeyGen, MlDsa65, B32};
mod private_seal {
pub trait Sealed {}
impl Sealed for super::SoftwareMlDsa65Signer {}
impl Sealed for super::SoftwareMlDsa65Verifier {}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum PqcSignError {
Provider(String),
}
impl core::fmt::Display for PqcSignError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Provider(m) => write!(f, "PQC signer provider error: {}", m),
}
}
}
impl std::error::Error for PqcSignError {}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum PqcVerifyError {
WrongLength,
Mismatch,
}
impl core::fmt::Display for PqcVerifyError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::WrongLength => write!(f, "PQC signature wrong length"),
Self::Mismatch => write!(f, "PQC signature did not validate"),
}
}
}
impl std::error::Error for PqcVerifyError {}
pub trait PqcSigner: private_seal::Sealed + Send + Sync {
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, PqcSignError>;
fn verifying_key_bytes(&self) -> Vec<u8>;
}
pub trait PqcVerifier: private_seal::Sealed + Send + Sync {
fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), PqcVerifyError>;
}
pub struct SoftwareMlDsa65Signer {
signing_key: ml_dsa::SigningKey<MlDsa65>,
verifying_key_cache: ml_dsa::VerifyingKey<MlDsa65>,
}
impl SoftwareMlDsa65Signer {
pub fn from_seed(seed: [u8; 32]) -> Self {
let xi: B32 = seed.into();
let signing_key = MlDsa65::from_seed(&xi);
let verifying_key_cache = signing_key.verifying_key();
Self {
signing_key,
verifying_key_cache,
}
}
}
impl PqcSigner for SoftwareMlDsa65Signer {
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, PqcSignError> {
let sig: ml_dsa::Signature<MlDsa65> = self
.signing_key
.try_sign(msg)
.map_err(|e| PqcSignError::Provider(format!("{}", e)))?;
Ok(sig.encode().to_vec())
}
fn verifying_key_bytes(&self) -> Vec<u8> {
self.verifying_key_cache.encode().to_vec()
}
}
impl core::fmt::Debug for SoftwareMlDsa65Signer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"SoftwareMlDsa65Signer {{ verifying_key_bytes: <1952B>, signing_key: <redacted> }}"
)
}
}
pub struct SoftwareMlDsa65Verifier {
verifying_key: ml_dsa::VerifyingKey<MlDsa65>,
}
impl SoftwareMlDsa65Verifier {
pub fn from_bytes(vk_bytes: &[u8]) -> Result<Self, PqcVerifyError> {
if vk_bytes.len() != 1952 {
return Err(PqcVerifyError::WrongLength);
}
let mut buf = EncodedVerifyingKey::<MlDsa65>::default();
buf.as_mut_slice().copy_from_slice(vk_bytes);
let verifying_key = ml_dsa::VerifyingKey::<MlDsa65>::decode(&buf);
Ok(Self { verifying_key })
}
}
impl PqcVerifier for SoftwareMlDsa65Verifier {
fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), PqcVerifyError> {
if sig.len() != 3309 {
return Err(PqcVerifyError::WrongLength);
}
let mut sig_buf = EncodedSignature::<MlDsa65>::default();
sig_buf.as_mut_slice().copy_from_slice(sig);
let sig_obj =
ml_dsa::Signature::<MlDsa65>::decode(&sig_buf).ok_or(PqcVerifyError::Mismatch)?;
self.verifying_key
.verify(msg, &sig_obj)
.map_err(|_| PqcVerifyError::Mismatch)
}
}
impl core::fmt::Debug for SoftwareMlDsa65Verifier {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"SoftwareMlDsa65Verifier {{ verifying_key_bytes: <1952B> }}"
)
}
}
#[derive(Debug)]
pub struct HybridSignature {
pub ed25519: [u8; 64],
pub pqc: Vec<u8>,
}
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
#[derive(Default)]
pub enum SignatureClass {
#[default]
None,
Ed25519 {
signing_key: SigningKey,
verifying_key: VerifyingKey,
},
Hybrid {
ed25519_signing_key: SigningKey,
ed25519_verifying_key: VerifyingKey,
pqc_signer: Box<dyn PqcSigner>,
},
}
impl SignatureClass {
pub fn new_ed25519_from_secret(secret: [u8; 32]) -> Self {
let signing_key = SigningKey::from_bytes(&secret);
let verifying_key = signing_key.verifying_key();
Self::Ed25519 {
signing_key,
verifying_key,
}
}
pub fn new_hybrid_from_secrets(ed25519_secret: [u8; 32], ml_dsa_seed: [u8; 32]) -> Self {
let ed25519_signing_key = SigningKey::from_bytes(&ed25519_secret);
let ed25519_verifying_key = ed25519_signing_key.verifying_key();
let pqc_signer = Box::new(SoftwareMlDsa65Signer::from_seed(ml_dsa_seed));
Self::Hybrid {
ed25519_signing_key,
ed25519_verifying_key,
pqc_signer,
}
}
pub fn verifying_key_bytes(&self) -> Option<[u8; 32]> {
match self {
Self::None => None,
Self::Ed25519 { verifying_key, .. } => Some(verifying_key.to_bytes()),
Self::Hybrid {
ed25519_verifying_key,
..
} => Some(ed25519_verifying_key.to_bytes()),
}
}
pub fn verifying_key_pqc_bytes(&self) -> Option<Vec<u8>> {
match self {
Self::None | Self::Ed25519 { .. } => None,
Self::Hybrid { pqc_signer, .. } => Some(pqc_signer.verifying_key_bytes()),
}
}
pub(crate) fn sign(&self, body_bytes: &[u8]) -> Option<[u8; 64]> {
match self {
Self::None => None,
Self::Ed25519 { signing_key, .. } => Some(signing_key.sign(body_bytes).to_bytes()),
Self::Hybrid {
ed25519_signing_key,
..
} => Some(ed25519_signing_key.sign(body_bytes).to_bytes()),
}
}
pub(crate) fn sign_hybrid(&self, body_bytes: &[u8]) -> Option<HybridSignature> {
match self {
Self::Hybrid {
ed25519_signing_key,
pqc_signer,
..
} => {
let ed25519 = ed25519_signing_key.sign(body_bytes).to_bytes();
let pqc = pqc_signer.sign(body_bytes).ok()?;
Some(HybridSignature { ed25519, pqc })
}
_ => None,
}
}
}
impl core::fmt::Debug for SignatureClass {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::None => write!(f, "SignatureClass::None"),
Self::Ed25519 { verifying_key, .. } => write!(
f,
"SignatureClass::Ed25519 {{ verifying_key: {:?}, signing_key: <redacted> }}",
verifying_key.to_bytes()
),
Self::Hybrid {
ed25519_verifying_key,
..
} => write!(
f,
"SignatureClass::Hybrid {{ ed25519_verifying_key: {:?}, ed25519_signing_key: <redacted>, pqc_signer: <redacted> }}",
ed25519_verifying_key.to_bytes()
),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub(crate) enum VerifierInitError {
InvalidEd25519Key,
InvalidPqcKey,
PqcWithoutEd25519,
}
#[derive(Debug)]
#[non_exhaustive]
pub(crate) enum SignatureVerifyError {
WrongLength,
Mismatch,
}
#[non_exhaustive]
pub(crate) enum VerifierClass {
None,
Ed25519(VerifyingKey),
Hybrid {
ed25519: VerifyingKey,
pqc: Box<dyn PqcVerifier>,
},
}
impl VerifierClass {
pub(crate) fn from_header_bytes(
vk_ed25519: Option<&[u8; 32]>,
vk_pqc: Option<&[u8]>,
) -> Result<Self, VerifierInitError> {
match (vk_ed25519, vk_pqc) {
(None, None) => Ok(Self::None),
(Some(vk), None) => VerifyingKey::from_bytes(vk)
.map(Self::Ed25519)
.map_err(|_| VerifierInitError::InvalidEd25519Key),
(Some(vk), Some(vk_p)) => {
let ed25519 = VerifyingKey::from_bytes(vk)
.map_err(|_| VerifierInitError::InvalidEd25519Key)?;
let pqc = SoftwareMlDsa65Verifier::from_bytes(vk_p)
.map_err(|_| VerifierInitError::InvalidPqcKey)?;
Ok(Self::Hybrid {
ed25519,
pqc: Box::new(pqc),
})
}
(None, Some(_)) => Err(VerifierInitError::PqcWithoutEd25519),
}
}
pub(crate) fn verify(&self, body_bytes: &[u8], sig: &[u8]) -> Result<(), SignatureVerifyError> {
match self {
Self::None => {
unreachable!("VerifierClass::None.verify(): caller must guard with matches!")
}
Self::Ed25519(vk) => Self::verify_ed25519(vk, body_bytes, sig),
Self::Hybrid { ed25519, .. } => Self::verify_ed25519(ed25519, body_bytes, sig),
}
}
pub(crate) fn verify_hybrid(
&self,
body_bytes: &[u8],
sig: &[u8],
sig_pqc: &[u8],
) -> Result<(), SignatureVerifyError> {
match self {
Self::Hybrid { ed25519, pqc } => {
Self::verify_ed25519(ed25519, body_bytes, sig)?;
pqc.verify(body_bytes, sig_pqc)
.map_err(|_| SignatureVerifyError::Mismatch)
}
_ => unreachable!("VerifierClass::verify_hybrid(): caller must guard with matches!"),
}
}
fn verify_ed25519(
vk: &VerifyingKey,
body_bytes: &[u8],
sig: &[u8],
) -> Result<(), SignatureVerifyError> {
if sig.len() != 64 {
return Err(SignatureVerifyError::WrongLength);
}
let mut sig_bytes = [0u8; 64];
sig_bytes.copy_from_slice(sig);
let sig_obj = ed25519_dalek::Signature::from_bytes(&sig_bytes);
vk.verify(body_bytes, &sig_obj)
.map_err(|_| SignatureVerifyError::Mismatch)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn none_default_and_no_verifying_key() {
let s = SignatureClass::default();
assert!(matches!(s, SignatureClass::None));
assert!(s.verifying_key_bytes().is_none());
assert!(s.verifying_key_pqc_bytes().is_none());
assert!(s.sign(b"anything").is_none());
assert!(s.sign_hybrid(b"anything").is_none());
}
#[test]
fn ed25519_from_secret_yields_verifying_key() {
let s = SignatureClass::new_ed25519_from_secret([7u8; 32]);
let vk = s.verifying_key_bytes().expect("Ed25519 has key");
assert_eq!(vk.len(), 32);
assert!(s.verifying_key_pqc_bytes().is_none());
}
#[test]
fn ed25519_sign_is_deterministic() {
let s1 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
let s2 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
let body = b"the body bytes to sign";
let sig1 = s1.sign(body).expect("ed25519 signs");
let sig2 = s2.sign(body).expect("ed25519 signs");
assert_eq!(sig1, sig2);
}
#[test]
fn debug_redacts_signing_key() {
let s = SignatureClass::new_ed25519_from_secret([42u8; 32]);
let dbg = format!("{:?}", s);
assert!(dbg.contains("<redacted>"));
assert!(!dbg.contains("signing_key:") || dbg.contains("<redacted>"));
}
#[test]
fn verifier_class_good_signature_validates() {
let sig_class = SignatureClass::new_ed25519_from_secret([29u8; 32]);
let vk_bytes = sig_class.verifying_key_bytes().unwrap();
let body = b"verifier round-trip body";
let sig = sig_class.sign(body).expect("ed25519 signs");
let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
.expect("valid Ed25519 vk parses");
assert!(verifier.verify(body, &sig).is_ok());
}
#[test]
fn verifier_class_wrong_length_signature_rejected() {
let sig_class = SignatureClass::new_ed25519_from_secret([31u8; 32]);
let vk_bytes = sig_class.verifying_key_bytes().unwrap();
let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
.expect("valid Ed25519 vk parses");
let too_short = [0u8; 63];
assert!(matches!(
verifier.verify(b"any body", &too_short),
Err(SignatureVerifyError::WrongLength)
));
let too_long = [0u8; 65];
assert!(matches!(
verifier.verify(b"any body", &too_long),
Err(SignatureVerifyError::WrongLength)
));
}
#[test]
fn verifier_class_wrong_sig_bytes_rejected() {
let sig_class = SignatureClass::new_ed25519_from_secret([37u8; 32]);
let vk_bytes = sig_class.verifying_key_bytes().unwrap();
let body = b"wrong-sig body";
let mut sig = sig_class.sign(body).expect("ed25519 signs");
sig[0] ^= 0xFF; let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
.expect("valid Ed25519 vk parses");
assert!(matches!(
verifier.verify(body, &sig),
Err(SignatureVerifyError::Mismatch)
));
}
#[test]
fn ml_dsa_65_software_signer_round_trip() {
let signer = SoftwareMlDsa65Signer::from_seed([11u8; 32]);
let body = b"ml-dsa 65 round-trip body";
let sig = signer.sign(body).expect("ml-dsa 65 signs");
let vk_bytes = signer.verifying_key_bytes();
let verifier = SoftwareMlDsa65Verifier::from_bytes(&vk_bytes).expect("vk bytes round-trip");
assert!(verifier.verify(body, &sig).is_ok());
}
#[test]
fn ml_dsa_65_signature_size_3309_bytes() {
let signer = SoftwareMlDsa65Signer::from_seed([13u8; 32]);
let sig = signer.sign(b"size pin").expect("ml-dsa 65 signs");
assert_eq!(sig.len(), 3309);
}
#[test]
fn ml_dsa_65_verifying_key_size_1952_bytes() {
let signer = SoftwareMlDsa65Signer::from_seed([17u8; 32]);
let vk_bytes = signer.verifying_key_bytes();
assert_eq!(vk_bytes.len(), 1952);
}
#[test]
fn pqc_signer_trait_software_witness() {
fn witness<T: PqcSigner>(_: &T) {}
let signer = SoftwareMlDsa65Signer::from_seed([0u8; 32]);
witness(&signer);
}
#[test]
fn pqc_verifier_trait_software_witness() {
fn witness<T: PqcVerifier>(_: &T) {}
let signer = SoftwareMlDsa65Signer::from_seed([1u8; 32]);
let verifier = SoftwareMlDsa65Verifier::from_bytes(&signer.verifying_key_bytes())
.expect("vk bytes round-trip");
witness(&verifier);
}
#[test]
fn hybrid_signature_class_construct() {
let s = SignatureClass::new_hybrid_from_secrets([23u8; 32], [29u8; 32]);
assert!(matches!(s, SignatureClass::Hybrid { .. }));
let vk_ed = s.verifying_key_bytes().expect("Hybrid has Ed25519 key");
assert_eq!(vk_ed.len(), 32);
let vk_pqc = s.verifying_key_pqc_bytes().expect("Hybrid has PQC key");
assert_eq!(vk_pqc.len(), 1952);
let body = b"hybrid construct body";
let hyb = s.sign_hybrid(body).expect("hybrid signs");
assert_eq!(hyb.ed25519.len(), 64);
assert_eq!(hyb.pqc.len(), 3309);
}
}