Skip to content

Commit 95fa6ea

Browse files
committed
Hash to curve as defined in the standard
The current implementation is not compatible with the current definition of the standard. This PR provides a hash-to-curve implementation as defined in draft-irtf-cfrg-hash-to-curve-12. * Implementation of `hash_to_field` as defined in the standard * Implementation of `hash_to_curve` as defined in the standard, by changing the mechanism over which we chose the sign. * For the point above, had to change the `elligator_encode` to return whether `eps` is a square or not (required for `hash_to_curve`). * Included test vectors of the draft. * Included `FieldElement::from_bytes_wide(bytes: &u8; 64])` to reduce integers encoded in 64 bytes.
1 parent 076cf34 commit 95fa6ea

File tree

8 files changed

+204
-8
lines changed

8 files changed

+204
-8
lines changed

src/backend/serial/u32/constants.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ pub(crate) const EDWARDS_D: FieldElement2625 = FieldElement2625([
2929
56195235, 13857412, 51736253, 6949390, 114729, 24766616, 60832955, 30306712, 48412415, 21499315,
3030
]);
3131

32+
/// sqrt(-486664)
33+
pub(crate) const ED25519_SQRTAM2: FieldElement2625 = FieldElement2625([
34+
54885894, 25242303, 55597453, 9067496, 51808079, 33312638, 25456129, 14121551, 54921728, 3972023,
35+
]);
36+
3237
/// Edwards `2*d` value, equal to `2*(-121665/121666) mod p`.
3338
pub(crate) const EDWARDS_D2: FieldElement2625 = FieldElement2625([
3439
45281625, 27714825, 36363642, 13898781, 229458, 15978800, 54557047, 27058993, 29715967, 9444199,

src/backend/serial/u32/field.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,26 @@ impl FieldElement2625 {
414414
FieldElement2625::reduce(h)
415415
}
416416

417+
/// Load a `FieldElement51` from 64 bytes, by reducing modulo q.
418+
pub fn from_bytes_wide(hash: &[u8; 64]) -> FieldElement2625 {
419+
let mut fl = [0u8; 32];
420+
let mut gl = [0u8; 32];
421+
fl.copy_from_slice(&hash[..32]);
422+
gl.copy_from_slice(&hash[32..]);
423+
fl[31] &= 0x7f;
424+
gl[31] &= 0x7f;
425+
426+
let fe_f = Self::from_bytes(&fl);
427+
let fe_g = Self::from_bytes(&gl);
428+
let mut fe_f64 = fe_f.0.map(|i| i as u64);
429+
let fe_g64 = fe_g.0.map(|i| i as u64);
430+
fe_f64[0] = fe_f64[0] + (hash[31] >> 7) as u64 * 19 + (hash[63] >> 7) as u64 * 722;
431+
for i in 0..10 {
432+
fe_f64[i] += 38 * fe_g64[i];
433+
}
434+
Self::reduce(fe_f64)
435+
}
436+
417437
/// Serialize this `FieldElement51` to a 32-byte array. The
418438
/// encoding is canonical.
419439
pub fn to_bytes(&self) -> [u8; 32] {

src/backend/serial/u64/constants.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ pub(crate) const EDWARDS_D: FieldElement51 = FieldElement51([
3535
1442794654840575,
3636
]);
3737

38+
/// sqrt(-486664)
39+
pub(crate) const ED25519_SQRTAM2: FieldElement51 = FieldElement51([
40+
1693982333959686,
41+
608509411481997,
42+
2235573344831311,
43+
947681270984193,
44+
266558006233600
45+
]);
46+
3847
/// Edwards `2*d` value, equal to `2*(-121665/121666) mod p`.
3948
pub(crate) const EDWARDS_D2: FieldElement51 = FieldElement51([
4049
1859910466990425,

src/backend/serial/u64/field.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,24 @@ impl FieldElement51 {
355355
])
356356
}
357357

358+
/// Load a `FieldElement51` from 64 bytes, by reducing modulo q.
359+
pub fn from_bytes_wide(hash: &[u8; 64]) -> FieldElement51 {
360+
let mut fl = [0u8; 32];
361+
let mut gl = [0u8; 32];
362+
fl.copy_from_slice(&hash[..32]);
363+
gl.copy_from_slice(&hash[32..]);
364+
fl[31] &= 0x7f;
365+
gl[31] &= 0x7f;
366+
367+
let mut fe_f = Self::from_bytes(&fl);
368+
let fe_g = Self::from_bytes(&gl);
369+
fe_f.0[0] = fe_f.0[0] + (hash[31] >> 7) as u64 * 19 + (hash[63] >> 7) as u64 * 722;
370+
for i in 0..5 {
371+
fe_f.0[i] += 38 * fe_g.0[i];
372+
}
373+
Self::reduce(fe_f.0)
374+
}
375+
358376
/// Serialize this `FieldElement51` to a 32-byte array. The
359377
/// encoding is canonical.
360378
pub fn to_bytes(&self) -> [u8; 32] {

src/edwards.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ impl CompressedEdwardsY {
221221
use serde::{self, Serialize, Deserialize, Serializer, Deserializer};
222222
#[cfg(feature = "serde")]
223223
use serde::de::Visitor;
224+
use constants::ED25519_SQRTAM2;
224225

225226
#[cfg(feature = "serde")]
226227
impl Serialize for EdwardsPoint {
@@ -526,9 +527,26 @@ impl EdwardsPoint {
526527
CompressedEdwardsY(s)
527528
}
528529

529-
/// Perform hashing to the group using the Elligator2 map
530+
/// Perform hashing to curve, with explicit hash function and DST using the suite
531+
/// edwards25519_XMD:SHA-512_ELL2_NU_
530532
///
531-
/// See https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-10#section-6.7.1
533+
/// See https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-6.8.2
534+
pub fn hash_to_curve_dst<D>(bytes: &[u8], dst: &[u8]) -> EdwardsPoint
535+
where
536+
D: Digest<OutputSize = U64> + Default,
537+
{
538+
let fe = FieldElement::hash_to_field::<D>(bytes, dst);
539+
let (M1, is_sq) = crate::montgomery::elligator_encode(&fe);
540+
let mut E1_opt = M1.to_edwards(0).expect("Montgomery conversion to Edwards point in Elligator failed");
541+
542+
// Now we recover v, to ensure that we got the sign right.
543+
let mont_v = &(&ED25519_SQRTAM2*&FieldElement::from_bytes(&M1.to_bytes()))*&E1_opt.X.invert();
544+
E1_opt.X.conditional_negate(is_sq ^ mont_v.is_negative());
545+
E1_opt
546+
.mul_by_cofactor()
547+
}
548+
549+
/// Hash from bytes, following the elligator2 version implemented in signal
532550
pub fn hash_from_bytes<D>(bytes: &[u8]) -> EdwardsPoint
533551
where
534552
D: Digest<OutputSize = U64> + Default,
@@ -543,7 +561,7 @@ impl EdwardsPoint {
543561

544562
let fe = FieldElement::from_bytes(&res);
545563

546-
let M1 = crate::montgomery::elligator_encode(&fe);
564+
let (M1, _) = crate::montgomery::elligator_encode(&fe);
547565
let E1_opt = M1.to_edwards(sign_bit);
548566

549567
E1_opt
@@ -1208,6 +1226,7 @@ mod test {
12081226
use subtle::ConditionallySelectable;
12091227
use constants;
12101228
use super::*;
1229+
use sha2::Sha512;
12111230

12121231
/// X coordinate of the basepoint.
12131232
/// = 15112221349535400772501151409588531511454012693041857206046113283949847762202
@@ -1807,4 +1826,44 @@ mod test {
18071826
assert_eq!(point.compress().to_bytes(), output[..]);
18081827
}
18091828
}
1829+
1830+
// Hash-to-curve test vectors from
1831+
// https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/master/draft-irtf-cfrg-hash-to-curve.md
1832+
fn test_vectors_h2c() -> Vec<Vec<&'static str>> {
1833+
vec![
1834+
vec![
1835+
"",
1836+
"222e314d04a4d5725e9f2aff9fb2a6b69ef375a1214eb19021ceab2d687f0f9b",
1837+
],
1838+
vec![
1839+
"abc",
1840+
"67732d50f9a26f73111dd1ed5dba225614e538599db58ba30aaea1f5c827fa42",
1841+
],
1842+
vec![
1843+
"abcdef0123456789",
1844+
"af8a6c24dd1adde73909cada6a4a137577b0f179d336685c4a955a0a8e1a86fb",
1845+
],
1846+
vec![
1847+
"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
1848+
"aaf6ff6ef5ebba128b0774f4296cb4c2279a074658b083b8dcca91f57a603450",
1849+
],
1850+
vec![
1851+
"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1852+
"ac90c3d39eb18ff291d33441b35f3262cdd307162cc97c31bfcc7a4245891a37",
1853+
],
1854+
]
1855+
}
1856+
1857+
#[test]
1858+
fn elligator_hash_to_curve_test_vectors(){
1859+
let dst = b"QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_";
1860+
for (index, vector) in test_vectors_h2c().iter().enumerate() {
1861+
let input = vector[0].as_bytes();
1862+
let mut output = hex::decode(vector[1]).unwrap();
1863+
output.reverse();
1864+
1865+
let point = EdwardsPoint::hash_to_curve_dst::<Sha512>(&input, dst).compress().to_bytes();
1866+
assert!(!(point != output[..]), "Failed in test {}", index);
1867+
}
1868+
}
18101869
}

src/field.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ pub type FieldElement = backend::serial::u64::field::FieldElement51;
6060

6161
#[cfg(feature = "u32_backend")]
6262
pub use backend::serial::u32::field::*;
63+
use digest::{generic_array::typenum::U64, Digest};
64+
6365
/// A `FieldElement` represents an element of the field
6466
/// \\( \mathbb Z / (2\^{255} - 19)\\).
6567
///
@@ -289,12 +291,44 @@ impl FieldElement {
289291
pub fn invsqrt(&self) -> (Choice, FieldElement) {
290292
FieldElement::sqrt_ratio_i(&FieldElement::one(), self)
291293
}
294+
295+
/// Hash_to_field as described in hash_to_curve standard
296+
pub fn hash_to_field<D>(bytes: &[u8], dst: &[u8]) -> Self
297+
where
298+
D: Digest<OutputSize = U64> + Default,
299+
{
300+
let len_in_bytes = 48;
301+
let l_i_b_str = [0u8, len_in_bytes];
302+
let z_pad = [0u8; 128];
303+
let dst_prime = [dst, &[dst.len() as u8]].concat();
304+
305+
let b_0 = D::new()
306+
.chain(z_pad)
307+
.chain(bytes)
308+
.chain(l_i_b_str)
309+
.chain([0u8])
310+
.chain(&dst_prime)
311+
.finalize();
312+
313+
let b_1 = D::new()
314+
.chain(b_0.as_slice())
315+
.chain([1u8])
316+
.chain(&dst_prime)
317+
.finalize();
318+
319+
let mut bytes_wide = [0u8; 64];
320+
bytes_wide[..48].copy_from_slice(&b_1.as_slice()[..48]);
321+
bytes_wide[..48].reverse();
322+
323+
FieldElement::from_bytes_wide(&bytes_wide)
324+
}
292325
}
293326

294327
#[cfg(test)]
295328
mod test {
296329
use field::*;
297330
use subtle::ConditionallyNegatable;
331+
use sha2::Sha512;
298332

299333
/// Random element a of GF(2^255-19), from Sage
300334
/// a = 1070314506888354081329385823235218444233221\
@@ -473,4 +507,51 @@ mod test {
473507
fn batch_invert_empty() {
474508
FieldElement::batch_invert(&mut []);
475509
}
510+
511+
#[test]
512+
fn from_hash_wide() {
513+
let mut test_vec= [0x6d, 0x2f, 0x2f, 0xfa, 0x94, 0x12, 0xb4, 0x15, 0x2f, 0x6a, 0xb6, 0x28, 0x41, 0xb8, 0x25, 0x92, 0x4a, 0x44, 0x90, 0x65, 0x15, 0x2b, 0x95, 0x47, 0x6f, 0x12, 0x1d, 0xe8, 0x99, 0xbb, 0x77, 0xbd, 0x48, 0x24, 0x6a, 0x37, 0x8e, 0x31, 0x33, 0xfb, 0x30, 0x23, 0x2a, 0xad, 0xa9, 0x20, 0xae, 0x04];
514+
let mut hash_wide = [0u8; 64];
515+
hash_wide[..48].copy_from_slice(&test_vec);
516+
517+
let mut reduce_fe = FieldElement::from_bytes_wide(&hash_wide);
518+
let mut expected_reduced = [0x30, 0x92, 0xf0, 0x33, 0xb1, 0x6d, 0x4d, 0x5f, 0x74, 0xa3, 0xf7, 0xdc, 0x70, 0x91, 0xfe, 0x43, 0x4b, 0x44, 0x90, 0x65, 0x15, 0x2b, 0x95, 0x47, 0x6f, 0x12, 0x1d, 0xe8, 0x99, 0xbb, 0x77, 0x3d];
519+
520+
assert_eq!(reduce_fe.to_bytes(), expected_reduced);
521+
522+
test_vec= [0xae, 0x69, 0x22, 0xd7, 0x28, 0xc1, 0x21, 0xf6, 0x90, 0x48, 0x61, 0xbd, 0x67, 0x49, 0x67, 0xb3, 0x19, 0xd4, 0x6d, 0xee, 0x9d, 0x04, 0x7f, 0x86, 0xc4, 0x27, 0xc5, 0x3f, 0x8b, 0x29, 0xa5, 0x5c, 0xdb, 0xe1, 0x5e, 0xae, 0x23, 0x4e, 0xb7, 0x84, 0xe5, 0x9d, 0x6d, 0x20, 0x2a, 0x78, 0x20, 0xd6];
523+
hash_wide = [0u8; 64];
524+
hash_wide[..48].copy_from_slice(&test_vec);
525+
526+
reduce_fe = FieldElement::from_bytes_wide(&hash_wide);
527+
expected_reduced = [0x30, 0xf0, 0x37, 0xb9, 0x74, 0x5a, 0x57, 0xa9, 0xa2, 0xb8, 0xa6, 0x8d, 0xa8, 0x1f, 0x39, 0x7c, 0x39, 0xd4, 0x6d, 0xee, 0x9d, 0x04, 0x7f, 0x86, 0xc4, 0x27, 0xc5, 0x3f, 0x8b, 0x29, 0xa5, 0x5c];
528+
529+
assert_eq!(reduce_fe.to_bytes(), expected_reduced);
530+
531+
test_vec= [0x3a, 0x7a, 0x2b, 0x29, 0x83, 0xe8, 0x88, 0x61, 0x25, 0x20, 0xcf, 0x6a, 0xfe, 0xbb, 0xea, 0x6b, 0x21, 0x8b, 0x58, 0x15, 0xf2, 0x38, 0x80, 0x09, 0x2a, 0x92, 0x5a, 0xf9, 0x4c, 0xd6, 0xfa, 0x24, 0x6a, 0x35, 0xb7, 0xdb, 0xed, 0x1e, 0x8f, 0xdf, 0xfd, 0xcd, 0x36, 0x6e, 0x55, 0xed, 0x0a, 0xbe];
532+
hash_wide = [0u8; 64];
533+
hash_wide[..48].copy_from_slice(&test_vec);
534+
535+
reduce_fe = FieldElement::from_bytes_wide(&hash_wide);
536+
expected_reduced = [0xf6, 0x67, 0x5d, 0xc6, 0xd1, 0x7f, 0xc7, 0x90, 0xd4, 0xb3, 0xf1, 0xc6, 0xac, 0xf6, 0x89, 0xa1, 0x3d, 0x8b, 0x58, 0x15, 0xf2, 0x38, 0x80, 0x09, 0x2a, 0x92, 0x5a, 0xf9, 0x4c, 0xd6, 0xfa, 0x24];
537+
538+
assert_eq!(reduce_fe.to_bytes(), expected_reduced);
539+
}
540+
541+
#[test]
542+
fn hash_to_field() {
543+
let message = [0xfc, 0x51, 0xcd, 0x8e, 0x62, 0x18, 0xa1, 0xa3, 0x8d, 0xa4, 0x7e, 0xd0, 0x02, 0x30, 0xf0, 0x58, 0x08, 0x16, 0xed, 0x13, 0xba, 0x33, 0x03, 0xac, 0x5d, 0xeb, 0x91, 0x15, 0x48, 0x90, 0x80, 0x25, 0xaf, 0x82];
544+
let dst = b"ECVRF_edwards25519_XMD:SHA-512_ELL2_NU_\x04";
545+
let fe = FieldElement::hash_to_field::<Sha512>(&message, dst);
546+
let expected_fe = FieldElement::from_bytes(&[0xf6, 0x67, 0x5d, 0xc6, 0xd1, 0x7f, 0xc7, 0x90, 0xd4, 0xb3, 0xf1, 0xc6, 0xac, 0xf6, 0x89, 0xa1, 0x3d, 0x8b, 0x58, 0x15, 0xf2, 0x38, 0x80, 0x09, 0x2a, 0x92, 0x5a, 0xf9, 0x4c, 0xd6, 0xfa, 0x24]);
547+
assert_eq!(fe, expected_fe);
548+
549+
let message = "";
550+
let dst = "QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_";
551+
let fe = FieldElement::hash_to_field::<Sha512>(message.as_bytes(), dst.as_bytes());
552+
let mut expected_fe_bytes = [0x7f, 0x3e, 0x7f, 0xb9, 0x42, 0x81, 0x03, 0xad, 0x7f, 0x52, 0xdb, 0x32, 0xf9, 0xdf, 0x32, 0x50, 0x5d, 0x7b, 0x42, 0x7d, 0x89, 0x4c, 0x50, 0x93, 0xf7, 0xa0, 0xf0, 0x37, 0x4a, 0x30, 0x64, 0x1d];
553+
expected_fe_bytes.reverse();
554+
let expected_fe = FieldElement::from_bytes(&expected_fe_bytes);
555+
assert_eq!(fe, expected_fe);
556+
}
476557
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ extern crate fiat_crypto;
272272
// Used for traits related to constant-time code.
273273
extern crate subtle;
274274

275+
#[cfg(test)]
276+
extern crate sha2;
275277
#[cfg(all(test, feature = "serde"))]
276278
extern crate bincode;
277279
#[cfg(feature = "serde")]

src/montgomery.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,16 @@ impl MontgomeryPoint {
164164
}
165165
}
166166

167-
/// Perform the Elligator2 mapping to a Montgomery point.
167+
/// Perform the Elligator2 mapping to a Montgomery point. Returns a Montgomery point and a `Choice`
168+
/// determining whether eps is a square. This is required by the standard to determine the
169+
/// sign of the v coordinate.
168170
///
169171
/// See <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-10#section-6.7.1>
170172
//
171173
// TODO Determine how much of the hash-to-group API should be exposed after the CFRG
172174
// draft gets into a more polished/accepted state.
173175
#[allow(unused)]
174-
pub(crate) fn elligator_encode(r_0: &FieldElement) -> MontgomeryPoint {
176+
pub(crate) fn elligator_encode(r_0: &FieldElement) -> (MontgomeryPoint, Choice) {
175177
let one = FieldElement::one();
176178
let d_1 = &one + &r_0.square2(); /* 2r^2 */
177179

@@ -190,7 +192,7 @@ pub(crate) fn elligator_encode(r_0: &FieldElement) -> MontgomeryPoint {
190192
let mut u = &d + &Atemp; /* d, or d+A if nonsquare */
191193
u.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */
192194

193-
MontgomeryPoint(u.to_bytes())
195+
(MontgomeryPoint(u.to_bytes()), eps_is_sq)
194196
}
195197

196198
/// A `ProjectivePoint` holds a point on the projective line
@@ -465,15 +467,15 @@ mod test {
465467
let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");
466468

467469
let fe = FieldElement::from_bytes(&bits_in);
468-
let eg = elligator_encode(&fe);
470+
let (eg, _) = elligator_encode(&fe);
469471
assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
470472
}
471473

472474
#[test]
473475
fn montgomery_elligator_zero_zero() {
474476
let zero = [0u8; 32];
475477
let fe = FieldElement::from_bytes(&zero);
476-
let eg = elligator_encode(&fe);
478+
let (eg, _) = elligator_encode(&fe);
477479
assert_eq!(eg.to_bytes(), zero);
478480
}
479481
}

0 commit comments

Comments
 (0)