1use core::fmt;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14use zeroize::{Zeroize, ZeroizeOnDrop};
15
16use crate::classic::crypto_box::crypto_box_seed_keypair_inplace;
17use crate::constants::{
18 CRYPTO_BOX_BEFORENMBYTES, CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES,
19 CRYPTO_BOX_SEEDBYTES,
20};
21use crate::error::Error;
22use crate::precalc::PrecalcSecretKey;
23use crate::types::*;
24use crate::utils::ct_eq_bytes;
25
26pub type PublicKey = StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>;
28pub type SecretKey = StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>;
30pub type StackKeyPair = KeyPair<PublicKey, SecretKey>;
32
33#[derive(Zeroize, ZeroizeOnDrop, Clone)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35pub struct KeyPair<
42 PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
43 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
44> {
45 pub public_key: PublicKey,
47 pub secret_key: SecretKey,
49}
50
51impl<
52 PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
53 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
54> fmt::Debug for KeyPair<PublicKey, SecretKey>
55{
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.debug_struct("KeyPair")
58 .field("public_key", &"[REDACTED]")
59 .field("secret_key", &"[REDACTED]")
60 .finish()
61 }
62}
63
64impl<
65 PublicKey: NewByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
66 SecretKey: NewByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
67> KeyPair<PublicKey, SecretKey>
68{
69 #[must_use]
71 pub fn generate() -> Self {
72 use crate::classic::crypto_box::crypto_box_keypair_inplace;
73
74 let mut public_key = PublicKey::new_byte_array();
75 let mut secret_key = SecretKey::new_byte_array();
76 crypto_box_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
77
78 Self {
79 public_key,
80 secret_key,
81 }
82 }
83
84 #[must_use]
87 pub fn from_secret_key(secret_key: SecretKey) -> Self {
88 use crate::classic::crypto_core::crypto_scalarmult_base;
89
90 let mut public_key = PublicKey::new_byte_array();
91 crypto_scalarmult_base(public_key.as_mut_array(), secret_key.as_array());
92
93 Self {
94 public_key,
95 secret_key,
96 }
97 }
98
99 #[must_use]
101 pub fn from_seed<Seed: ByteArray<CRYPTO_BOX_SEEDBYTES>>(seed: &Seed) -> Self {
102 let mut public_key = PublicKey::new_byte_array();
103 let mut secret_key = SecretKey::new_byte_array();
104
105 crypto_box_seed_keypair_inplace(
106 public_key.as_mut_array(),
107 secret_key.as_mut_array(),
108 seed.as_array(),
109 );
110
111 Self {
112 public_key,
113 secret_key,
114 }
115 }
116}
117
118impl<
119 'a,
120 PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + core::convert::TryFrom<&'a [u8]> + Zeroize,
121 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + core::convert::TryFrom<&'a [u8]> + Zeroize,
122> KeyPair<PublicKey, SecretKey>
123{
124 pub fn from_slices(public_key: &'a [u8], secret_key: &'a [u8]) -> Result<Self, Error> {
132 validate_length!(
133 exact CRYPTO_BOX_PUBLICKEYBYTES,
134 public_key.len(),
135 crate::ErrorContext::PublicKey
136 );
137 validate_length!(
138 exact CRYPTO_BOX_SECRETKEYBYTES,
139 secret_key.len(),
140 crate::ErrorContext::SecretKey
141 );
142
143 Ok(Self {
144 public_key: PublicKey::try_from(public_key)
145 .map_err(|_| Error::invalid_key(crate::ErrorContext::PublicKey))?,
146 secret_key: SecretKey::try_from(secret_key)
147 .map_err(|_| Error::invalid_key(crate::ErrorContext::SecretKey))?,
148 })
149 }
150}
151
152#[must_use]
188pub fn is_valid_public_key<PK: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(key: &PK) -> bool {
189 let scalar = [0u8; CRYPTO_BOX_SECRETKEYBYTES];
190 let mut shared_secret = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
191
192 crate::classic::crypto_core::crypto_scalarmult(&mut shared_secret, &scalar, key.as_array())
193 .is_ok()
194}
195
196impl<
197 PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
198 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
199> KeyPair<PublicKey, SecretKey>
200{
201 #[inline]
211 pub fn precalculate<OtherPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(
212 &self,
213 third_party_public_key: &OtherPublicKey,
214 ) -> Result<PrecalcSecretKey<StackByteArray<CRYPTO_BOX_BEFORENMBYTES>>, Error> {
215 PrecalcSecretKey::precalculate(third_party_public_key, &self.secret_key)
216 }
217}
218
219#[cfg(any(
220 all(feature = "protected", any(unix, windows)),
221 all(doc, not(doctest), feature = "std")
222))]
223#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
224mod protected {
225 use super::*;
227 use crate::classic::crypto_box::crypto_box_keypair_inplace;
228 use crate::protected::*;
229
230 impl
231 KeyPair<
232 Locked<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
233 Locked<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
234 >
235 {
236 pub fn generate_locked_keypair() -> Result<Self, Error> {
249 let mut res = Self {
250 public_key: HeapByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::new_locked()?,
251 secret_key: HeapByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::new_locked()?,
252 };
253
254 crypto_box_keypair_inplace(
255 res.public_key.as_mut_array(),
256 res.secret_key.as_mut_array(),
257 );
258
259 Ok(res)
260 }
261
262 #[inline]
278 pub fn precalculate_locked<OtherPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(
279 &self,
280 third_party_public_key: &OtherPublicKey,
281 ) -> Result<PrecalcSecretKey<Locked<HeapByteArray<CRYPTO_BOX_BEFORENMBYTES>>>, Error>
282 {
283 PrecalcSecretKey::precalculate_locked(third_party_public_key, &self.secret_key)
284 }
285 }
286
287 impl
288 KeyPair<
289 LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
290 LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
291 >
292 {
293 pub fn generate_readonly_locked_keypair() -> Result<Self, Error> {
306 let mut public_key = HeapByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::new_locked()?;
307 let mut secret_key = HeapByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::new_locked()?;
308
309 crypto_box_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
310
311 let public_key = public_key.mprotect_readonly()?;
312 let secret_key = secret_key.mprotect_readonly()?;
313
314 Ok(Self {
315 public_key,
316 secret_key,
317 })
318 }
319
320 #[inline]
337 pub fn precalculate_readonly_locked<
338 OtherPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
339 >(
340 &self,
341 third_party_public_key: &OtherPublicKey,
342 ) -> Result<PrecalcSecretKey<LockedRO<HeapByteArray<CRYPTO_BOX_BEFORENMBYTES>>>, Error>
343 {
344 PrecalcSecretKey::precalculate_readonly_locked(third_party_public_key, &self.secret_key)
345 }
346 }
347}
348
349impl<
350 PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
351 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
352> PartialEq<KeyPair<PublicKey, SecretKey>> for KeyPair<PublicKey, SecretKey>
353{
354 fn eq(&self, other: &Self) -> bool {
355 ct_eq_bytes(self.public_key.as_slice(), other.public_key.as_slice())
356 && ct_eq_bytes(self.secret_key.as_slice(), other.secret_key.as_slice())
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn keypair_debug_redacts_keys() {
366 let keypair = StackKeyPair::generate();
367 let debug = format!("{keypair:?}");
368
369 assert_eq!(
370 debug,
371 "KeyPair { public_key: \"[REDACTED]\", secret_key: \"[REDACTED]\" }"
372 );
373 }
374
375 #[test]
376 fn test_from_secret_key() {
377 let keypair_1 = KeyPair::<
378 StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
379 StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
380 >::generate();
381 let keypair_2 = KeyPair::from_secret_key(keypair_1.secret_key.clone());
382
383 assert_eq!(keypair_1.public_key, keypair_2.public_key);
384 }
385
386 const SEED_KEYPAIRS: [([u8; CRYPTO_BOX_SEEDBYTES], &str, &str); 2] = [
389 (
390 [1u8; CRYPTO_BOX_SEEDBYTES],
391 "1b1b58dd50ea14b60da17b790cd02754d970c9bab864ebb3c0f3016fe51d3f57",
392 "5ce86efb75fa4e2c410f46e16de9f6acae1a1703528651b69bc176c088bef3ee",
393 ),
394 (
395 [2u8; CRYPTO_BOX_SEEDBYTES],
396 "60346e7c911a5f6ba154129174cafe75b294ac3bbd5549632f48cec6266f8410",
397 "aa3c626bc9c38c8c201878ebb1d5b0b50ac40e8986c78793db1d4ef369fca1ce",
398 ),
399 ];
400
401 const ALICE_SK: &str = "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a";
404 const ALICE_PK: &str = "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a";
405 const BOB_SK: &str = "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb";
406 const BOB_PK: &str = "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f";
407 const SHARED_KEY: &str = "1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389";
408
409 fn keypair_from_hex(public_key: &str, secret_key: &str) -> StackKeyPair {
410 KeyPair::from_slices(
411 &hex::decode(public_key).expect("hex"),
412 &hex::decode(secret_key).expect("hex"),
413 )
414 .expect("keypair")
415 }
416
417 #[test]
418 fn from_seed_matches_libsodium_and_classic_seed_keypair() {
419 for (seed, public_key, secret_key) in SEED_KEYPAIRS {
420 let keypair: StackKeyPair = KeyPair::from_seed(&seed);
421 assert_eq!(keypair, keypair_from_hex(public_key, secret_key));
422
423 let (classic_pk, classic_sk) =
424 crate::classic::crypto_box::crypto_box_seed_keypair(&seed);
425 assert_eq!(keypair.public_key.as_array(), &classic_pk);
426 assert_eq!(keypair.secret_key.as_array(), &classic_sk);
427
428 assert_eq!(
429 KeyPair::from_secret_key(keypair.secret_key.clone()),
430 keypair
431 );
432 assert!(is_valid_public_key(&keypair.public_key));
433 }
434 assert_ne!(
435 StackKeyPair::from_seed(&SEED_KEYPAIRS[0].0),
436 StackKeyPair::from_seed(&SEED_KEYPAIRS[1].0)
437 );
438 }
439
440 #[test]
441 fn generated_public_key_is_the_base_point_multiple_of_the_secret_key() {
442 use crate::classic::crypto_core::crypto_scalarmult_base;
443
444 let keypair = StackKeyPair::generate();
445 let mut public_key = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
446 crypto_scalarmult_base(&mut public_key, keypair.secret_key.as_array());
447 assert_eq!(keypair.public_key.as_array(), &public_key);
448 assert_eq!(
449 KeyPair::from_secret_key(keypair.secret_key.clone()),
450 keypair
451 );
452 }
453
454 #[test]
455 fn from_slices_accepts_exact_lengths_and_reports_the_short_side() {
456 let alice = keypair_from_hex(ALICE_PK, ALICE_SK);
457 assert_eq!(
458 alice.public_key.as_slice(),
459 hex::decode(ALICE_PK).expect("hex")
460 );
461 assert_eq!(
462 alice.secret_key.as_slice(),
463 hex::decode(ALICE_SK).expect("hex")
464 );
465 assert_eq!(
466 StackKeyPair::from_secret_key(alice.secret_key.clone()).public_key,
467 alice.public_key
468 );
469
470 for len in [
471 0,
472 CRYPTO_BOX_PUBLICKEYBYTES - 1,
473 CRYPTO_BOX_PUBLICKEYBYTES + 1,
474 ] {
475 assert!(matches!(
476 StackKeyPair::from_slices(&vec![0u8; len], alice.secret_key.as_slice()),
477 Err(Error::InvalidLength {
478 context: crate::ErrorContext::PublicKey,
479 actual,
480 ..
481 }) if actual == len
482 ));
483 }
484 for len in [
485 0,
486 CRYPTO_BOX_SECRETKEYBYTES - 1,
487 CRYPTO_BOX_SECRETKEYBYTES + 1,
488 ] {
489 assert!(matches!(
490 StackKeyPair::from_slices(alice.public_key.as_slice(), &vec![0u8; len]),
491 Err(Error::InvalidLength {
492 context: crate::ErrorContext::SecretKey,
493 actual,
494 ..
495 }) if actual == len
496 ));
497 }
498 }
499
500 #[test]
501 fn precalculate_matches_nacl_shared_key() {
502 let alice = keypair_from_hex(ALICE_PK, ALICE_SK);
503 let bob = keypair_from_hex(BOB_PK, BOB_SK);
504 let expected = hex::decode(SHARED_KEY).expect("hex");
505
506 let from_alice = alice.precalculate(&bob.public_key).expect("precalc");
507 let from_bob = bob.precalculate(&alice.public_key).expect("precalc");
508 assert_eq!(from_alice.as_slice(), expected.as_slice());
509 assert_eq!(from_alice, from_bob);
510 assert!(alice.precalculate(&PublicKey::default()).is_err());
511 }
512
513 #[cfg(all(feature = "protected", any(unix, windows)))]
514 #[test]
515 fn locked_precalculate_matches_nacl_shared_key() {
516 use crate::protected::*;
517
518 let alice = keypair_from_hex(ALICE_PK, ALICE_SK);
519 let bob = keypair_from_hex(BOB_PK, BOB_SK);
520 let expected = hex::decode(SHARED_KEY).expect("hex");
521 let locked_alice = KeyPair {
522 public_key: HeapByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::from_slice_into_locked(
523 alice.public_key.as_slice(),
524 )
525 .expect("lock pk"),
526 secret_key: HeapByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::from_slice_into_locked(
527 alice.secret_key.as_slice(),
528 )
529 .expect("lock sk"),
530 };
531
532 let locked = locked_alice
533 .precalculate_locked(&bob.public_key)
534 .expect("precalc locked");
535 assert_eq!(locked.as_slice(), expected.as_slice());
536 assert!(
537 locked_alice
538 .precalculate_locked(&PublicKey::default())
539 .is_err()
540 );
541 }
542
543 #[cfg(all(feature = "serde", feature = "alloc"))]
544 #[test]
545 fn serde_round_trip_keeps_the_keypair_usable_for_boxes() {
546 use crate::dryocbox::{DryocBox, Nonce, VecBox};
547
548 let alice = keypair_from_hex(ALICE_PK, ALICE_SK);
549 let bob = keypair_from_hex(BOB_PK, BOB_SK);
550
551 let json = serde_json::to_string(&bob).expect("serialize");
552 let decoded: StackKeyPair = serde_json::from_str(&json).expect("deserialize");
553 assert_eq!(decoded, bob);
554
555 let nonce = Nonce::from([3u8; crate::constants::CRYPTO_BOX_NONCEBYTES]);
556 let dryocbox =
557 DryocBox::encrypt_to_vecbox(b"for bob", &nonce, &decoded.public_key, &alice.secret_key)
558 .expect("encrypt");
559 assert_eq!(
560 VecBox::from_bytes(&dryocbox.to_vec())
561 .expect("parse")
562 .decrypt_to_vec(&nonce, &alice.public_key, &decoded.secret_key)
563 .expect("decrypt"),
564 b"for bob"
565 );
566
567 let swapped = json
570 .replacen("public_key", "tmp", 1)
571 .replacen("secret_key", "public_key", 1)
572 .replacen("tmp", "secret_key", 1);
573 let swapped: StackKeyPair = serde_json::from_str(&swapped).expect("deserialize");
574 assert_ne!(swapped, bob);
575 assert!(
576 dryocbox
577 .decrypt_to_vec(&nonce, &alice.public_key, &swapped.secret_key)
578 .is_err()
579 );
580 }
581
582 #[test]
583 fn test_is_valid_public_key() {
584 let valid_pk_bytes = [
587 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114,
588 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26,
589 ];
590 let valid_pk = PublicKey::from(valid_pk_bytes);
591 assert!(
592 is_valid_public_key(&valid_pk),
593 "Known valid key failed validation"
594 );
595
596 let mut high_bit_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
599 high_bit_bytes[0] = 9;
600 high_bit_bytes[31] = 0x80;
601 let high_bit = PublicKey::from(high_bit_bytes);
602 assert!(
603 is_valid_public_key(&high_bit),
604 "RFC 7748 high-bit encoding should be accepted"
605 );
606
607 let zero_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
609 let zero_pk = PublicKey::from(zero_bytes);
610 assert!(!is_valid_public_key(&zero_pk), "Zero key should be invalid");
611
612 let mut identity_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
613 identity_bytes[0] = 1;
614 let identity = PublicKey::from(identity_bytes);
615 assert!(
616 !is_valid_public_key(&identity),
617 "Low-order key should be invalid"
618 );
619
620 let kp = StackKeyPair::generate();
622 assert!(
623 is_valid_public_key(&kp.public_key),
624 "Generated key failed validation"
625 );
626 }
627
628 #[cfg(dryoc_native_tests)]
629 mod native_tests {
630 use super::*;
631
632 #[test]
633 fn test_gen_keypair() {
634 use crate::classic::crypto_core::crypto_scalarmult_base;
635 use crate::native_test_util::scalarmult_curve25519_base;
636
637 let keypair = KeyPair::<
638 StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
639 StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
640 >::generate();
641
642 let mut public_key = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
643 crypto_scalarmult_base(&mut public_key, keypair.secret_key.as_array());
644
645 assert_eq!(keypair.public_key.as_array(), &public_key);
646
647 let ge = scalarmult_curve25519_base(&keypair.secret_key);
648
649 assert_eq!(ge, public_key);
650 }
651 }
652}