Skip to content

Commit

Permalink
Elligator2 ntor Edwards Fixes (#1)
Browse files Browse the repository at this point in the history
Edwards rfc9380 tests and elligator representative randomness using tweaks.
  • Loading branch information
jmwample committed Apr 24, 2024
1 parent c69bda8 commit 43ecd08
Show file tree
Hide file tree
Showing 5 changed files with 498 additions and 188 deletions.
2 changes: 2 additions & 0 deletions curve25519-dalek/Cargo.toml
Expand Up @@ -37,6 +37,8 @@ hex = "0.4.2"
json = "0.12.4"
rand = "0.8"
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
rand_distr = "0.4.3"
kolmogorov_smirnov = "1.1.0"

[build-dependencies]
rustc_version = "0.4.0"
Expand Down
108 changes: 49 additions & 59 deletions curve25519-dalek/src/edwards.rs
Expand Up @@ -103,8 +103,8 @@ use core::ops::{Mul, MulAssign};

use cfg_if::cfg_if;

#[cfg(feature = "digest")]
use crate::elligator2::map_to_point;
#[cfg(feature = "elligator2")]
use crate::elligator2::{map_fe_to_edwards, MASK_UNSET_BYTE};
#[cfg(feature = "digest")]
use digest::{generic_array::typenum::U64, Digest};

Expand Down Expand Up @@ -598,57 +598,42 @@ impl EdwardsPoint {

let sign_bit = (res[31] & 0x80) >> 7;

let fe1 = map_to_point(&res);
let fe1 = MontgomeryPoint::map_to_point_unbounded(&res);
let E1_opt = fe1.to_edwards(sign_bit);

E1_opt
.expect("Montgomery conversion to Edwards point in Elligator failed")
.mul_by_cofactor()
}

#[cfg(elligator2)]
/// Build an [`EdwardsPoint`] using the birational mapping from (the
/// extended `(u, v)` form of) a montgomery point.
pub fn from_uv(u: &[u8; 32], v: &[u8; 32]) -> EdwardsPoint {
let u_fe = FieldElement::from_bytes(u);
let v_fe = FieldElement::from_bytes(v);
let (x, y) = Self::new_edwards_point(&u_fe, &v_fe);
Self::from_xy(x, y)
}

#[cfg(elligator2)]
fn new_edwards_point(u: &FieldElement, v: &FieldElement) -> (FieldElement, FieldElement) {
// Per RFC 7748: (x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))

let two = &FieldElement::ONE + &FieldElement::ONE;
let (_, sqrt_neg_a_plus_two) =
FieldElement::sqrt_ratio_i(&(&MONTGOMERY_A_NEG + &two), &FieldElement::ONE);

let mut x = &(u * &v.invert()) * &sqrt_neg_a_plus_two;

let u_plus_one = u + &FieldElement::ONE;
let u_minus_one = u - &FieldElement::ONE;

let mut y = &u_minus_one * &u_plus_one.invert();

// This mapping is undefined when t == 0 or s == -1, i.e., when the
// denominator of either of the above rational functions is zero.
// Implementations MUST detect exceptional cases and return the value
// (v, w) = (0, 1), which is the identity point on all twisted Edwards
// curves.
let result_undefined = v.is_zero() | u_plus_one.is_zero();
x.conditional_assign(&FieldElement::ZERO, result_undefined);
y.conditional_assign(&FieldElement::ONE, result_undefined);

// Convert from Edwards (x, y) to extended (x, y, z, t) coordinates.
// new_edwards_from_xy(x, y)

(x, y)
#[cfg(feature = "elligator2")]
/// Perform the Elligator2 mapping to an [`EdwardsPoint`].
///
/// Calculates a point on elliptic curve E (Curve25519) from an element of
/// the finite field F over which E is defined. See section 6.7.1 of the
/// RFC.
///
/// The input u and output P are elements of the field F. Note that
/// the output P is a point on the edwards curve and as such it's byte
/// representation is distinguishable from uniform random.
///
/// Input:
/// * u -> an element of field F.
///
/// Output:
/// * P - a point on the Edwards elliptic curve.
///
/// See <https://datatracker.ietf.org/doc/rfc9380/>
pub fn map_to_point(r: &[u8; 32]) -> EdwardsPoint {
let mut clamped = *r;
clamped[31] &= MASK_UNSET_BYTE;
let r_0 = FieldElement::from_bytes(&clamped);
let (x, y) = map_fe_to_edwards(&r_0);
Self::from_xy(&x, &y)
}

#[cfg(elligator2)]
#[cfg(feature = "elligator2")]
fn from_xy(x: &FieldElement, y: &FieldElement) -> EdwardsPoint {
// Yeah yeah yeah, no where better to put this. :(
let z = FieldElement::ONE;
let t = x * y;

Expand Down Expand Up @@ -2275,10 +2260,6 @@ mod test {
"2eb10d432702ea7f79207da95d206f82d5a3b374f5f89f17a199531f78d3bea6",
"d8f8b508edffbb8b6dab0f602f86a9dd759f800fe18f782fdcac47c234883e7f",
],
vec![
"84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9",
"93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453",
],
vec![
"c85165952490dc1839cb69012a3d9f2cc4b02343613263ab93a26dc89fd58267",
"43cbe8685fd3c90665b91835debb89ff1477f906f5170f38a192f6a199556537",
Expand All @@ -2291,35 +2272,44 @@ mod test {
"1618c08ef0233f94f0f163f9435ec7457cd7a8cd4bb6b160315d15818c30f7a2",
"da0b703593b29dbcd28ebd6e7baea17b6f61971f3641cae774f6a5137a12294c",
],
vec![
"48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c",
"ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966",
],
vec![
"a744d582b3a34d14d311b7629da06d003045ae77cebceeb4e0e72734d63bd07d",
"fad25a5ea15d4541258af8785acaf697a886c1b872c793790e60a6837b1adbc0",
],
vec![
"80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095",
"57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47",
],
vec![
"f06fc939bc10551a0fd415aebf107ef0b9c4ee1ef9a164157bdd089127782617",
"785b2a6a00a5579cc9da1ff997ce8339b6f9fb46c6f10cf7a12ff2986341a6e0",
],
// Non Least-Square-Root representative values. (i.e. representative > 2^254-10 )
vec![
"84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9",
"93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453",
],
vec![
"48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c",
"ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966",
],
vec![
"80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095",
"57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47",
],
]
}

#[test]
#[allow(deprecated)]
#[cfg(all(feature = "alloc", feature = "digest"))]
fn elligator_signal_test_vectors() {
for vector in test_vectors().iter() {
let input = hex::decode(vector[0]).unwrap();
let output = hex::decode(vector[1]).unwrap();
for (n, vector) in test_vectors().iter().enumerate() {
let input = hex::decode(vector[0]).expect("failed to decode hex input");
let output = hex::decode(vector[1]).expect("failed to decode hex output");

let point = EdwardsPoint::nonspec_map_to_curve::<sha2::Sha512>(&input);
assert_eq!(point.compress().to_bytes(), output[..]);
assert_eq!(
hex::encode(point.compress().to_bytes()),
hex::encode(&output[..]),
"signal map_to_curve failed for test {n}"
);
}
}
}

0 comments on commit 43ecd08

Please sign in to comment.