Skip to content

Commit 25a9dbb

Browse files
authored
curve: Hash to curve and field as defined in the standard (#377)
* 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 44bb8cb commit 25a9dbb

File tree

13 files changed

+501
-50
lines changed

13 files changed

+501
-50
lines changed

curve25519-dalek/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ major series.
66
## Unreleased
77

88
* Move AVX-512 backend selection logic to a separate CFG flag that requires nightly
9+
* Add Elligator2 hashing methods `EdwardsPoint::hash_to_curve()` and `FieldElement::hash_to_field()`
910

1011
## 4.x series
1112

curve25519-dalek/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ zeroize = { version = "1", default-features = false, optional = true }
5959
cpufeatures = "0.2.17"
6060

6161
[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies]
62-
fiat-crypto = { version = "0.2.1", default-features = false }
62+
fiat-crypto = { version = "0.3.0", default-features = false }
6363

6464
[features]
6565
default = ["alloc", "precomputed-tables", "zeroize"]
@@ -68,6 +68,7 @@ precomputed-tables = []
6868
legacy_compatibility = []
6969
group = ["dep:group", "rand_core"]
7070
group-bits = ["group", "ff/bits"]
71+
digest = ["dep:digest", "digest/core-api"]
7172

7273
[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies]
7374
curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" }

curve25519-dalek/benches/dalek_benchmarks.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#![allow(non_snake_case)]
22

3-
use rand::{rngs::OsRng, thread_rng};
3+
use rand::{rngs::OsRng, thread_rng, RngCore};
44

55
use criterion::{
66
criterion_main, measurement::Measurement, BatchSize, BenchmarkGroup, BenchmarkId, Criterion,
77
};
8+
#[cfg(feature = "digest")]
9+
use sha2::Sha512;
810

911
use curve25519_dalek::constants;
1012
use curve25519_dalek::scalar::Scalar;
@@ -72,6 +74,21 @@ mod edwards_benches {
7274
});
7375
}
7476

77+
#[cfg(feature = "digest")]
78+
fn hash_to_curve<M: Measurement>(c: &mut BenchmarkGroup<M>) {
79+
let mut rng = thread_rng();
80+
81+
let mut msg = [0u8; 32];
82+
let mut domain_sep = [0u8; 32];
83+
rng.fill_bytes(&mut msg);
84+
rng.fill_bytes(&mut domain_sep);
85+
86+
c.bench_function(
87+
"Elligator2 hash to curve (SHA-512, input size 32 bytes)",
88+
|b| b.iter(|| EdwardsPoint::hash_to_curve::<Sha512>(&[&msg], &[&domain_sep])),
89+
);
90+
}
91+
7592
pub(crate) fn edwards_benches() {
7693
let mut c = Criterion::default();
7794
let mut g = c.benchmark_group("edwards benches");
@@ -83,6 +100,7 @@ mod edwards_benches {
83100
consttime_fixed_base_scalar_mul(&mut g);
84101
consttime_variable_base_scalar_mul(&mut g);
85102
vartime_double_base_scalar_mul(&mut g);
103+
hash_to_curve(&mut g);
86104
}
87105
}
88106

