1use std::collections::BTreeMap;
19
20pub mod pqxdh_crypto;
22pub use pqxdh_crypto::*;
23use serde::{Deserialize, Serialize};
24use zeroize::{Zeroize, ZeroizeOnDrop};
25
26use crate::{Signature, VerifyingKey};
27
28#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
30pub enum InboxType {
31 Private = 0,
33 Public = 9,
35}
36
37#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
49pub struct PqxdhPrekeyBundle {
50 pub signed_prekey: x25519_dalek::PublicKey,
53 pub signed_prekey_signature: Signature,
55 pub signed_prekey_id: String,
57
58 pub one_time_prekeys: BTreeMap<String, x25519_dalek::PublicKey>,
60
61 pub pq_signed_prekey: Vec<u8>, pub pq_signed_prekey_signature: Signature,
66 pub pq_signed_prekey_id: String,
68
69 pub pq_one_time_keys: BTreeMap<String, Vec<u8>>, pub pq_one_time_signatures: BTreeMap<String, Signature>,
73}
74
75#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
78pub struct PqxdhInbox {
79 pub inbox_type: InboxType,
81 pub pqxdh_prekeys: PqxdhPrekeyBundle,
83 pub max_echo_size: Option<u32>,
85 pub expires_at: Option<u64>,
87}
88
89#[derive(Clone, Serialize, Deserialize)]
94pub struct PqxdhPrivateKeys {
95 pub signed_prekey_private: x25519_dalek::StaticSecret,
97 pub one_time_prekey_privates: BTreeMap<String, x25519_dalek::StaticSecret>,
99 pub pq_signed_prekey_private: Vec<u8>, pub pq_one_time_prekey_privates: BTreeMap<String, Vec<u8>>, }
104
105impl PartialEq for PqxdhPrivateKeys {
106 fn eq(&self, other: &Self) -> bool {
107 self.one_time_prekey_privates
111 .keys()
112 .collect::<std::collections::BTreeSet<_>>()
113 == other
114 .one_time_prekey_privates
115 .keys()
116 .collect::<std::collections::BTreeSet<_>>()
117 && self
118 .pq_one_time_prekey_privates
119 .keys()
120 .collect::<std::collections::BTreeSet<_>>()
121 == other
122 .pq_one_time_prekey_privates
123 .keys()
124 .collect::<std::collections::BTreeSet<_>>()
125 }
126}
127
128#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
134pub struct PqxdhInitialMessage {
135 pub initiator_identity: VerifyingKey,
137 pub ephemeral_key: x25519_dalek::PublicKey,
139 pub kem_ciphertext: Vec<u8>,
141 pub signed_prekey_id: String,
143 pub one_time_prekey_id: Option<String>,
145 pub pq_signed_prekey_id: String,
147 pub pq_one_time_key_id: Option<String>,
149 pub encrypted_payload: Vec<u8>,
151}
152
153#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
158pub struct PqxdhSessionMessage {
159 pub sequence_number: u64,
161 pub encrypted_payload: Vec<u8>,
163 pub auth_tag: [u8; 16],
165}
166
167#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
169pub struct PqxdhSharedSecret {
170 pub shared_key: [u8; 32],
172 pub consumed_one_time_key_ids: Vec<String>,
174}
175
176#[derive(Serialize, Deserialize, Debug, Clone)]
182pub struct PqxdhInitialPayload<T> {
183 pub user_payload: T,
185 pub session_channel_id_prefix: [u8; 32],
187}
188
189#[derive(Debug, thiserror::Error)]
191pub enum PqxdhError {
192 #[error("Cryptographic operation failed: {0}")]
193 CryptoError(String),
194 #[error("Invalid prekey bundle: {0}")]
195 InvalidPrekeyBundle(String),
196 #[error("Missing required prekey: {0}")]
197 MissingPrekey(String),
198 #[error("Signature verification failed")]
199 SignatureVerificationFailed,
200 #[error("Key derivation failed: {0}")]
201 KeyDerivationFailed(String),
202 #[error("Serialization error: {0}")]
203 SerializationError(#[from] postcard::Error),
204}
205
206impl PqxdhPrekeyBundle {
207 pub fn verify_signatures(
229 &self,
230 identity_key: &VerifyingKey,
231 ) -> std::result::Result<(), PqxdhError> {
232 identity_key
234 .verify(self.signed_prekey.as_bytes(), &self.signed_prekey_signature)
235 .map_err(|_| PqxdhError::SignatureVerificationFailed)?;
236
237 identity_key
239 .verify(&self.pq_signed_prekey, &self.pq_signed_prekey_signature)
240 .map_err(|_| PqxdhError::SignatureVerificationFailed)?;
241
242 for (key_id, pq_key) in &self.pq_one_time_keys {
244 let signature = self.pq_one_time_signatures.get(key_id).ok_or_else(|| {
245 PqxdhError::InvalidPrekeyBundle(format!(
246 "Missing signature for PQ one-time key: {key_id}"
247 ))
248 })?;
249
250 identity_key
251 .verify(pq_key, signature)
252 .map_err(|_| PqxdhError::SignatureVerificationFailed)?;
253 }
254
255 for key_id in self.pq_one_time_signatures.keys() {
257 if !self.pq_one_time_keys.contains_key(key_id) {
258 return Err(PqxdhError::InvalidPrekeyBundle(format!(
259 "Extra signature found for non-existent PQ one-time key: {key_id}"
260 )));
261 }
262 }
263
264 Ok(())
265 }
266
267 pub fn one_time_key_count(&self) -> usize {
269 std::cmp::min(self.one_time_prekeys.len(), self.pq_one_time_keys.len())
270 }
271
272 pub fn has_one_time_keys(&self) -> bool {
274 !self.one_time_prekeys.is_empty() && !self.pq_one_time_keys.is_empty()
275 }
276}
277
278impl PqxdhInbox {
279 pub fn new(
281 inbox_type: InboxType,
282 pqxdh_prekeys: PqxdhPrekeyBundle,
283 max_echo_size: Option<u32>,
284 expires_at: Option<u64>,
285 ) -> Self {
286 Self {
287 inbox_type,
288 pqxdh_prekeys,
289 max_echo_size,
290 expires_at,
291 }
292 }
293
294 pub fn is_expired(&self, current_time: u64) -> bool {
296 self.expires_at.is_some_and(|expiry| current_time > expiry)
297 }
298
299 pub fn accepts_payload_size(&self, size: u32) -> bool {
301 self.max_echo_size.is_none_or(|max_size| size <= max_size)
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::KeyPair;
309 use assert_matches::assert_matches;
310 use rand::rngs::OsRng;
311
312 fn create_test_identity() -> (KeyPair, VerifyingKey) {
314 let keypair = KeyPair::generate_ed25519(&mut OsRng);
315 let identity = keypair.public_key();
316 (keypair, identity)
317 }
318
319 fn create_signed_prekey_bundle(identity_keypair: &KeyPair) -> PqxdhPrekeyBundle {
321 let signed_prekey = x25519_dalek::PublicKey::from([1u8; 32]);
322 let pq_signed_prekey = vec![2u8; 1184]; let signed_prekey_signature = identity_keypair.sign(signed_prekey.as_bytes());
326 let pq_signed_prekey_signature = identity_keypair.sign(&pq_signed_prekey);
327
328 let mut pq_one_time_keys = BTreeMap::new();
330 let mut pq_one_time_signatures = BTreeMap::new();
331
332 let pq_otk_1 = vec![3u8; 1184];
333 let pq_otk_2 = vec![4u8; 1184];
334
335 pq_one_time_keys.insert("pq_otk_1".to_string(), pq_otk_1.clone());
336 pq_one_time_keys.insert("pq_otk_2".to_string(), pq_otk_2.clone());
337
338 pq_one_time_signatures.insert("pq_otk_1".to_string(), identity_keypair.sign(&pq_otk_1));
339 pq_one_time_signatures.insert("pq_otk_2".to_string(), identity_keypair.sign(&pq_otk_2));
340
341 PqxdhPrekeyBundle {
342 signed_prekey,
343 signed_prekey_signature,
344 signed_prekey_id: "spk_001".to_string(),
345 one_time_prekeys: BTreeMap::new(),
346 pq_signed_prekey,
347 pq_signed_prekey_signature,
348 pq_signed_prekey_id: "pqspk_001".to_string(),
349 pq_one_time_keys,
350 pq_one_time_signatures,
351 }
352 }
353
354 fn create_invalid_prekey_bundle() -> PqxdhPrekeyBundle {
356 let wrong_keypair = KeyPair::generate_ed25519(&mut OsRng);
358
359 let signed_prekey = x25519_dalek::PublicKey::from([1u8; 32]);
360 let pq_signed_prekey = vec![2u8; 1184];
361
362 let signed_prekey_signature = wrong_keypair.sign(signed_prekey.as_bytes());
364 let pq_signed_prekey_signature = wrong_keypair.sign(&pq_signed_prekey);
365
366 let mut pq_one_time_keys = BTreeMap::new();
367 let mut pq_one_time_signatures = BTreeMap::new();
368
369 let pq_otk_1 = vec![3u8; 1184];
370 pq_one_time_keys.insert("pq_otk_1".to_string(), pq_otk_1.clone());
371 pq_one_time_signatures.insert("pq_otk_1".to_string(), wrong_keypair.sign(&pq_otk_1));
372
373 PqxdhPrekeyBundle {
374 signed_prekey,
375 signed_prekey_signature,
376 signed_prekey_id: "spk_001".to_string(),
377 one_time_prekeys: BTreeMap::new(),
378 pq_signed_prekey,
379 pq_signed_prekey_signature,
380 pq_signed_prekey_id: "pqspk_001".to_string(),
381 pq_one_time_keys,
382 pq_one_time_signatures,
383 }
384 }
385
386 #[test]
387 fn test_signature_verification_with_valid_signatures_should_pass() {
388 let (identity_keypair, identity_key) = create_test_identity();
389 let bundle = create_signed_prekey_bundle(&identity_keypair);
390
391 let result = bundle.verify_signatures(&identity_key);
393
394 assert!(result.is_ok(), "Valid signatures should pass verification");
397 }
398
399 #[test]
400 fn test_signature_verification_with_invalid_signatures_should_fail() {
401 let (_identity_keypair, identity_key) = create_test_identity();
402 let bundle = create_invalid_prekey_bundle();
403
404 let result = bundle.verify_signatures(&identity_key);
406
407 assert!(
408 result.is_err(),
409 "Invalid signatures should fail verification"
410 );
411 assert_matches!(result.unwrap_err(), PqxdhError::SignatureVerificationFailed);
412 }
413
414 #[test]
415 fn test_signature_verification_with_wrong_identity_key() {
416 let (identity_keypair, _identity_key) = create_test_identity();
417 let bundle = create_signed_prekey_bundle(&identity_keypair);
418
419 let (_wrong_keypair, wrong_identity_key) = create_test_identity();
421
422 let result = bundle.verify_signatures(&wrong_identity_key);
424
425 assert!(
426 result.is_err(),
427 "Wrong identity key should fail verification"
428 );
429 assert!(matches!(
430 result.unwrap_err(),
431 PqxdhError::SignatureVerificationFailed
432 ));
433 }
434
435 #[test]
436 fn test_signature_verification_with_tampered_signed_prekey() {
437 let (identity_keypair, identity_key) = create_test_identity();
438 let mut bundle = create_signed_prekey_bundle(&identity_keypair);
439
440 bundle.signed_prekey = x25519_dalek::PublicKey::from([99u8; 32]);
442
443 let result = bundle.verify_signatures(&identity_key);
444
445 assert!(
446 result.is_err(),
447 "Tampered signed prekey should fail verification"
448 );
449 assert!(matches!(
450 result.unwrap_err(),
451 PqxdhError::SignatureVerificationFailed
452 ));
453 }
454
455 #[test]
456 fn test_signature_verification_with_tampered_pq_signed_prekey() {
457 let (identity_keypair, identity_key) = create_test_identity();
458 let mut bundle = create_signed_prekey_bundle(&identity_keypair);
459
460 bundle.pq_signed_prekey = vec![99u8; 1184];
462
463 let result = bundle.verify_signatures(&identity_key);
464
465 assert!(
466 result.is_err(),
467 "Tampered PQ signed prekey should fail verification"
468 );
469 assert!(matches!(
470 result.unwrap_err(),
471 PqxdhError::SignatureVerificationFailed
472 ));
473 }
474
475 #[test]
476 fn test_signature_verification_with_tampered_pq_one_time_key() {
477 let (identity_keypair, identity_key) = create_test_identity();
478 let mut bundle = create_signed_prekey_bundle(&identity_keypair);
479
480 bundle
482 .pq_one_time_keys
483 .insert("pq_otk_1".to_string(), vec![99u8; 1184]);
484
485 let result = bundle.verify_signatures(&identity_key);
486
487 assert!(
488 result.is_err(),
489 "Tampered PQ one-time key should fail verification"
490 );
491 assert!(matches!(
492 result.unwrap_err(),
493 PqxdhError::SignatureVerificationFailed
494 ));
495 }
496
497 #[test]
498 fn test_signature_verification_with_missing_pq_one_time_signature() {
499 let (identity_keypair, identity_key) = create_test_identity();
500 let mut bundle = create_signed_prekey_bundle(&identity_keypair);
501
502 bundle.pq_one_time_signatures.remove("pq_otk_1");
504
505 let result = bundle.verify_signatures(&identity_key);
506
507 assert!(
508 result.is_err(),
509 "Missing PQ one-time signature should fail verification"
510 );
511 assert!(matches!(
512 result.unwrap_err(),
513 PqxdhError::InvalidPrekeyBundle(_)
514 ));
515 }
516
517 #[test]
518 fn test_signature_verification_with_extra_pq_one_time_signature() {
519 let (identity_keypair, identity_key) = create_test_identity();
520 let mut bundle = create_signed_prekey_bundle(&identity_keypair);
521
522 let fake_key = vec![88u8; 1184];
524 bundle
525 .pq_one_time_signatures
526 .insert("fake_key".to_string(), identity_keypair.sign(&fake_key));
527
528 let result = bundle.verify_signatures(&identity_key);
529
530 assert!(
531 result.is_err(),
532 "Extra PQ one-time signature should fail verification"
533 );
534 assert!(matches!(
535 result.unwrap_err(),
536 PqxdhError::InvalidPrekeyBundle(_)
537 ));
538 }
539
540 #[test]
541 fn test_signature_verification_with_empty_bundle() {
542 let (_identity_keypair, identity_key) = create_test_identity();
543
544 let bundle = PqxdhPrekeyBundle {
546 signed_prekey: x25519_dalek::PublicKey::from([0u8; 32]),
547 signed_prekey_signature: crate::Signature::Ed25519(Box::new(
548 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
549 )),
550 signed_prekey_id: "spk_001".to_string(),
551 one_time_prekeys: BTreeMap::new(),
552 pq_signed_prekey: vec![0u8; 1184],
553 pq_signed_prekey_signature: crate::Signature::Ed25519(Box::new(
554 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
555 )),
556 pq_signed_prekey_id: "pqspk_001".to_string(),
557 pq_one_time_keys: BTreeMap::new(),
558 pq_one_time_signatures: BTreeMap::new(),
559 };
560
561 let result = bundle.verify_signatures(&identity_key);
562
563 assert!(
565 result.is_err(),
566 "Invalid dummy signatures should fail verification"
567 );
568 assert!(matches!(
569 result.unwrap_err(),
570 PqxdhError::SignatureVerificationFailed
571 ));
572 }
573
574 #[test]
575 fn test_signature_verification_with_mismatched_signature_types() {
576 let (identity_keypair, identity_key) = create_test_identity();
577 let mut bundle = create_signed_prekey_bundle(&identity_keypair);
578
579 let mldsa_keypair = KeyPair::generate_ml_dsa65(&mut OsRng);
581 let mldsa_signature = mldsa_keypair.sign(bundle.signed_prekey.as_bytes());
582
583 bundle.signed_prekey_signature = mldsa_signature;
585
586 let result = bundle.verify_signatures(&identity_key);
587
588 assert!(
591 result.is_err(),
592 "Mismatched signature types should fail verification"
593 );
594 assert!(matches!(
595 result.unwrap_err(),
596 PqxdhError::SignatureVerificationFailed
597 ));
598 }
599
600 #[test]
601 fn test_inbox_type_serialization() {
602 let inbox_types = vec![InboxType::Private, InboxType::Public];
603
604 let serialized = postcard::to_stdvec(&inbox_types).unwrap();
606 let deserialized: Vec<InboxType> = postcard::from_bytes(&serialized).unwrap();
607
608 assert_eq!(inbox_types, deserialized);
609 }
610
611 #[test]
612 fn test_inbox_type_values() {
613 assert_eq!(InboxType::Private as u8, 0);
615 assert_eq!(InboxType::Public as u8, 9);
616 }
617
618 #[test]
619 fn test_pqxdh_echo_service_inbox_creation() {
620 let prekey_bundle = PqxdhPrekeyBundle {
622 signed_prekey: x25519_dalek::PublicKey::from([0u8; 32]),
623 signed_prekey_signature: crate::Signature::Ed25519(Box::new(
624 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
625 )),
626 signed_prekey_id: "spk_001".to_string(),
627 one_time_prekeys: BTreeMap::new(),
628 pq_signed_prekey: vec![0u8; 1184], pq_signed_prekey_signature: crate::Signature::Ed25519(Box::new(
630 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
631 )),
632 pq_signed_prekey_id: "pqspk_001".to_string(),
633 pq_one_time_keys: BTreeMap::new(),
634 pq_one_time_signatures: BTreeMap::new(),
635 };
636
637 let inbox = PqxdhInbox::new(
638 InboxType::Public,
639 prekey_bundle,
640 Some(1024),
641 Some(1640995200),
642 );
643
644 assert_eq!(inbox.inbox_type, InboxType::Public);
645 assert_eq!(inbox.max_echo_size, Some(1024));
646 assert_eq!(inbox.expires_at, Some(1640995200));
647 assert!(inbox.accepts_payload_size(512));
648 assert!(!inbox.accepts_payload_size(2048));
649 assert!(inbox.is_expired(1640995201));
650 assert!(!inbox.is_expired(1640995199));
651 }
652
653 #[test]
654 fn test_prekey_bundle_one_time_key_count() {
655 let mut prekey_bundle = PqxdhPrekeyBundle {
656 signed_prekey: x25519_dalek::PublicKey::from([0u8; 32]),
657 signed_prekey_signature: crate::Signature::Ed25519(Box::new(
658 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
659 )),
660 signed_prekey_id: "spk_001".to_string(),
661 one_time_prekeys: BTreeMap::new(),
662 pq_signed_prekey: vec![0u8; 1184], pq_signed_prekey_signature: crate::Signature::Ed25519(Box::new(
664 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
665 )),
666 pq_signed_prekey_id: "pqspk_001".to_string(),
667 pq_one_time_keys: BTreeMap::new(),
668 pq_one_time_signatures: BTreeMap::new(),
669 };
670
671 assert_eq!(prekey_bundle.one_time_key_count(), 0);
672 assert!(!prekey_bundle.has_one_time_keys());
673
674 prekey_bundle.one_time_prekeys.insert(
676 "otk_001".to_string(),
677 x25519_dalek::PublicKey::from([1u8; 32]),
678 );
679 assert_eq!(prekey_bundle.one_time_key_count(), 0); assert!(!prekey_bundle.has_one_time_keys());
681
682 prekey_bundle.pq_one_time_keys.insert(
684 "pqotk_001".to_string(),
685 vec![1u8; 1184], );
687 assert_eq!(prekey_bundle.one_time_key_count(), 1); assert!(prekey_bundle.has_one_time_keys());
689 }
690
691 #[test]
692 fn test_pqxdh_structures_serialization() {
693 let prekey_bundle = PqxdhPrekeyBundle {
695 signed_prekey: x25519_dalek::PublicKey::from([0u8; 32]),
696 signed_prekey_signature: crate::Signature::Ed25519(Box::new(
697 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
698 )),
699 signed_prekey_id: "spk_001".to_string(),
700 one_time_prekeys: BTreeMap::new(),
701 pq_signed_prekey: vec![0u8; 1184], pq_signed_prekey_signature: crate::Signature::Ed25519(Box::new(
703 ed25519_dalek::Signature::from_bytes(&[0u8; 64]),
704 )),
705 pq_signed_prekey_id: "pqspk_001".to_string(),
706 pq_one_time_keys: BTreeMap::new(),
707 pq_one_time_signatures: BTreeMap::new(),
708 };
709
710 let inbox = PqxdhInbox::new(InboxType::Private, prekey_bundle, None, None);
711
712 let serialized = postcard::to_stdvec(&inbox).unwrap();
714 let deserialized: PqxdhInbox = postcard::from_bytes(&serialized).unwrap();
715
716 assert_eq!(inbox, deserialized);
717 }
718
719 #[test]
720 fn test_pqxdh_private_keys_random_serialization() {
721 use rand::RngCore;
722
723 let mut rng = rand::thread_rng();
725
726 let signed_prekey_private = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
728 let mut one_time_prekey_privates = BTreeMap::new();
729 for i in 0..3 {
730 let key_id = format!("otk_{i}");
731 let private_key = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
732 one_time_prekey_privates.insert(key_id, private_key);
733 }
734
735 let mut pq_signed_prekey_private = vec![0u8; 2400]; rng.fill_bytes(&mut pq_signed_prekey_private);
738
739 let mut pq_one_time_prekey_privates = BTreeMap::new();
740 for i in 0..2 {
741 let key_id = format!("pq_otk_{i}");
742 let mut private_key = vec![0u8; 2400];
743 rng.fill_bytes(&mut private_key);
744 pq_one_time_prekey_privates.insert(key_id, private_key);
745 }
746
747 let private_keys = PqxdhPrivateKeys {
748 signed_prekey_private,
749 one_time_prekey_privates,
750 pq_signed_prekey_private,
751 pq_one_time_prekey_privates,
752 };
753
754 let serialized = postcard::to_stdvec(&private_keys).unwrap();
756 let deserialized: PqxdhPrivateKeys = postcard::from_bytes(&serialized).unwrap();
757
758 assert_eq!(
760 private_keys.signed_prekey_private.to_bytes(),
761 deserialized.signed_prekey_private.to_bytes()
762 );
763 assert_eq!(
764 private_keys.one_time_prekey_privates.len(),
765 deserialized.one_time_prekey_privates.len()
766 );
767 assert_eq!(
768 private_keys.pq_signed_prekey_private,
769 deserialized.pq_signed_prekey_private
770 );
771 assert_eq!(
772 private_keys.pq_one_time_prekey_privates,
773 deserialized.pq_one_time_prekey_privates
774 );
775
776 for (key_id, original_key) in &private_keys.one_time_prekey_privates {
778 let deserialized_key = &deserialized.one_time_prekey_privates[key_id];
779 assert_eq!(original_key.to_bytes(), deserialized_key.to_bytes());
780 }
781
782 let signed_prekey_private2 = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
784 assert_ne!(
785 private_keys.signed_prekey_private.to_bytes(),
786 signed_prekey_private2.to_bytes(),
787 "Random keys should be different"
788 );
789 }
790}