1+ use curve25519_dalek:: edwards:: CompressedEdwardsY ;
2+
13use crate :: constants:: {
2- CRYPTO_CORE_HCHACHA20_INPUTBYTES , CRYPTO_CORE_HCHACHA20_KEYBYTES ,
4+ CRYPTO_CORE_ED25519_BYTES , CRYPTO_CORE_HCHACHA20_INPUTBYTES , CRYPTO_CORE_HCHACHA20_KEYBYTES ,
35 CRYPTO_CORE_HCHACHA20_OUTPUTBYTES , CRYPTO_CORE_HSALSA20_INPUTBYTES ,
46 CRYPTO_CORE_HSALSA20_KEYBYTES , CRYPTO_CORE_HSALSA20_OUTPUTBYTES , CRYPTO_SCALARMULT_BYTES ,
57 CRYPTO_SCALARMULT_SCALARBYTES ,
@@ -22,6 +24,8 @@ pub type HSalsa20Input = [u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
2224pub type HSalsa20Key = [ u8 ; CRYPTO_CORE_HSALSA20_KEYBYTES ] ;
2325/// Stack-allocated HSalsa20 output.
2426pub type HSalsa20Output = [ u8 ; CRYPTO_CORE_HSALSA20_OUTPUTBYTES ] ;
27+ /// Stack-allocated Ed25519 point.
28+ pub type Ed25519Point = [ u8 ; CRYPTO_CORE_ED25519_BYTES ] ;
2529
2630/// Computes the public key for a previously generated secret key.
2731///
@@ -123,6 +127,73 @@ pub fn crypto_core_hchacha20(
123127 output[ 28 ..32 ] . copy_from_slice ( & x15. to_le_bytes ( ) ) ;
124128}
125129
130+ /// Checks if a given point is on the Ed25519 curve.
131+ ///
132+ /// This function determines if a given point is a valid point on the Ed25519
133+ /// curve that can be safely used for cryptographic operations.
134+ ///
135+ /// # Security Note
136+ ///
137+ /// This implementation uses `curve25519-dalek` for validation and is stricter
138+ /// than libsodium's `crypto_core_ed25519_is_valid_point`. Specifically, it may
139+ /// reject certain points, such as small-order points (e.g., the point
140+ /// represented by `[1, 0, ..., 0]`), which libsodium might accept. While
141+ /// libsodium's behavior provides compatibility, using points rejected by this
142+ /// function can lead to security vulnerabilities in certain protocols. Relying
143+ /// on this stricter check is generally recommended for new applications.
144+ ///
145+ /// # Example
146+ ///
147+ /// ```
148+ /// use dryoc::classic::crypto_core::{Ed25519Point, crypto_core_ed25519_is_valid_point};
149+ /// use dryoc::classic::crypto_sign::crypto_sign_keypair;
150+ ///
151+ /// // Get a valid Ed25519 public key (valid point)
152+ /// let (pk, _) = crypto_sign_keypair();
153+ ///
154+ /// // Check if the point is valid
155+ /// assert!(crypto_core_ed25519_is_valid_point(&pk));
156+ ///
157+ /// // Invalid point check
158+ /// let mut invalid_point = [0u8; 32];
159+ /// invalid_point[31] = 0x80; // Set high bit, making it invalid
160+ /// assert!(!crypto_core_ed25519_is_valid_point(&invalid_point));
161+ /// ```
162+ ///
163+ /// Not fully compatible with libsodium's `crypto_core_ed25519_is_valid_point`
164+ /// due to stricter checks.
165+ pub fn crypto_core_ed25519_is_valid_point ( p : & Ed25519Point ) -> bool {
166+ // Check 1: Canonical encoding. The high bit of the last byte must be 0.
167+ if p[ CRYPTO_CORE_ED25519_BYTES - 1 ] & 0x80 != 0 {
168+ return false ;
169+ }
170+
171+ // Check 2: Reject the all-zero point, which is invalid.
172+ const ZERO_POINT : Ed25519Point = [ 0u8 ; CRYPTO_CORE_ED25519_BYTES ] ;
173+ if p == & ZERO_POINT {
174+ return false ;
175+ }
176+
177+ // Check 3: Reject the identity element ([1, 0, ..., 0]) which is a small-order
178+ // point.
179+ const SMALL_ORDER_POINT_IDENTITY : Ed25519Point = [
180+ 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 , 0 ,
181+ 0 , 0 ,
182+ ] ;
183+ if p == & SMALL_ORDER_POINT_IDENTITY {
184+ return false ;
185+ }
186+
187+ // Check 4: Use curve25519-dalek decompression for point-on-curve check.
188+ // This will also reject points not in the prime-order subgroup if the feature
189+ // `serde` is not enabled for curve25519-dalek, but we explicitly checked
190+ // identity.
191+ match CompressedEdwardsY :: from_slice ( p) {
192+ Ok ( compressed) => compressed. decompress ( ) . is_some ( ) ,
193+ Err ( _) => false , // Should not happen if length is correct, but handle defensively.
194+ }
195+ }
196+
126197#[ inline]
127198fn salsa20_rotl32 ( x : u32 , y : u32 , rot : u32 ) -> u32 {
128199 x. wrapping_add ( y) . rotate_left ( rot)
@@ -333,4 +404,61 @@ mod tests {
333404 ) ;
334405 }
335406 }
407+
408+ #[ test]
409+ fn test_crypto_core_ed25519_is_valid_point ( ) {
410+ // Test with a known valid public key (from one of the crypto_sign test vectors)
411+ // This point is on the curve and correctly encoded.
412+ let valid_pk = [
413+ 215 , 90 , 152 , 1 , 130 , 177 , 10 , 183 , 213 , 75 , 254 , 211 , 201 , 100 , 7 , 58 , 14 , 225 , 114 ,
414+ 243 , 218 , 166 , 35 , 37 , 175 , 2 , 26 , 104 , 247 , 7 , 81 , 26 ,
415+ ] ;
416+ assert ! (
417+ crypto_core_ed25519_is_valid_point( & valid_pk) ,
418+ "Known valid Ed25519 public key should be considered valid"
419+ ) ;
420+
421+ // Test a point with the high bit set (invalid compressed format)
422+ // Standard Ed25519 compression requires the high bit of the last byte to be 0.
423+ let mut invalid_point_high_bit = [ 0u8 ; CRYPTO_CORE_ED25519_BYTES ] ;
424+ invalid_point_high_bit[ 31 ] = 0x80 ; // Set high bit, making it invalid
425+ assert ! (
426+ !crypto_core_ed25519_is_valid_point( & invalid_point_high_bit) ,
427+ "Point with high bit set in last byte should be invalid"
428+ ) ;
429+
430+ // Test the identity element (0, 1), which is a valid point.
431+ // Its compressed form is [1, 0, ..., 0].
432+ // While mathematically valid, this is a small-order point.
433+ // Stricter implementations (like curve25519-dalek) reject small-order points
434+ // because they can cause issues in some protocols. Libsodium accepts this
435+ // point.
436+ let small_order_point_identity = [
437+ 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 ,
438+ 0 , 0 , 0 ,
439+ ] ;
440+ assert ! (
441+ !crypto_core_ed25519_is_valid_point( & small_order_point_identity) ,
442+ "Small-order point (identity element) should be rejected by stricter validation"
443+ ) ;
444+
445+ // Test a point that is not on the curve (but is canonically encoded)
446+ // Example: A point generated randomly is unlikely to be on the curve.
447+ // We expect this to be rejected by the decompression check.
448+ let mut point_not_on_curve = [ 0u8 ; CRYPTO_CORE_ED25519_BYTES ] ;
449+ // Fill with some non-zero value that's unlikely to form a valid point
450+ // but is canonically encoded (last byte < 128)
451+ point_not_on_curve[ 0 ] = 2 ; // Example modification
452+ assert ! (
453+ !crypto_core_ed25519_is_valid_point( & point_not_on_curve) ,
454+ "Point not on the curve should be invalid"
455+ ) ;
456+
457+ // Test the zero point [0, ..., 0], which is invalid encoding.
458+ let zero_point = [ 0u8 ; CRYPTO_CORE_ED25519_BYTES ] ;
459+ assert ! (
460+ !crypto_core_ed25519_is_valid_point( & zero_point) ,
461+ "Zero point represents invalid encoding"
462+ ) ;
463+ }
336464}
0 commit comments