curve25519-dalek/src/backend/serial/fiat_u32/field.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl FieldElement2625 {
230230
/// encoding of every field element should decode, re-encode to
231231
/// the canonical encoding, and check that the input was
232232
/// canonical.
233-
pub fn from_bytes(data: &[u8; 32]) -> FieldElement2625 {
233+
pub const fn from_bytes(data: &[u8; 32]) -> FieldElement2625 {
234234
let mut temp = [0u8; 32];
235235
temp.copy_from_slice(data);
236236
temp[31] &= 127u8;

curve25519-dalek/src/backend/serial/fiat_u64/field.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ impl FieldElement51 {
207207
/// the canonical encoding, and check that the input was
208208
/// canonical.
209209
///
210-
pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 {
210+
pub const fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 {
211211
let mut temp = [0u8; 32];
212212
temp.copy_from_slice(bytes);
213213
temp[31] &= 127u8;

curve25519-dalek/src/backend/serial/u32/constants.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ pub(crate) const MINUS_ONE: FieldElement2625 = FieldElement2625::from_limbs([
3030
33554431,
3131
]);
3232

33+
/// sqrt(-486664)
34+
#[cfg(feature = "digest")]
35+
pub(crate) const ED25519_SQRTAM2: FieldElement2625 = FieldElement2625::from_limbs([
36+
54885894, 25242303, 55597453, 9067496, 51808079, 33312638, 25456129, 14121551, 54921728,
37+
3972023,
38+
]);
39+
3340
/// Edwards `d` value, equal to `-121665/121666 mod p`.
3441
pub(crate) const EDWARDS_D: FieldElement2625 = FieldElement2625::from_limbs([
3542
56195235, 13857412, 51736253, 6949390, 114729, 24766616, 60832955, 30306712, 48412415, 21499315,

curve25519-dalek/src/backend/serial/u32/field.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -333,14 +333,14 @@ impl FieldElement2625 {
333333
/// In other words, each coefficient of the result is bounded by
334334
/// either `2^(25 + 0.007)` or `2^(26 + 0.007)`, as appropriate.
335335
#[rustfmt::skip] // keep alignment of carry chain
336-
fn reduce(mut z: [u64; 10]) -> FieldElement2625 {
336+
const fn reduce(mut z: [u64; 10]) -> FieldElement2625 {
337337

338338
const LOW_25_BITS: u64 = (1 << 25) - 1;
339339
const LOW_26_BITS: u64 = (1 << 26) - 1;
340340

341341
/// Carry the value from limb i = 0..8 to limb i+1
342342
#[inline(always)]
343-
fn carry(z: &mut [u64; 10], i: usize) {
343+
const fn carry(z: &mut [u64; 10], i: usize) {
344344
debug_assert!(i < 9);
345345
if i % 2 == 0 {
346346
// Even limbs have 26 bits
@@ -401,29 +401,32 @@ impl FieldElement2625 {
401401
/// the canonical encoding, and check that the input was
402402
/// canonical.
403403
#[rustfmt::skip] // keep alignment of h[*] values
404-
pub fn from_bytes(data: &[u8; 32]) -> FieldElement2625 {
404+
pub const fn from_bytes(data: &[u8; 32]) -> FieldElement2625 {
405405
#[inline]
406-
fn load3(b: &[u8]) -> u64 {
407-
(b[0] as u64) | ((b[1] as u64) << 8) | ((b[2] as u64) << 16)
406+
const fn load3_at(b: &[u8], i: usize) -> u64 {
407+
(b[i] as u64) | ((b[i + 1] as u64) << 8) | ((b[i + 2] as u64) << 16)
408408
}
409409

410410
#[inline]
411-
fn load4(b: &[u8]) -> u64 {
412-
(b[0] as u64) | ((b[1] as u64) << 8) | ((b[2] as u64) << 16) | ((b[3] as u64) << 24)
411+
const fn load4_at(b: &[u8], i: usize) -> u64 {
412+
(b[i] as u64)
413+
| ((b[i + 1] as u64) << 8)
414+
| ((b[i + 2] as u64) << 16)
415+
| ((b[i + 3] as u64) << 24)
413416
}
414417

415418
let mut h = [0u64;10];
416419
const LOW_23_BITS: u64 = (1 << 23) - 1;
417-
h[0] = load4(&data[ 0..]);
418-
h[1] = load3(&data[ 4..]) << 6;
419-
h[2] = load3(&data[ 7..]) << 5;
420-
h[3] = load3(&data[10..]) << 3;
421-
h[4] = load3(&data[13..]) << 2;
422-
h[5] = load4(&data[16..]);
423-
h[6] = load3(&data[20..]) << 7;
424-
h[7] = load3(&data[23..]) << 5;
425-
h[8] = load3(&data[26..]) << 4;
426-
h[9] = (load3(&data[29..]) & LOW_23_BITS) << 2;
420+
h[0] = load4_at(data, 0);
421+
h[1] = load3_at(data, 4) << 6;
422+
h[2] = load3_at(data, 7) << 5;
423+
h[3] = load3_at(data, 10) << 3;
424+
h[4] = load3_at(data, 13) << 2;
425+
h[5] = load4_at(data, 16);
426+
h[6] = load3_at(data, 20) << 7;
427+
h[7] = load3_at(data, 23) << 5;
428+
h[8] = load3_at(data, 26) << 4;
429+
h[9] = (load3_at(data, 29) & LOW_23_BITS) << 2;
427430

428431
FieldElement2625::reduce(h)
429432
}

curve25519-dalek/src/backend/serial/u64/constants.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ pub(crate) const MINUS_ONE: FieldElement51 = FieldElement51::from_limbs([
3131
2251799813685247,
3232
]);
3333

34+
/// sqrt(-486664)
35+
#[cfg(feature = "digest")]
36+
pub(crate) const ED25519_SQRTAM2: FieldElement51 = FieldElement51::from_limbs([
37+
1693982333959686,
38+
608509411481997,
39+
2235573344831311,
40+
947681270984193,
41+
266558006233600,
42+
]);
43+
3444
/// Edwards `d` value, equal to `-121665/121666 mod p`.
3545
pub(crate) const EDWARDS_D: FieldElement51 = FieldElement51::from_limbs([
3646
929955233495203,

curve25519-dalek/src/backend/serial/u64/field.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -335,30 +335,30 @@ impl FieldElement51 {
335335
/// canonical.
336336
///
337337
#[rustfmt::skip] // keep alignment of bit shifts
338-
pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 {
339-
let load8 = |input: &[u8]| -> u64 {
340-
(input[0] as u64)
341-
| ((input[1] as u64) << 8)
342-
| ((input[2] as u64) << 16)
343-
| ((input[3] as u64) << 24)
344-
| ((input[4] as u64) << 32)
345-
| ((input[5] as u64) << 40)
346-
| ((input[6] as u64) << 48)
347-
| ((input[7] as u64) << 56)
348-
};
338+
pub const fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 {
339+
const fn load8_at(input: &[u8], i: usize) -> u64 {
340+
(input[i] as u64)
341+
| ((input[i + 1] as u64) << 8)
342+
| ((input[i + 2] as u64) << 16)
343+
| ((input[i + 3] as u64) << 24)
344+
| ((input[i + 4] as u64) << 32)
345+
| ((input[i + 5] as u64) << 40)
346+
| ((input[i + 6] as u64) << 48)
347+
| ((input[i + 7] as u64) << 56)
348+
}
349349

350350
let low_51_bit_mask = (1u64 << 51) - 1;
351351
FieldElement51(
352352
// load bits [ 0, 64), no shift
353-
[ load8(&bytes[ 0..]) & low_51_bit_mask
353+
[ load8_at(bytes, 0) & low_51_bit_mask
354354
// load bits [ 48,112), shift to [ 51,112)
355-
, (load8(&bytes[ 6..]) >> 3) & low_51_bit_mask
355+
, (load8_at(bytes, 6) >> 3) & low_51_bit_mask
356356
// load bits [ 96,160), shift to [102,160)
357-
, (load8(&bytes[12..]) >> 6) & low_51_bit_mask
357+
, (load8_at(bytes, 12) >> 6) & low_51_bit_mask
358358
// load bits [152,216), shift to [153,216)
359-
, (load8(&bytes[19..]) >> 1) & low_51_bit_mask
359+
, (load8_at(bytes, 19) >> 1) & low_51_bit_mask
360360
// load bits [192,256), shift to [204,112)
361-
, (load8(&bytes[24..]) >> 12) & low_51_bit_mask
361+
, (load8_at(bytes, 24) >> 12) & low_51_bit_mask
362362
])
363363
}
364364

curve25519-dalek/src/constants.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,14 @@ mod test {
175175
let should_be_ad_minus_one = constants::SQRT_AD_MINUS_ONE.square();
176176
assert_eq!(should_be_ad_minus_one, ad_minus_one);
177177
}
178+
179+
/// Test that ED25519_SQRTAM2 squared is MONTGOMERY_A_NEG - 2
180+
#[test]
181+
#[cfg(feature = "digest")]
182+
fn test_sqrt_a_minus_2() {
183+
let one = FieldElement::ONE;
184+
let a_minus_two = &(&constants::MONTGOMERY_A_NEG - &one) - &one;
185+
186+
assert_eq!(constants::ED25519_SQRTAM2.square(), a_minus_two)
187+
}
178188
}

0 commit comments

Comments
 (0)