Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 43ecd08

Browse files
authoredApr 24, 2024
Elligator2 ntor Edwards Fixes (#1)
Edwards rfc9380 tests and elligator representative randomness using tweaks.
1 parent c69bda8 commit 43ecd08

File tree

5 files changed

+498
-188
lines changed

5 files changed

+498
-188
lines changed
 

‎curve25519-dalek/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ hex = "0.4.2"
3737
json = "0.12.4"
3838
rand = "0.8"
3939
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
40+
rand_distr = "0.4.3"
41+
kolmogorov_smirnov = "1.1.0"
4042

4143
[build-dependencies]
4244
rustc_version = "0.4.0"

‎curve25519-dalek/src/edwards.rs

+49-59
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ use core::ops::{Mul, MulAssign};
103103

104104
use cfg_if::cfg_if;
105105

106-
#[cfg(feature = "digest")]
107-
use crate::elligator2::map_to_point;
106+
#[cfg(feature = "elligator2")]
107+
use crate::elligator2::{map_fe_to_edwards, MASK_UNSET_BYTE};
108108
#[cfg(feature = "digest")]
109109
use digest::{generic_array::typenum::U64, Digest};
110110

@@ -598,57 +598,42 @@ impl EdwardsPoint {
598598

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

601-
let fe1 = map_to_point(&res);
601+
let fe1 = MontgomeryPoint::map_to_point_unbounded(&res);
602602
let E1_opt = fe1.to_edwards(sign_bit);
603603

604604
E1_opt
605605
.expect("Montgomery conversion to Edwards point in Elligator failed")
606606
.mul_by_cofactor()
607607
}
608608

609-
#[cfg(elligator2)]
610-
/// Build an [`EdwardsPoint`] using the birational mapping from (the
611-
/// extended `(u, v)` form of) a montgomery point.
612-
pub fn from_uv(u: &[u8; 32], v: &[u8; 32]) -> EdwardsPoint {
613-
let u_fe = FieldElement::from_bytes(u);
614-
let v_fe = FieldElement::from_bytes(v);
615-
let (x, y) = Self::new_edwards_point(&u_fe, &v_fe);
616-
Self::from_xy(x, y)
617-
}
618-
619-
#[cfg(elligator2)]
620-
fn new_edwards_point(u: &FieldElement, v: &FieldElement) -> (FieldElement, FieldElement) {
621-
// Per RFC 7748: (x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))
622-
623-
let two = &FieldElement::ONE + &FieldElement::ONE;
624-
let (_, sqrt_neg_a_plus_two) =
625-
FieldElement::sqrt_ratio_i(&(&MONTGOMERY_A_NEG + &two), &FieldElement::ONE);
626-
627-
let mut x = &(u * &v.invert()) * &sqrt_neg_a_plus_two;
628-
629-
let u_plus_one = u + &FieldElement::ONE;
630-
let u_minus_one = u - &FieldElement::ONE;
631-
632-
let mut y = &u_minus_one * &u_plus_one.invert();
633-
634-
// This mapping is undefined when t == 0 or s == -1, i.e., when the
635-
// denominator of either of the above rational functions is zero.
636-
// Implementations MUST detect exceptional cases and return the value
637-
// (v, w) = (0, 1), which is the identity point on all twisted Edwards
638-
// curves.
639-
let result_undefined = v.is_zero() | u_plus_one.is_zero();
640-
x.conditional_assign(&FieldElement::ZERO, result_undefined);
641-
y.conditional_assign(&FieldElement::ONE, result_undefined);
642-
643-
// Convert from Edwards (x, y) to extended (x, y, z, t) coordinates.
644-
// new_edwards_from_xy(x, y)
645-
646-
(x, y)
609+
#[cfg(feature = "elligator2")]
610+
/// Perform the Elligator2 mapping to an [`EdwardsPoint`].
611+
///
612+
/// Calculates a point on elliptic curve E (Curve25519) from an element of
613+
/// the finite field F over which E is defined. See section 6.7.1 of the
614+
/// RFC.
615+
///
616+
/// The input u and output P are elements of the field F. Note that
617+
/// the output P is a point on the edwards curve and as such it's byte
618+
/// representation is distinguishable from uniform random.
619+
///
620+
/// Input:
621+
/// * u -> an element of field F.
622+
///
623+
/// Output:
624+
/// * P - a point on the Edwards elliptic curve.
625+
///
626+
/// See <https://datatracker.ietf.org/doc/rfc9380/>
627+
pub fn map_to_point(r: &[u8; 32]) -> EdwardsPoint {
628+
let mut clamped = *r;
629+
clamped[31] &= MASK_UNSET_BYTE;
630+
let r_0 = FieldElement::from_bytes(&clamped);
631+
let (x, y) = map_fe_to_edwards(&r_0);
632+
Self::from_xy(&x, &y)
647633
}
648634

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

@@ -2275,10 +2260,6 @@ mod test {
22752260
"2eb10d432702ea7f79207da95d206f82d5a3b374f5f89f17a199531f78d3bea6",
22762261
"d8f8b508edffbb8b6dab0f602f86a9dd759f800fe18f782fdcac47c234883e7f",
22772262
],
2278-
vec![
2279-
"84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9",
2280-
"93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453",
2281-
],
22822263
vec![
22832264
"c85165952490dc1839cb69012a3d9f2cc4b02343613263ab93a26dc89fd58267",
22842265
"43cbe8685fd3c90665b91835debb89ff1477f906f5170f38a192f6a199556537",
@@ -2291,35 +2272,44 @@ mod test {
22912272
"1618c08ef0233f94f0f163f9435ec7457cd7a8cd4bb6b160315d15818c30f7a2",
22922273
"da0b703593b29dbcd28ebd6e7baea17b6f61971f3641cae774f6a5137a12294c",
22932274
],
2294-
vec![
2295-
"48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c",
2296-
"ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966",
2297-
],
22982275
vec![
22992276
"a744d582b3a34d14d311b7629da06d003045ae77cebceeb4e0e72734d63bd07d",
23002277
"fad25a5ea15d4541258af8785acaf697a886c1b872c793790e60a6837b1adbc0",
23012278
],
2302-
vec![
2303-
"80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095",
2304-
"57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47",
2305-
],
23062279
vec![
23072280
"f06fc939bc10551a0fd415aebf107ef0b9c4ee1ef9a164157bdd089127782617",
23082281
"785b2a6a00a5579cc9da1ff997ce8339b6f9fb46c6f10cf7a12ff2986341a6e0",
23092282
],
2283+
// Non Least-Square-Root representative values. (i.e. representative > 2^254-10 )
2284+
vec![
2285+
"84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9",
2286+
"93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453",
2287+
],
2288+
vec![
2289+
"48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c",
2290+
"ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966",
2291+
],
2292+
vec![
2293+
"80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095",
2294+
"57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47",
2295+
],
23102296
]
23112297
}
23122298

23132299
#[test]
23142300
#[allow(deprecated)]
23152301
#[cfg(all(feature = "alloc", feature = "digest"))]
23162302
fn elligator_signal_test_vectors() {
2317-
for vector in test_vectors().iter() {
2318-
let input = hex::decode(vector[0]).unwrap();
2319-
let output = hex::decode(vector[1]).unwrap();
2303+
for (n, vector) in test_vectors().iter().enumerate() {
2304+
let input = hex::decode(vector[0]).expect("failed to decode hex input");
2305+
let output = hex::decode(vector[1]).expect("failed to decode hex output");
23202306

23212307
let point = EdwardsPoint::nonspec_map_to_curve::<sha2::Sha512>(&input);
2322-
assert_eq!(point.compress().to_bytes(), output[..]);
2308+
assert_eq!(
2309+
hex::encode(point.compress().to_bytes()),
2310+
hex::encode(&output[..]),
2311+
"signal map_to_curve failed for test {n}"
2312+
);
23232313
}
23242314
}
23252315
}

‎curve25519-dalek/src/elligator2.rs

+371-113
Large diffs are not rendered by default.

‎curve25519-dalek/src/montgomery.rs

+58-5
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,61 @@ impl MontgomeryPoint {
248248
}
249249

250250
#[cfg(feature = "elligator2")]
251-
/// This decodes an elligator2 hidden point to a curve point on Curve25519.
252-
pub fn from_representative(&self) -> MontgomeryPoint {
253-
elligator2::map_to_point(&self.0)
251+
/// Perform the Elligator2 mapping to a [`MontgomeryPoint`].
252+
///
253+
/// Calculates a point on elliptic curve E (Curve25519) from an element of
254+
/// the finite field F over which E is defined. See section 6.7.1 of the
255+
/// RFC. The unbounded variant does NOT assume that input values are always
256+
/// going to be the least-square-root representation of the field element.
257+
/// This is divergent from both the elligator2 specification and RFC9380,
258+
/// however, some implementations miss this detail. This allows us to be
259+
/// compatible with those alternate implementations if necessary, since the
260+
/// resulting point will be different for inputs with either of the
261+
/// high-order two bits set.
262+
///
263+
/// The input u and output P are elements of the field F. Note that
264+
/// the output P is a point on the Montgomery curve and as such it's byte
265+
/// representation is distinguishable from uniform random.
266+
///
267+
/// Input:
268+
/// * u -> an element of field F.
269+
///
270+
/// Output:
271+
/// * P - a point on the Montgomery elliptic curve.
272+
///
273+
/// See <https://datatracker.ietf.org/doc/rfc9380/>
274+
pub fn map_to_point_unbounded(r: &[u8; 32]) -> MontgomeryPoint {
275+
let r_0 = FieldElement::from_bytes(r);
276+
let (p, _) = elligator2::map_fe_to_montgomery(&r_0);
277+
MontgomeryPoint(p.as_bytes())
278+
}
279+
280+
#[cfg(feature = "elligator2")]
281+
/// Perform the Elligator2 mapping to a [`MontgomeryPoint`].
282+
///
283+
/// Calculates a point on elliptic curve E (Curve25519) from an element of
284+
/// the finite field F over which E is defined. See section 6.7.1 of the
285+
/// RFC. It is assumed that input values are always going to be the
286+
/// least-square-root representation of the field element in allignment
287+
/// with both the elligator2 specification and RFC9380.
288+
///
289+
/// The input u and output P are elements of the field F. Note that
290+
/// the output P is a point on the Montgomery curve and as such it's byte
291+
/// representation is distinguishable from uniform random.
292+
///
293+
/// Input:
294+
/// * u -> an element of field F.
295+
///
296+
/// Output:
297+
/// * P - a point on the Montgomery elliptic curve.
298+
///
299+
/// See <https://datatracker.ietf.org/doc/rfc9380/>
300+
pub fn map_to_point(r: &[u8; 32]) -> MontgomeryPoint {
301+
let mut clamped = *r;
302+
clamped[31] &= elligator2::MASK_UNSET_BYTE;
303+
let r_0 = FieldElement::from_bytes(&clamped);
304+
let (p, _) = elligator2::map_fe_to_montgomery(&r_0);
305+
MontgomeryPoint(p.as_bytes())
254306
}
255307
}
256308

@@ -408,6 +460,7 @@ mod test {
408460
use crate::constants;
409461

410462
#[cfg(feature = "alloc")]
463+
#[cfg(feature = "elligator2")]
411464
use alloc::vec::Vec;
412465

413466
use rand_core::{CryptoRng, RngCore};
@@ -625,15 +678,15 @@ mod test {
625678
let bytes: Vec<u8> = (0u8..32u8).collect();
626679
let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");
627680

628-
let eg = elligator2::map_to_point(&bits_in);
681+
let eg = MontgomeryPoint::map_to_point(&bits_in);
629682
assert_eq!(eg.0, ELLIGATOR_CORRECT_OUTPUT);
630683
}
631684

632685
#[test]
633686
#[cfg(feature = "elligator2")]
634687
fn montgomery_elligator_zero_zero() {
635688
let zero = [0u8; 32];
636-
let eg = elligator2::map_to_point(&zero);
689+
let eg = MontgomeryPoint::map_to_point(&zero);
637690
assert_eq!(eg.0, zero);
638691
}
639692
}

‎x25519-dalek/src/x25519.rs

+18-11
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl AsRef<[u8]> for PublicKey {
7272
/// secret is used at most once.
7373
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
7474
#[cfg_attr(feature = "zeroize", zeroize(drop))]
75-
pub struct EphemeralSecret(pub(crate) [u8; 32]);
75+
pub struct EphemeralSecret(pub(crate) [u8; 32], pub(crate) u8);
7676

7777
impl EphemeralSecret {
7878
/// Perform a Diffie-Hellman key agreement between `self` and
@@ -94,8 +94,10 @@ impl EphemeralSecret {
9494
pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
9595
// The secret key is random bytes. Clamping is done later.
9696
let mut bytes = [0u8; 32];
97+
let mut tweak = [0u8; 1];
9798
csprng.fill_bytes(&mut bytes);
98-
EphemeralSecret(bytes)
99+
csprng.fill_bytes(&mut tweak);
100+
EphemeralSecret(bytes, tweak[0])
99101
}
100102

101103
/// Generate a new [`EphemeralSecret`].
@@ -134,7 +136,7 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey {
134136
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
135137
#[cfg_attr(feature = "zeroize", zeroize(drop))]
136138
#[derive(Clone)]
137-
pub struct ReusableSecret(pub(crate) [u8; 32]);
139+
pub struct ReusableSecret(pub(crate) [u8; 32], pub(crate) u8);
138140

139141
#[cfg(feature = "reusable_secrets")]
140142
impl ReusableSecret {
@@ -157,8 +159,10 @@ impl ReusableSecret {
157159
pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
158160
// The secret key is random bytes. Clamping is done later.
159161
let mut bytes = [0u8; 32];
162+
let mut tweak = [0u8; 1];
160163
csprng.fill_bytes(&mut bytes);
161-
ReusableSecret(bytes)
164+
csprng.fill_bytes(&mut tweak);
165+
ReusableSecret(bytes, tweak[0])
162166
}
163167

164168
/// Generate a new [`ReusableSecret`].
@@ -195,7 +199,7 @@ impl<'a> From<&'a ReusableSecret> for PublicKey {
195199
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
196200
#[cfg_attr(feature = "zeroize", zeroize(drop))]
197201
#[derive(Clone)]
198-
pub struct StaticSecret([u8; 32]);
202+
pub struct StaticSecret([u8; 32], u8);
199203

200204
#[cfg(feature = "static_secrets")]
201205
impl StaticSecret {
@@ -218,8 +222,10 @@ impl StaticSecret {
218222
pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
219223
// The secret key is random bytes. Clamping is done later.
220224
let mut bytes = [0u8; 32];
225+
let mut tweak = [0u8; 1];
221226
csprng.fill_bytes(&mut bytes);
222-
StaticSecret(bytes)
227+
csprng.fill_bytes(&mut tweak);
228+
StaticSecret(bytes, tweak[0])
223229
}
224230

225231
/// Generate a new [`StaticSecret`].
@@ -245,7 +251,7 @@ impl StaticSecret {
245251
impl From<[u8; 32]> for StaticSecret {
246252
/// Load a secret key from a byte array.
247253
fn from(bytes: [u8; 32]) -> StaticSecret {
248-
StaticSecret(bytes)
254+
StaticSecret(bytes, 0u8)
249255
}
250256
}
251257

@@ -473,7 +479,7 @@ impl<'a> From<&'a [u8; 32]> for PublicRepresentative {
473479
impl<'a> From<&'a EphemeralSecret> for Option<PublicRepresentative> {
474480
/// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicRepresentative`].
475481
fn from(secret: &'a EphemeralSecret) -> Option<PublicRepresentative> {
476-
let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0);
482+
let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0, secret.1);
477483
let res: Option<[u8; 32]> = repres;
478484
Some(PublicRepresentative(res?))
479485
}
@@ -484,7 +490,7 @@ impl<'a> From<&'a EphemeralSecret> for Option<PublicRepresentative> {
484490
impl<'a> From<&'a ReusableSecret> for Option<PublicRepresentative> {
485491
/// Given an x25519 [`ReusableSecret`] key, compute its corresponding [`PublicRepresentative`].
486492
fn from(secret: &'a ReusableSecret) -> Option<PublicRepresentative> {
487-
let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0);
493+
let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0, secret.1);
488494
let res: Option<[u8; 32]> = repres;
489495
Some(PublicRepresentative(res?))
490496
}
@@ -495,7 +501,7 @@ impl<'a> From<&'a ReusableSecret> for Option<PublicRepresentative> {
495501
impl<'a> From<&'a StaticSecret> for Option<PublicRepresentative> {
496502
/// Given an x25519 [`StaticSecret`] key, compute its corresponding [`PublicRepresentative`].
497503
fn from(secret: &'a StaticSecret) -> Option<PublicRepresentative> {
498-
let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0);
504+
let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0, secret.1);
499505
let res: Option<[u8; 32]> = repres;
500506
Some(PublicRepresentative(res?))
501507
}
@@ -505,7 +511,8 @@ impl<'a> From<&'a StaticSecret> for Option<PublicRepresentative> {
505511
impl<'a> From<&'a PublicRepresentative> for PublicKey {
506512
/// Given an elligator2 [`PublicRepresentative`], compute its corresponding [`PublicKey`].
507513
fn from(representative: &'a PublicRepresentative) -> PublicKey {
508-
let point = curve25519_dalek::elligator2::map_to_point(&representative.0);
514+
let point = MontgomeryPoint::map_to_point(&representative.0);
509515
PublicKey(point)
510516
}
511517
}
518+

0 commit comments

Comments
 (0)
Please sign in to comment.