@@ -217,10 +217,57 @@ impl CompressedEdwardsY {
217217 None
218218 }
219219 }
220+
221+ /// Attempt to decompress to an `EdwardsPoint` according to NIST rules.
222+ ///
223+ /// Returns `None` if the input overflows the field modulus or is not the
224+ /// \\(y\\)-coordinate of a curve point. With `PointValidation::Full` it
225+ /// will also return `None` if the point is the identity point or is not
226+ /// contained in the prime-order subgroup.
227+ pub fn validated_decompress ( & self , validation : PointValidation ) -> Option < EdwardsPoint > {
228+ if !bool:: from ( decompress:: check_modulus ( self ) ) {
229+ return None ;
230+ }
231+
232+ let ( is_valid_y_coord, X , Y , Z ) = decompress:: step_1 ( self ) ;
233+
234+ if !bool:: from ( is_valid_y_coord) {
235+ return None ;
236+ }
237+
238+ let point = decompress:: step_2 ( self , X , Y , Z ) ;
239+
240+ if let PointValidation :: Full = validation {
241+ ( !point. is_identity ( ) && point. is_torsion_free ( ) ) . then_some ( point)
242+ } else {
243+ Some ( point)
244+ }
245+ }
246+ }
247+
248+ /// NIST validation rules.
249+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
250+ pub enum PointValidation {
251+ /// Checks for overflowing the field modulus.
252+ Partial ,
253+ /// Checks for overflowing the field modulus, prime-order subgroup and
254+ /// identity point.
255+ Full ,
220256}
221257
222258mod decompress {
223259 use super :: * ;
260+ use subtle:: ConstantTimeLess ;
261+
262+ pub ( super ) fn check_modulus ( repr : & CompressedEdwardsY ) -> Choice {
263+ let high_without_sign = repr. as_bytes ( ) [ 31 ] & 0x7f ;
264+
265+ let high = high_without_sign. ct_lt ( & 0x8f ) ;
266+ let mid = repr. as_bytes ( ) [ 1 ..31 ] . ct_eq ( & [ 0xff ; 30 ] ) ;
267+ let low = repr. as_bytes ( ) [ 0 ] . ct_lt ( & 0xed ) ;
268+
269+ high & ( !mid | low)
270+ }
224271
225272 #[ rustfmt:: skip] // keep alignment of explanatory comments
226273 pub ( super ) fn step_1 (
@@ -1484,8 +1531,12 @@ impl GroupEncoding for EdwardsPoint {
14841531
14851532 fn from_bytes ( bytes : & Self :: Repr ) -> CtOption < Self > {
14861533 let repr = CompressedEdwardsY ( * bytes) ;
1534+ let is_in_modulus = decompress:: check_modulus ( & repr) ;
14871535 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)
1536+ CtOption :: new (
1537+ decompress:: step_2 ( & repr, X , Y , Z ) ,
1538+ is_in_modulus & is_valid_y_coord,
1539+ )
14891540 }
14901541
14911542 fn from_bytes_unchecked ( bytes : & Self :: Repr ) -> CtOption < Self > {
@@ -1779,6 +1830,7 @@ impl CofactorGroup for EdwardsPoint {
17791830mod test {
17801831 use super :: * ;
17811832
1833+ use hex_literal:: hex;
17821834 use rand_core:: TryRngCore ;
17831835
17841836 #[ cfg( feature = "alloc" ) ]
@@ -1872,6 +1924,42 @@ mod test {
18721924 assert_eq ! ( minus_basepoint. T , -( & constants:: ED25519_BASEPOINT_POINT . T ) ) ;
18731925 }
18741926
1927+ /// Test validated decompression
1928+ #[ test]
1929+ fn validated_decompression ( ) {
1930+ const OVERFLOWING_POINT : CompressedEdwardsY = CompressedEdwardsY ( hex ! (
1931+ "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
1932+ ) ) ;
1933+
1934+ let point = OVERFLOWING_POINT . decompress ( ) . unwrap ( ) ;
1935+ assert_eq ! ( point. compress( ) . 0 , [ 0 ; 32 ] ) ;
1936+ assert ! (
1937+ OVERFLOWING_POINT
1938+ . validated_decompress( PointValidation :: Partial )
1939+ . is_none( )
1940+ ) ;
1941+
1942+ const TORSION_POINT : CompressedEdwardsY = CompressedEdwardsY ( hex ! (
1943+ "5d78934d0311e9791fb9577f568a47605f347b367db825c4e27c52eb0cbe944d"
1944+ ) ) ;
1945+
1946+ let point = TORSION_POINT
1947+ . validated_decompress ( PointValidation :: Partial )
1948+ . unwrap ( ) ;
1949+ assert ! ( !point. is_torsion_free( ) ) ;
1950+ assert ! (
1951+ OVERFLOWING_POINT
1952+ . validated_decompress( PointValidation :: Full )
1953+ . is_none( )
1954+ ) ;
1955+
1956+ assert ! (
1957+ CompressedEdwardsY :: identity( )
1958+ . validated_decompress( PointValidation :: Full )
1959+ . is_none( )
1960+ ) ;
1961+ }
1962+
18751963 /// Test that computing 1*basepoint gives the correct basepoint.
18761964 #[ cfg( feature = "precomputed-tables" ) ]
18771965 #[ test]
0 commit comments