@@ -31,8 +31,11 @@ impl RistrettoPoint {
3131 let digest = D :: digest ( data) ;
3232 fe_bytes[ 0 ..32 ] . copy_from_slice ( digest. as_slice ( ) ) ;
3333 fe_bytes[ 8 ..24 ] . copy_from_slice ( data) ;
34- // Clear the appropriate bits to make this a reduced positive field elem, and map to curve
35- RistrettoPoint :: map_pos_felem_to_curve ( fe_bytes)
34+ // Clear the appropriate bits to be able to call map_to_curve_restricted
35+ fe_bytes[ 31 ] &= 0b00111111 ;
36+ fe_bytes[ 0 ] &= 0b11111110 ;
37+
38+ RistrettoPoint :: map_to_curve_restricted ( fe_bytes)
3639 }
3740
3841 /// Decode 16 bytes of data from a RistrettoPoint, using the Lizard method. Returns `None` if
@@ -180,37 +183,45 @@ impl RistrettoPoint {
180183 ]
181184 }
182185
183- /// Clears bits in `bytes` to make it a positive, reduced field element, then performs
184- /// [`RistrettoPoint::map_to_curve`]). Specifically, clears the the bottom bit (bottom bit of
185- /// `bytes[0]`) and second-to-top bit (second-to-top bit of `bytes[31]`). The first is to ensure
186- /// we have a positive field element and the second is to ensure we are below the modulus
187- /// (map-to-curve clears the topmost bit for us).
188- pub fn map_pos_felem_to_curve ( mut bytes : [ u8 ; 32 ] ) -> RistrettoPoint {
189- bytes[ 0 ] &= 0b11111110 ;
190- bytes[ 31 ] &= 0b10111111 ;
186+ /// Does the same as [`RistrettoPoint::map_to_curve`], but only operates on a restricted domain,
187+ /// namely `bytes` with the bottom bit and the top two bits unset. It is guaranteed that
188+ /// `map_to_curve_inverse(map_to_curve_restricted(bytes))` contains `bytes` in one of its first
189+ /// 8 return values (i.e., the positive ones).
190+ ///
191+ /// # Panics
192+ /// Panics if the bottom bit (bottom bit of `bytes[0]`) or either of the top two bits (top two
193+ /// bits of `bytes[31]`) are set.
194+ pub fn map_to_curve_restricted ( bytes : [ u8 ; 32 ] ) -> RistrettoPoint {
195+ // Ensure it's a positive field element
196+ assert_eq ! ( bytes[ 0 ] & 0b00000001 , 0 ) ;
197+ // Ensure it's less than below 2^254 (which is less than p)
198+ assert_eq ! ( bytes[ 31 ] & 0b11000000 , 0 ) ;
191199
192200 RistrettoPoint :: map_to_curve ( bytes)
193201 }
194202
195203 /// Interprets the given bytestring as a field element and computes the Ristretto Elligator map.
196- /// This is the MAP function in
197- /// [RFC 9496](https://www.rfc-editor.org/rfc/rfc9496.html#section-4.3.4-4).
198- /// Note this clears the top bit (`bytes[31] & 0x80`).
204+ /// This is the [MAP](https://www.rfc-editor.org/rfc/rfc9496.html#section-4.3.4-4) function in
205+ /// RFC 9496.
199206 ///
200207 /// # Warning
201208 ///
202209 /// This function does not produce cryptographically random-looking Ristretto points. Use
203210 /// [`Self::hash_from_bytes`] for that. DO NOT USE THIS FUNCTION unless you really know what
204211 /// you're doing.
205- pub fn map_to_curve ( mut bytes : [ u8 ; 32 ] ) -> RistrettoPoint {
206- bytes [ 31 ] &= 0b01111111 ;
207-
212+ pub fn map_to_curve ( bytes : [ u8 ; 32 ] ) -> RistrettoPoint {
213+ // MAP must clear the top bit. This is done in `from_bytes` for every field backend, so we
214+ // don't have to do it ourselves here
208215 let fe = FieldElement :: from_bytes ( & bytes) ;
209216 RistrettoPoint :: elligator_ristretto_flavor ( & fe)
210217 }
211218
212219 /// Computes the possible bytestrings that could have produced this point via
213220 /// [`Self::map_to_curve`].
221+ ///
222+ /// The first 8 return values are positive field elements (i.e., have the LSB (`bytes[0] & 1`)
223+ /// unset), if defined. The last 8 return values are negative field elements (i.e., have the LSB
224+ /// set), if defined.
214225 pub fn map_to_curve_inverse ( & self ) -> [ CtOption < [ u8 ; 32 ] > ; 16 ] {
215226 // Compute the inverses
216227 let fes = self . elligator_ristretto_flavor_inverse ( ) ;
@@ -365,7 +376,8 @@ mod test {
365376 }
366377 }
367378
368- // Tests that map_to_curve_inverse ○ map_pos_felem_to_curve is the identity
379+ // Tests that map_to_curve_inverse ○ map_to_curve_restricted is the identity and has a non-None
380+ // return value in the first 8 elements
369381 #[ test]
370382 fn map_pos_felem_to_curve_inverse ( ) {
371383 let mut rng = rand:: rng ( ) ;
@@ -374,22 +386,25 @@ mod test {
374386 let mut input = [ 0u8 ; 32 ] ;
375387 rng. fill_bytes ( & mut input) ;
376388
377- // Map to Ristretto and invert it
378- let pt = RistrettoPoint :: map_pos_felem_to_curve ( input ) ;
379- let inverses = pt . map_to_curve_inverse ( ) ;
389+ // Clear bits so we can call map_to_curve_restricted
390+ input [ 31 ] &= 0b00111111 ;
391+ input [ 0 ] &= 0b11111110 ;
380392
381- // map_pos_felem_to_curve masks the bottom bit and top two bits of `input`
382- let mut expected_inverse = input;
383- expected_inverse[ 31 ] &= 0b00111111 ;
384- expected_inverse[ 0 ] &= 0b11111110 ;
393+ let pt = RistrettoPoint :: map_to_curve_restricted ( input) ;
394+ let inverses = pt. map_to_curve_inverse ( ) ;
385395
386396 // Check that one of the valid inverses matches the input
387397 let mut found = false ;
388- for inv in inverses. into_iter ( ) {
389- if inv. is_some ( ) . into ( ) && inv. unwrap ( ) == expected_inverse {
398+ for ( i, inv) in inverses. into_iter ( ) . enumerate ( ) {
399+ if inv. is_some ( ) . into ( ) && inv. unwrap ( ) == input {
400+ // map_to_curve_inverse returns all the positive solutions in the first 8
401+ // values, and all the negative solution in the second 8 values
402+ if i >= 8 {
403+ panic ! ( "input is in the latter 8 inverses ({input:02x?})" ) ;
404+ }
390405 // Per the README, the probability of finding two inverses is ~2^-122
391406 if found == true {
392- panic ! ( "found two inverses for input { :02x?}" , input ) ;
407+ panic ! ( "found two inverses ({input :02x?})" ) ;
393408 }
394409
395410 found = true ;
0 commit comments