1use ed25519_dalek::{Signer, Verifier};
53use ml_dsa::{EncodedSignature, EncodedVerifyingKey, MlDsa65};
54use zeroize::{Zeroize, Zeroizing};
55
56use crate::{
57 error::{ExoError, Result},
58 types::{PqPublicKey, PqSecretKey, PublicKey, SecretKey, Signature},
59};
60
61pub struct KeyPair {
68 pub public: PublicKey,
69 secret: SecretKey,
70}
71
72impl KeyPair {
73 #[must_use]
75 pub fn generate() -> Self {
76 let (public, secret) = generate_keypair();
77 Self { public, secret }
78 }
79
80 pub fn from_secret_bytes(bytes: [u8; 32]) -> Result<Self> {
87 let signing_key = ed25519_dalek::SigningKey::from_bytes(&bytes);
88 let verifying_key = signing_key.verifying_key();
89 Ok(Self {
90 public: PublicKey::from_bytes(verifying_key.to_bytes()),
91 secret: SecretKey::from_bytes(bytes),
92 })
93 }
94
95 #[must_use]
97 pub fn sign(&self, message: &[u8]) -> Signature {
98 sign(message, &self.secret)
99 }
100
101 #[must_use]
105 pub fn verify(&self, message: &[u8], signature: &Signature) -> bool {
106 verify(message, signature, &self.public)
107 }
108
109 #[must_use]
111 pub fn public_key(&self) -> &PublicKey {
112 &self.public
113 }
114
115 #[must_use]
117 pub fn secret_key(&self) -> &SecretKey {
118 &self.secret
119 }
120}
121
122impl Drop for KeyPair {
123 fn drop(&mut self) {
124 self.secret.zeroize();
125 }
126}
127
128impl core::fmt::Debug for KeyPair {
129 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130 f.debug_struct("KeyPair")
131 .field("public", &self.public)
132 .field("secret", &"***")
133 .finish()
134 }
135}
136
137pub struct PqKeyPair {
146 pub public: PqPublicKey,
147 secret: PqSecretKey,
148}
149
150impl PqKeyPair {
151 #[must_use]
153 pub fn generate() -> Self {
154 let (public, secret) = generate_pq_keypair();
155 Self { public, secret }
156 }
157
158 pub fn sign(&self, message: &[u8]) -> Result<Signature> {
164 sign_pq(message, &self.secret)
165 }
166
167 #[must_use]
169 pub fn verify(&self, message: &[u8], signature: &Signature) -> bool {
170 verify_pq(message, signature, &self.public)
171 }
172
173 #[must_use]
175 pub fn public_key(&self) -> &PqPublicKey {
176 &self.public
177 }
178}
179
180impl core::fmt::Debug for PqKeyPair {
181 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
182 f.debug_struct("PqKeyPair")
183 .field("public", &self.public)
184 .field("secret", &"***")
185 .finish()
186 }
187}
188
189#[must_use]
195pub fn generate_keypair() -> (PublicKey, SecretKey) {
196 let mut csprng = rand::rngs::OsRng;
197 let signing_key = ed25519_dalek::SigningKey::generate(&mut csprng);
198 let verifying_key = signing_key.verifying_key();
199 (
200 PublicKey::from_bytes(verifying_key.to_bytes()),
201 SecretKey::from_bytes(signing_key.to_bytes()),
202 )
203}
204
205#[must_use]
207pub fn sign(message: &[u8], secret: &SecretKey) -> Signature {
208 let signing_key = ed25519_dalek::SigningKey::from_bytes(secret.as_bytes());
209 let sig = signing_key.sign(message);
210 Signature::Ed25519(sig.to_bytes())
211}
212
213#[must_use]
222pub fn verify(message: &[u8], signature: &Signature, public: &PublicKey) -> bool {
223 let sig_bytes = match signature {
224 Signature::Ed25519(b) => b,
225 Signature::Hybrid { .. } => return false,
228 Signature::PostQuantum(_) => return false,
229 Signature::Empty => return false,
230 };
231 let Ok(verifying_key) = ed25519_dalek::VerifyingKey::from_bytes(public.as_bytes()) else {
232 return false;
233 };
234 let Ok(sig) = ed25519_dalek::Signature::from_slice(sig_bytes) else {
235 return false;
236 };
237 verifying_key.verify(message, &sig).is_ok()
238}
239
240#[must_use]
252pub fn generate_pq_keypair() -> (PqPublicKey, PqSecretKey) {
253 let mut seed_bytes = Zeroizing::new([0u8; 32]);
257 rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut *seed_bytes);
258
259 let seed: ml_dsa::Seed = (*seed_bytes).into();
260 let sk = ml_dsa::SigningKey::<MlDsa65>::from_seed(&seed);
261 let vk = sk.verifying_key();
262 let vk_encoded: EncodedVerifyingKey<MlDsa65> = vk.encode();
263 let vk_bytes: Vec<u8> = AsRef::<[u8]>::as_ref(&vk_encoded).to_vec();
264
265 (
266 PqPublicKey::from_bytes(vk_bytes),
267 PqSecretKey::from_bytes(seed_bytes.to_vec()),
268 )
269}
270
271pub fn sign_pq(message: &[u8], secret: &PqSecretKey) -> Result<Signature> {
284 let seed_arr: [u8; 32] = secret
285 .as_bytes()
286 .try_into()
287 .map_err(|_| ExoError::CryptoError {
288 reason: format!(
289 "ML-DSA seed must be 32 bytes, got {}",
290 secret.as_bytes().len()
291 ),
292 })?;
293 let seed: ml_dsa::Seed = seed_arr.into();
294 let sk = ml_dsa::SigningKey::<MlDsa65>::from_seed(&seed);
295 let pq_sig = sk
296 .sign_deterministic(message, &[])
297 .map_err(|e| ExoError::CryptoError {
298 reason: format!("ML-DSA-65 sign failed: {e}"),
299 })?;
300 let sig_encoded: EncodedSignature<MlDsa65> = pq_sig.encode();
301 Ok(Signature::PostQuantum(
302 AsRef::<[u8]>::as_ref(&sig_encoded).to_vec(),
303 ))
304}
305
306#[must_use]
310pub fn verify_pq(message: &[u8], signature: &Signature, public: &PqPublicKey) -> bool {
311 let Signature::PostQuantum(sig_bytes) = signature else {
312 return false;
313 };
314 let Ok(encoded_vk) = EncodedVerifyingKey::<MlDsa65>::try_from(public.as_bytes()) else {
315 return false;
316 };
317 let vk = ml_dsa::VerifyingKey::<MlDsa65>::decode(&encoded_vk);
318 let Ok(ml_sig) = ml_dsa::Signature::<MlDsa65>::try_from(sig_bytes.as_slice()) else {
319 return false;
320 };
321 vk.verify_with_context(message, &[], &ml_sig)
322}
323
324pub fn sign_hybrid(
338 message: &[u8],
339 classical_secret: &SecretKey,
340 pq_secret: &PqSecretKey,
341) -> Result<Signature> {
342 let Signature::Ed25519(classical) = sign(message, classical_secret) else {
343 return Err(ExoError::CryptoError {
345 reason: "unexpected non-Ed25519 variant from sign()".into(),
346 });
347 };
348 let Signature::PostQuantum(pq) = sign_pq(message, pq_secret)? else {
349 return Err(ExoError::CryptoError {
350 reason: "unexpected non-PostQuantum variant from sign_pq()".into(),
351 });
352 };
353 Ok(Signature::Hybrid { classical, pq })
354}
355
356#[must_use]
365pub fn verify_hybrid(
366 message: &[u8],
367 signature: &Signature,
368 classical_public: &PublicKey,
369 pq_public: &PqPublicKey,
370) -> bool {
371 let Signature::Hybrid { classical, pq } = signature else {
372 return false;
373 };
374
375 let classical_ok = verify_ed25519_bytes(message, classical, classical_public);
378 let pq_ok = verify_pq(message, &Signature::PostQuantum(pq.clone()), pq_public);
379
380 classical_ok & pq_ok
382}
383
384fn verify_ed25519_bytes(message: &[u8], sig_bytes: &[u8; 64], public: &PublicKey) -> bool {
386 let Ok(verifying_key) = ed25519_dalek::VerifyingKey::from_bytes(public.as_bytes()) else {
387 return false;
388 };
389 let Ok(sig) = ed25519_dalek::Signature::from_slice(sig_bytes) else {
390 return false;
391 };
392 verifying_key.verify(message, &sig).is_ok()
393}
394
395#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
408 fn generate_keypair_produces_valid_pair() {
409 let (pk, sk) = generate_keypair();
410 let msg = b"test message";
411 let sig = sign(msg, &sk);
412 assert!(verify(msg, &sig, &pk));
413 }
414
415 #[test]
416 fn sign_verify_roundtrip() {
417 let (pk, sk) = generate_keypair();
418 let msg = b"hello exochain";
419 let sig = sign(msg, &sk);
420 assert!(verify(msg, &sig, &pk));
421 }
422
423 #[test]
424 fn verify_fails_wrong_message() {
425 let (pk, sk) = generate_keypair();
426 let sig = sign(b"original", &sk);
427 assert!(!verify(b"tampered", &sig, &pk));
428 }
429
430 #[test]
431 fn verify_fails_wrong_key() {
432 let (_pk1, sk1) = generate_keypair();
433 let (pk2, _sk2) = generate_keypair();
434 let sig = sign(b"msg", &sk1);
435 assert!(!verify(b"msg", &sig, &pk2));
436 }
437
438 #[test]
439 fn verify_fails_corrupt_signature() {
440 let (pk, sk) = generate_keypair();
441 let sig = sign(b"msg", &sk);
442 let corrupted = match sig {
443 Signature::Ed25519(mut b) => {
444 b[0] ^= 0xff;
445 Signature::Ed25519(b)
446 }
447 _ => panic!("expected Ed25519"),
448 };
449 assert!(!verify(b"msg", &corrupted, &pk));
450 }
451
452 #[test]
453 fn verify_rejects_empty_signature() {
454 let (pk, _) = generate_keypair();
455 assert!(!verify(b"msg", &Signature::Empty, &pk));
456 }
457
458 #[test]
459 fn verify_rejects_pq_signature_via_classical_path() {
460 let (pk, _) = generate_keypair();
462 assert!(!verify(b"msg", &Signature::PostQuantum(vec![1, 2, 3]), &pk));
463 }
464
465 #[test]
466 fn verify_rejects_hybrid_via_classical_path() {
467 let (pk, sk) = generate_keypair();
470 let classical = match sign(b"msg", &sk) {
471 Signature::Ed25519(b) => b,
472 _ => panic!("expected Ed25519"),
473 };
474 let hybrid = Signature::Hybrid {
475 classical,
476 pq: vec![0u8; 32],
477 };
478 assert!(
479 !verify(b"msg", &hybrid, &pk),
480 "verify() must not silently downgrade Hybrid to Ed25519-only"
481 );
482 }
483
484 #[test]
485 fn verify_fails_invalid_public_key() {
486 let (_, sk) = generate_keypair();
487 let sig = sign(b"msg", &sk);
488 let bad_pk = PublicKey::from_bytes([0u8; 32]);
489 assert!(!verify(b"msg", &sig, &bad_pk));
490 }
491
492 #[test]
493 fn keypair_generate_and_use() {
494 let kp = KeyPair::generate();
495 let msg = b"keypair test";
496 let sig = kp.sign(msg);
497 assert!(kp.verify(msg, &sig));
498 }
499
500 #[test]
501 fn keypair_from_secret_bytes() {
502 let (_, sk) = generate_keypair();
503 let kp = KeyPair::from_secret_bytes(*sk.as_bytes()).expect("valid");
504 let msg = b"from bytes";
505 let sig = kp.sign(msg);
506 assert!(kp.verify(msg, &sig));
507 }
508
509 #[test]
510 fn keypair_public_key_accessor() {
511 let kp = KeyPair::generate();
512 let pk = kp.public_key();
513 assert_eq!(*pk, kp.public);
514 }
515
516 #[test]
517 fn keypair_secret_key_accessor() {
518 let kp = KeyPair::generate();
519 let sk = kp.secret_key();
520 let sig = sign(b"test", sk);
521 assert!(verify(b"test", &sig, kp.public_key()));
522 }
523
524 #[test]
525 fn keypair_debug_redacts_secret() {
526 let kp = KeyPair::generate();
527 let dbg = format!("{kp:?}");
528 assert!(dbg.contains("***"));
529 assert!(dbg.contains("KeyPair"));
530 }
531
532 #[test]
533 fn keypair_deterministic_from_same_bytes() {
534 let (_, sk) = generate_keypair();
535 let bytes = *sk.as_bytes();
536 let kp1 = KeyPair::from_secret_bytes(bytes).expect("ok");
537 let kp2 = KeyPair::from_secret_bytes(bytes).expect("ok");
538 assert_eq!(kp1.public, kp2.public);
539 }
540
541 #[test]
542 fn signature_deterministic() {
543 let (_, sk) = generate_keypair();
544 let msg = b"determinism test";
545 let sig1 = sign(msg, &sk);
546 let sig2 = sign(msg, &sk);
547 assert_eq!(sig1, sig2);
548 }
549
550 #[test]
551 fn empty_message_sign_verify() {
552 let (pk, sk) = generate_keypair();
553 let sig = sign(b"", &sk);
554 assert!(verify(b"", &sig, &pk));
555 }
556
557 #[test]
558 fn large_message_sign_verify() {
559 let (pk, sk) = generate_keypair();
560 let msg = vec![0xab_u8; 10_000];
561 let sig = sign(&msg, &sk);
562 assert!(verify(&msg, &sig, &pk));
563 }
564
565 #[test]
570 fn pq_generate_keypair_produces_valid_sizes() {
571 let (pk, sk) = generate_pq_keypair();
572 assert_eq!(
574 pk.as_bytes().len(),
575 1952,
576 "PQ public key should be 1952 bytes"
577 );
578 assert_eq!(
579 sk.as_bytes().len(),
580 32,
581 "PQ secret key (seed) should be 32 bytes"
582 );
583 }
584
585 #[test]
586 fn generate_pq_keypair_zeroizes_stack_seed_buffer() {
587 let source = include_str!("crypto.rs");
588 let Some(production) = source.split("#[cfg(test)]").next() else {
589 panic!("production source section exists");
590 };
591 let Some(start) = production.find("pub fn generate_pq_keypair()") else {
592 panic!("generate_pq_keypair source exists");
593 };
594 let Some(end) = production[start..].find("/// Sign `message`") else {
595 panic!("sign_pq docs follow generate_pq_keypair");
596 };
597 let generate_pq_keypair_source = &production[start..start + end];
598
599 assert!(
600 generate_pq_keypair_source.contains("Zeroizing::new([0u8; 32])"),
601 "ML-DSA seed scratch buffer must be wrapped in Zeroizing"
602 );
603 assert!(
604 !generate_pq_keypair_source.contains("let mut seed_bytes = [0u8; 32];"),
605 "plain stack seed buffer can leave ML-DSA seed bytes behind after key generation"
606 );
607 }
608
609 #[test]
610 fn pq_sign_verify_roundtrip() {
611 let (pk, sk) = generate_pq_keypair();
612 let msg = b"hello post-quantum exochain";
613 let sig = sign_pq(msg, &sk).expect("sign_pq should succeed");
614 assert!(
615 verify_pq(msg, &sig, &pk),
616 "verify_pq should accept a valid PostQuantum signature"
617 );
618 }
619
620 #[test]
621 fn pq_verify_fails_wrong_message() {
622 let (pk, sk) = generate_pq_keypair();
623 let sig = sign_pq(b"original", &sk).expect("sign_pq");
624 assert!(!verify_pq(b"tampered", &sig, &pk));
625 }
626
627 #[test]
628 fn pq_verify_fails_wrong_key() {
629 let (_pk1, sk1) = generate_pq_keypair();
630 let (pk2, _sk2) = generate_pq_keypair();
631 let sig = sign_pq(b"msg", &sk1).expect("sign_pq");
632 assert!(!verify_pq(b"msg", &sig, &pk2));
633 }
634
635 #[test]
636 fn pq_verify_fails_corrupt_signature() {
637 let (pk, sk) = generate_pq_keypair();
638 let sig = sign_pq(b"msg", &sk).expect("sign_pq");
639 let corrupted = match sig {
640 Signature::PostQuantum(mut b) => {
641 b[0] ^= 0xff;
642 Signature::PostQuantum(b)
643 }
644 _ => panic!("expected PostQuantum"),
645 };
646 assert!(!verify_pq(b"msg", &corrupted, &pk));
647 }
648
649 #[test]
650 fn pq_verify_rejects_wrong_variant() {
651 let (pk, _) = generate_pq_keypair();
652 assert!(!verify_pq(b"msg", &Signature::Empty, &pk));
653 let (_, classical_sk) = generate_keypair();
655 let ed_sig = sign(b"msg", &classical_sk);
656 assert!(!verify_pq(b"msg", &ed_sig, &pk));
657 }
658
659 #[test]
660 fn pq_signature_has_correct_byte_length() {
661 let (_, sk) = generate_pq_keypair();
662 let sig = sign_pq(b"msg", &sk).expect("sign_pq");
663 let Signature::PostQuantum(bytes) = sig else {
664 panic!("expected PostQuantum variant");
665 };
666 assert_eq!(
668 bytes.len(),
669 3309,
670 "ML-DSA-65 signature should be 3309 bytes"
671 );
672 }
673
674 #[test]
675 fn pq_sign_is_deterministic() {
676 let (_, sk) = generate_pq_keypair();
677 let msg = b"determinism";
678 let sig1 = sign_pq(msg, &sk).expect("sign_pq");
679 let sig2 = sign_pq(msg, &sk).expect("sign_pq");
680 assert_eq!(
681 sig1, sig2,
682 "ML-DSA-65 deterministic signing must be reproducible"
683 );
684 }
685
686 #[test]
687 fn pq_keypair_struct_roundtrip() {
688 let kp = PqKeyPair::generate();
689 let msg = b"pq keypair test";
690 let sig = kp.sign(msg).expect("PqKeyPair::sign");
691 assert!(kp.verify(msg, &sig));
692 }
693
694 #[test]
695 fn pq_keypair_debug_redacts_secret() {
696 let kp = PqKeyPair::generate();
697 let dbg = format!("{kp:?}");
698 assert!(dbg.contains("***"));
699 assert!(dbg.contains("PqKeyPair"));
700 }
701
702 #[test]
703 fn pq_invalid_sk_bytes_returns_error() {
704 let bad_sk = PqSecretKey::from_bytes(vec![0u8; 8]); let result = sign_pq(b"msg", &bad_sk);
706 assert!(
707 result.is_err(),
708 "sign_pq with wrong-length seed should fail"
709 );
710 }
711
712 #[test]
717 fn hybrid_sign_verify_roundtrip() {
718 let (classical_pk, classical_sk) = generate_keypair();
719 let (pq_pk, pq_sk) = generate_pq_keypair();
720 let msg = b"hybrid dual-sign";
721 let sig = sign_hybrid(msg, &classical_sk, &pq_sk).expect("sign_hybrid");
722 assert!(
723 verify_hybrid(msg, &sig, &classical_pk, &pq_pk),
724 "verify_hybrid should accept a valid Hybrid signature"
725 );
726 }
727
728 #[test]
729 fn hybrid_verification_docs_do_not_overstate_component_timing_privacy() {
730 let source = include_str!("crypto.rs");
731 let Some(production) = source.split("#[cfg(test)]").next() else {
732 panic!("production source section exists");
733 };
734
735 assert!(
736 !production.contains("does not reveal which component failed"),
737 "hybrid verifier docs must not promise component-level timing indistinguishability"
738 );
739 assert!(
740 production.contains("component-level timing remains governed by the verifier crates"),
741 "hybrid verifier docs must state the remaining component-level timing boundary"
742 );
743 }
744
745 #[test]
746 fn hybrid_verify_fails_wrong_message() {
747 let (classical_pk, classical_sk) = generate_keypair();
748 let (pq_pk, pq_sk) = generate_pq_keypair();
749 let sig = sign_hybrid(b"original", &classical_sk, &pq_sk).expect("sign_hybrid");
750 assert!(!verify_hybrid(b"tampered", &sig, &classical_pk, &pq_pk));
751 }
752
753 #[test]
754 fn hybrid_verify_fails_wrong_classical_key() {
755 let (_classical_pk1, classical_sk1) = generate_keypair();
756 let (classical_pk2, _classical_sk2) = generate_keypair();
757 let (pq_pk, pq_sk) = generate_pq_keypair();
758 let sig = sign_hybrid(b"msg", &classical_sk1, &pq_sk).expect("sign_hybrid");
759 assert!(!verify_hybrid(b"msg", &sig, &classical_pk2, &pq_pk));
760 }
761
762 #[test]
763 fn hybrid_verify_fails_wrong_pq_key() {
764 let (classical_pk, classical_sk) = generate_keypair();
765 let (_pq_pk1, pq_sk1) = generate_pq_keypair();
766 let (pq_pk2, _pq_sk2) = generate_pq_keypair();
767 let sig = sign_hybrid(b"msg", &classical_sk, &pq_sk1).expect("sign_hybrid");
768 assert!(!verify_hybrid(b"msg", &sig, &classical_pk, &pq_pk2));
769 }
770
771 #[test]
772 fn hybrid_verify_fails_stripped_pq_component() {
773 let (classical_pk, classical_sk) = generate_keypair();
778 let (pq_pk, pq_sk) = generate_pq_keypair();
779 let sig = sign_hybrid(b"msg", &classical_sk, &pq_sk).expect("sign_hybrid");
780 let tampered = match sig {
781 Signature::Hybrid { classical, mut pq } => {
782 pq[0] ^= 0xff;
783 Signature::Hybrid { classical, pq }
784 }
785 _ => panic!("expected Hybrid"),
786 };
787 assert!(
788 !verify_hybrid(b"msg", &tampered, &classical_pk, &pq_pk),
789 "tampered PQ component must cause rejection (ExistentialSafeguard)"
790 );
791 }
792
793 #[test]
794 fn hybrid_verify_fails_stripped_classical_component() {
795 let (classical_pk, classical_sk) = generate_keypair();
796 let (pq_pk, pq_sk) = generate_pq_keypair();
797 let sig = sign_hybrid(b"msg", &classical_sk, &pq_sk).expect("sign_hybrid");
798 let tampered = match sig {
799 Signature::Hybrid { mut classical, pq } => {
800 classical[0] ^= 0xff;
801 Signature::Hybrid { classical, pq }
802 }
803 _ => panic!("expected Hybrid"),
804 };
805 assert!(
806 !verify_hybrid(b"msg", &tampered, &classical_pk, &pq_pk),
807 "tampered Ed25519 component must cause rejection (DualControl)"
808 );
809 }
810
811 #[test]
812 fn hybrid_verify_rejects_wrong_variant() {
813 let (classical_pk, _) = generate_keypair();
814 let (pq_pk, _) = generate_pq_keypair();
815 assert!(!verify_hybrid(
816 b"msg",
817 &Signature::Empty,
818 &classical_pk,
819 &pq_pk
820 ));
821 assert!(!verify_hybrid(
822 b"msg",
823 &Signature::PostQuantum(vec![0u8; 32]),
824 &classical_pk,
825 &pq_pk
826 ));
827 }
828}