@@ -217,10 +217,59 @@ impl CompressedEdwardsY {
217217 None
218218 }
219219 }
220+
221+ /// Attempt to decompress to an `EdwardsPoint` according to RFC 8032 rules.
222+ ///
223+ /// Returns `None` if the input overflows the field modulus or is not the
224+ /// \\(y\\)-coordinate of a curve point.
225+ pub fn decompress_rfc8032 ( & self ) -> Option < EdwardsPoint > {
226+ if !bool:: from ( decompress:: check_modulus ( self ) ) {
227+ return None ;
228+ }
229+
230+ self . decompress ( )
231+ }
232+
233+ /// Attempt to decompress to an `EdwardsPoint` according to NIST's full
234+ /// validation.
235+ ///
236+ /// Returns `None` if the input overflows the field modulus, is not the
237+ /// \\(y\\)-coordinate of a curve point, is the identity point or is not
238+ /// contained in the prime-order subgroup.
239+ pub fn decompress_nist_full ( & self ) -> Option < EdwardsPoint > {
240+ let point = self . decompress_rfc8032 ( ) ?;
241+
242+ if !point. is_identity ( ) && point. is_torsion_free ( ) {
243+ Some ( point)
244+ } else {
245+ None
246+ }
247+ }
220248}
221249
222250mod decompress {
223251 use super :: * ;
252+ use subtle:: ConstantTimeGreater ;
253+
254+ pub ( super ) fn check_modulus ( repr : & CompressedEdwardsY ) -> Choice {
255+ // The modulus is:
256+ // 0xecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f
257+
258+ // Remove the sign from the MSB.
259+ let high_without_sign = repr. as_bytes ( ) [ 31 ] & 0x7f ;
260+
261+ // High-byte can never be bigger than 0x7f, point is always valid as
262+ // long as its smaller.
263+ let high = high_without_sign. ct_eq ( & 0x7f ) ;
264+ // If low-byte is bigger than 0xec and all other bytes are 0xff point
265+ // is invalid.
266+ let low = repr. as_bytes ( ) [ 0 ] . ct_gt ( & 0xec ) ;
267+ // If all other bytes are 0xff and the low-byte is bigger than 0xec
268+ // point is invalid.
269+ let mid = repr. as_bytes ( ) [ 1 ..31 ] . ct_eq ( & [ 0xff ; 30 ] ) ;
270+
271+ !high | ( !mid | !low)
272+ }
224273
225274 #[ rustfmt:: skip] // keep alignment of explanatory comments
226275 pub ( super ) fn step_1 (
@@ -1484,8 +1533,12 @@ impl GroupEncoding for EdwardsPoint {
14841533
14851534 fn from_bytes ( bytes : & Self :: Repr ) -> CtOption < Self > {
14861535 let repr = CompressedEdwardsY ( * bytes) ;
1536+ let is_in_modulus = decompress:: check_modulus ( & repr) ;
14871537 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)
1538+ CtOption :: new (
1539+ decompress:: step_2 ( & repr, X , Y , Z ) ,
1540+ is_in_modulus & is_valid_y_coord,
1541+ )
14891542 }
14901543
14911544 fn from_bytes_unchecked ( bytes : & Self :: Repr ) -> CtOption < Self > {
@@ -1779,6 +1832,7 @@ impl CofactorGroup for EdwardsPoint {
17791832mod test {
17801833 use super :: * ;
17811834
1835+ use hex_literal:: hex;
17821836 use rand_core:: TryRngCore ;
17831837
17841838 #[ cfg( feature = "alloc" ) ]
@@ -1872,6 +1926,81 @@ mod test {
18721926 assert_eq ! ( minus_basepoint. T , -( & constants:: ED25519_BASEPOINT_POINT . T ) ) ;
18731927 }
18741928
1929+ /// Test validated decompression
1930+ #[ test]
1931+ fn validated_decompression ( ) {
1932+ const VALID_POINTS : [ CompressedEdwardsY ; 4 ] = [
1933+ CompressedEdwardsY ( hex ! (
1934+ "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
1935+ ) ) ,
1936+ CompressedEdwardsY ( hex ! (
1937+ "faffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f"
1938+ ) ) ,
1939+ CompressedEdwardsY ( hex ! (
1940+ "faffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80"
1941+ ) ) ,
1942+ CompressedEdwardsY ( hex ! (
1943+ "fdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"
1944+ ) ) ,
1945+ ] ;
1946+
1947+ for compressed in VALID_POINTS {
1948+ assert ! ( compressed. decompress( ) . is_some( ) , "{:02x?}" , compressed. 0 ) ;
1949+ assert ! (
1950+ compressed. decompress_rfc8032( ) . is_some( ) ,
1951+ "{:02x?}" ,
1952+ compressed. 0
1953+ ) ;
1954+ }
1955+
1956+ const OVERFLOWING_POINTS : [ ( CompressedEdwardsY , [ u8 ; 32 ] ) ; 4 ] = [
1957+ (
1958+ CompressedEdwardsY ( hex ! (
1959+ "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
1960+ ) ) ,
1961+ [ 0 ; 32 ] ,
1962+ ) ,
1963+ (
1964+ CompressedEdwardsY ( hex ! (
1965+ "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
1966+ ) ) ,
1967+ hex ! ( "0100000000000000000000000000000000000000000000000000000000000000" ) ,
1968+ ) ,
1969+ (
1970+ CompressedEdwardsY ( hex ! (
1971+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
1972+ ) ) ,
1973+ hex ! ( "1200000000000000000000000000000000000000000000000000000000000000" ) ,
1974+ ) ,
1975+ (
1976+ CompressedEdwardsY ( hex ! (
1977+ "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1978+ ) ) ,
1979+ hex ! ( "0000000000000000000000000000000000000000000000000000000000000080" ) ,
1980+ ) ,
1981+ ] ;
1982+
1983+ for ( compressed, result) in OVERFLOWING_POINTS {
1984+ let point = compressed. decompress ( ) . unwrap ( ) ;
1985+ assert_eq ! ( point. compress( ) . 0 , result) ;
1986+ assert ! ( compressed. decompress_rfc8032( ) . is_none( ) ) ;
1987+ }
1988+
1989+ const TORSION_POINT : CompressedEdwardsY = CompressedEdwardsY ( hex ! (
1990+ "5d78934d0311e9791fb9577f568a47605f347b367db825c4e27c52eb0cbe944d"
1991+ ) ) ;
1992+
1993+ let point = TORSION_POINT . decompress_rfc8032 ( ) . unwrap ( ) ;
1994+ assert ! ( !point. is_torsion_free( ) ) ;
1995+ assert ! ( TORSION_POINT . decompress_nist_full( ) . is_none( ) ) ;
1996+
1997+ assert ! (
1998+ CompressedEdwardsY :: identity( )
1999+ . decompress_nist_full( )
2000+ . is_none( )
2001+ ) ;
2002+ }
2003+
18752004 /// Test that computing 1*basepoint gives the correct basepoint.
18762005 #[ cfg( feature = "precomputed-tables" ) ]
18772006 #[ test]
0 commit comments