@@ -209,43 +209,81 @@ impl CompressedEdwardsY {
209209 /// Returns `None` if the input is not the \\(y\\)-coordinate of a
210210 /// curve point.
211211 pub fn decompress ( & self ) -> Option < EdwardsPoint > {
212- let ( is_valid_y_coord, X , Y , Z ) = decompress:: step_1 ( self ) ;
212+ let Y = FieldElement :: from_bytes ( self . as_bytes ( ) ) ;
213+ let ( is_valid_y_coord, X , Z ) = decompress:: step_1 ( & Y ) ;
213214
214215 if is_valid_y_coord. into ( ) {
215- Some ( decompress:: step_2 ( self , X , Y , Z ) )
216+ Some ( decompress:: step_2 ( self . as_bytes ( ) , X , Y , Z ) )
216217 } else {
217218 None
218219 }
219220 }
221+
222+ /// Attempt to decompress to an `EdwardsPoint` according to NIST rules.
223+ ///
224+ /// Returns `None` if the input overflows the field modulus or is not the
225+ /// \\(y\\)-coordinate of a curve point. With `PointValidation::Full` it
226+ /// will also return `None` if the point is not contained in the
227+ /// prime-order subgroup.
228+ pub fn validated_decompress ( & self , validation : PointValidation ) -> Option < EdwardsPoint > {
229+ let Y = FieldElement :: from_bytes ( self . as_bytes ( ) ) ;
230+
231+ if & Y . to_bytes ( ) != self . as_bytes ( ) {
232+ return None ;
233+ }
234+
235+ let ( is_valid_y_coord, X , Z ) = decompress:: step_1 ( & Y ) ;
236+
237+ if !bool:: from ( is_valid_y_coord) {
238+ return None ;
239+ }
240+
241+ let point = decompress:: step_2 ( self . as_bytes ( ) , X , Y , Z ) ;
242+
243+ if let PointValidation :: Full = validation {
244+ point. is_torsion_free ( ) . then_some ( point)
245+ } else {
246+ Some ( point)
247+ }
248+ }
249+ }
250+
251+ /// NIST validation rules.
252+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
253+ pub enum PointValidation {
254+ /// Partial validation. Only checks for overflowing the field modulus.
255+ Partial ,
256+ /// Full validation. Checks for overflowing the field modulus and
257+ /// prime-order subgroup.
258+ Full ,
220259}
221260
222261mod decompress {
223262 use super :: * ;
224263
225264 #[ rustfmt:: skip] // keep alignment of explanatory comments
226265 pub ( super ) fn step_1 (
227- repr : & CompressedEdwardsY ,
228- ) -> ( Choice , FieldElement , FieldElement , FieldElement ) {
229- let Y = FieldElement :: from_bytes ( repr. as_bytes ( ) ) ;
266+ Y : & FieldElement ,
267+ ) -> ( Choice , FieldElement , FieldElement ) {
230268 let Z = FieldElement :: ONE ;
231269 let YY = Y . square ( ) ;
232270 let u = & YY - & Z ; // u = y²-1
233271 let v = & ( & YY * & constants:: EDWARDS_D ) + & Z ; // v = dy²+1
234272 let ( is_valid_y_coord, X ) = FieldElement :: sqrt_ratio_i ( & u, & v) ;
235273
236- ( is_valid_y_coord, X , Y , Z )
274+ ( is_valid_y_coord, X , Z )
237275 }
238276
239277 #[ rustfmt:: skip]
240278 pub ( super ) fn step_2 (
241- repr : & CompressedEdwardsY ,
279+ repr : & [ u8 ; 32 ] ,
242280 mut X : FieldElement ,
243281 Y : FieldElement ,
244282 Z : FieldElement ,
245283 ) -> EdwardsPoint {
246284 // FieldElement::sqrt_ratio_i always returns the nonnegative square root,
247285 // so we negate according to the supplied sign bit.
248- let compressed_sign_bit = Choice :: from ( repr. as_bytes ( ) [ 31 ] >> 7 ) ;
286+ let compressed_sign_bit: Choice = Choice :: from ( repr[ 31 ] >> 7 ) ;
249287 X . conditional_negate ( compressed_sign_bit) ;
250288
251289 EdwardsPoint {
@@ -1483,9 +1521,13 @@ impl GroupEncoding for EdwardsPoint {
14831521 type Repr = [ u8 ; 32 ] ;
14841522
14851523 fn from_bytes ( bytes : & Self :: Repr ) -> CtOption < Self > {
1486- let repr = CompressedEdwardsY ( * bytes) ;
1487- let ( is_valid_y_coord, X , Y , Z ) = decompress:: step_1 ( & repr) ;
1488- CtOption :: new ( decompress:: step_2 ( & repr, X , Y , Z ) , is_valid_y_coord)
1524+ let Y = FieldElement :: from_bytes ( bytes) ;
1525+ let y_encoding_is_canonical = Y . to_bytes ( ) . ct_eq ( bytes) ;
1526+ let ( is_valid_y_coord, X , Z ) = decompress:: step_1 ( & Y ) ;
1527+ CtOption :: new (
1528+ decompress:: step_2 ( bytes, X , Y , Z ) ,
1529+ y_encoding_is_canonical & is_valid_y_coord,
1530+ )
14891531 }
14901532
14911533 fn from_bytes_unchecked ( bytes : & Self :: Repr ) -> CtOption < Self > {
@@ -1779,6 +1821,7 @@ impl CofactorGroup for EdwardsPoint {
17791821mod test {
17801822 use super :: * ;
17811823
1824+ use hex_literal:: hex;
17821825 use rand_core:: TryRngCore ;
17831826
17841827 #[ cfg( feature = "alloc" ) ]
@@ -1872,6 +1915,35 @@ mod test {
18721915 assert_eq ! ( minus_basepoint. T , -( & constants:: ED25519_BASEPOINT_POINT . T ) ) ;
18731916 }
18741917
1918+ /// Test validated decompression
1919+ #[ test]
1920+ fn validated_decompression ( ) {
1921+ const OVERFLOWING_POINT : CompressedEdwardsY = CompressedEdwardsY ( hex ! (
1922+ "e184c7ba7f37cd33bd357f70dc0dfb537fb397761a3fd0a75eb3314b34cc78b5"
1923+ ) ) ;
1924+
1925+ assert ! ( OVERFLOWING_POINT . decompress( ) . is_some( ) ) ;
1926+ assert ! (
1927+ OVERFLOWING_POINT
1928+ . validated_decompress( PointValidation :: Partial )
1929+ . is_none( )
1930+ ) ;
1931+
1932+ const TORSION_POINT : CompressedEdwardsY = CompressedEdwardsY ( hex ! (
1933+ "5d78934d0311e9791fb9577f568a47605f347b367db825c4e27c52eb0cbe944d"
1934+ ) ) ;
1935+
1936+ let point = TORSION_POINT
1937+ . validated_decompress ( PointValidation :: Partial )
1938+ . unwrap ( ) ;
1939+ assert ! ( !point. is_torsion_free( ) ) ;
1940+ assert ! (
1941+ OVERFLOWING_POINT
1942+ . validated_decompress( PointValidation :: Full )
1943+ . is_none( )
1944+ ) ;
1945+ }
1946+
18751947 /// Test that computing 1*basepoint gives the correct basepoint.
18761948 #[ cfg( feature = "precomputed-tables" ) ]
18771949 #[ test]
0 commit comments