1use ed25519_dalek::{Signer as Ed25519SignerTrait, SigningKey, VerifyingKey};
21use ml_dsa::signature::{
22 Keypair as MlDsaKeypairTrait, Signer as MlDsaSignerTrait, Verifier as MlDsaVerifierTrait,
23};
24use ml_dsa::{EncodedSignature, EncodedVerifyingKey, MlDsa65, B32};
25use zeroize::Zeroize;
26
27mod private_seal {
33 pub trait Sealed {}
34 impl Sealed for super::SoftwareMlDsa65Signer {}
35 impl Sealed for super::SoftwareMlDsa65Verifier {}
36}
37
38#[derive(Debug, Clone)]
40#[non_exhaustive]
41pub enum PqcSignError {
42 Provider(String),
44}
45
46impl core::fmt::Display for PqcSignError {
47 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 match self {
49 Self::Provider(m) => write!(f, "PQC signer provider error: {}", m),
50 }
51 }
52}
53
54impl std::error::Error for PqcSignError {}
55
56#[derive(Debug, Clone)]
58#[non_exhaustive]
59pub enum PqcVerifyError {
60 WrongLength,
63 Mismatch,
66}
67
68impl core::fmt::Display for PqcVerifyError {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 match self {
71 Self::WrongLength => write!(f, "PQC signature wrong length"),
72 Self::Mismatch => write!(f, "PQC signature did not validate"),
73 }
74 }
75}
76
77impl std::error::Error for PqcVerifyError {}
78
79pub trait PqcSigner: private_seal::Sealed + Send + Sync {
82 fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, PqcSignError>;
85 fn verifying_key_bytes(&self) -> Vec<u8>;
88}
89
90pub trait PqcVerifier: private_seal::Sealed + Send + Sync {
93 fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), PqcVerifyError>;
95}
96
97pub struct SoftwareMlDsa65Signer {
104 signing_key: ml_dsa::SigningKey<MlDsa65>,
105 verifying_key_cache: ml_dsa::VerifyingKey<MlDsa65>,
106}
107
108impl SoftwareMlDsa65Signer {
109 pub fn from_seed(mut seed: [u8; 32]) -> Self {
112 let xi: B32 = seed.into();
113 let signing_key = ml_dsa::SigningKey::<MlDsa65>::from_seed(&xi);
114 let verifying_key_cache = signing_key.verifying_key();
115 seed.zeroize();
118 Self {
119 signing_key,
120 verifying_key_cache,
121 }
122 }
123}
124
125impl PqcSigner for SoftwareMlDsa65Signer {
126 fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, PqcSignError> {
127 let sig: ml_dsa::Signature<MlDsa65> = self
131 .signing_key
132 .try_sign(msg)
133 .map_err(|e| PqcSignError::Provider(format!("{}", e)))?;
134 Ok(sig.encode().to_vec())
135 }
136
137 fn verifying_key_bytes(&self) -> Vec<u8> {
138 self.verifying_key_cache.encode().to_vec()
139 }
140}
141
142impl core::fmt::Debug for SoftwareMlDsa65Signer {
143 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144 write!(
145 f,
146 "SoftwareMlDsa65Signer {{ verifying_key_bytes: <1952B>, signing_key: <redacted> }}"
147 )
148 }
149}
150
151pub struct SoftwareMlDsa65Verifier {
153 verifying_key: ml_dsa::VerifyingKey<MlDsa65>,
154}
155
156impl SoftwareMlDsa65Verifier {
157 pub fn from_bytes(vk_bytes: &[u8]) -> Result<Self, PqcVerifyError> {
160 if vk_bytes.len() != 1952 {
161 return Err(PqcVerifyError::WrongLength);
162 }
163 let mut buf = EncodedVerifyingKey::<MlDsa65>::default();
164 buf.as_mut_slice().copy_from_slice(vk_bytes);
165 let verifying_key = ml_dsa::VerifyingKey::<MlDsa65>::decode(&buf);
166 Ok(Self { verifying_key })
167 }
168}
169
170impl PqcVerifier for SoftwareMlDsa65Verifier {
171 fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), PqcVerifyError> {
172 if sig.len() != 3309 {
173 return Err(PqcVerifyError::WrongLength);
174 }
175 let mut sig_buf = EncodedSignature::<MlDsa65>::default();
176 sig_buf.as_mut_slice().copy_from_slice(sig);
177 let sig_obj =
178 ml_dsa::Signature::<MlDsa65>::decode(&sig_buf).ok_or(PqcVerifyError::Mismatch)?;
179 self.verifying_key
180 .verify(msg, &sig_obj)
181 .map_err(|_| PqcVerifyError::Mismatch)
182 }
183}
184
185impl core::fmt::Debug for SoftwareMlDsa65Verifier {
186 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
187 write!(
188 f,
189 "SoftwareMlDsa65Verifier {{ verifying_key_bytes: <1952B> }}"
190 )
191 }
192}
193
194#[derive(Debug)]
198pub struct HybridSignature {
199 pub ed25519: [u8; 64],
201 pub pqc: Vec<u8>,
203}
204
205#[allow(clippy::large_enum_variant)]
214#[non_exhaustive]
215#[derive(Default)]
216pub enum SignatureClass {
217 #[default]
219 None,
220 Ed25519 {
222 signing_key: SigningKey,
224 verifying_key: VerifyingKey,
227 },
228 Hybrid {
231 ed25519_signing_key: SigningKey,
233 ed25519_verifying_key: VerifyingKey,
235 pqc_signer: Box<dyn PqcSigner>,
238 },
239}
240
241const WAL_SIG_DOMAIN: &[u8] = b"arkhe-kernel v0.14 WAL record signature domain";
248
249#[inline]
250fn domain_separated(body_bytes: &[u8]) -> Vec<u8> {
251 let mut m = Vec::with_capacity(WAL_SIG_DOMAIN.len() + body_bytes.len());
252 m.extend_from_slice(WAL_SIG_DOMAIN);
253 m.extend_from_slice(body_bytes);
254 m
255}
256
257impl SignatureClass {
258 pub fn new_ed25519_from_secret(mut secret: [u8; 32]) -> Self {
261 let signing_key = SigningKey::from_bytes(&secret);
262 let verifying_key = signing_key.verifying_key();
263 secret.zeroize();
264 Self::Ed25519 {
265 signing_key,
266 verifying_key,
267 }
268 }
269
270 pub fn new_hybrid_from_secrets(
275 mut ed25519_secret: [u8; 32],
276 mut ml_dsa_seed: [u8; 32],
277 ) -> Self {
278 let ed25519_signing_key = SigningKey::from_bytes(&ed25519_secret);
279 let ed25519_verifying_key = ed25519_signing_key.verifying_key();
280 let pqc_signer = Box::new(SoftwareMlDsa65Signer::from_seed(ml_dsa_seed));
281 ed25519_secret.zeroize();
282 ml_dsa_seed.zeroize();
283 Self::Hybrid {
284 ed25519_signing_key,
285 ed25519_verifying_key,
286 pqc_signer,
287 }
288 }
289
290 pub fn verifying_key_bytes(&self) -> Option<[u8; 32]> {
294 match self {
295 Self::None => None,
296 Self::Ed25519 { verifying_key, .. } => Some(verifying_key.to_bytes()),
297 Self::Hybrid {
298 ed25519_verifying_key,
299 ..
300 } => Some(ed25519_verifying_key.to_bytes()),
301 }
302 }
303
304 pub fn verifying_key_pqc_bytes(&self) -> Option<Vec<u8>> {
308 match self {
309 Self::None | Self::Ed25519 { .. } => None,
310 Self::Hybrid { pqc_signer, .. } => Some(pqc_signer.verifying_key_bytes()),
311 }
312 }
313
314 pub(crate) fn sign(&self, body_bytes: &[u8]) -> Option<[u8; 64]> {
319 match self {
320 Self::None => None,
321 Self::Ed25519 { signing_key, .. } => {
322 Some(signing_key.sign(&domain_separated(body_bytes)).to_bytes())
323 }
324 Self::Hybrid {
325 ed25519_signing_key,
326 ..
327 } => Some(ed25519_signing_key.sign(&domain_separated(body_bytes)).to_bytes()),
328 }
329 }
330
331 pub(crate) fn sign_hybrid(&self, body_bytes: &[u8]) -> Option<HybridSignature> {
337 match self {
338 Self::Hybrid {
339 ed25519_signing_key,
340 pqc_signer,
341 ..
342 } => {
343 let msg = domain_separated(body_bytes);
344 let ed25519 = ed25519_signing_key.sign(&msg).to_bytes();
345 let pqc = pqc_signer.sign(&msg).ok()?;
346 Some(HybridSignature { ed25519, pqc })
347 }
348 _ => None,
349 }
350 }
351}
352
353impl core::fmt::Debug for SignatureClass {
354 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
355 match self {
356 Self::None => write!(f, "SignatureClass::None"),
357 Self::Ed25519 { verifying_key, .. } => write!(
358 f,
359 "SignatureClass::Ed25519 {{ verifying_key: {:?}, signing_key: <redacted> }}",
360 verifying_key.to_bytes()
361 ),
362 Self::Hybrid {
363 ed25519_verifying_key,
364 ..
365 } => write!(
366 f,
367 "SignatureClass::Hybrid {{ ed25519_verifying_key: {:?}, ed25519_signing_key: <redacted>, pqc_signer: <redacted> }}",
368 ed25519_verifying_key.to_bytes()
369 ),
370 }
371 }
372}
373
374#[derive(Debug)]
377#[non_exhaustive]
378pub(crate) enum VerifierInitError {
379 InvalidEd25519Key,
381 InvalidPqcKey,
383 PqcWithoutEd25519,
387}
388
389#[derive(Debug)]
391#[non_exhaustive]
392pub(crate) enum SignatureVerifyError {
393 WrongLength,
395 Mismatch,
398}
399
400#[non_exhaustive]
404pub(crate) enum VerifierClass {
405 None,
409 Ed25519(VerifyingKey),
411 Hybrid {
413 ed25519: VerifyingKey,
415 pqc: Box<dyn PqcVerifier>,
419 },
420}
421
422impl VerifierClass {
423 pub(crate) fn from_header_bytes(
430 vk_ed25519: Option<&[u8; 32]>,
431 vk_pqc: Option<&[u8]>,
432 ) -> Result<Self, VerifierInitError> {
433 match (vk_ed25519, vk_pqc) {
434 (None, None) => Ok(Self::None),
435 (Some(vk), None) => VerifyingKey::from_bytes(vk)
436 .map(Self::Ed25519)
437 .map_err(|_| VerifierInitError::InvalidEd25519Key),
438 (Some(vk), Some(vk_p)) => {
439 let ed25519 = VerifyingKey::from_bytes(vk)
440 .map_err(|_| VerifierInitError::InvalidEd25519Key)?;
441 let pqc = SoftwareMlDsa65Verifier::from_bytes(vk_p)
442 .map_err(|_| VerifierInitError::InvalidPqcKey)?;
443 Ok(Self::Hybrid {
444 ed25519,
445 pqc: Box::new(pqc),
446 })
447 }
448 (None, Some(_)) => Err(VerifierInitError::PqcWithoutEd25519),
449 }
450 }
451
452 pub(crate) fn verify(&self, body_bytes: &[u8], sig: &[u8]) -> Result<(), SignatureVerifyError> {
465 match self {
466 Self::None => {
467 unreachable!("VerifierClass::None.verify(): caller must guard with matches!")
468 }
469 Self::Ed25519(vk) => Self::verify_ed25519(vk, body_bytes, sig),
470 Self::Hybrid { ed25519, .. } => Self::verify_ed25519(ed25519, body_bytes, sig),
471 }
472 }
473
474 pub(crate) fn verify_hybrid(
484 &self,
485 body_bytes: &[u8],
486 sig: &[u8],
487 sig_pqc: &[u8],
488 ) -> Result<(), SignatureVerifyError> {
489 match self {
490 Self::Hybrid { ed25519, pqc } => {
491 Self::verify_ed25519(ed25519, body_bytes, sig)?;
492 pqc.verify(&domain_separated(body_bytes), sig_pqc)
493 .map_err(|_| SignatureVerifyError::Mismatch)
494 }
495 _ => unreachable!("VerifierClass::verify_hybrid(): caller must guard with matches!"),
496 }
497 }
498
499 fn verify_ed25519(
500 vk: &VerifyingKey,
501 body_bytes: &[u8],
502 sig: &[u8],
503 ) -> Result<(), SignatureVerifyError> {
504 if sig.len() != 64 {
505 return Err(SignatureVerifyError::WrongLength);
506 }
507 let mut sig_bytes = [0u8; 64];
508 sig_bytes.copy_from_slice(sig);
509 let sig_obj = ed25519_dalek::Signature::from_bytes(&sig_bytes);
510 vk.verify_strict(&domain_separated(body_bytes), &sig_obj)
513 .map_err(|_| SignatureVerifyError::Mismatch)
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 #[test]
522 fn none_default_and_no_verifying_key() {
523 let s = SignatureClass::default();
524 assert!(matches!(s, SignatureClass::None));
525 assert!(s.verifying_key_bytes().is_none());
526 assert!(s.verifying_key_pqc_bytes().is_none());
527 assert!(s.sign(b"anything").is_none());
528 assert!(s.sign_hybrid(b"anything").is_none());
529 }
530
531 #[test]
532 fn ed25519_from_secret_yields_verifying_key() {
533 let s = SignatureClass::new_ed25519_from_secret([7u8; 32]);
534 let vk = s.verifying_key_bytes().expect("Ed25519 has key");
535 assert_eq!(vk.len(), 32);
536 assert!(s.verifying_key_pqc_bytes().is_none());
537 }
538
539 #[test]
540 fn ed25519_sign_is_deterministic() {
541 let s1 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
543 let s2 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
544 let body = b"the body bytes to sign";
545 let sig1 = s1.sign(body).expect("ed25519 signs");
546 let sig2 = s2.sign(body).expect("ed25519 signs");
547 assert_eq!(sig1, sig2);
548 }
549
550 #[test]
551 fn debug_redacts_signing_key() {
552 let s = SignatureClass::new_ed25519_from_secret([42u8; 32]);
553 let dbg = format!("{:?}", s);
554 assert!(dbg.contains("<redacted>"));
555 assert!(!dbg.contains("signing_key:") || dbg.contains("<redacted>"));
556 }
557
558 #[test]
559 fn verifier_class_good_signature_validates() {
560 let sig_class = SignatureClass::new_ed25519_from_secret([29u8; 32]);
562 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
563 let body = b"verifier round-trip body";
564 let sig = sig_class.sign(body).expect("ed25519 signs");
565 let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
566 .expect("valid Ed25519 vk parses");
567 assert!(verifier.verify(body, &sig).is_ok());
568 }
569
570 #[test]
571 fn verifier_class_wrong_length_signature_rejected() {
572 let sig_class = SignatureClass::new_ed25519_from_secret([31u8; 32]);
576 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
577 let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
578 .expect("valid Ed25519 vk parses");
579 let too_short = [0u8; 63];
580 assert!(matches!(
581 verifier.verify(b"any body", &too_short),
582 Err(SignatureVerifyError::WrongLength)
583 ));
584 let too_long = [0u8; 65];
585 assert!(matches!(
586 verifier.verify(b"any body", &too_long),
587 Err(SignatureVerifyError::WrongLength)
588 ));
589 }
590
591 #[test]
592 fn verifier_class_wrong_sig_bytes_rejected() {
593 let sig_class = SignatureClass::new_ed25519_from_secret([37u8; 32]);
595 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
596 let body = b"wrong-sig body";
597 let mut sig = sig_class.sign(body).expect("ed25519 signs");
598 sig[0] ^= 0xFF; let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
600 .expect("valid Ed25519 vk parses");
601 assert!(matches!(
602 verifier.verify(body, &sig),
603 Err(SignatureVerifyError::Mismatch)
604 ));
605 }
606
607 #[test]
610 fn ml_dsa_65_software_signer_round_trip() {
611 let signer = SoftwareMlDsa65Signer::from_seed([11u8; 32]);
614 let body = b"ml-dsa 65 round-trip body";
615 let sig = signer.sign(body).expect("ml-dsa 65 signs");
616 let vk_bytes = signer.verifying_key_bytes();
617 let verifier = SoftwareMlDsa65Verifier::from_bytes(&vk_bytes).expect("vk bytes round-trip");
618 assert!(verifier.verify(body, &sig).is_ok());
619 }
620
621 #[test]
622 fn ml_dsa_65_signature_size_3309_bytes() {
623 let signer = SoftwareMlDsa65Signer::from_seed([13u8; 32]);
625 let sig = signer.sign(b"size pin").expect("ml-dsa 65 signs");
626 assert_eq!(sig.len(), 3309);
627 }
628
629 #[test]
630 fn ml_dsa_65_verifying_key_size_1952_bytes() {
631 let signer = SoftwareMlDsa65Signer::from_seed([17u8; 32]);
633 let vk_bytes = signer.verifying_key_bytes();
634 assert_eq!(vk_bytes.len(), 1952);
635 }
636
637 #[test]
638 fn pqc_signer_trait_software_witness() {
639 fn witness<T: PqcSigner>(_: &T) {}
643 let signer = SoftwareMlDsa65Signer::from_seed([0u8; 32]);
644 witness(&signer);
645 }
646
647 #[test]
648 fn pqc_verifier_trait_software_witness() {
649 fn witness<T: PqcVerifier>(_: &T) {}
652 let signer = SoftwareMlDsa65Signer::from_seed([1u8; 32]);
653 let verifier = SoftwareMlDsa65Verifier::from_bytes(&signer.verifying_key_bytes())
654 .expect("vk bytes round-trip");
655 witness(&verifier);
656 }
657
658 #[test]
659 fn hybrid_signature_class_construct() {
660 let s = SignatureClass::new_hybrid_from_secrets([23u8; 32], [29u8; 32]);
663 assert!(matches!(s, SignatureClass::Hybrid { .. }));
664 let vk_ed = s.verifying_key_bytes().expect("Hybrid has Ed25519 key");
665 assert_eq!(vk_ed.len(), 32);
666 let vk_pqc = s.verifying_key_pqc_bytes().expect("Hybrid has PQC key");
667 assert_eq!(vk_pqc.len(), 1952);
668 let body = b"hybrid construct body";
669 let hyb = s.sign_hybrid(body).expect("hybrid signs");
670 assert_eq!(hyb.ed25519.len(), 64);
671 assert_eq!(hyb.pqc.len(), 3309);
672 }
673}