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 mut 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();
121 xi.zeroize();
122 Self {
123 signing_key,
124 verifying_key_cache,
125 }
126 }
127}
128
129impl PqcSigner for SoftwareMlDsa65Signer {
130 fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, PqcSignError> {
131 let sig: ml_dsa::Signature<MlDsa65> = self
135 .signing_key
136 .try_sign(msg)
137 .map_err(|e| PqcSignError::Provider(format!("{}", e)))?;
138 Ok(sig.encode().to_vec())
139 }
140
141 fn verifying_key_bytes(&self) -> Vec<u8> {
142 self.verifying_key_cache.encode().to_vec()
143 }
144}
145
146impl core::fmt::Debug for SoftwareMlDsa65Signer {
147 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
148 write!(
149 f,
150 "SoftwareMlDsa65Signer {{ verifying_key_bytes: <1952B>, signing_key: <redacted> }}"
151 )
152 }
153}
154
155pub struct SoftwareMlDsa65Verifier {
157 verifying_key: ml_dsa::VerifyingKey<MlDsa65>,
158}
159
160impl SoftwareMlDsa65Verifier {
161 pub fn from_bytes(vk_bytes: &[u8]) -> Result<Self, PqcVerifyError> {
164 if vk_bytes.len() != 1952 {
165 return Err(PqcVerifyError::WrongLength);
166 }
167 let mut buf = EncodedVerifyingKey::<MlDsa65>::default();
168 buf.as_mut_slice().copy_from_slice(vk_bytes);
169 let verifying_key = ml_dsa::VerifyingKey::<MlDsa65>::decode(&buf);
170 Ok(Self { verifying_key })
171 }
172}
173
174impl PqcVerifier for SoftwareMlDsa65Verifier {
175 fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), PqcVerifyError> {
176 if sig.len() != 3309 {
177 return Err(PqcVerifyError::WrongLength);
178 }
179 let mut sig_buf = EncodedSignature::<MlDsa65>::default();
180 sig_buf.as_mut_slice().copy_from_slice(sig);
181 let sig_obj =
182 ml_dsa::Signature::<MlDsa65>::decode(&sig_buf).ok_or(PqcVerifyError::Mismatch)?;
183 self.verifying_key
184 .verify(msg, &sig_obj)
185 .map_err(|_| PqcVerifyError::Mismatch)
186 }
187}
188
189impl core::fmt::Debug for SoftwareMlDsa65Verifier {
190 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
191 write!(
192 f,
193 "SoftwareMlDsa65Verifier {{ verifying_key_bytes: <1952B> }}"
194 )
195 }
196}
197
198#[derive(Debug)]
202pub struct HybridSignature {
203 pub ed25519: [u8; 64],
205 pub pqc: Vec<u8>,
207}
208
209#[allow(clippy::large_enum_variant)]
218#[non_exhaustive]
219#[derive(Default)]
220pub enum SignatureClass {
221 #[default]
223 None,
224 Ed25519 {
226 signing_key: SigningKey,
228 verifying_key: VerifyingKey,
231 },
232 Hybrid {
235 ed25519_signing_key: SigningKey,
237 ed25519_verifying_key: VerifyingKey,
239 pqc_signer: Box<dyn PqcSigner>,
242 },
243}
244
245const WAL_SIG_DOMAIN: &[u8] = b"arkhe-kernel v0.14 WAL record signature domain";
252
253#[inline]
254fn domain_separated(body_bytes: &[u8]) -> Vec<u8> {
255 let mut m = Vec::with_capacity(WAL_SIG_DOMAIN.len() + body_bytes.len());
256 m.extend_from_slice(WAL_SIG_DOMAIN);
257 m.extend_from_slice(body_bytes);
258 m
259}
260
261impl SignatureClass {
262 pub fn new_ed25519_from_secret(mut secret: [u8; 32]) -> Self {
265 let signing_key = SigningKey::from_bytes(&secret);
266 let verifying_key = signing_key.verifying_key();
267 secret.zeroize();
268 Self::Ed25519 {
269 signing_key,
270 verifying_key,
271 }
272 }
273
274 pub fn new_hybrid_from_secrets(
279 mut ed25519_secret: [u8; 32],
280 mut ml_dsa_seed: [u8; 32],
281 ) -> Self {
282 let ed25519_signing_key = SigningKey::from_bytes(&ed25519_secret);
283 let ed25519_verifying_key = ed25519_signing_key.verifying_key();
284 let pqc_signer = Box::new(SoftwareMlDsa65Signer::from_seed(ml_dsa_seed));
285 ed25519_secret.zeroize();
286 ml_dsa_seed.zeroize();
287 Self::Hybrid {
288 ed25519_signing_key,
289 ed25519_verifying_key,
290 pqc_signer,
291 }
292 }
293
294 pub fn verifying_key_bytes(&self) -> Option<[u8; 32]> {
298 match self {
299 Self::None => None,
300 Self::Ed25519 { verifying_key, .. } => Some(verifying_key.to_bytes()),
301 Self::Hybrid {
302 ed25519_verifying_key,
303 ..
304 } => Some(ed25519_verifying_key.to_bytes()),
305 }
306 }
307
308 pub fn verifying_key_pqc_bytes(&self) -> Option<Vec<u8>> {
312 match self {
313 Self::None | Self::Ed25519 { .. } => None,
314 Self::Hybrid { pqc_signer, .. } => Some(pqc_signer.verifying_key_bytes()),
315 }
316 }
317
318 pub(crate) fn sign(&self, body_bytes: &[u8]) -> Option<[u8; 64]> {
323 match self {
324 Self::None => None,
325 Self::Ed25519 { signing_key, .. } => {
326 Some(signing_key.sign(&domain_separated(body_bytes)).to_bytes())
327 }
328 Self::Hybrid {
329 ed25519_signing_key,
330 ..
331 } => Some(ed25519_signing_key.sign(&domain_separated(body_bytes)).to_bytes()),
332 }
333 }
334
335 pub(crate) fn sign_hybrid(&self, body_bytes: &[u8]) -> Option<HybridSignature> {
341 match self {
342 Self::Hybrid {
343 ed25519_signing_key,
344 pqc_signer,
345 ..
346 } => {
347 let msg = domain_separated(body_bytes);
348 let ed25519 = ed25519_signing_key.sign(&msg).to_bytes();
349 let pqc = pqc_signer.sign(&msg).ok()?;
350 Some(HybridSignature { ed25519, pqc })
351 }
352 _ => None,
353 }
354 }
355}
356
357impl core::fmt::Debug for SignatureClass {
358 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
359 match self {
360 Self::None => write!(f, "SignatureClass::None"),
361 Self::Ed25519 { verifying_key, .. } => write!(
362 f,
363 "SignatureClass::Ed25519 {{ verifying_key: {:?}, signing_key: <redacted> }}",
364 verifying_key.to_bytes()
365 ),
366 Self::Hybrid {
367 ed25519_verifying_key,
368 ..
369 } => write!(
370 f,
371 "SignatureClass::Hybrid {{ ed25519_verifying_key: {:?}, ed25519_signing_key: <redacted>, pqc_signer: <redacted> }}",
372 ed25519_verifying_key.to_bytes()
373 ),
374 }
375 }
376}
377
378#[derive(Debug)]
381#[non_exhaustive]
382pub(crate) enum VerifierInitError {
383 InvalidEd25519Key,
385 InvalidPqcKey,
387 PqcWithoutEd25519,
391}
392
393#[derive(Debug)]
395#[non_exhaustive]
396pub(crate) enum SignatureVerifyError {
397 WrongLength,
399 Mismatch,
402}
403
404#[non_exhaustive]
408pub(crate) enum VerifierClass {
409 None,
413 Ed25519(VerifyingKey),
415 Hybrid {
417 ed25519: VerifyingKey,
419 pqc: Box<dyn PqcVerifier>,
423 },
424}
425
426impl VerifierClass {
427 pub(crate) fn from_header_bytes(
434 vk_ed25519: Option<&[u8; 32]>,
435 vk_pqc: Option<&[u8]>,
436 ) -> Result<Self, VerifierInitError> {
437 match (vk_ed25519, vk_pqc) {
438 (None, None) => Ok(Self::None),
439 (Some(vk), None) => VerifyingKey::from_bytes(vk)
440 .map(Self::Ed25519)
441 .map_err(|_| VerifierInitError::InvalidEd25519Key),
442 (Some(vk), Some(vk_p)) => {
443 let ed25519 = VerifyingKey::from_bytes(vk)
444 .map_err(|_| VerifierInitError::InvalidEd25519Key)?;
445 let pqc = SoftwareMlDsa65Verifier::from_bytes(vk_p)
446 .map_err(|_| VerifierInitError::InvalidPqcKey)?;
447 Ok(Self::Hybrid {
448 ed25519,
449 pqc: Box::new(pqc),
450 })
451 }
452 (None, Some(_)) => Err(VerifierInitError::PqcWithoutEd25519),
453 }
454 }
455
456 pub(crate) fn verify(&self, body_bytes: &[u8], sig: &[u8]) -> Result<(), SignatureVerifyError> {
469 match self {
470 Self::None => {
471 unreachable!("VerifierClass::None.verify(): caller must guard with matches!")
472 }
473 Self::Ed25519(vk) => Self::verify_ed25519(vk, body_bytes, sig),
474 Self::Hybrid { ed25519, .. } => Self::verify_ed25519(ed25519, body_bytes, sig),
475 }
476 }
477
478 pub(crate) fn verify_hybrid(
488 &self,
489 body_bytes: &[u8],
490 sig: &[u8],
491 sig_pqc: &[u8],
492 ) -> Result<(), SignatureVerifyError> {
493 match self {
494 Self::Hybrid { ed25519, pqc } => {
495 Self::verify_ed25519(ed25519, body_bytes, sig)?;
496 pqc.verify(&domain_separated(body_bytes), sig_pqc)
497 .map_err(|_| SignatureVerifyError::Mismatch)
498 }
499 _ => unreachable!("VerifierClass::verify_hybrid(): caller must guard with matches!"),
500 }
501 }
502
503 fn verify_ed25519(
504 vk: &VerifyingKey,
505 body_bytes: &[u8],
506 sig: &[u8],
507 ) -> Result<(), SignatureVerifyError> {
508 if sig.len() != 64 {
509 return Err(SignatureVerifyError::WrongLength);
510 }
511 let mut sig_bytes = [0u8; 64];
512 sig_bytes.copy_from_slice(sig);
513 let sig_obj = ed25519_dalek::Signature::from_bytes(&sig_bytes);
514 vk.verify_strict(&domain_separated(body_bytes), &sig_obj)
517 .map_err(|_| SignatureVerifyError::Mismatch)
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn none_default_and_no_verifying_key() {
527 let s = SignatureClass::default();
528 assert!(matches!(s, SignatureClass::None));
529 assert!(s.verifying_key_bytes().is_none());
530 assert!(s.verifying_key_pqc_bytes().is_none());
531 assert!(s.sign(b"anything").is_none());
532 assert!(s.sign_hybrid(b"anything").is_none());
533 }
534
535 #[test]
536 fn ed25519_from_secret_yields_verifying_key() {
537 let s = SignatureClass::new_ed25519_from_secret([7u8; 32]);
538 let vk = s.verifying_key_bytes().expect("Ed25519 has key");
539 assert_eq!(vk.len(), 32);
540 assert!(s.verifying_key_pqc_bytes().is_none());
541 }
542
543 #[test]
544 fn ed25519_sign_is_deterministic() {
545 let s1 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
547 let s2 = SignatureClass::new_ed25519_from_secret([3u8; 32]);
548 let body = b"the body bytes to sign";
549 let sig1 = s1.sign(body).expect("ed25519 signs");
550 let sig2 = s2.sign(body).expect("ed25519 signs");
551 assert_eq!(sig1, sig2);
552 }
553
554 #[test]
555 fn debug_redacts_signing_key() {
556 let s = SignatureClass::new_ed25519_from_secret([42u8; 32]);
557 let dbg = format!("{:?}", s);
558 assert!(dbg.contains("<redacted>"));
559 assert!(!dbg.contains("signing_key:") || dbg.contains("<redacted>"));
560 }
561
562 #[test]
563 fn verifier_class_good_signature_validates() {
564 let sig_class = SignatureClass::new_ed25519_from_secret([29u8; 32]);
566 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
567 let body = b"verifier round-trip body";
568 let sig = sig_class.sign(body).expect("ed25519 signs");
569 let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
570 .expect("valid Ed25519 vk parses");
571 assert!(verifier.verify(body, &sig).is_ok());
572 }
573
574 #[test]
575 fn verifier_class_wrong_length_signature_rejected() {
576 let sig_class = SignatureClass::new_ed25519_from_secret([31u8; 32]);
580 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
581 let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
582 .expect("valid Ed25519 vk parses");
583 let too_short = [0u8; 63];
584 assert!(matches!(
585 verifier.verify(b"any body", &too_short),
586 Err(SignatureVerifyError::WrongLength)
587 ));
588 let too_long = [0u8; 65];
589 assert!(matches!(
590 verifier.verify(b"any body", &too_long),
591 Err(SignatureVerifyError::WrongLength)
592 ));
593 }
594
595 #[test]
596 fn verifier_class_wrong_sig_bytes_rejected() {
597 let sig_class = SignatureClass::new_ed25519_from_secret([37u8; 32]);
599 let vk_bytes = sig_class.verifying_key_bytes().unwrap();
600 let body = b"wrong-sig body";
601 let mut sig = sig_class.sign(body).expect("ed25519 signs");
602 sig[0] ^= 0xFF; let verifier = VerifierClass::from_header_bytes(Some(&vk_bytes), None)
604 .expect("valid Ed25519 vk parses");
605 assert!(matches!(
606 verifier.verify(body, &sig),
607 Err(SignatureVerifyError::Mismatch)
608 ));
609 }
610
611 #[test]
614 fn ml_dsa_65_software_signer_round_trip() {
615 let signer = SoftwareMlDsa65Signer::from_seed([11u8; 32]);
618 let body = b"ml-dsa 65 round-trip body";
619 let sig = signer.sign(body).expect("ml-dsa 65 signs");
620 let vk_bytes = signer.verifying_key_bytes();
621 let verifier = SoftwareMlDsa65Verifier::from_bytes(&vk_bytes).expect("vk bytes round-trip");
622 assert!(verifier.verify(body, &sig).is_ok());
623 }
624
625 #[test]
626 fn ml_dsa_65_signature_size_3309_bytes() {
627 let signer = SoftwareMlDsa65Signer::from_seed([13u8; 32]);
629 let sig = signer.sign(b"size pin").expect("ml-dsa 65 signs");
630 assert_eq!(sig.len(), 3309);
631 }
632
633 #[test]
634 fn ml_dsa_65_verifying_key_size_1952_bytes() {
635 let signer = SoftwareMlDsa65Signer::from_seed([17u8; 32]);
637 let vk_bytes = signer.verifying_key_bytes();
638 assert_eq!(vk_bytes.len(), 1952);
639 }
640
641 #[test]
642 fn pqc_signer_trait_software_witness() {
643 fn witness<T: PqcSigner>(_: &T) {}
647 let signer = SoftwareMlDsa65Signer::from_seed([0u8; 32]);
648 witness(&signer);
649 }
650
651 #[test]
652 fn pqc_verifier_trait_software_witness() {
653 fn witness<T: PqcVerifier>(_: &T) {}
656 let signer = SoftwareMlDsa65Signer::from_seed([1u8; 32]);
657 let verifier = SoftwareMlDsa65Verifier::from_bytes(&signer.verifying_key_bytes())
658 .expect("vk bytes round-trip");
659 witness(&verifier);
660 }
661
662 #[test]
663 fn hybrid_signature_class_construct() {
664 let s = SignatureClass::new_hybrid_from_secrets([23u8; 32], [29u8; 32]);
667 assert!(matches!(s, SignatureClass::Hybrid { .. }));
668 let vk_ed = s.verifying_key_bytes().expect("Hybrid has Ed25519 key");
669 assert_eq!(vk_ed.len(), 32);
670 let vk_pqc = s.verifying_key_pqc_bytes().expect("Hybrid has PQC key");
671 assert_eq!(vk_pqc.len(), 1952);
672 let body = b"hybrid construct body";
673 let hyb = s.sign_hybrid(body).expect("hybrid signs");
674 assert_eq!(hyb.ed25519.len(), 64);
675 assert_eq!(hyb.pqc.len(), 3309);
676 }
677}