Skip to main content

dryoc/
keypair.rs

1//! # Public/secret keypair tools
2//!
3//! Provides an implementation for handling public/private keypairs based on
4//! libsodium's crypto_box, which uses X25519.
5//!
6//! Refer to the [protected] mod for details on usage with protected memory.
7
8use std::fmt;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12use subtle::ConstantTimeEq;
13use zeroize::{Zeroize, ZeroizeOnDrop};
14
15use crate::classic::crypto_box::crypto_box_seed_keypair_inplace;
16use crate::constants::{
17    CRYPTO_BOX_BEFORENMBYTES, CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES,
18    CRYPTO_BOX_SEEDBYTES, CRYPTO_KX_SESSIONKEYBYTES,
19};
20use crate::error::Error;
21use crate::kx;
22use crate::precalc::PrecalcSecretKey;
23use crate::types::*;
24
25/// Stack-allocated public key type alias.
26pub type PublicKey = StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>;
27/// Stack-allocated secret key type alias.
28pub type SecretKey = StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>;
29/// Stack-allocated key pair type alias.
30pub type StackKeyPair = KeyPair<PublicKey, SecretKey>;
31
32#[cfg_attr(
33    feature = "serde",
34    derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize, Clone)
35)]
36#[cfg_attr(not(feature = "serde"), derive(Zeroize, ZeroizeOnDrop, Clone))]
37/// Public/secret keypair for use with [`crate::dryocbox::DryocBox`] and
38/// libsodium-compatible public-key encryption.
39pub struct KeyPair<
40    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
41    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
42> {
43    /// Public key
44    pub public_key: PublicKey,
45    /// Secret key
46    pub secret_key: SecretKey,
47}
48
49impl<
50    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
51    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
52> fmt::Debug for KeyPair<PublicKey, SecretKey>
53{
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        f.debug_struct("KeyPair")
56            .field("public_key", &"[REDACTED]")
57            .field("secret_key", &"[REDACTED]")
58            .finish()
59    }
60}
61
62impl<
63    PublicKey: NewByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
64    SecretKey: NewByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
65> KeyPair<PublicKey, SecretKey>
66{
67    /// Creates a new, empty keypair.
68    pub fn new() -> Self {
69        Self {
70            public_key: PublicKey::new_byte_array(),
71            secret_key: SecretKey::new_byte_array(),
72        }
73    }
74
75    /// Generates a random keypair.
76    pub fn generate() -> Self {
77        use crate::classic::crypto_box::crypto_box_keypair_inplace;
78
79        let mut public_key = PublicKey::new_byte_array();
80        let mut secret_key = SecretKey::new_byte_array();
81        crypto_box_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
82
83        Self {
84            public_key,
85            secret_key,
86        }
87    }
88
89    /// Generates a random keypair.
90    ///
91    /// Prefer [`generate`](Self::generate). `gen` is retained for compatibility
92    /// with older Rust editions.
93    #[deprecated(note = "use generate() instead")]
94    pub fn r#gen() -> Self {
95        Self::generate()
96    }
97
98    /// Derives the public key for `secret_key` and returns the complete
99    /// keypair, consuming the secret key.
100    pub fn from_secret_key(secret_key: SecretKey) -> Self {
101        use crate::classic::crypto_core::crypto_scalarmult_base;
102
103        let mut public_key = PublicKey::new_byte_array();
104        crypto_scalarmult_base(public_key.as_mut_array(), secret_key.as_array());
105
106        Self {
107            public_key,
108            secret_key,
109        }
110    }
111
112    /// Deterministically derives a keypair from `seed`.
113    pub fn from_seed<Seed: ByteArray<CRYPTO_BOX_SEEDBYTES>>(seed: &Seed) -> Self {
114        let mut public_key = PublicKey::new_byte_array();
115        let mut secret_key = SecretKey::new_byte_array();
116
117        crypto_box_seed_keypair_inplace(
118            public_key.as_mut_array(),
119            secret_key.as_mut_array(),
120            seed.as_array(),
121        );
122
123        Self {
124            public_key,
125            secret_key,
126        }
127    }
128}
129
130impl KeyPair<StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>, StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>> {
131    /// Randomly generates a new keypair, using default types
132    /// (stack-allocated byte arrays). Provided for convenience.
133    pub fn generate_with_defaults() -> Self {
134        Self::generate()
135    }
136
137    /// Randomly generates a new keypair, using default types
138    /// (stack-allocated byte arrays). Provided for convenience.
139    ///
140    /// Prefer [`generate_with_defaults`](Self::generate_with_defaults). This
141    /// method is retained for compatibility.
142    #[deprecated(note = "use generate_with_defaults() instead")]
143    pub fn gen_with_defaults() -> Self {
144        Self::generate_with_defaults()
145    }
146}
147
148impl<
149    'a,
150    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + std::convert::TryFrom<&'a [u8]> + Zeroize,
151    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + std::convert::TryFrom<&'a [u8]> + Zeroize,
152> KeyPair<PublicKey, SecretKey>
153{
154    /// Constructs a new keypair from key slices, consuming them. Does not check
155    /// validity or authenticity of keypair.
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if either slice does not have the required key length,
160    /// or if the target key type rejects the key bytes.
161    pub fn from_slices(public_key: &'a [u8], secret_key: &'a [u8]) -> Result<Self, Error> {
162        validate_length!(
163            exact CRYPTO_BOX_PUBLICKEYBYTES,
164            public_key.len(),
165            crate::ErrorContext::PublicKey
166        );
167        validate_length!(
168            exact CRYPTO_BOX_SECRETKEYBYTES,
169            secret_key.len(),
170            crate::ErrorContext::SecretKey
171        );
172
173        Ok(Self {
174            public_key: PublicKey::try_from(public_key)
175                .map_err(|_| Error::invalid_key(crate::ErrorContext::PublicKey))?,
176            secret_key: SecretKey::try_from(secret_key)
177                .map_err(|_| Error::invalid_key(crate::ErrorContext::SecretKey))?,
178        })
179    }
180}
181
182impl<
183    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
184    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
185> KeyPair<PublicKey, SecretKey>
186{
187    /// Checks if the given public key is valid according to X25519 rules.
188    ///
189    /// For X25519 ([`crypto_box`](`crate::classic::crypto_box`),
190    /// [`DryocBox`](`crate::dryocbox::DryocBox`)), this performs a trial scalar
191    /// multiplication and rejects public keys that produce an all-zero shared
192    /// secret, including low-order inputs rejected by libsodium. As required by
193    /// RFC 7748, the high bit of the encoded public key is ignored.
194    ///
195    /// ## Validating Protected Keys
196    ///
197    /// You can validate keys stored in protected memory directly, as the
198    /// validation functions operate on references.
199    ///
200    /// ```
201    /// # #![cfg_attr(not(all(feature = "protected", any(unix, windows))), ignore)]
202    /// # #[cfg(all(feature = "protected", any(unix, windows)))]
203    /// # {
204    /// use dryoc::constants::{CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES};
205    /// use dryoc::keypair::protected::{HeapByteArray, LockedRO};
206    /// use dryoc::keypair::{KeyPair, PublicKey, SecretKey};
207    ///
208    /// // Generate a keypair stored in locked, read-only memory
209    /// let protected_kp: KeyPair<
210    ///     LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
211    ///     LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
212    /// > = KeyPair::generate_readonly_locked_keypair().expect("Failed to generate locked keypair");
213    ///
214    /// // Validate the X25519 public key.
215    /// let is_x25519_valid = KeyPair::<
216    ///     LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
217    ///     LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
218    /// >::is_valid_public_key(&protected_kp.public_key);
219    ///
220    /// assert!(is_x25519_valid, "Protected X25519 key should be valid");
221    /// # }
222    /// ```
223    pub fn is_valid_public_key<PK: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(key: &PK) -> bool {
224        let scalar = [0u8; CRYPTO_BOX_SECRETKEYBYTES];
225        let mut shared_secret = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
226
227        crate::classic::crypto_core::crypto_scalarmult(&mut shared_secret, &scalar, key.as_array())
228            .is_ok()
229    }
230
231    /// Checks if the given key is a valid prime-order Ed25519 public key.
232    ///
233    /// The canonical compressed encoding is required. The high bit, which
234    /// encodes the sign of the x-coordinate, may legitimately be set.
235    ///
236    /// This is a strict prime-subgroup policy, not a generic Ed25519 signature
237    /// validity predicate. Use it when an application or point-arithmetic
238    /// protocol requires canonical, nonidentity, prime-order keys. Verify
239    /// signatures with
240    /// [`crypto_sign_verify_detached`](crate::classic::crypto_sign::crypto_sign_verify_detached)
241    /// instead; some signature profiles intentionally define different
242    /// point-acceptance rules.
243    /// `is_valid_public_key` should be used for X25519 keys used in crypto_box.
244    pub fn is_valid_ed25519_key<PK: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(key: &PK) -> bool {
245        crate::classic::crypto_core::crypto_core_ed25519_is_valid_point(key.as_array())
246    }
247
248    /// Creates new client session keys using this keypair and
249    /// `server_public_key`, assuming this keypair is for the client.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if `server_public_key` is unacceptable, including a
254    /// low-order point that would produce an all-zero shared secret.
255    pub fn kx_new_client_session<
256        SessionKey: NewByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop,
257    >(
258        &self,
259        server_public_key: &PublicKey,
260    ) -> Result<kx::Session<SessionKey>, Error> {
261        kx::Session::new_client(self, server_public_key)
262    }
263
264    /// Creates new server session keys using this keypair and
265    /// `client_public_key`, assuming this keypair is for the server.
266    ///
267    /// # Errors
268    ///
269    /// Returns an error if `client_public_key` is unacceptable, including a
270    /// low-order point that would produce an all-zero shared secret.
271    pub fn kx_new_server_session<
272        SessionKey: NewByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop,
273    >(
274        &self,
275        client_public_key: &PublicKey,
276    ) -> Result<kx::Session<SessionKey>, Error> {
277        kx::Session::new_server(self, client_public_key)
278    }
279
280    /// Computes a stack-allocated shared secret key using a secret key from
281    /// this keypair and `third_party_public_key`.
282    ///
283    /// Compatible with libsodium's `crypto_box_beforenm`.
284    ///
285    /// # Errors
286    ///
287    /// Returns an error if `third_party_public_key` is an unacceptable
288    /// low-order point.
289    #[inline]
290    pub fn precalculate(
291        &self,
292        third_party_public_key: &PublicKey,
293    ) -> Result<PrecalcSecretKey<StackByteArray<CRYPTO_BOX_BEFORENMBYTES>>, Error> {
294        PrecalcSecretKey::precalculate(third_party_public_key, &self.secret_key)
295    }
296}
297
298impl<
299    PublicKey: NewByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
300    SecretKey: NewByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
301> Default for KeyPair<PublicKey, SecretKey>
302{
303    fn default() -> Self {
304        Self::new()
305    }
306}
307
308#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
309#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
310pub mod protected {
311    //! # Protected memory for [`KeyPair`]
312    use super::*;
313    use crate::classic::crypto_box::crypto_box_keypair_inplace;
314    pub use crate::protected::*;
315
316    impl
317        KeyPair<
318            Locked<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
319            Locked<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
320        >
321    {
322        /// Returns a new zero-filled locked keypair.
323        ///
324        /// # Errors
325        ///
326        /// Returns [`Error::Io`] if either allocation cannot be locked,
327        /// commonly because the process has reached its locked-memory limit.
328        ///
329        /// # Panics
330        ///
331        /// Panics if either page-aligned allocation cannot be created or its
332        /// size cannot be represented with guard pages.
333        pub fn new_locked_keypair() -> Result<Self, Error> {
334            Ok(Self {
335                public_key: HeapByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::new_locked()?,
336                secret_key: HeapByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::new_locked()?,
337            })
338        }
339
340        /// Returns a new randomly generated locked keypair.
341        ///
342        /// # Errors
343        ///
344        /// Returns [`Error::Io`] if either allocation cannot be locked.
345        ///
346        /// # Panics
347        ///
348        /// Panics if either page-aligned allocation cannot be created, its
349        /// size cannot be represented with guard pages, or the operating
350        /// system's random number generator fails.
351        pub fn generate_locked_keypair() -> Result<Self, Error> {
352            let mut res = Self::new_locked_keypair()?;
353
354            crypto_box_keypair_inplace(
355                res.public_key.as_mut_array(),
356                res.secret_key.as_mut_array(),
357            );
358
359            Ok(res)
360        }
361
362        /// Returns a new randomly generated locked keypair.
363        ///
364        /// Prefer [`generate_locked_keypair`](Self::generate_locked_keypair).
365        /// This method is retained for compatibility.
366        ///
367        /// # Errors
368        ///
369        /// Returns the same errors as
370        /// [`generate_locked_keypair`](Self::generate_locked_keypair).
371        ///
372        /// # Panics
373        ///
374        /// Panics under the same conditions as
375        /// [`generate_locked_keypair`](Self::generate_locked_keypair).
376        #[deprecated(note = "use generate_locked_keypair() instead")]
377        pub fn gen_locked_keypair() -> Result<Self, Error> {
378            Self::generate_locked_keypair()
379        }
380
381        /// Computes a heap-allocated, page-aligned, locked shared secret key
382        /// using a secret key from this keypair and
383        /// `third_party_public_key`.
384        ///
385        /// Compatible with libsodium's `crypto_box_beforenm`.
386        ///
387        /// # Errors
388        ///
389        /// Returns an error if `third_party_public_key` is an unacceptable
390        /// low-order point or the shared-key allocation cannot be locked.
391        ///
392        /// # Panics
393        ///
394        /// Panics if the page-aligned shared-key allocation cannot be created
395        /// or its size cannot be represented with guard pages.
396        #[inline]
397        pub fn precalculate_locked<OtherPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(
398            &self,
399            third_party_public_key: &OtherPublicKey,
400        ) -> Result<PrecalcSecretKey<Locked<HeapByteArray<CRYPTO_BOX_BEFORENMBYTES>>>, Error>
401        {
402            PrecalcSecretKey::precalculate_locked(third_party_public_key, &self.secret_key)
403        }
404    }
405
406    impl
407        KeyPair<
408            LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
409            LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
410        >
411    {
412        /// Returns a new randomly generated locked, read-only keypair.
413        ///
414        /// # Errors
415        ///
416        /// Returns [`Error::Io`] if either allocation cannot be locked or its
417        /// page permissions cannot be changed to read-only.
418        ///
419        /// # Panics
420        ///
421        /// Panics if either page-aligned allocation cannot be created, its
422        /// size cannot be represented with guard pages, or the operating
423        /// system's random number generator fails.
424        pub fn generate_readonly_locked_keypair() -> Result<Self, Error> {
425            let mut public_key = HeapByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::new_locked()?;
426            let mut secret_key = HeapByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::new_locked()?;
427
428            crypto_box_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
429
430            let public_key = public_key.mprotect_readonly()?;
431            let secret_key = secret_key.mprotect_readonly()?;
432
433            Ok(Self {
434                public_key,
435                secret_key,
436            })
437        }
438
439        /// Returns a new randomly generated locked, read-only keypair.
440        ///
441        /// Prefer
442        /// [`generate_readonly_locked_keypair`](Self::generate_readonly_locked_keypair).
443        /// This method is retained for compatibility.
444        ///
445        /// # Errors
446        ///
447        /// Returns the same errors as
448        /// [`generate_readonly_locked_keypair`](Self::generate_readonly_locked_keypair).
449        ///
450        /// # Panics
451        ///
452        /// Panics under the same conditions as
453        /// [`generate_readonly_locked_keypair`](Self::generate_readonly_locked_keypair).
454        #[deprecated(note = "use generate_readonly_locked_keypair() instead")]
455        pub fn gen_readonly_locked_keypair() -> Result<Self, Error> {
456            Self::generate_readonly_locked_keypair()
457        }
458
459        /// Computes a heap-allocated, page-aligned, locked, read-only shared
460        /// secret key using a secret key from this keypair and
461        /// `third_party_public_key`.
462        ///
463        /// Compatible with libsodium's `crypto_box_beforenm`.
464        ///
465        /// # Errors
466        ///
467        /// Returns an error if `third_party_public_key` is an unacceptable
468        /// low-order point, the shared-key allocation cannot be locked, or its
469        /// page permissions cannot be changed to read-only.
470        ///
471        /// # Panics
472        ///
473        /// Panics if the page-aligned shared-key allocation cannot be created
474        /// or its size cannot be represented with guard pages.
475        #[inline]
476        pub fn precalculate_readonly_locked<
477            OtherPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
478        >(
479            &self,
480            third_party_public_key: &OtherPublicKey,
481        ) -> Result<PrecalcSecretKey<LockedRO<HeapByteArray<CRYPTO_BOX_BEFORENMBYTES>>>, Error>
482        {
483            PrecalcSecretKey::precalculate_readonly_locked(third_party_public_key, &self.secret_key)
484        }
485    }
486}
487
488impl<
489    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
490    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
491> PartialEq<KeyPair<PublicKey, SecretKey>> for KeyPair<PublicKey, SecretKey>
492{
493    fn eq(&self, other: &Self) -> bool {
494        self.public_key
495            .as_slice()
496            .ct_eq(other.public_key.as_slice())
497            .unwrap_u8()
498            == 1
499            && self
500                .secret_key
501                .as_slice()
502                .ct_eq(other.secret_key.as_slice())
503                .unwrap_u8()
504                == 1
505    }
506}
507
508#[cfg(test)]
509mod tests {
510
511    use super::*;
512    use crate::kx::Session;
513
514    #[test]
515    fn keypair_debug_redacts_keys() {
516        let keypair = StackKeyPair::generate();
517        let debug = format!("{keypair:?}");
518
519        assert_eq!(
520            debug,
521            "KeyPair { public_key: \"[REDACTED]\", secret_key: \"[REDACTED]\" }"
522        );
523    }
524
525    fn all_eq<T>(t: &[T], v: T) -> bool
526    where
527        T: PartialEq,
528    {
529        t.iter().all(|x| *x == v)
530    }
531
532    #[test]
533    fn test_new() {
534        let keypair = KeyPair::<
535            StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
536            StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
537        >::new();
538
539        assert!(all_eq(&keypair.public_key, 0));
540        assert!(all_eq(&keypair.secret_key, 0));
541    }
542
543    #[test]
544    fn test_default() {
545        let keypair = KeyPair::<
546            StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
547            StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
548        >::default();
549
550        assert!(all_eq(&keypair.public_key, 0));
551        assert!(all_eq(&keypair.secret_key, 0));
552    }
553
554    #[test]
555    fn test_from_secret_key() {
556        let keypair_1 = KeyPair::<
557            StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
558            StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
559        >::generate();
560        let keypair_2 = KeyPair::from_secret_key(keypair_1.secret_key.clone());
561
562        assert_eq!(keypair_1.public_key, keypair_2.public_key);
563    }
564
565    #[test]
566    fn test_keypair_precalculate() {
567        let kp1 = KeyPair::generate_with_defaults();
568        let kp2 = KeyPair::generate_with_defaults();
569        let precalc = kp1.precalculate(&kp2.public_key).unwrap();
570        assert_eq!(precalc.len(), crate::constants::CRYPTO_BOX_BEFORENMBYTES);
571    }
572
573    #[cfg(all(feature = "protected", any(unix, windows)))]
574    #[test]
575    fn test_keypair_precalculate_locked() {
576        use crate::keypair::protected::*;
577        let kp1 = KeyPair::generate_locked_keypair().unwrap();
578        let kp2 = KeyPair::generate_locked_keypair().unwrap();
579        let precalc = kp1.precalculate_locked(&kp2.public_key).unwrap();
580        assert_eq!(precalc.len(), crate::constants::CRYPTO_BOX_BEFORENMBYTES);
581    }
582
583    #[test]
584    fn test_keypair_kx_new_client_session() {
585        let server_kp = KeyPair::generate_with_defaults();
586        let client_kp = KeyPair::generate_with_defaults();
587        let session: Session<StackByteArray<CRYPTO_KX_SESSIONKEYBYTES>> = client_kp
588            .kx_new_client_session(&server_kp.public_key)
589            .unwrap();
590        assert_eq!(
591            session.rx_as_slice().len(),
592            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
593        );
594        assert_eq!(
595            session.tx_as_slice().len(),
596            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
597        );
598    }
599
600    #[test]
601    fn test_keypair_kx_new_server_session() {
602        let client_kp = KeyPair::generate_with_defaults();
603        let server_kp = KeyPair::generate_with_defaults();
604        let session: Session<StackByteArray<CRYPTO_KX_SESSIONKEYBYTES>> = server_kp
605            .kx_new_server_session(&client_kp.public_key)
606            .unwrap();
607        assert_eq!(
608            session.rx_as_slice().len(),
609            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
610        );
611        assert_eq!(
612            session.tx_as_slice().len(),
613            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
614        );
615    }
616
617    #[test]
618    fn test_keypair_from_seed() {
619        let seed = [42u8; 32];
620        let kp: StackKeyPair = KeyPair::from_seed(&seed);
621        assert!(!kp.public_key.iter().all(|x| *x == 0));
622    }
623
624    #[test]
625    fn test_keypair_generate_with_defaults() {
626        let kp = KeyPair::generate_with_defaults();
627        assert!(!kp.public_key.iter().all(|x| *x == 0));
628    }
629
630    #[test]
631    fn test_is_valid_public_key() {
632        // Known valid key (assuming it meets X25519 criteria)
633        // This specific key is also a valid Ed25519 key.
634        let valid_pk_bytes = [
635            215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114,
636            243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26,
637        ];
638        let valid_pk = PublicKey::from(valid_pk_bytes);
639        assert!(
640            KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&valid_pk),
641            "Known valid key failed validation"
642        );
643
644        // RFC 7748 requires the high bit to be ignored when decoding X25519
645        // public keys.
646        let mut high_bit_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
647        high_bit_bytes[0] = 9;
648        high_bit_bytes[31] = 0x80;
649        let high_bit = PublicKey::from(high_bit_bytes);
650        assert!(
651            KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&high_bit),
652            "RFC 7748 high-bit encoding should be accepted"
653        );
654
655        // Invalid: Zero point
656        let zero_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
657        let zero_pk = PublicKey::from(zero_bytes);
658        assert!(
659            !KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&zero_pk),
660            "Zero key should be invalid"
661        );
662
663        let mut identity_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
664        identity_bytes[0] = 1;
665        let identity = PublicKey::from(identity_bytes);
666        assert!(
667            !KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&identity),
668            "Low-order key should be invalid"
669        );
670
671        // Generated key should be valid
672        let kp = KeyPair::generate_with_defaults();
673        assert!(
674            KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&kp.public_key),
675            "Generated key failed validation"
676        );
677    }
678
679    #[test]
680    fn test_is_valid_ed25519_key() {
681        let (valid_pk, _) = crate::classic::crypto_sign::crypto_sign_keypair();
682        assert!(
683            KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&valid_pk),
684            "Ed25519 key from crypto_sign_keypair should pass validation"
685        );
686
687        let mut negative_basepoint =
688            curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
689        negative_basepoint[31] |= 0x80;
690        assert!(
691            KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&negative_basepoint),
692            "the Ed25519 x-coordinate sign bit should be accepted"
693        );
694
695        let zero_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
696        let zero_pk = PublicKey::from(zero_bytes);
697        assert!(
698            !KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&zero_pk),
699            "zero key should be invalid"
700        );
701
702        let identity_bytes = [
703            1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
704            0, 0, 0,
705        ];
706        let identity_pk = PublicKey::from(identity_bytes);
707        assert!(
708            !KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&identity_pk),
709            "identity element should be invalid"
710        );
711
712        let mut noncanonical_identity = [0xff; CRYPTO_BOX_PUBLICKEYBYTES];
713        noncanonical_identity[0] = 0xee;
714        noncanonical_identity[31] = 0x7f;
715        assert!(
716            !KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&noncanonical_identity),
717            "noncanonical identity encoding should be invalid"
718        );
719
720        let mut mixed_order = [0x99; CRYPTO_BOX_PUBLICKEYBYTES];
721        mixed_order[0] = 0x95;
722        assert!(
723            !KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&mixed_order),
724            "mixed-order Ed25519 key should fail the prime-subgroup policy"
725        );
726    }
727
728    #[cfg(dryoc_native_tests)]
729    mod native_tests {
730        use super::*;
731
732        #[test]
733        fn test_gen_keypair() {
734            use sodiumoxide::crypto::scalarmult::curve25519::{Scalar, scalarmult_base};
735
736            use crate::classic::crypto_core::crypto_scalarmult_base;
737
738            let keypair = KeyPair::<
739                StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
740                StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
741            >::generate();
742
743            let mut public_key = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
744            crypto_scalarmult_base(&mut public_key, keypair.secret_key.as_array());
745
746            assert_eq!(keypair.public_key.as_array(), &public_key);
747
748            let ge = scalarmult_base(&Scalar::from_slice(&keypair.secret_key).unwrap());
749
750            assert_eq!(ge.as_ref(), public_key);
751        }
752    }
753}