1#![forbid(unsafe_code)]
2
3use oxicrypto_core::{CryptoError, KeyAgreement, SecretKey, SecretVec};
22use p256::elliptic_curve::Generate;
23use x25519_dalek::{PublicKey, StaticSecret};
24
25pub mod hpke;
26
27#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct X25519PublicKey(pub [u8; 32]);
36
37impl X25519PublicKey {
38 #[must_use]
40 pub fn as_bytes(&self) -> &[u8; 32] {
41 &self.0
42 }
43
44 #[must_use]
46 pub fn to_bytes(self) -> [u8; 32] {
47 self.0
48 }
49}
50
51impl From<[u8; 32]> for X25519PublicKey {
52 fn from(bytes: [u8; 32]) -> Self {
53 Self(bytes)
54 }
55}
56
57impl AsRef<[u8]> for X25519PublicKey {
58 fn as_ref(&self) -> &[u8] {
59 &self.0
60 }
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct X448PublicKey(pub [u8; 56]);
70
71impl X448PublicKey {
72 #[must_use]
74 pub fn as_bytes(&self) -> &[u8; 56] {
75 &self.0
76 }
77
78 #[must_use]
80 pub fn to_bytes(self) -> [u8; 56] {
81 self.0
82 }
83}
84
85impl From<[u8; 56]> for X448PublicKey {
86 fn from(bytes: [u8; 56]) -> Self {
87 Self(bytes)
88 }
89}
90
91impl AsRef<[u8]> for X448PublicKey {
92 fn as_ref(&self) -> &[u8] {
93 &self.0
94 }
95}
96
97#[derive(Debug, Default, Clone, Copy)]
106pub struct X25519;
107
108impl KeyAgreement for X25519 {
109 fn name(&self) -> &'static str {
110 "X25519"
111 }
112 fn scalar_len(&self) -> usize {
113 32
114 }
115 fn point_len(&self) -> usize {
116 32
117 }
118 fn agree(
119 &self,
120 my_secret: &[u8],
121 their_public: &[u8],
122 shared_out: &mut [u8],
123 ) -> Result<(), CryptoError> {
124 if shared_out.len() < 32 {
125 return Err(CryptoError::BufferTooSmall);
126 }
127 let secret_bytes: [u8; 32] = my_secret.try_into().map_err(|_| CryptoError::InvalidKey)?;
128 let public_bytes: [u8; 32] = their_public
129 .try_into()
130 .map_err(|_| CryptoError::InvalidKey)?;
131
132 let secret = StaticSecret::from(secret_bytes);
133 let public = PublicKey::from(public_bytes);
134 let shared = secret.diffie_hellman(&public);
135 if oxicrypto_core::ct_is_zero(shared.as_bytes()) {
137 return Err(CryptoError::Kex);
138 }
139 shared_out[..32].copy_from_slice(shared.as_bytes());
140 Ok(())
141 }
142}
143
144#[derive(Debug, Default, Clone, Copy)]
152pub struct EcdhP256;
153
154impl KeyAgreement for EcdhP256 {
155 fn name(&self) -> &'static str {
156 "ECDH-P256"
157 }
158 fn scalar_len(&self) -> usize {
159 32
160 }
161 fn point_len(&self) -> usize {
162 33 }
164 fn agree(
165 &self,
166 my_secret: &[u8],
167 their_public: &[u8],
168 shared_out: &mut [u8],
169 ) -> Result<(), CryptoError> {
170 if shared_out.len() < 32 {
171 return Err(CryptoError::BufferTooSmall);
172 }
173 let sk = p256::SecretKey::from_slice(my_secret).map_err(|_| CryptoError::InvalidKey)?;
174 let pk =
175 p256::PublicKey::from_sec1_bytes(their_public).map_err(|_| CryptoError::InvalidKey)?;
176
177 let shared_secret = p256::ecdh::diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine());
178 let raw = shared_secret.raw_secret_bytes();
179 if oxicrypto_core::ct_is_zero(raw) {
181 return Err(CryptoError::Kex);
182 }
183 shared_out[..32].copy_from_slice(raw);
184 Ok(())
185 }
186}
187
188#[derive(Debug, Default, Clone, Copy)]
196pub struct EcdhP384;
197
198impl KeyAgreement for EcdhP384 {
199 fn name(&self) -> &'static str {
200 "ECDH-P384"
201 }
202 fn scalar_len(&self) -> usize {
203 48
204 }
205 fn point_len(&self) -> usize {
206 49 }
208 fn agree(
209 &self,
210 my_secret: &[u8],
211 their_public: &[u8],
212 shared_out: &mut [u8],
213 ) -> Result<(), CryptoError> {
214 if shared_out.len() < 48 {
215 return Err(CryptoError::BufferTooSmall);
216 }
217 let sk = p384::SecretKey::from_slice(my_secret).map_err(|_| CryptoError::InvalidKey)?;
218 let pk =
219 p384::PublicKey::from_sec1_bytes(their_public).map_err(|_| CryptoError::InvalidKey)?;
220
221 let shared_secret = p384::ecdh::diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine());
222 let raw = shared_secret.raw_secret_bytes();
223 if oxicrypto_core::ct_is_zero(raw) {
224 return Err(CryptoError::Kex);
225 }
226 shared_out[..48].copy_from_slice(raw);
227 Ok(())
228 }
229}
230
231#[derive(Debug, Default, Clone, Copy)]
244pub struct EcdhP521;
245
246impl KeyAgreement for EcdhP521 {
247 fn name(&self) -> &'static str {
248 "ECDH-P521"
249 }
250 fn scalar_len(&self) -> usize {
251 66
252 }
253 fn point_len(&self) -> usize {
254 133 }
256 fn agree(
257 &self,
258 my_secret: &[u8],
259 their_public: &[u8],
260 shared_out: &mut [u8],
261 ) -> Result<(), CryptoError> {
262 if shared_out.len() < 66 {
263 return Err(CryptoError::BufferTooSmall);
264 }
265 let sk = p521::SecretKey::from_slice(my_secret).map_err(|_| CryptoError::InvalidKey)?;
266 let pk =
267 p521::PublicKey::from_sec1_bytes(their_public).map_err(|_| CryptoError::InvalidKey)?;
268 let shared_secret = p521::ecdh::diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine());
269 let raw = shared_secret.raw_secret_bytes();
270 if oxicrypto_core::ct_is_zero(raw) {
271 return Err(CryptoError::Kex);
272 }
273 shared_out[..66].copy_from_slice(raw);
274 Ok(())
275 }
276}
277
278#[must_use = "result must be checked"]
289pub fn x25519_generate_keypair<R>(rng: &mut R) -> Result<(SecretKey<32>, [u8; 32]), CryptoError>
290where
291 R: rand_core::TryCryptoRng + ?Sized,
292{
293 let mut seed = [0u8; 32];
294 rng.try_fill_bytes(&mut seed)
295 .map_err(|_| CryptoError::Rng)?;
296 let secret = StaticSecret::from(seed);
297 let public = PublicKey::from(&secret);
298 Ok((SecretKey::new(seed), *public.as_bytes()))
299}
300
301#[must_use = "result must be checked"]
311pub fn ecdh_p256_generate_keypair<R>(rng: &mut R) -> Result<(SecretVec, Vec<u8>), CryptoError>
312where
313 R: rand_core::TryCryptoRng + ?Sized,
314{
315 let secret_key = p256::SecretKey::try_generate_from_rng(rng).map_err(|_| CryptoError::Rng)?;
316 let public_key = secret_key.public_key();
317 let sk_bytes = SecretVec::from_slice(secret_key.to_bytes().as_slice());
318 let pk_bytes = public_key.to_sec1_bytes().to_vec();
319 Ok((sk_bytes, pk_bytes))
320}
321
322#[must_use = "result must be checked"]
331pub fn ecdh_p384_generate_keypair<R>(rng: &mut R) -> Result<(SecretVec, Vec<u8>), CryptoError>
332where
333 R: rand_core::TryCryptoRng + ?Sized,
334{
335 let secret_key = p384::SecretKey::try_generate_from_rng(rng).map_err(|_| CryptoError::Rng)?;
336 let public_key = secret_key.public_key();
337 let sk_bytes = SecretVec::from_slice(secret_key.to_bytes().as_slice());
338 let pk_bytes = public_key.to_sec1_bytes().to_vec();
339 Ok((sk_bytes, pk_bytes))
340}
341
342#[must_use = "result must be checked"]
353pub fn ecdh_p521_generate_keypair<R>(rng: &mut R) -> Result<(SecretVec, Vec<u8>), CryptoError>
354where
355 R: rand_core::TryCryptoRng + ?Sized,
356{
357 let secret_key = p521::SecretKey::try_generate_from_rng(rng).map_err(|_| CryptoError::Rng)?;
358 let public_key = secret_key.public_key();
359 let sk_bytes = SecretVec::from_slice(secret_key.to_bytes().as_slice());
360 let pk_bytes = public_key.to_sec1_bytes().to_vec();
361 Ok((sk_bytes, pk_bytes))
362}
363
364#[derive(Debug, Default, Clone, Copy)]
374pub struct X448;
375
376impl KeyAgreement for X448 {
377 fn name(&self) -> &'static str {
378 "X448"
379 }
380 fn scalar_len(&self) -> usize {
381 56
382 }
383 fn point_len(&self) -> usize {
384 56
385 }
386 fn agree(
395 &self,
396 my_secret: &[u8],
397 their_public: &[u8],
398 shared_out: &mut [u8],
399 ) -> Result<(), CryptoError> {
400 if shared_out.len() < 56 {
401 return Err(CryptoError::BufferTooSmall);
402 }
403 let scalar: [u8; 56] = my_secret.try_into().map_err(|_| CryptoError::InvalidKey)?;
405 let point: [u8; 56] = their_public
406 .try_into()
407 .map_err(|_| CryptoError::InvalidKey)?;
408 let shared = x448::x448(scalar, point).ok_or(CryptoError::Kex)?;
410 if oxicrypto_core::ct_is_zero(&shared) {
412 return Err(CryptoError::Kex);
413 }
414 shared_out[..56].copy_from_slice(&shared);
415 Ok(())
416 }
417}
418
419#[must_use = "result must be checked"]
431pub fn x448_generate_keypair<R>(rng: &mut R) -> Result<(SecretKey<56>, [u8; 56]), CryptoError>
432where
433 R: rand_core::TryCryptoRng + ?Sized,
434{
435 let mut seed = [0u8; 56];
436 rng.try_fill_bytes(&mut seed)
437 .map_err(|_| CryptoError::Rng)?;
438 seed[0] &= 252;
440 seed[55] |= 128;
441 let public = x448::x448(seed, x448::X448_BASEPOINT_BYTES).ok_or(CryptoError::Rng)?;
444 Ok((SecretKey::new(seed), public))
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use rand_chacha::ChaCha20Rng;
451 use rand_core::SeedableRng;
452
453 const ALICE_SECRET: [u8; 32] = [0xaau8; 32];
454 const BOB_SECRET: [u8; 32] = [0xbbu8; 32];
455
456 fn public_from_secret(secret_bytes: &[u8; 32]) -> [u8; 32] {
457 let secret = StaticSecret::from(*secret_bytes);
458 let public = PublicKey::from(&secret);
459 *public.as_bytes()
460 }
461
462 fn test_rng() -> ChaCha20Rng {
463 ChaCha20Rng::from_seed([42u8; 32])
464 }
465
466 #[test]
469 fn x25519_both_parties_agree() {
470 let kex = X25519;
471
472 let alice_pub = public_from_secret(&ALICE_SECRET);
473 let bob_pub = public_from_secret(&BOB_SECRET);
474
475 let mut alice_shared = [0u8; 32];
476 kex.agree(&ALICE_SECRET, &bob_pub, &mut alice_shared)
477 .expect("Alice agree failed");
478
479 let mut bob_shared = [0u8; 32];
480 kex.agree(&BOB_SECRET, &alice_pub, &mut bob_shared)
481 .expect("Bob agree failed");
482
483 assert_eq!(
484 alice_shared, bob_shared,
485 "Alice and Bob must derive the same shared secret"
486 );
487 }
488
489 #[test]
490 fn x25519_shared_is_32_bytes() {
491 let kex = X25519;
492 assert_eq!(kex.scalar_len(), 32);
493 assert_eq!(kex.point_len(), 32);
494
495 let bob_pub = public_from_secret(&BOB_SECRET);
496 let mut shared = [0u8; 32];
497 kex.agree(&ALICE_SECRET, &bob_pub, &mut shared)
498 .expect("X25519 agree failed");
499 assert_ne!(shared, [0u8; 32], "Shared secret should not be all zeros");
500 }
501
502 #[test]
503 fn x25519_invalid_key_length() {
504 let kex = X25519;
505 let mut shared = [0u8; 32];
506 let result = kex.agree(&[0u8; 16], &[0u8; 32], &mut shared);
507 assert_eq!(result, Err(CryptoError::InvalidKey));
508 }
509
510 #[test]
511 fn x25519_buffer_too_small() {
512 let kex = X25519;
513 let bob_pub = public_from_secret(&BOB_SECRET);
514 let mut shared = [0u8; 16];
515 let result = kex.agree(&ALICE_SECRET, &bob_pub, &mut shared);
516 assert_eq!(result, Err(CryptoError::BufferTooSmall));
517 }
518
519 #[test]
522 fn x25519_zero_rejection() {
523 let kex = X25519;
524 let zero_pk = [0u8; 32]; let mut shared = [0u8; 32];
526 let result = kex.agree(&ALICE_SECRET, &zero_pk, &mut shared);
527 assert_eq!(
528 result,
529 Err(CryptoError::Kex),
530 "X25519 must reject all-zero shared secret from low-order public key"
531 );
532 }
533
534 #[test]
539 fn x25519_keygen_then_agree() {
540 let mut rng = test_rng();
541 let (alice_sk, alice_pk) = x25519_generate_keypair(&mut rng).expect("Alice keygen");
542 let (bob_sk, bob_pk) = x25519_generate_keypair(&mut rng).expect("Bob keygen");
543
544 let kex = X25519;
545 let mut alice_shared = [0u8; 32];
546 kex.agree(alice_sk.as_bytes(), &bob_pk, &mut alice_shared)
547 .expect("Alice agree");
548
549 let mut bob_shared = [0u8; 32];
550 kex.agree(bob_sk.as_bytes(), &alice_pk, &mut bob_shared)
551 .expect("Bob agree");
552
553 assert_eq!(
554 alice_shared, bob_shared,
555 "x25519_keygen: Alice and Bob must derive the same shared secret"
556 );
557 assert_ne!(alice_shared, [0u8; 32]);
558 }
559
560 fn p256_keypair(scalar_bytes: &[u8; 32]) -> (Vec<u8>, Vec<u8>) {
563 let sk = p256::SecretKey::from_slice(scalar_bytes).expect("valid P-256 scalar");
564 let pk = sk.public_key();
565 (scalar_bytes.to_vec(), pk.to_sec1_bytes().to_vec())
566 }
567
568 #[test]
569 fn ecdh_p256_both_parties_agree() {
570 let alice_scalar: [u8; 32] = {
572 let mut s = [0u8; 32];
573 s[0] = 0x01;
574 s[31] = 0x01;
575 s
576 };
577 let bob_scalar: [u8; 32] = {
578 let mut s = [0u8; 32];
579 s[0] = 0x02;
580 s[31] = 0x02;
581 s
582 };
583 let (_alice_sk, alice_pk) = p256_keypair(&alice_scalar);
584 let (_bob_sk, bob_pk) = p256_keypair(&bob_scalar);
585
586 let kex = EcdhP256;
587 let mut alice_shared = [0u8; 32];
588 kex.agree(&alice_scalar, &bob_pk, &mut alice_shared)
589 .expect("Alice ECDH-P256 agree failed");
590
591 let mut bob_shared = [0u8; 32];
592 kex.agree(&bob_scalar, &alice_pk, &mut bob_shared)
593 .expect("Bob ECDH-P256 agree failed");
594
595 assert_eq!(
596 alice_shared, bob_shared,
597 "ECDH-P256: Alice and Bob must derive the same shared secret"
598 );
599 assert_ne!(alice_shared, [0u8; 32]);
600 }
601
602 #[test]
603 fn ecdh_p256_buffer_too_small() {
604 let kex = EcdhP256;
605 let scalar: [u8; 32] = {
606 let mut s = [0u8; 32];
607 s[0] = 0x01;
608 s[31] = 0x01;
609 s
610 };
611 let (_, pk) = p256_keypair(&scalar);
612 let mut shared = [0u8; 16];
613 let result = kex.agree(&scalar, &pk, &mut shared);
614 assert_eq!(result, Err(CryptoError::BufferTooSmall));
615 }
616
617 #[test]
619 fn ecdh_p256_keygen_agree() {
620 let mut rng = test_rng();
621 let (alice_sk, alice_pk) =
622 ecdh_p256_generate_keypair(&mut rng).expect("Alice P-256 keygen");
623 let (bob_sk, bob_pk) = ecdh_p256_generate_keypair(&mut rng).expect("Bob P-256 keygen");
624
625 let kex = EcdhP256;
626 let mut alice_shared = [0u8; 32];
627 kex.agree(alice_sk.as_bytes(), &bob_pk, &mut alice_shared)
628 .expect("Alice P-256 agree");
629
630 let mut bob_shared = [0u8; 32];
631 kex.agree(bob_sk.as_bytes(), &alice_pk, &mut bob_shared)
632 .expect("Bob P-256 agree");
633
634 assert_eq!(
635 alice_shared, bob_shared,
636 "ecdh_p256_keygen_agree: secrets must match"
637 );
638 assert_ne!(alice_shared, [0u8; 32]);
639 }
640
641 fn p384_keypair(scalar_bytes: &[u8; 48]) -> (Vec<u8>, Vec<u8>) {
644 let sk = p384::SecretKey::from_slice(scalar_bytes).expect("valid P-384 scalar");
645 let pk = sk.public_key();
646 (scalar_bytes.to_vec(), pk.to_sec1_bytes().to_vec())
647 }
648
649 #[test]
650 fn ecdh_p384_both_parties_agree() {
651 let alice_scalar: [u8; 48] = {
652 let mut s = [0u8; 48];
653 s[0] = 0x01;
654 s[47] = 0x01;
655 s
656 };
657 let bob_scalar: [u8; 48] = {
658 let mut s = [0u8; 48];
659 s[0] = 0x02;
660 s[47] = 0x02;
661 s
662 };
663 let (_alice_sk, alice_pk) = p384_keypair(&alice_scalar);
664 let (_bob_sk, bob_pk) = p384_keypair(&bob_scalar);
665
666 let kex = EcdhP384;
667 let mut alice_shared = [0u8; 48];
668 kex.agree(&alice_scalar, &bob_pk, &mut alice_shared)
669 .expect("Alice ECDH-P384 agree failed");
670
671 let mut bob_shared = [0u8; 48];
672 kex.agree(&bob_scalar, &alice_pk, &mut bob_shared)
673 .expect("Bob ECDH-P384 agree failed");
674
675 assert_eq!(
676 alice_shared, bob_shared,
677 "ECDH-P384: Alice and Bob must derive the same shared secret"
678 );
679 assert_ne!(alice_shared, [0u8; 48]);
680 }
681
682 #[test]
683 fn ecdh_p384_invalid_key() {
684 let kex = EcdhP384;
685 let mut shared = [0u8; 48];
686 let result = kex.agree(&[0u8; 16], &[0u8; 49], &mut shared);
687 assert_eq!(result, Err(CryptoError::InvalidKey));
688 }
689
690 #[test]
692 fn ecdh_p384_keygen_agree() {
693 let mut rng = test_rng();
694 let (alice_sk, alice_pk) =
695 ecdh_p384_generate_keypair(&mut rng).expect("Alice P-384 keygen");
696 let (bob_sk, bob_pk) = ecdh_p384_generate_keypair(&mut rng).expect("Bob P-384 keygen");
697
698 let kex = EcdhP384;
699 let mut alice_shared = [0u8; 48];
700 kex.agree(alice_sk.as_bytes(), &bob_pk, &mut alice_shared)
701 .expect("Alice P-384 agree");
702
703 let mut bob_shared = [0u8; 48];
704 kex.agree(bob_sk.as_bytes(), &alice_pk, &mut bob_shared)
705 .expect("Bob P-384 agree");
706
707 assert_eq!(
708 alice_shared, bob_shared,
709 "ecdh_p384_keygen_agree: secrets must match"
710 );
711 assert_ne!(alice_shared, [0u8; 48]);
712 }
713
714 fn p521_keypair_from_scalar(scalar_bytes: &[u8; 66]) -> (Vec<u8>, Vec<u8>) {
717 let sk = p521::SecretKey::from_slice(scalar_bytes).expect("valid P-521 scalar");
718 let pk = sk.public_key();
719 (scalar_bytes.to_vec(), pk.to_sec1_bytes().to_vec())
720 }
721
722 #[test]
724 fn ecdh_p521_both_parties_agree() {
725 let alice_scalar: [u8; 66] = {
730 let mut s = [0u8; 66];
731 s[63] = 0xAB;
732 s[64] = 0xCD;
733 s[65] = 0x01;
734 s
735 };
736 let bob_scalar: [u8; 66] = {
737 let mut s = [0u8; 66];
738 s[63] = 0x12;
739 s[64] = 0x34;
740 s[65] = 0x56;
741 s
742 };
743 let (_alice_sk, alice_pk) = p521_keypair_from_scalar(&alice_scalar);
744 let (_bob_sk, bob_pk) = p521_keypair_from_scalar(&bob_scalar);
745
746 let kex = EcdhP521;
747 let mut alice_shared = [0u8; 66];
748 kex.agree(&alice_scalar, &bob_pk, &mut alice_shared)
749 .expect("Alice ECDH-P521 agree failed");
750
751 let mut bob_shared = [0u8; 66];
752 kex.agree(&bob_scalar, &alice_pk, &mut bob_shared)
753 .expect("Bob ECDH-P521 agree failed");
754
755 assert_eq!(
756 alice_shared, bob_shared,
757 "ECDH-P521: Alice and Bob must derive the same shared secret"
758 );
759 assert_ne!(alice_shared, [0u8; 66]);
760 }
761
762 #[test]
764 fn ecdh_p521_buffer_too_small() {
765 let alice_scalar: [u8; 66] = {
766 let mut s = [0u8; 66];
767 s[63] = 0xAB;
768 s[64] = 0xCD;
769 s[65] = 0x01;
770 s
771 };
772 let (_alice_sk, alice_pk) = p521_keypair_from_scalar(&alice_scalar);
773 let kex = EcdhP521;
774 let mut shared = [0u8; 32]; let result = kex.agree(&alice_scalar, &alice_pk, &mut shared);
776 assert_eq!(result, Err(CryptoError::BufferTooSmall));
777 }
778
779 #[test]
781 fn ecdh_p521_keygen_agree() {
782 let mut rng = test_rng();
783 let (alice_sk, alice_pk) =
784 ecdh_p521_generate_keypair(&mut rng).expect("Alice P-521 keygen");
785 let (bob_sk, bob_pk) = ecdh_p521_generate_keypair(&mut rng).expect("Bob P-521 keygen");
786
787 let kex = EcdhP521;
788 let mut alice_shared = [0u8; 66];
789 kex.agree(alice_sk.as_bytes(), &bob_pk, &mut alice_shared)
790 .expect("Alice P-521 agree");
791
792 let mut bob_shared = [0u8; 66];
793 kex.agree(bob_sk.as_bytes(), &alice_pk, &mut bob_shared)
794 .expect("Bob P-521 agree");
795
796 assert_eq!(
797 alice_shared, bob_shared,
798 "ecdh_p521_keygen_agree: secrets must match"
799 );
800 assert_ne!(alice_shared, [0u8; 66]);
801 }
802
803 #[test]
807 fn x448_both_parties_agree() {
808 let kex = X448;
809 let mut rng = test_rng();
810 let (alice_sk, alice_pk) = x448_generate_keypair(&mut rng).expect("Alice X448 keygen");
811 let (bob_sk, bob_pk) = x448_generate_keypair(&mut rng).expect("Bob X448 keygen");
812
813 let mut alice_shared = [0u8; 56];
814 kex.agree(alice_sk.as_bytes(), &bob_pk, &mut alice_shared)
815 .expect("Alice X448 agree");
816
817 let mut bob_shared = [0u8; 56];
818 kex.agree(bob_sk.as_bytes(), &alice_pk, &mut bob_shared)
819 .expect("Bob X448 agree");
820
821 assert_eq!(
822 alice_shared, bob_shared,
823 "X448: Alice and Bob must derive the same shared secret"
824 );
825 assert_ne!(alice_shared, [0u8; 56]);
826 }
827
828 #[test]
830 fn x448_metadata() {
831 let kex = X448;
832 assert_eq!(kex.name(), "X448");
833 assert_eq!(kex.scalar_len(), 56);
834 assert_eq!(kex.point_len(), 56);
835 assert_eq!(kex.shared_secret_len(), 56);
836 }
837
838 #[test]
840 fn x448_agree_to_vec_matches_agree() {
841 let kex = X448;
842 let mut rng = test_rng();
843 let (alice_sk, _) = x448_generate_keypair(&mut rng).expect("Alice X448 keygen");
844 let (_, bob_pk) = x448_generate_keypair(&mut rng).expect("Bob X448 keygen");
845
846 let mut shared_fixed = [0u8; 56];
847 kex.agree(alice_sk.as_bytes(), &bob_pk, &mut shared_fixed)
848 .expect("agree failed");
849
850 let shared_vec = kex
851 .agree_to_vec(alice_sk.as_bytes(), &bob_pk)
852 .expect("agree_to_vec failed");
853
854 assert_eq!(shared_fixed.as_slice(), shared_vec.as_slice());
855 assert_eq!(shared_vec.len(), 56);
856 }
857
858 #[test]
860 fn x448_invalid_secret_length() {
861 let kex = X448;
862 let mut shared = [0u8; 56];
863 let result = kex.agree(&[0u8; 32], &[5u8; 56], &mut shared);
864 assert_eq!(result, Err(CryptoError::InvalidKey));
865 }
866
867 #[test]
869 fn x448_invalid_public_length() {
870 let kex = X448;
871 let mut shared = [0u8; 56];
872 let result = kex.agree(&[0u8; 56], &[5u8; 32], &mut shared);
874 assert_eq!(result, Err(CryptoError::InvalidKey));
875 }
876
877 #[test]
879 fn x448_buffer_too_small() {
880 let kex = X448;
881 let mut rng = test_rng();
882 let (alice_sk, _) = x448_generate_keypair(&mut rng).expect("keygen");
883 let (_, bob_pk) = x448_generate_keypair(&mut rng).expect("keygen");
884 let mut shared = [0u8; 32];
885 let result = kex.agree(alice_sk.as_bytes(), &bob_pk, &mut shared);
886 assert_eq!(result, Err(CryptoError::BufferTooSmall));
887 }
888
889 #[test]
891 fn x448_zero_public_key_rejection() {
892 let kex = X448;
893 let mut rng = test_rng();
894 let (alice_sk, _) = x448_generate_keypair(&mut rng).expect("keygen");
895 let zero_pk = [0u8; 56];
896 let mut shared = [0u8; 56];
897 let result = kex.agree(alice_sk.as_bytes(), &zero_pk, &mut shared);
898 assert_eq!(
899 result,
900 Err(CryptoError::Kex),
901 "X448 must reject all-zero (low-order) public key"
902 );
903 }
904}