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
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10use subtle::ConstantTimeEq;
11use zeroize::{Zeroize, ZeroizeOnDrop};
12
13use crate::classic::crypto_box::crypto_box_seed_keypair_inplace;
14use crate::constants::{
15    CRYPTO_BOX_BEFORENMBYTES, CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES,
16    CRYPTO_KX_SESSIONKEYBYTES,
17};
18use crate::error::Error;
19use crate::kx;
20use crate::precalc::PrecalcSecretKey;
21use crate::types::*;
22
23/// Stack-allocated public key type alias.
24pub type PublicKey = StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>;
25/// Stack-allocated secret key type alias.
26pub type SecretKey = StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>;
27/// Stack-allocated key pair type alias.
28pub type StackKeyPair = KeyPair<PublicKey, SecretKey>;
29
30#[cfg_attr(
31    feature = "serde",
32    derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize, Debug, Clone)
33)]
34#[cfg_attr(not(feature = "serde"), derive(Zeroize, ZeroizeOnDrop, Debug, Clone))]
35/// Public/private keypair for use with [`crate::dryocbox::DryocBox`], aka
36/// libsodium box
37pub struct KeyPair<
38    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
39    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
40> {
41    /// Public key
42    pub public_key: PublicKey,
43    /// Secret key
44    pub secret_key: SecretKey,
45}
46
47impl<
48    PublicKey: NewByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
49    SecretKey: NewByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
50> KeyPair<PublicKey, SecretKey>
51{
52    /// Creates a new, empty keypair.
53    pub fn new() -> Self {
54        Self {
55            public_key: PublicKey::new_byte_array(),
56            secret_key: SecretKey::new_byte_array(),
57        }
58    }
59
60    /// Generates a random keypair.
61    pub fn gen() -> Self {
62        use crate::classic::crypto_box::crypto_box_keypair_inplace;
63
64        let mut public_key = PublicKey::new_byte_array();
65        let mut secret_key = SecretKey::new_byte_array();
66        crypto_box_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
67
68        Self {
69            public_key,
70            secret_key,
71        }
72    }
73
74    /// Derives a keypair from `secret_key`, and consumes it, and returns a new
75    /// keypair.
76    pub fn from_secret_key(secret_key: SecretKey) -> Self {
77        use crate::classic::crypto_core::crypto_scalarmult_base;
78
79        let mut public_key = PublicKey::new_byte_array();
80        crypto_scalarmult_base(public_key.as_mut_array(), secret_key.as_array());
81
82        Self {
83            public_key,
84            secret_key,
85        }
86    }
87
88    /// Derives a keypair from `seed`, returning
89    /// a new keypair.
90    pub fn from_seed<Seed: Bytes>(seed: &Seed) -> Self {
91        let mut public_key = PublicKey::new_byte_array();
92        let mut secret_key = SecretKey::new_byte_array();
93
94        crypto_box_seed_keypair_inplace(
95            public_key.as_mut_array(),
96            secret_key.as_mut_array(),
97            seed.as_slice(),
98        );
99
100        Self {
101            public_key,
102            secret_key,
103        }
104    }
105}
106
107impl KeyPair<StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>, StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>> {
108    /// Randomly generates a new keypair, using default types
109    /// (stack-allocated byte arrays). Provided for convenience.
110    pub fn gen_with_defaults() -> Self {
111        Self::gen()
112    }
113}
114
115impl<
116    'a,
117    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + std::convert::TryFrom<&'a [u8]> + Zeroize,
118    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + std::convert::TryFrom<&'a [u8]> + Zeroize,
119> KeyPair<PublicKey, SecretKey>
120{
121    /// Constructs a new keypair from key slices, consuming them. Does not check
122    /// validity or authenticity of keypair.
123    pub fn from_slices(public_key: &'a [u8], secret_key: &'a [u8]) -> Result<Self, Error> {
124        Ok(Self {
125            public_key: PublicKey::try_from(public_key)
126                .map_err(|_e| dryoc_error!("invalid public key"))?,
127            secret_key: SecretKey::try_from(secret_key)
128                .map_err(|_e| dryoc_error!("invalid secret key"))?,
129        })
130    }
131}
132
133impl<
134    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
135    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
136> KeyPair<PublicKey, SecretKey>
137{
138    /// Checks if the given public key is valid according to X25519 rules.
139    ///
140    /// For X25519 ([`crypto_box`](`crate::classic::crypto_box`),
141    /// [`DryocBox`](`crate::dryocbox::DryocBox`)), a public key is considered
142    /// valid if:
143    /// - It is not the all-zero point `[0, ..., 0]`.
144    /// - The high bit of the last byte is 0.
145    ///
146    /// This function verifies these conditions.
147    ///
148    /// **Note:** This validation is specific to X25519 keys used in
149    /// Diffie-Hellman key exchange (`crypto_box`). It primarily aims to
150    /// exclude degenerate keys and does **not** explicitly verify that the
151    /// point lies on the underlying curve, unlike stricter Ed25519 point
152    /// validation (see
153    /// [`crypto_core_ed25519_is_valid_point`](`crate::classic::crypto_core::crypto_core_ed25519_is_valid_point`)).
154    ///
155    /// ## Validating Protected Keys
156    ///
157    /// You can validate keys stored in protected memory directly, as the
158    /// validation functions operate on references.
159    ///
160    /// ```
161    /// # #![cfg_attr(not(feature = "nightly"), ignore)]
162    /// # #[cfg(feature = "nightly")]
163    /// # {
164    /// use dryoc::constants::{CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES};
165    /// use dryoc::keypair::protected::{HeapByteArray, LockedRO};
166    /// use dryoc::keypair::{KeyPair, PublicKey, SecretKey};
167    ///
168    /// // Generate a keypair stored in locked, read-only memory
169    /// let protected_kp: KeyPair<
170    ///     LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
171    ///     LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
172    /// > = KeyPair::gen_readonly_locked_keypair().expect("Failed to generate locked keypair");
173    ///
174    /// // Validate the Ed25519 public key using the relaxed rules appropriate for
175    /// // keys generated by crypto_sign_keypair (even though this is an X25519 keypair,
176    /// // the validation function itself can be called).
177    /// // Note: For an actual Ed25519 keypair from crypto_sign, you'd use the
178    /// // crypto_core_ed25519_is_valid_point_relaxed function directly.
179    /// // Here we demonstrate calling the KeyPair method.
180    /// let is_valid = KeyPair::<
181    ///     LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
182    ///     LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
183    /// >::is_valid_ed25519_key(&protected_kp.public_key);
184    ///
185    /// // For keys generated by crypto_sign_keypair, relaxed validation should pass.
186    /// // (This assertion might depend on the specific key generation details,
187    /// // but illustrates the call)
188    /// // assert!(is_valid, "Protected key should be valid (relaxed check)");
189    ///
190    /// // Similarly, validate the X25519 public key
191    /// let is_x25519_valid = KeyPair::<
192    ///     LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
193    ///     LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
194    /// >::is_valid_public_key(&protected_kp.public_key);
195    ///
196    /// assert!(is_x25519_valid, "Protected X25519 key should be valid");
197    /// # }
198    /// ```
199    pub fn is_valid_public_key<PK: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(key: &PK) -> bool {
200        const ZERO_POINT: [u8; CRYPTO_BOX_PUBLICKEYBYTES] = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
201        let key_array = key.as_array();
202
203        // Check 1: Not the all-zero point
204        if key_array == &ZERO_POINT {
205            return false;
206        }
207
208        // Check 2: High bit of the last byte must be 0
209        // Although clamping during generation usually ensures this, we check it.
210        if key_array[CRYPTO_BOX_PUBLICKEYBYTES - 1] & 0x80 != 0 {
211            return false;
212        }
213
214        // If both checks pass, it's considered a valid X25519 public key
215        // representation.
216        true
217    }
218
219    /// Checks if the given key is a valid Ed25519 public key, using relaxed
220    /// validation rules that allow the high bit to be set.
221    ///
222    /// For Ed25519 public keys, generated by `crypto_sign_keypair()`, we need
223    /// to use more permissive validation since these keys can have the high
224    /// bit set.
225    ///
226    /// This method should be used for validating Ed25519 keys (used in
227    /// signatures), while `is_valid_public_key` should be used for X25519
228    /// keys (used in crypto_box).
229    pub fn is_valid_ed25519_key<PK: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(key: &PK) -> bool {
230        crate::classic::crypto_core::crypto_core_ed25519_is_valid_point_relaxed(key.as_array())
231    }
232
233    /// Creates new client session keys using this keypair and
234    /// `server_public_key`, assuming this keypair is for the client.
235    pub fn kx_new_client_session<SessionKey: NewByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize>(
236        &self,
237        server_public_key: &PublicKey,
238    ) -> Result<kx::Session<SessionKey>, Error> {
239        kx::Session::new_client(self, server_public_key)
240    }
241
242    /// Creates new server session keys using this keypair and
243    /// `client_public_key`, assuming this keypair is for the server.
244    pub fn kx_new_server_session<SessionKey: NewByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize>(
245        &self,
246        client_public_key: &PublicKey,
247    ) -> Result<kx::Session<SessionKey>, Error> {
248        kx::Session::new_server(self, client_public_key)
249    }
250
251    /// Computes a stack-allocated shared secret key using a secret key from
252    /// this keypair and `third_party_public_key`.
253    ///
254    /// Compatible with libsodium's `crypto_box_beforenm`.
255    #[inline]
256    pub fn precalculate(
257        &self,
258        third_party_public_key: &PublicKey,
259    ) -> PrecalcSecretKey<StackByteArray<CRYPTO_BOX_BEFORENMBYTES>> {
260        PrecalcSecretKey::precalculate(third_party_public_key, &self.secret_key)
261    }
262}
263
264impl<
265    PublicKey: NewByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
266    SecretKey: NewByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
267> Default for KeyPair<PublicKey, SecretKey>
268{
269    fn default() -> Self {
270        Self::new()
271    }
272}
273
274#[cfg(any(feature = "nightly", all(doc, not(doctest))))]
275#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "nightly")))]
276pub mod protected {
277    //! #  Protected memory for [`KeyPair`]
278    use super::*;
279    use crate::classic::crypto_box::crypto_box_keypair_inplace;
280    pub use crate::protected::*;
281
282    impl
283        KeyPair<
284            Locked<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
285            Locked<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
286        >
287    {
288        /// Returns a new locked keypair.
289        pub fn new_locked_keypair() -> Result<Self, std::io::Error> {
290            Ok(Self {
291                public_key: HeapByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::new_locked()?,
292                secret_key: HeapByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::new_locked()?,
293            })
294        }
295
296        /// Returns a new randomly generated locked keypair.
297        pub fn gen_locked_keypair() -> Result<Self, std::io::Error> {
298            let mut res = Self::new_locked_keypair()?;
299
300            crypto_box_keypair_inplace(
301                res.public_key.as_mut_array(),
302                res.secret_key.as_mut_array(),
303            );
304
305            Ok(res)
306        }
307
308        /// Computes a heap-allocated, page-aligned, locked shared secret key
309        /// using a secret key from this keypair and
310        /// `third_party_public_key`.
311        ///
312        /// Compatible with libsodium's `crypto_box_beforenm`.
313        #[inline]
314        pub fn precalculate_locked<OtherPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>(
315            &self,
316            third_party_public_key: &OtherPublicKey,
317        ) -> Result<PrecalcSecretKey<Locked<HeapByteArray<CRYPTO_BOX_BEFORENMBYTES>>>, std::io::Error>
318        {
319            PrecalcSecretKey::precalculate_locked(third_party_public_key, &self.secret_key)
320        }
321    }
322
323    impl
324        KeyPair<
325            LockedRO<HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>>,
326            LockedRO<HeapByteArray<CRYPTO_BOX_SECRETKEYBYTES>>,
327        >
328    {
329        /// Returns a new randomly generated locked, read-only keypair.
330        pub fn gen_readonly_locked_keypair() -> Result<Self, std::io::Error> {
331            let mut public_key = HeapByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::new_locked()?;
332            let mut secret_key = HeapByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::new_locked()?;
333
334            crypto_box_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
335
336            let public_key = public_key.mprotect_readonly()?;
337            let secret_key = secret_key.mprotect_readonly()?;
338
339            Ok(Self {
340                public_key,
341                secret_key,
342            })
343        }
344
345        /// Computes a heap-allocated, page-aligned, locked, read-only shared
346        /// secret key using a secret key from this keypair and
347        /// `third_party_public_key`.
348        ///
349        /// Compatible with libsodium's `crypto_box_beforenm`.
350        #[inline]
351        pub fn precalculate_readonly_locked<
352            OtherPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
353        >(
354            &self,
355            third_party_public_key: &OtherPublicKey,
356        ) -> Result<
357            PrecalcSecretKey<LockedRO<HeapByteArray<CRYPTO_BOX_BEFORENMBYTES>>>,
358            std::io::Error,
359        > {
360            PrecalcSecretKey::precalculate_readonly_locked(third_party_public_key, &self.secret_key)
361        }
362    }
363}
364
365impl<
366    PublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES> + Zeroize,
367    SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES> + Zeroize,
368> PartialEq<KeyPair<PublicKey, SecretKey>> for KeyPair<PublicKey, SecretKey>
369{
370    fn eq(&self, other: &Self) -> bool {
371        self.public_key
372            .as_slice()
373            .ct_eq(other.public_key.as_slice())
374            .unwrap_u8()
375            == 1
376            && self
377                .secret_key
378                .as_slice()
379                .ct_eq(other.secret_key.as_slice())
380                .unwrap_u8()
381                == 1
382    }
383}
384
385#[cfg(test)]
386mod tests {
387
388    use super::*;
389    use crate::kx::Session;
390
391    fn all_eq<T>(t: &[T], v: T) -> bool
392    where
393        T: PartialEq,
394    {
395        t.iter().all(|x| *x == v)
396    }
397
398    #[test]
399    fn test_new() {
400        let keypair = KeyPair::<
401            StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
402            StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
403        >::new();
404
405        assert!(all_eq(&keypair.public_key, 0));
406        assert!(all_eq(&keypair.secret_key, 0));
407    }
408
409    #[test]
410    fn test_default() {
411        let keypair = KeyPair::<
412            StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
413            StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
414        >::default();
415
416        assert!(all_eq(&keypair.public_key, 0));
417        assert!(all_eq(&keypair.secret_key, 0));
418    }
419
420    #[test]
421    fn test_gen_keypair() {
422        use sodiumoxide::crypto::scalarmult::curve25519::{Scalar, scalarmult_base};
423
424        use crate::classic::crypto_core::crypto_scalarmult_base;
425
426        let keypair = KeyPair::<
427            StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
428            StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
429        >::gen();
430
431        let mut public_key = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
432        crypto_scalarmult_base(&mut public_key, keypair.secret_key.as_array());
433
434        assert_eq!(keypair.public_key.as_array(), &public_key);
435
436        let ge = scalarmult_base(&Scalar::from_slice(&keypair.secret_key).unwrap());
437
438        assert_eq!(ge.as_ref(), public_key);
439    }
440
441    #[test]
442    fn test_from_secret_key() {
443        let keypair_1 = KeyPair::<
444            StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
445            StackByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
446        >::gen();
447        let keypair_2 = KeyPair::from_secret_key(keypair_1.secret_key.clone());
448
449        assert_eq!(keypair_1.public_key, keypair_2.public_key);
450    }
451
452    #[test]
453    fn test_keypair_precalculate() {
454        let kp1 = KeyPair::gen_with_defaults();
455        let kp2 = KeyPair::gen_with_defaults();
456        let precalc = kp1.precalculate(&kp2.public_key);
457        assert_eq!(precalc.len(), crate::constants::CRYPTO_BOX_BEFORENMBYTES);
458    }
459
460    #[cfg(feature = "nightly")]
461    #[test]
462    fn test_keypair_precalculate_locked() {
463        use crate::keypair::protected::*;
464        let kp1 = KeyPair::gen_locked_keypair().unwrap();
465        let kp2 = KeyPair::gen_locked_keypair().unwrap();
466        let precalc = kp1.precalculate_locked(&kp2.public_key).unwrap();
467        assert_eq!(precalc.len(), crate::constants::CRYPTO_BOX_BEFORENMBYTES);
468    }
469
470    #[test]
471    fn test_keypair_kx_new_client_session() {
472        let server_kp = KeyPair::gen_with_defaults();
473        let client_kp = KeyPair::gen_with_defaults();
474        let session: Session<StackByteArray<CRYPTO_KX_SESSIONKEYBYTES>> = client_kp
475            .kx_new_client_session(&server_kp.public_key)
476            .unwrap();
477        assert_eq!(
478            session.rx_as_slice().len(),
479            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
480        );
481        assert_eq!(
482            session.tx_as_slice().len(),
483            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
484        );
485    }
486
487    #[test]
488    fn test_keypair_kx_new_server_session() {
489        let client_kp = KeyPair::gen_with_defaults();
490        let server_kp = KeyPair::gen_with_defaults();
491        let session: Session<StackByteArray<CRYPTO_KX_SESSIONKEYBYTES>> = server_kp
492            .kx_new_server_session(&client_kp.public_key)
493            .unwrap();
494        assert_eq!(
495            session.rx_as_slice().len(),
496            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
497        );
498        assert_eq!(
499            session.tx_as_slice().len(),
500            crate::constants::CRYPTO_KX_SESSIONKEYBYTES
501        );
502    }
503
504    #[test]
505    fn test_keypair_from_seed() {
506        let seed = [42u8; 32];
507        let kp: StackKeyPair = KeyPair::from_seed(&seed);
508        assert!(!kp.public_key.iter().all(|x| *x == 0));
509    }
510
511    #[test]
512    fn test_keypair_gen_with_defaults() {
513        let kp = KeyPair::gen_with_defaults();
514        assert!(!kp.public_key.iter().all(|x| *x == 0));
515    }
516
517    #[test]
518    fn test_is_valid_public_key() {
519        // Known valid key (assuming it meets X25519 criteria)
520        // This specific key is also a valid Ed25519 key.
521        let valid_pk_bytes = [
522            215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114,
523            243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26,
524        ];
525        let valid_pk = PublicKey::from(valid_pk_bytes);
526        assert!(
527            KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&valid_pk),
528            "Known valid key failed validation"
529        );
530
531        // Invalid: High bit set
532        let mut invalid_high_bit_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
533        invalid_high_bit_bytes[31] = 0x80;
534        let invalid_high_bit = PublicKey::from(invalid_high_bit_bytes);
535        assert!(
536            !KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&invalid_high_bit),
537            "Key with high bit set should be invalid"
538        );
539
540        // Invalid: Zero point
541        let zero_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
542        let zero_pk = PublicKey::from(zero_bytes);
543        assert!(
544            !KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&zero_pk),
545            "Zero key should be invalid"
546        );
547
548        // The identity point [1, 0, ..., 0] is NOT necessarily invalid for X25519,
549        // unlike Ed25519 validation. We don't explicitly test its rejection here.
550
551        // Generated key should be valid
552        let kp = KeyPair::gen_with_defaults();
553        assert!(
554            KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&kp.public_key),
555            "Generated key failed validation"
556        );
557    }
558
559    #[test]
560    fn test_is_valid_ed25519_key() {
561        // Get a key from crypto_sign_keypair which may have high bit set
562        let (valid_pk, _) = crate::classic::crypto_sign::crypto_sign_keypair();
563
564        // Should pass relaxed validation
565        assert!(
566            KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&valid_pk),
567            "Ed25519 key from crypto_sign_keypair should pass relaxed validation"
568        );
569
570        // Keys that are invalid with any validation
571
572        // Invalid: all zeros
573        let zero_bytes = [0u8; CRYPTO_BOX_PUBLICKEYBYTES];
574        let zero_pk = PublicKey::from(zero_bytes);
575        assert!(
576            !KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&zero_pk),
577            "Zero key should be invalid even with relaxed validation"
578        );
579
580        // Test the identity element (small-order point)
581        let identity_bytes = [
582            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,
583            0, 0, 0,
584        ];
585        let identity_pk = PublicKey::from(identity_bytes);
586        assert!(
587            !KeyPair::<PublicKey, SecretKey>::is_valid_ed25519_key(&identity_pk),
588            "Identity element (small order point) should be invalid even with relaxed validation"
589        );
590    }
591}