@@ -635,50 +635,106 @@ impl EdwardsPoint {
635635 }
636636
637637 #[ cfg( feature = "digest" ) ]
638- /// Perform hashing to curve, with explicit hash function and domain separator, `domain_sep`,
639- /// using the suite `edwards25519_XMD:SHA-512_ELL2_NU_`. The input is the concatenation of the
640- /// elements of `bytes`. Likewise for the domain separator with `domain_sep`. At least one
641- /// element of `domain_sep`, MUST be nonempty, and the concatenation MUST NOT exceed
642- /// 255 bytes.
638+ // The function `map_to_curve` calculates an [EdwardsPoint] from a [FieldElement].
639+ fn map_to_curve ( fe : FieldElement ) -> EdwardsPoint {
640+ let c1 = ED25519_SQRTAM2 ;
641+
642+ // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u)
643+ let ( xMn, xMd, yMn, yMd) = crate :: montgomery:: elligator_encode ( & fe) ;
644+ // 2. xn = xMn * yMd
645+ let xn = & xMn * & yMd;
646+ // 3. xn = xn * c1
647+ let xn = & xn * & c1;
648+ // 4. xd = xMd * yMn
649+ let xd = & xMd * & yMn;
650+ // 5. yn = xMn - xMd
651+ let yn = & xMn - & xMd;
652+ // 6. yd = xMn + xMd
653+ let yd = & xMn + & xMd;
654+ // 7. tv1 = xd * yd
655+ let tv1 = & xd * & yd;
656+ // 8. e = tv1 == 0
657+ let e = tv1. ct_eq ( & FieldElement :: ZERO ) ;
658+ // 9. xn = CMOV(xn, 0, e)
659+ let xn = FieldElement :: conditional_select ( & xn, & FieldElement :: ZERO , e) ;
660+ // 10. xd = CMOV(xd, 1, e)
661+ let xd = FieldElement :: conditional_select ( & xd, & FieldElement :: ONE , e) ;
662+ // 11. yn = CMOV(yn, 1, e)
663+ let yn = FieldElement :: conditional_select ( & yn, & FieldElement :: ONE , e) ;
664+ // 12. yd = CMOV(yd, 1, e)
665+ let yd = FieldElement :: conditional_select ( & yd, & FieldElement :: ONE , e) ;
666+ // 13. return (xn, xd, yn, yd)
667+
668+ EdwardsPoint {
669+ X : & xn * & yd,
670+ Y : & xd * & yn,
671+ Z : & xd * & yd,
672+ T : & xn * & yn,
673+ }
674+ }
675+
676+ #[ cfg( feature = "digest" ) ]
677+ /// Perform encode to curve per RFC 9380, with explicit hash function and domain separator
678+ /// `domain_sep`, using the Twisted Edwards Elligator 2 method. The input is the concatenation
679+ /// of the elements of `bytes`. Likewise for the domain separator with `domain_sep`. At least
680+ /// one element of `domain_sep`, MUST be nonempty, and the concatenation MUST NOT exceed 255
681+ /// bytes.
682+ ///
683+ /// The specification names SHA-512 as an example of a secure hash to use with this function,
684+ /// but you may use any 512-bit hash within reason (see the
685+ /// [`spec`](https://www.rfc-editor.org/rfc/rfc9380.html#section-5.2) for details).
686+ ///
687+ /// # Warning
688+ /// `encode_to_curve` is a nonuniform encoding from byte strings to points in `G`. That is,
689+ /// the distribution of its output is not uniformly random in `G`: the set of possible outputs
690+ /// of encode_to_curve is only a fraction of the points in `G`, and some points in this set
691+ /// are more likely to be output than others.
692+ ///
693+ /// If your application needs the distribution of the output to be statistically close to
694+ /// uniform in `G`, use [Self::hash_to_curve] instead.
643695 ///
644696 /// # Panics
645697 /// Panics if `domain_sep.collect().len() == 0` or `> 255`
646- pub fn hash_to_curve < D > ( bytes : & [ & [ u8 ] ] , domain_sep : & [ & [ u8 ] ] ) -> EdwardsPoint
698+ pub fn encode_to_curve < D > ( bytes : & [ & [ u8 ] ] , domain_sep : & [ & [ u8 ] ] ) -> EdwardsPoint
647699 where
648700 D : BlockSizeUser + Default + FixedOutput < OutputSize = U64 > + HashMarker ,
649701 D :: BlockSize : IsGreater < D :: OutputSize , Output = True > ,
650702 {
651703 // For reference see
652704 // https://www.rfc-editor.org/rfc/rfc9380.html#name-elligator-2-method-2
653705
654- let fe = FieldElement :: hash_to_field :: < D > ( bytes, domain_sep) ;
655- let ( M1 , is_sq) = crate :: montgomery:: elligator_encode ( & fe) ;
706+ let fe = FieldElement :: hash_to_field :: < D , 1 > ( bytes, domain_sep) ;
707+ let Q = Self :: map_to_curve ( fe[ 0 ] ) ;
708+ Q . mul_by_cofactor ( )
709+ }
656710
657- // The `to_edwards` conversion we're performing takes as input the sign of the Edwards
658- // `y` coordinate. However, the specification uses `is_sq` to determine the sign of the
659- // Montgomery `v` coordinate. Our approach reconciles this mismatch as follows:
660- //
661- // * We arbitrarily fix the sign of the Edwards `y` coordinate (we choose 0).
662- // * Using the Montgomery `u` coordinate and the Edwards `X` coordinate, we recover `v`.
663- // * We verify that the sign of `v` matches the expected one, i.e., `is_sq == mont_v.is_negative()`.
664- // * If it does not match, we conditionally negate to correct the sign.
665- //
666- // Note: This logic aligns with the RFC draft specification:
667- // https://www.rfc-editor.org/rfc/rfc9380.html#name-elligator-2-method-2
668- // followed by the mapping
669- // https://www.rfc-editor.org/rfc/rfc9380.html#name-mappings-for-twisted-edward
670- // The only difference is that our `elligator_encode` returns only the Montgomery `u` coordinate,
671- // so we apply this workaround to reconstruct and validate the sign.
711+ #[ cfg( feature = "digest" ) ]
712+ /// Perform a hash to curve per RFC 9380, with explicit hash function and domain separator
713+ /// `domain_sep`, using the Twisted Edwards Elligator 2 method. The input is the concatenation
714+ /// of the elements of `bytes`. Likewise for the domain separator with `domain_sep`. At least
715+ /// one element of `domain_sep`, MUST be nonempty, and the concatenation MUST NOT exceed
716+ /// 255 bytes.
717+ ///
718+ /// The specification names SHA-512 as an example of a secure hash to use with this function,
719+ /// but you may use any 512-bit hash within reason (see the
720+ /// [`spec`](https://www.rfc-editor.org/rfc/rfc9380.html#section-5.2) for details).
721+ ///
722+ /// # Panics
723+ /// Panics if `domain_sep.collect().len() == 0` or `> 255`
724+ pub fn hash_to_curve < D > ( bytes : & [ & [ u8 ] ] , domain_sep : & [ & [ u8 ] ] ) -> EdwardsPoint
725+ where
726+ D : BlockSizeUser + Default + FixedOutput < OutputSize = U64 > + HashMarker ,
727+ D :: BlockSize : IsGreater < D :: OutputSize , Output = True > ,
728+ {
729+ // For reference see
730+ // https://www.rfc-editor.org/rfc/rfc9380.html#name-elligator-2-method-2
672731
673- let mut E1_opt = M1
674- . to_edwards ( 0 )
675- . expect ( "Montgomery conversion to Edwards point in Elligator failed" ) ;
732+ let fe = FieldElement :: hash_to_field :: < D , 2 > ( bytes , domain_sep ) ;
733+ let Q0 = Self :: map_to_curve ( fe [ 0 ] ) ;
734+ let Q1 = Self :: map_to_curve ( fe [ 1 ] ) ;
676735
677- // Now we recover v, to ensure that we got the sign right.
678- let mont_v =
679- & ( & ED25519_SQRTAM2 * & FieldElement :: from_bytes ( & M1 . to_bytes ( ) ) ) * & E1_opt . X . invert ( ) ;
680- E1_opt . X . conditional_negate ( is_sq ^ mont_v. is_negative ( ) ) ;
681- E1_opt . mul_by_cofactor ( )
736+ let R = Q0 + Q1 ;
737+ R . mul_by_cofactor ( )
682738 }
683739
684740 /// Return an `EdwardsPoint` chosen uniformly at random using a user-provided RNG.
@@ -2387,10 +2443,10 @@ mod test {
23872443 }
23882444
23892445 // Hash-to-curve test vectors from
2390- // https://www.rfc-editor.org/rfc/rfc9380.html#name-edwards25519_xmdsha-512_ell2
2446+ // https://www.rfc-editor.org/rfc/rfc9380.html#appendix-J.5.2
23912447 // These are of the form (input_msg, output_x, output_y)
23922448 #[ cfg( all( feature = "alloc" , feature = "digest" ) ) ]
2393- const RFC_HASH_TO_CURVE_KAT : & [ ( & [ u8 ] , & str , & str ) ] = & [
2449+ const RFC_ENCODE_TO_CURVE_KAT : & [ ( & [ u8 ] , & str , & str ) ] = & [
23942450 (
23952451 b"" ,
23962452 "1ff2b70ecf862799e11b7ae744e3489aa058ce805dd323a936375a84695e76da" ,
@@ -2424,32 +2480,88 @@ mod test {
24242480 )
24252481 ] ;
24262482
2483+ #[ cfg( all( feature = "alloc" , feature = "digest" ) ) ]
2484+ fn hex_str_to_fe ( hex_str : & str ) -> FieldElement {
2485+ let mut bytes = hex:: decode ( hex_str) . unwrap ( ) . to_vec ( ) ;
2486+ bytes. reverse ( ) ;
2487+ FieldElement :: from_bytes ( & bytes. try_into ( ) . unwrap ( ) )
2488+ }
2489+
24272490 #[ test]
24282491 #[ cfg( all( feature = "alloc" , feature = "digest" ) ) ]
2429- fn elligator_hash_to_curve_test_vectors ( ) {
2492+ fn elligator_encode_to_curve_test_vectors ( ) {
24302493 let dst = b"QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_" ;
2494+ for ( index, vector) in RFC_ENCODE_TO_CURVE_KAT . iter ( ) . enumerate ( ) {
2495+ let input = vector. 0 ;
2496+
2497+ let expected_output = {
2498+ let x = hex_str_to_fe ( vector. 1 ) ;
2499+ let y = hex_str_to_fe ( vector. 2 ) ;
2500+ AffinePoint { x, y } . to_edwards ( )
2501+ } ;
2502+
2503+ let computed = EdwardsPoint :: encode_to_curve :: < sha2:: Sha512 > ( & [ & input] , & [ dst] ) ;
2504+ assert_eq ! ( computed, expected_output, "Failed in test {}" , index) ;
2505+ }
2506+ }
2507+
2508+ // Hash-to-curve test vectors from
2509+ // https://www.rfc-editor.org/rfc/rfc9380.html#appendix-J.5.1
2510+ // These are of the form (input_msg, output_x, output_y)
2511+ #[ cfg( all( feature = "alloc" , feature = "digest" ) ) ]
2512+ const RFC_HASH_TO_CURVE_KAT : & [ ( & [ u8 ] , & str , & str ) ] = & [
2513+ (
2514+ b"" ,
2515+ "3c3da6925a3c3c268448dcabb47ccde5439559d9599646a8260e47b1e4822fc6" ,
2516+ "09a6c8561a0b22bef63124c588ce4c62ea83a3c899763af26d795302e115dc21" ,
2517+ ) ,
2518+
2519+ (
2520+ b"abc" ,
2521+ "608040b42285cc0d72cbb3985c6b04c935370c7361f4b7fbdb1ae7f8c1a8ecad" ,
2522+ "1a8395b88338f22e435bbd301183e7f20a5f9de643f11882fb237f88268a5531" ,
2523+ ) ,
2524+
2525+ (
2526+ b"abcdef0123456789" ,
2527+ "6d7fabf47a2dc03fe7d47f7dddd21082c5fb8f86743cd020f3fb147d57161472" ,
2528+ "53060a3d140e7fbcda641ed3cf42c88a75411e648a1add71217f70ea8ec561a6" ,
2529+ ) ,
2530+ (
2531+ b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
2532+ qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
2533+ "5fb0b92acedd16f3bcb0ef83f5c7b7a9466b5f1e0d8d217421878ea3686f8524" ,
2534+ "2eca15e355fcfa39d2982f67ddb0eea138e2994f5956ed37b7f72eea5e89d2f7" ,
2535+ ) ,
2536+ (
2537+ b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
2538+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
2539+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
2540+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
2541+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
2542+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
2543+ "0efcfde5898a839b00997fbe40d2ebe950bc81181afbd5cd6b9618aa336c1e8c" ,
2544+ "6dc2fc04f266c5c27f236a80b14f92ccd051ef1ff027f26a07f8c0f327d8f995"
2545+ )
2546+ ] ;
2547+
2548+ #[ test]
2549+ #[ cfg( all( feature = "alloc" , feature = "digest" ) ) ]
2550+ fn elligator_hash_to_curve_test_vectors ( ) {
2551+ let dst = b"QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_RO_" ;
24312552 for ( index, vector) in RFC_HASH_TO_CURVE_KAT . iter ( ) . enumerate ( ) {
24322553 let input = vector. 0 ;
24332554
24342555 let expected_output = {
2435- let mut x_bytes = hex:: decode ( vector. 1 ) . unwrap ( ) ;
2436- x_bytes. reverse ( ) ;
2437- let x = FieldElement :: from_bytes ( & x_bytes. try_into ( ) . unwrap ( ) ) ;
2438-
2439- let mut y_bytes = hex:: decode ( vector. 2 ) . unwrap ( ) ;
2440- y_bytes. reverse ( ) ;
2441- let y = FieldElement :: from_bytes ( & y_bytes. try_into ( ) . unwrap ( ) ) ;
2442-
2443- EdwardsPoint {
2444- X : x,
2445- Y : y,
2446- Z : FieldElement :: ONE ,
2447- T : & x * & y,
2448- }
2556+ let x = hex_str_to_fe ( vector. 1 ) ;
2557+ let y = hex_str_to_fe ( vector. 2 ) ;
2558+
2559+ AffinePoint { x, y } . to_edwards ( )
24492560 } ;
24502561
24512562 let computed = EdwardsPoint :: hash_to_curve :: < sha2:: Sha512 > ( & [ & input] , & [ dst] ) ;
2452- assert_eq ! ( computed, expected_output, "Failed in test {}" , index) ;
2563+
2564+ assert_eq ! ( expected_output, computed, "Failed in test {}" , index) ;
24532565 }
24542566 }
24552567}
0 commit comments