1use ed25519_dalek::{
21 Signer as Ed25519SignerTrait, SigningKey, Verifier as Ed25519VerifierTrait, VerifyingKey,
22};
23use ml_dsa::signature::{
24 Keypair as MlDsaKeypairTrait, Signer as MlDsaSignerTrait, Verifier as MlDsaVerifierTrait,
25};
26use ml_dsa::{EncodedSignature, EncodedVerifyingKey, KeyGen, MlDsa65, B32};
27
28mod private_seal {
34 pub trait Sealed {}
35 impl Sealed for super::SoftwareMlDsa65Signer {}
36 impl Sealed for super::SoftwareMlDsa65Verifier {}
37}
38
39#[derive(Debug, Clone)]
41#[non_exhaustive]
42pub enum PqcSignError {
43 Provider(String),
45}
46
47impl core::fmt::Display for PqcSignError {
48 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49 match self {
50 Self::Provider(m) => write!(f, "PQC signer provider error: {}", m),
51 }
52 }
53}
54
55impl std::error::Error for PqcSignError {}
56
57#[derive(Debug, Clone)]
59#[non_exhaustive]
60pub enum PqcVerifyError {
61 WrongLength,
64 Mismatch,
67}
68
69impl core::fmt::Display for PqcVerifyError {
70 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71 match self {
72 Self::WrongLength => write!(f, "PQC signature wrong length"),
73 Self::Mismatch => write!(f, "PQC signature did not validate"),
74 }
75 }
76}
77
78impl std::error::Error for PqcVerifyError {}
79
80pub trait PqcSigner: private_seal::Sealed + Send + Sync {
83 fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, PqcSignError>;
86 fn verifying_key_bytes(&self) -> Vec<u8>;
89}
90
91pub trait PqcVerifier: private_seal::Sealed + Send + Sync {
94 fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), PqcVerifyError>;
96}
97
98pub struct SoftwareMlDsa65Signer {
105 signing_key: ml_dsa::SigningKey<MlDsa65>,
106 verifying_key_cache: ml_dsa::VerifyingKey<MlDsa65>,
107}
108
109impl SoftwareMlDsa65Signer {
110 pub fn from_seed(seed: [u8; 32]) -> Self {
113 let xi: B32 = seed.into();
114 let signing_key = MlDsa65::from_seed(&xi);
115 let verifying_key_cache = signing_key.verifying_key();
116 Self {
117 signing_key,
118 verifying_key_cache,
119 }
120 }
121}
122
123impl PqcSigner for SoftwareMlDsa65Signer {
124 fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, PqcSignError> {
125 let sig: ml_dsa::Signature<MlDsa65> = self
129 .signing_key
130 .try_sign(msg)
131 .map_err(|e| PqcSignError::Provider(format!("{}", e)))?;
132 Ok(sig.encode().to_vec())
133 }
134
135 fn verifying_key_bytes(&self) -> Vec<u8> {
136 self.verifying_key_cache.encode().to_vec()
137 }
138}
139
140impl core::fmt::Debug for SoftwareMlDsa65Signer {
141 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
142 write!(
143 f,
144 "SoftwareMlDsa65Signer {{ verifying_key_bytes: <1952B>, signing_key: <redacted> }}"
145 )
146 }
147}
148
149pub struct SoftwareMlDsa65Verifier {
151 verifying_key: ml_dsa::VerifyingKey<MlDsa65>,
152}
153
154impl SoftwareMlDsa65Verifier {
155 pub fn from_bytes(vk_bytes: &[u8]) -> Result<Self, PqcVerifyError> {
158 if vk_bytes.len() != 1952 {
159 return Err(PqcVerifyError::WrongLength);
160 }
161 let mut buf = EncodedVerifyingKey::<MlDsa65>::default();
162 buf.as_mut_slice().copy_from_slice(vk_bytes);
163 let verifying_key = ml_dsa::VerifyingKey::<MlDsa65>::decode(&buf);
164 Ok(Self { verifying_key })
165 }
166}
167
168impl PqcVerifier for SoftwareMlDsa65Verifier {
169 fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), PqcVerifyError> {
170 if sig.len() != 3309 {
171 return Err(PqcVerifyError::WrongLength);
172 }
173 let mut sig_buf = EncodedSignature::<MlDsa65>::default();
174 sig_buf.as_mut_slice().copy_from_slice(sig);
175 let sig_obj =
176 ml_dsa::Signature::<MlDsa65>::decode(&sig_buf).ok_or(PqcVerifyError::Mismatch)?;
177 self.verifying_key
178 .verify(msg, &sig_obj)
179 .map_err(|_| PqcVerifyError::Mismatch)
180 }
181}
182
183impl core::fmt::Debug for SoftwareMlDsa65Verifier {
184 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185 write!(
186 f,
187 "SoftwareMlDsa65Verifier {{ verifying_key_bytes: <1952B> }}"
188 )
189 }
190}
191
192#[derive(Debug)]
196pub struct HybridSignature {
197 pub ed25519: [u8; 64],
199 pub pqc: Vec<u8>,
201}
202
203#[allow(clippy::large_enum_variant)]
212#[non_exhaustive]
213#[derive(Default)]
214pub enum SignatureClass {
215 #[default]
217 None,
218 Ed25519 {
220 signing_key: SigningKey,
222 verifying_key: VerifyingKey,
225 },
226 Hybrid {
229 ed25519_signing_key: SigningKey,
231 ed25519_verifying_key: VerifyingKey,
233 pqc_signer: Box<dyn PqcSigner>,
236 },
237}
238
239impl SignatureClass {
240 pub fn new_ed25519_from_secret(secret: [u8; 32]) -> Self {
243 let signing_key = SigningKey::from_bytes(&secret);
244 let verifying_key = signing_key.verifying_key();
245 Self::Ed25519 {
246 signing_key,
247 verifying_key,
248 }
249 }
250
251 pub fn new_hybrid_from_secrets(ed25519_secret: [u8; 32], ml_dsa_seed: [u8; 32]) -> Self {
256 let ed25519_signing_key = SigningKey::from_bytes(&ed25519_secret);
257 let ed25519_verifying_key = ed25519_signing_key.verifying_key();
258 let pqc_signer = Box::new(SoftwareMlDsa65Signer::from_seed(ml_dsa_seed));
259 Self::Hybrid {
260 ed25519_signing_key,
261 ed25519_verifying_key,
262 pqc_signer,
263 }
264 }
265
266 pub fn verifying_key_bytes(&self) -> Option<[u8; 32]> {
270 match self {
271 Self::None => None,
272 Self::Ed25519 { verifying_key, .. } => Some(verifying_key.to_bytes()),
273 Self::Hybrid {
274 ed25519_verifying_key,
275 ..
276 } => Some(ed25519_verifying_key.to_bytes()),
277 }
278 }
279
280 pub fn verifying_key_pqc_bytes(&self) -> Option<Vec<u8>> {
284 match self {
285 Self::None | Self::Ed25519 { .. } => None,
286 Self::Hybrid { pqc_signer, .. } => Some(pqc_signer.verifying_key_bytes()),
287 }
288 }
289
290 pub(crate) fn sign(&self, body_bytes: &[u8]) -> Option<[u8; 64]> {
295 match self {
296 Self::None => None,
297 Self::Ed25519 { signing_key, .. } => Some(signing_key.sign(body_bytes).to_bytes()),
298 Self::Hybrid {
299 ed25519_signing_key,
300 ..
301 } => Some(ed25519_signing_key.sign(body_bytes).to_bytes()),
302 }
303 }
304
305 pub(crate) fn sign_hybrid(&self, body_bytes: &[u8]) -> Option<HybridSignature> {
311 match self {
312 Self::Hybrid {
313 ed25519_signing_key,
314 pqc_signer,
315 ..
316 } => {
317 let ed25519 = ed25519_signing_key.sign(body_bytes).to_bytes();
318 let pqc = pqc_signer.sign(body_bytes).ok()?;
319 Some(HybridSignature { ed25519, pqc })
320 }
321 _ => None,
322 }
323 }
324}
325
326impl core::fmt::Debug for SignatureClass {
327 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328 match self {
329 Self::None => write!(f, "SignatureClass::None"),
330 Self::Ed25519 { verifying_key, .. } => write!(
331 f,
332 "SignatureClass::Ed25519 {{ verifying_key: {:?}, signing_key: <redacted> }}",
333 verifying_key.to_bytes()
334 ),
335 Self::Hybrid {
336 ed25519_verifying_key,
337 ..
338 } => write!(
339 f,
340 "SignatureClass::Hybrid {{ ed25519_verifying_key: {:?}, ed25519_signing_key: <redacted>, pqc_signer: <redacted> }}",
341 ed25519_verifying_key.to_bytes()
342 ),
343 }
344 }
345}
346
347#[derive(Debug)]
350#[non_exhaustive]
351pub(crate) enum VerifierInitError {
352 InvalidEd25519Key,
354 InvalidPqcKey,
356 PqcWithoutEd25519,
360}
361
362#[derive(Debug)]
364#[non_exhaustive]
365pub(crate) enum SignatureVerifyError {
366 WrongLength,
368 Mismatch,
371}
372
373#[non_exhaustive]
377pub(crate) enum VerifierClass {
378 None,
382 Ed25519(VerifyingKey),
384 Hybrid {
386 ed25519: VerifyingKey,
388 pqc: Box<dyn PqcVerifier>,
392 },
393}
394
395impl VerifierClass {
396 pub(crate) fn from_header_bytes(
403 vk_ed25519: Option<&[u8; 32]>,
404 vk_pqc: Option<&[u8]>,
405 ) -> Result<Self, VerifierInitError> {
406 match (vk_ed25519, vk_pqc) {
407 (None, None) => Ok(Self::None),
408 (Some(vk), None) => VerifyingKey::from_bytes(vk)
409 .map(Self::Ed25519)
410 .map_err(|_| VerifierInitError::InvalidEd25519Key),
411 (Some(vk), Some(vk_p)) => {
412 let ed25519 = VerifyingKey::from_bytes(vk)
413 .map_err(|_| VerifierInitError::InvalidEd25519Key)?;
414 let pqc = SoftwareMlDsa65Verifier::from_bytes(vk_p)
415 .map_err(|_| VerifierInitError::InvalidPqcKey)?;
416 Ok(Self::Hybrid {
417 ed25519,
418 pqc: Box::new(pqc),
419 })
420 }
421 (None, Some(_)) => Err(VerifierInitError::PqcWithoutEd25519),
422 }
423 }
424
425 pub(crate) fn verify(&self, body_bytes: &[u8], sig: &[u8]) -> Result<(), SignatureVerifyError> {
438 match self {
439 Self::None => {
440 unreachable!("VerifierClass::None.verify(): caller must guard with matches!")
441 }
442 Self::Ed25519(vk) => Self::verify_ed25519(vk, body_bytes, sig),
443 Self::Hybrid { ed25519, .. } => Self::verify_ed25519(ed25519, body_bytes, sig),
444 }
445 }
446
447 pub(crate) fn verify_hybrid(
457 &self,
458 body_bytes: &[u8],
459 sig: &[u8],
460 sig_pqc: &[u8],
461 ) -> Result<(), SignatureVerifyError> {
462 match self {
463 Self::Hybrid { ed25519, pqc } => {
464 Self::verify_ed25519(ed25519, body_bytes, sig)?;
465 pqc.verify(body_bytes, sig_pqc)
466 .map_err(|_| SignatureVerifyError::Mismatch)
467 }
468 _ => unreachable!("VerifierClass::verify_hybrid(): caller must guard with matches!"),
469 }
470 }
471
472 fn verify_ed25519(
473 vk: &VerifyingKey,
474 body_bytes: &[u8],
475 sig: &[u8],
476 ) -> Result<(), SignatureVerifyError> {
477 if sig.len() != 64 {
478 return Err(SignatureVerifyError::WrongLength);
479 }
480 let mut sig_bytes = [0u8; 64];
481 sig_bytes.copy_from_slice(sig);
482 let sig_obj = ed25519_dalek::Signature::from_bytes(&sig_bytes);
483 vk.verify(body_bytes, &sig_obj)
484 .map_err(|_| SignatureVerifyError::Mismatch)
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn none_default_and_no_verifying_key() {
494 let s = SignatureClass::default();
495 assert!(matches!(s, SignatureClass::None));
496 assert!(s.verifying_key_bytes().is_none());
497 assert!(s.verifying_key_pqc_bytes().is_none());
498 assert!(s.sign(b"anything").is_none());
499 assert!(s.sign_hybrid(b"anything").is_none());
500 }
501
502 #[test]
503 fn ed25519_from_secret_yields_verifying_key() {
504 let s = SignatureClass::new_ed25519_from_secret([7u8; 32]);
505 let vk = s.verifying_key_bytes().expect("Ed25519 has key");
506 assert_eq!(vk.len(), 32);
507 assert!(s.verifying_key_pqc_bytes().is_none());
508 }
509
510 #[test]
511 fn ed25519_sign_is_deterministic() {
512 let s1 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
514 let s2 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
515 let body = b"the body bytes to sign";
516 let sig1 = s1.sign(body).expect("ed25519 signs");
517 let sig2 = s2.sign(body).expect("ed25519 signs");
518 assert_eq!(sig1, sig2);
519 }
520
521 #[test]
522 fn debug_redacts_signing_key() {
523 let s = SignatureClass::new_ed25519_from_secret([42u8; 32]);
524 let dbg = format!("{:?}", s);
525 assert!(dbg.contains("<redacted>"));
526 assert!(!dbg.contains("signing_key:") || dbg.contains("<redacted>"));
527 }
528
529 #[test]
530 fn verifier_class_good_signature_validates() {
531 let sig_class = SignatureClass::new_ed25519_from_secret([29u8; 32]);
533 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
534 let body = b"verifier round-trip body";
535 let sig = sig_class.sign(body).expect("ed25519 signs");
536 let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
537 .expect("valid Ed25519 vk parses");
538 assert!(verifier.verify(body, &sig).is_ok());
539 }
540
541 #[test]
542 fn verifier_class_wrong_length_signature_rejected() {
543 let sig_class = SignatureClass::new_ed25519_from_secret([31u8; 32]);
547 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
548 let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
549 .expect("valid Ed25519 vk parses");
550 let too_short = [0u8; 63];
551 assert!(matches!(
552 verifier.verify(b"any body", &too_short),
553 Err(SignatureVerifyError::WrongLength)
554 ));
555 let too_long = [0u8; 65];
556 assert!(matches!(
557 verifier.verify(b"any body", &too_long),
558 Err(SignatureVerifyError::WrongLength)
559 ));
560 }
561
562 #[test]
563 fn verifier_class_wrong_sig_bytes_rejected() {
564 let sig_class = SignatureClass::new_ed25519_from_secret([37u8; 32]);
566 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
567 let body = b"wrong-sig body";
568 let mut sig = sig_class.sign(body).expect("ed25519 signs");
569 sig[0] ^= 0xFF; let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
571 .expect("valid Ed25519 vk parses");
572 assert!(matches!(
573 verifier.verify(body, &sig),
574 Err(SignatureVerifyError::Mismatch)
575 ));
576 }
577
578 #[test]
581 fn ml_dsa_65_software_signer_round_trip() {
582 let signer = SoftwareMlDsa65Signer::from_seed([11u8; 32]);
585 let body = b"ml-dsa 65 round-trip body";
586 let sig = signer.sign(body).expect("ml-dsa 65 signs");
587 let vk_bytes = signer.verifying_key_bytes();
588 let verifier = SoftwareMlDsa65Verifier::from_bytes(&vk_bytes).expect("vk bytes round-trip");
589 assert!(verifier.verify(body, &sig).is_ok());
590 }
591
592 #[test]
593 fn ml_dsa_65_signature_size_3309_bytes() {
594 let signer = SoftwareMlDsa65Signer::from_seed([13u8; 32]);
596 let sig = signer.sign(b"size pin").expect("ml-dsa 65 signs");
597 assert_eq!(sig.len(), 3309);
598 }
599
600 #[test]
601 fn ml_dsa_65_verifying_key_size_1952_bytes() {
602 let signer = SoftwareMlDsa65Signer::from_seed([17u8; 32]);
604 let vk_bytes = signer.verifying_key_bytes();
605 assert_eq!(vk_bytes.len(), 1952);
606 }
607
608 #[test]
609 fn pqc_signer_trait_software_witness() {
610 fn witness<T: PqcSigner>(_: &T) {}
614 let signer = SoftwareMlDsa65Signer::from_seed([0u8; 32]);
615 witness(&signer);
616 }
617
618 #[test]
619 fn pqc_verifier_trait_software_witness() {
620 fn witness<T: PqcVerifier>(_: &T) {}
623 let signer = SoftwareMlDsa65Signer::from_seed([1u8; 32]);
624 let verifier = SoftwareMlDsa65Verifier::from_bytes(&signer.verifying_key_bytes())
625 .expect("vk bytes round-trip");
626 witness(&verifier);
627 }
628
629 #[test]
630 fn hybrid_signature_class_construct() {
631 let s = SignatureClass::new_hybrid_from_secrets([23u8; 32], [29u8; 32]);
634 assert!(matches!(s, SignatureClass::Hybrid { .. }));
635 let vk_ed = s.verifying_key_bytes().expect("Hybrid has Ed25519 key");
636 assert_eq!(vk_ed.len(), 32);
637 let vk_pqc = s.verifying_key_pqc_bytes().expect("Hybrid has PQC key");
638 assert_eq!(vk_pqc.len(), 1952);
639 let body = b"hybrid construct body";
640 let hyb = s.sign_hybrid(body).expect("hybrid signs");
641 assert_eq!(hyb.ed25519.len(), 64);
642 assert_eq!(hyb.pqc.len(), 3309);
643 }
644}