diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml
index dc3b2b4e..e0c0a057 100644
--- a/curve25519-dalek/Cargo.toml
+++ b/curve25519-dalek/Cargo.toml
@@ -27,13 +27,14 @@ rustdoc-args = [
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
"--cfg", "docsrs",
]
-features = ["serde", "rand_core", "digest", "legacy_compatibility", "group-bits"]
+features = ["serde", "rand_core", "elligator2", "digest", "legacy_compatibility", "group-bits"]
[dev-dependencies]
sha2 = { version = "0.10", default-features = false }
bincode = "1"
criterion = { version = "0.5", features = ["html_reports"] }
hex = "0.4.2"
+json = "0.12.4"
rand = "0.8"
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
@@ -68,6 +69,8 @@ precomputed-tables = []
legacy_compatibility = []
group = ["dep:group", "rand_core"]
group-bits = ["group", "ff/bits"]
+elligator2 = []
+digest = ["dep:digest", "elligator2"]
[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies]
curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" }
diff --git a/curve25519-dalek/README.md b/curve25519-dalek/README.md
index c918d695..c028a6e1 100644
--- a/curve25519-dalek/README.md
+++ b/curve25519-dalek/README.md
@@ -56,6 +56,7 @@ curve25519-dalek = ">= 4.0, < 4.2"
| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. |
| `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. |
| `group` | | Enables external `group` and `ff` crate traits |
+| `elligator2` | | Enables elligator2 functionality for supported types. This allows curve points to be encoded to uniform random representatives, and 32 byte values to be mapped (back) to curve points. |
To disable the default features when using `curve25519-dalek` as a dependency,
add `default-features = false` to the dependency in your `Cargo.toml`. To
diff --git a/curve25519-dalek/src/backend/serial/u32/constants.rs b/curve25519-dalek/src/backend/serial/u32/constants.rs
index 03e094e4..c1a1f2b3 100644
--- a/curve25519-dalek/src/backend/serial/u32/constants.rs
+++ b/curve25519-dalek/src/backend/serial/u32/constants.rs
@@ -71,12 +71,14 @@ pub(crate) const SQRT_M1: FieldElement2625 = FieldElement2625::from_limbs([
pub(crate) const APLUS2_OVER_FOUR: FieldElement2625 =
FieldElement2625::from_limbs([121666, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+#[cfg(feature = "elligator2")]
/// `MONTGOMERY_A` is equal to 486662, which is a constant of the curve equation
/// for Curve25519 in its Montgomery form. (This is used internally within the
/// Elligator map.)
pub(crate) const MONTGOMERY_A: FieldElement2625 =
FieldElement2625::from_limbs([486662, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+#[cfg(feature = "elligator2")]
/// `MONTGOMERY_A_NEG` is equal to -486662. (This is used internally within the
/// Elligator map.)
pub(crate) const MONTGOMERY_A_NEG: FieldElement2625 = FieldElement2625::from_limbs([
diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs
index 7319288a..a2ffe264 100644
--- a/curve25519-dalek/src/backend/serial/u32/field.rs
+++ b/curve25519-dalek/src/backend/serial/u32/field.rs
@@ -601,4 +601,24 @@ impl FieldElement2625 {
}
FieldElement2625::reduce(coeffs)
}
+
+ /// Returns 1 if self is greater than the other and 0 otherwise
+ // implementation based on C libgmp -> mpn_sub_n
+ pub(crate) fn gt(&self, other: &Self) -> Choice {
+ let mut _ul = 0_u32;
+ let mut _vl = 0_u32;
+ let mut _rl = 0_u32;
+
+ let mut cy = 0_u32;
+ for i in 0..10 {
+ _ul = self.0[i];
+ _vl = other.0[i];
+
+ let (_sl, _cy1) = _ul.overflowing_sub(_vl);
+ let (_rl, _cy2) = _sl.overflowing_sub(cy);
+ cy = _cy1 as u32 | _cy2 as u32;
+ }
+
+ Choice::from((cy != 0_u32) as u8)
+ }
}
diff --git a/curve25519-dalek/src/backend/serial/u64/constants.rs b/curve25519-dalek/src/backend/serial/u64/constants.rs
index baeb1dd5..cd9520e3 100644
--- a/curve25519-dalek/src/backend/serial/u64/constants.rs
+++ b/curve25519-dalek/src/backend/serial/u64/constants.rs
@@ -98,11 +98,13 @@ pub(crate) const SQRT_M1: FieldElement51 = FieldElement51::from_limbs([
pub(crate) const APLUS2_OVER_FOUR: FieldElement51 =
FieldElement51::from_limbs([121666, 0, 0, 0, 0]);
+#[cfg(feature = "elligator2")]
/// `MONTGOMERY_A` is equal to 486662, which is a constant of the curve equation
/// for Curve25519 in its Montgomery form. (This is used internally within the
/// Elligator map.)
pub(crate) const MONTGOMERY_A: FieldElement51 = FieldElement51::from_limbs([486662, 0, 0, 0, 0]);
+#[cfg(feature = "elligator2")]
/// `MONTGOMERY_A_NEG` is equal to -486662. (This is used internally within the
/// Elligator map.)
pub(crate) const MONTGOMERY_A_NEG: FieldElement51 = FieldElement51::from_limbs([
diff --git a/curve25519-dalek/src/backend/serial/u64/field.rs b/curve25519-dalek/src/backend/serial/u64/field.rs
index 1263d23e..673f5f92 100644
--- a/curve25519-dalek/src/backend/serial/u64/field.rs
+++ b/curve25519-dalek/src/backend/serial/u64/field.rs
@@ -572,4 +572,24 @@ impl FieldElement51 {
square
}
+
+ /// Returns 1 if self is greater than the other and 0 otherwise
+ // implementation based on C libgmp -> mpn_sub_n
+ pub(crate) fn gt(&self, other: &Self) -> Choice {
+ let mut _ul = 0_u64;
+ let mut _vl = 0_u64;
+ let mut _rl = 0_u64;
+
+ let mut cy = 0_u64;
+ for i in 0..5 {
+ _ul = self.0[i];
+ _vl = other.0[i];
+
+ let (_sl, _cy1) = _ul.overflowing_sub(_vl);
+ let (_rl, _cy2) = _sl.overflowing_sub(cy);
+ cy = _cy1 as u64 | _cy2 as u64;
+ }
+
+ Choice::from((cy != 0_u64) as u8)
+ }
}
diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs
index 856fac12..7392a571 100644
--- a/curve25519-dalek/src/edwards.rs
+++ b/curve25519-dalek/src/edwards.rs
@@ -103,6 +103,8 @@ use core::ops::{Mul, MulAssign};
use cfg_if::cfg_if;
+#[cfg(feature = "digest")]
+use crate::elligator2::map_to_point;
#[cfg(feature = "digest")]
use digest::{generic_array::typenum::U64, Digest};
@@ -596,15 +598,67 @@ impl EdwardsPoint {
let sign_bit = (res[31] & 0x80) >> 7;
- let fe = FieldElement::from_bytes(&res);
-
- let M1 = crate::montgomery::elligator_encode(&fe);
- let E1_opt = M1.to_edwards(sign_bit);
+ let fe1 = map_to_point(&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(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;
+
+ EdwardsPoint {
+ X: *x,
+ Y: *y,
+ Z: z,
+ T: t,
+ }
+ }
}
// ------------------------------------------------------------------------
diff --git a/curve25519-dalek/src/elligator2.rs b/curve25519-dalek/src/elligator2.rs
new file mode 100644
index 00000000..085cdbfa
--- /dev/null
+++ b/curve25519-dalek/src/elligator2.rs
@@ -0,0 +1,1091 @@
+// -*- mode: rust; -*-
+
+//! Functions mapping curve points to representative values
+//! (indistinguishable from random), and back again.
+use crate::constants::{MONTGOMERY_A, MONTGOMERY_A_NEG};
+use crate::field::FieldElement;
+use crate::montgomery::MontgomeryPoint;
+use crate::EdwardsPoint;
+
+use subtle::{
+ Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater,
+ CtOption,
+};
+
+/// (p - 1) / 2 = 2^254 - 10
+pub(crate) const DIVIDE_MINUS_P_1_2_BYTES: [u8; 32] = [
+ 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
+];
+
+/// Gets the public representative for a key pair using the private key
+pub fn representative_from_privkey(privkey: &[u8; 32]) -> Option<[u8; 32]> {
+ let pubkey = EdwardsPoint::mul_base_clamped(*privkey).to_montgomery();
+ let v_in_sqrt = v_in_sqrt(privkey);
+ point_to_representative(&pubkey, v_in_sqrt.into()).into()
+}
+
+/// This function is used to map a curve point (i.e. an x25519 public key)
+/// to a point that is effectively indistinguishable from random noise.
+///
+/// This operation may fail because not all curve points are capable of being
+/// hidden. On failure, this function will return None.
+///
+/// This implementation is adapted from both:
+/// - [kleshni C implementation](https://github.com/Kleshni/Elligator-2)
+/// - [agl/ed25519 golang forks](https://gitlab.com/yawning/edwards25519-extra)
+///
+///
+/// Note: the output field elements of this function are uniformly
+/// distributed among the nonnegative field elements, but only if the
+/// input points are also uniformly distributed among all points of
+/// the curve. In particular, if the inputs are only selected from
+/// members of the prime order group, then the outputs are
+/// distinguishable from random.
+///
+/// # Inputs
+///
+/// * `point`: the \\(u\\)-coordinate of a point on the curve. Not all
+/// points map to field elements.
+///
+/// * `v_in_sqrt`: true if the \\(v\\)-coordinate of the point is negative.
+///
+/// # Returns
+///
+/// Either `None`, if the point couldn't be mapped, or `Some(fe)` such
+/// that `fe` is nonnegative and `map_to_point(&fe) == point`.
+///
+/// [elligator paper](https://elligator.cr.yp.to/elligator-20130828.pdf)
+/// [elligator site](https://elligator.org/)
+///
+pub fn point_to_representative(point: &MontgomeryPoint, v_in_sqrt: bool) -> CtOption<[u8; 32]> {
+ let divide_minus_p_1_2 = FieldElement::from_bytes(&DIVIDE_MINUS_P_1_2_BYTES);
+
+ // a := point
+ let a = &FieldElement::from_bytes(&point.0);
+ let mut a_neg = *a;
+ a_neg.negate();
+
+ let is_encodable = is_encodable(a);
+
+ // Calculate r1 = sqrt(-a/(2*(a+A)))
+ let (_r1_sqrt, r1) = FieldElement::sqrt_ratio_i(
+ &a_neg,
+ &(&(a + &MONTGOMERY_A) * &(&FieldElement::ONE + &FieldElement::ONE)),
+ );
+
+ // Calculate r0 = sqrt(-(a+A)/(2a))
+ let (_r0_sqrt, r0) = FieldElement::sqrt_ratio_i(
+ &(&a_neg - &MONTGOMERY_A),
+ &(a * &(&FieldElement::ONE + &FieldElement::ONE)),
+ );
+
+ // if v_in_sqrt root=r0 otherwise r1
+ let mut b = FieldElement::conditional_select(&r1, &r0, Choice::from(v_in_sqrt as u8));
+
+ // If root > (p - 1) / 2, root := -root
+ let negate = divide_minus_p_1_2.ct_gt(&b);
+ FieldElement::conditional_negate(&mut b, negate);
+
+ CtOption::new(b.as_bytes(), is_encodable)
+}
+
+/// Determines whether a point is encodable as a representative. Approximately
+/// 50% of points are not encodable.
+#[inline]
+fn is_encodable(u: &FieldElement) -> Choice {
+ let b0 = u + &MONTGOMERY_A;
+ let b1 = &(&(&b0.square().square() * &b0.square()) * &b0) * u; // b1 = u * (u + A)^7
+ let c = b1.pow_p58();
+
+ let b2 = &(&b0.square().square().square() * &b0.square().square()) * &b0.square(); // (u + A)^14
+ let mut chi = &(&c.square().square() * &u.square()) * &b2; // chi = -c^4 * u^2 * (u + A)^14
+ chi.negate();
+
+ let chi_bytes = chi.as_bytes();
+
+ // chi[1] is either 0 or 0xff
+ chi_bytes[1].ct_eq(&0_u8)
+}
+
+/// `high_y` - Montgomery points can have have two different values for a
+/// single X coordinate (based on sign). The Kleshni implementation determines
+/// which sign value to use with this function.
+///
+/// If the point is 0, this should be ignored.
+#[inline]
+pub(crate) fn high_y(d: &FieldElement) -> Choice {
+ let d_sq = &d.square();
+ let au = &MONTGOMERY_A * &d;
+
+ let inner = &(d_sq + &au) + &FieldElement::ONE;
+ let eps = d * &inner; /* eps = d^3 + Ad^2 + d */
+
+ let (eps_is_sq, _) = FieldElement::sqrt_ratio_i(&eps, &FieldElement::ONE);
+
+ eps_is_sq
+}
+
+/// Determines if `V <= (p - 1)/2` for a Scalar (e.g an x25519 private key) and
+/// returns a [`Choice`] indicating the result.
+///
+/// Note: When using the elligator2 transformations on x25519 keypairs this
+/// requires the use of the clamped scalar_base_mult of the private key to
+/// get the edwards representation of the public key, otherwise we need to know
+/// the correct sign value for the edwards conversion or the derivation of
+/// the `V` value will be broken. As noted in [`EdwardsPoint::to_montgomery`],
+/// the sign information about the X coordinate is lost on conversion so we
+/// have to use the edwards point derived from the private key to guarantee the
+/// correct value here.
+///
+/// Alternatively you can keep track of the public key and sign bit manually
+/// and construct an EdwardsPoint for which [`v_in_sqrt_pubkey_edwards`] will
+/// give you the same result.
+///
+// As an interface, using the private key should work just fine. This allows
+// us to match the existing [`PublicKey`] generation interface for the
+// [`PublicRepresentative`] in the [`x25519_dalek`] crate. AFAIK there is no
+// need for anyone with only the public key to be able to generate the
+// representative.
+pub fn v_in_sqrt(key_input: &[u8; 32]) -> Choice {
+ let mut masked_pk = *key_input;
+ masked_pk[0] &= 0xf8;
+ masked_pk[31] &= 0x7f;
+ masked_pk[31] |= 0x40;
+
+ let pubkey = EdwardsPoint::mul_base_clamped(masked_pk);
+ v_in_sqrt_pubkey_edwards(&pubkey)
+}
+
+/// Determines if `V <= (p - 1)/2` for an EdwardsPoint (e.g an x25519 public key)
+/// and returns a [`Choice`] indicating the result.
+pub fn v_in_sqrt_pubkey_edwards(pubkey: &EdwardsPoint) -> Choice {
+ let divide_minus_p_1_2 = FieldElement::from_bytes(&DIVIDE_MINUS_P_1_2_BYTES);
+
+ // sqrtMinusAPlus2 is sqrt(-(486662+2))
+ let (_, sqrt_minus_a_plus_2) = FieldElement::sqrt_ratio_i(
+ &(&MONTGOMERY_A_NEG - &(&FieldElement::ONE + &FieldElement::ONE)),
+ &FieldElement::ONE,
+ );
+
+ // inv1 = 1/((A.Z - A.y) * A.X)
+ let inv1 = (&(&pubkey.Z - &pubkey.Y) * &pubkey.X).invert();
+
+ // t0 = A.Y + A.Z
+ let t0 = &pubkey.Y + &pubkey.Z;
+
+ // v = t0 * inv1 * A.Z * sqrtMinusAPlus2
+ let v = &(&t0 * &inv1) * &(&pubkey.Z * &sqrt_minus_a_plus_2);
+
+ // is v <= (q-1)/2 ?
+ divide_minus_p_1_2.ct_gt(&v)
+}
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+#[allow(unused, non_snake_case)]
+/// Perform the Elligator2 mapping to Curve25519 in accordance with RFC9380.
+///
+/// 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 r and outputs u and v are elements of the field F. The
+/// affine coordinates (u, v) specify a point on an elliptic curve
+/// defined over F. Note, however, that the point (u, v) is not a
+/// uniformly random point.
+///
+/// Input:
+/// * r -> an element of field F.
+///
+/// Output:
+/// * Q - a point in `(u,v)` for on the Montgomery elliptic curve.
+///
+/// See
+pub fn map_to_curve(r: &[u8; 32]) -> ([u8; 32], [u8; 32]) {
+ let fe = FieldElement::from_bytes(r);
+ let (x, y) = map_fe_to_curve(&fe);
+ (x.as_bytes(), y.as_bytes())
+}
+
+pub(crate) fn map_fe_to_curve(r: &FieldElement) -> (FieldElement, FieldElement) {
+ let zero = FieldElement::ZERO;
+ let one = FieldElement::ONE;
+ let mut minus_one = FieldElement::ONE;
+ minus_one.negate();
+
+ // Exceptional case 2u^2 == -1
+ let mut tv1 = r.square2();
+ tv1.conditional_assign(&zero, tv1.ct_eq(&minus_one));
+
+ let d_1 = &one + &tv1; /* 1 + 2u^2 */
+ let d = &MONTGOMERY_A_NEG * &(d_1.invert()); /* d = -A/(1+2u^2) */
+
+ let inner = &(&d.square() + &(&d * &MONTGOMERY_A)) + &one;
+ let gx1 = &d * &inner; /* gx1 = d^3 + Ad^2 + d */
+ let gx2 = &gx1 * &tv1;
+
+ let eps_is_sq = high_y(&d);
+
+ // complete X
+ /* A_temp = 0, or A if nonsquare*/
+ let a_temp = FieldElement::conditional_select(&MONTGOMERY_A, &zero, eps_is_sq);
+ let mut x = &d + &a_temp; /* d, or d+A if nonsquare */
+ x.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */
+
+ // complete Y
+ let y2 = FieldElement::conditional_select(&gx2, &gx1, eps_is_sq);
+ let (_, mut y) = FieldElement::sqrt_ratio_i(&y2, &one);
+ y.conditional_negate(eps_is_sq ^ y.is_negative());
+
+ (x, y)
+}
+
+#[allow(unused, non_snake_case)]
+/// Perform the Elligator2 mapping to a [`MontgomeryPoint`].
+///
+/// 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, however, that
+/// the output point P is not a uniformly random point.
+///
+/// Input:
+/// * u -> an element of field F.
+///
+/// Output:
+/// * P - a point on the Montgomery elliptic curve.
+///
+/// See
+pub fn map_to_point(r: &[u8; 32]) -> MontgomeryPoint {
+ let r_0 = FieldElement::from_bytes(r);
+ let (p, _) = map_fe_to_curve(&r_0);
+ MontgomeryPoint(p.as_bytes())
+}
+
+// ------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------
+
+#[cfg(test)]
+#[cfg(feature = "elligator2")]
+#[cfg(feature = "alloc")]
+mod test {
+ use super::*;
+
+ const MASK_UNSET_BYTE: u8 = 0x3f;
+
+ ////////////////////////////////////////////////////////////
+ // Ntor tests //
+ ////////////////////////////////////////////////////////////
+
+ #[test]
+ #[cfg(feature = "elligator2")]
+ fn repres_from_pubkey_kleshni() {
+ // testcases from kleshni
+ for (i, testcase) in encoding_testcases().iter().enumerate() {
+ let point: [u8; 32] = hex::decode(testcase.point).unwrap().try_into().unwrap();
+
+ let edw_point = MontgomeryPoint(point)
+ .to_edwards(testcase.high_y as u8)
+ .unwrap();
+ let v_in_sqrt = v_in_sqrt_pubkey_edwards(&edw_point);
+
+ let repres: Option<[u8; 32]> =
+ point_to_representative(&MontgomeryPoint(point), v_in_sqrt.into()).into();
+ if testcase.representative.is_some() {
+ assert_eq!(
+ testcase.representative.unwrap(),
+ hex::encode(repres.unwrap()),
+ "[good case] kleshni ({i}) bad pubkey from true representative"
+ );
+ } else {
+ assert!(
+ repres.is_none(),
+ "[good case] kleshni ({i}) expected none got repres {}",
+ hex::encode(repres.unwrap())
+ );
+ }
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "elligator2")]
+ /// Ensure private keys generate the expected representatives. These tests
+ /// are generated from agl/ed25519 to ensure compatibility.
+ fn repres_from_privkey_agl() {
+ for (i, vector) in ntor_valid_test_vectors().iter().enumerate() {
+ let privkey: [u8; 32] = hex::decode(vector[0]).unwrap().try_into().unwrap();
+ let true_repres = vector[2];
+
+ let repres_res = representative_from_privkey(&privkey);
+ assert!(
+ Into::::into(repres_res.is_some()),
+ "failed to get representative when we should have gotten one :("
+ );
+ let mut repres = repres_res.unwrap();
+
+ repres[31] &= MASK_UNSET_BYTE;
+ assert_eq!(
+ true_repres,
+ hex::encode(repres),
+ "[good case] agl/ed25519 ({i}) bad representative from privkey"
+ );
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "elligator2")]
+ fn pubkey_from_repres() {
+ // testcases from kleshni
+ for (i, testcase) in decoding_testcases().iter().enumerate() {
+ let repres: [u8; 32] = hex::decode(testcase.representative)
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let point = MontgomeryPoint::from_representative(&MontgomeryPoint(repres));
+ assert_eq!(
+ testcase.point,
+ hex::encode(point.to_bytes()),
+ "[good case] kleshni ({i}) bad representative from point"
+ );
+ }
+
+ // testcases from golang agl/ed25519
+ for (i, vector) in ntor_valid_test_vectors().iter().enumerate() {
+ let true_pubkey = vector[1];
+ let repres: [u8; 32] = hex::decode(vector[2]).unwrap().try_into().unwrap();
+
+ // ensure that the representative can be reversed to recover the
+ // original public key.
+ let pubkey = MontgomeryPoint::from_representative(&MontgomeryPoint(repres));
+ assert_eq!(
+ true_pubkey,
+ hex::encode(pubkey.to_bytes()),
+ "[good case] agl/ed25519 ({i}) bad pubkey from true representative"
+ );
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "elligator2")]
+ fn non_representable_points() {
+ for (i, key) in ntor_invalid_keys().iter().enumerate() {
+ let privkey: [u8; 32] = hex::decode(key).unwrap().try_into().unwrap();
+
+ // ensure that the representative can be reversed to recover the
+ // original public key.
+ let res: Option<[u8; 32]> = representative_from_privkey(&privkey).into();
+ assert!(
+ res.is_none(),
+ "[bad case] agl/ed25519 ({i}) expected None, got Some({})",
+ hex::encode(res.unwrap())
+ );
+ }
+ }
+
+ /// returns a set of keypair encodings generated by (a fork of) the golang
+ /// implementation agl/ed25519 which is used by the mainstream obfs4
+ /// library. Each set of three strings is
+ ///
+ /// 1) the generated private key scalar
+ /// 2) the associated public key point
+ /// 3) the successfully created representative
+ ///
+ /// All of these are valid keypairs and our public key to
+ /// representative implementation (and repres-to-pubkey) should match and
+ /// handle these cases.
+ ///
+ fn ntor_valid_test_vectors() -> [[&'static str; 3]; 20] {
+ [
+ [
+ "eec0c0e43a2f693557dac4938c9a0f44be8bf7999399f26a24e5eab3267517c8",
+ "309d1f477c62df666f47b87930d1883c072d007a169c03d1c231efe2e51cae1f",
+ "bfd7e6dc33b735403cf6c7235513463843db8e1d2c16e62f0d5cacc8a3817515",
+ ],
+ [
+ "d27f87a4850f85ef5211094eb417bc8fb9441dd8eedba8def6fd040da93fdf94",
+ "bb6fe9e93c929e104a6b9f956c5de1fdc977899a781d50e76dd8f8852f19e635",
+ "420c98e6ac9cabaccf54e02034916df64a45ad1e7799b5d2ab0403073c6f6a21",
+ ],
+ [
+ "54b0d4e7110fb3a6ca5424fa7ffdc7cc599f9280df9759d1eb5d04186a4e82dd",
+ "f305e32fbd38dd1e6b04ba32620c6b8db121ed3216f7118875580bd454eb077d",
+ "a2b1a54463ad048ea9780fe2f92e0517636d2cd537d77a18cb6be03f1f991c04",
+ ],
+ [
+ "9ce200c8a0c3e617c7c5605dc60d1ce67e30a608c492143d643880f91594a6dd",
+ "56a2e451811eb62c78090c3d076f4b179b2e9baa4d80188a3db319301031191b",
+ "c16f22f4899aae477d37c250164d10c9c898a820bf790b1532c3bc379b8d733e",
+ ],
+ [
+ "98f09f9dedc813654e4ba28c9cd545587b20c6133603f13d8d4da2b67b4eab8c",
+ "210d599d6b8d1659e4a6eb98fdebd1a7024b22ba148af2a603ab807735af6a57",
+ "fc4ad471aff8243195ab6778054b0b243f93b4d31d5ac3a5bda9354dc3def735",
+ ],
+ [
+ "fa850ae6663a8c7b84c71c6391e0b02df2d6adbc30a03b961c4b496b9979cf9d",
+ "7fc7a4a8ae33cd045b064d1618689a7e16c87ce611f8f519433b10134dc57b04",
+ "062d292892515a6a9e71e1430cc593e5cf90e4c18d7c0c0eaae7a07768e6f713",
+ ],
+ [
+ "c19e91e47db473ff36714a8490665eaf6e57d228ef116e7771febce437c9e4ef",
+ "5ca47c5affeaeac3d67ef6564f0faa0bd575857eb4e315b57458959b0034f75c",
+ "f22ceea9e0e3826a308fe60df0edf4b82177e3704750f7af6e41aa5c5d806f16",
+ ],
+ [
+ "012fb2f110c7da5d56bb86e28e23dff1d860bb6ed11e145be9344ba756dd4e6e",
+ "badb58d30c1a8b6bbc0ee97f627272aa1c5625a1cadb7bf75fa274f76c081964",
+ "62cf0d3d7c67715a7cc982c9186ccd4d151b64b2cef862433a963b7b2c13f033",
+ ],
+ [
+ "fc9fec244ec6064f1060aabad079d04d1bb180239b07d4d02383ecbb05a7095e",
+ "54fe5cc959be48e5a0251026ac4cd0f5a424b8f5c157a7c59857d2ab11521e4c",
+ "0326b2e14412ef42fdd0144f96f6b47a5e0954aa110ca7ba124d1aca82269e1f",
+ ],
+ [
+ "1cb297f54fd1b320995f885591abdd5b84ff35f55ed22f85475a00c0c97b648a",
+ "2b0472ba2ffd8af768c2b6dab2b6db21c0ca153537d4b3c638a7cf7708c99e2d",
+ "08bba17691f09c899bba92f8950dec551065249f77e59d46bf113c56fc54a20f",
+ ],
+ [
+ "4871c2e7ada1911544713cbf15e0553d4972cc9867a4e67a667643b8e7a77d30",
+ "d73258438f51a0f603180aa349e3276fd623bcce61ff9d2dbb9fd9a9aaf53e40",
+ "a5ad91e362963312d95659b2e97bfcfddca573841f864cf8a8e0e075a72e4d1c",
+ ],
+ [
+ "52959827c6c7d3e3b6b50089db8a9524243ae7cee3f9ad7eff477587683af118",
+ "031d982caf72aa660a68d79439cedd0e0c47d09f9b6c90d1a0c322291591616e",
+ "8dd9423dcc8d08c951330954c99eb508395ac26a3e6d864df907ad1b23fd6a37",
+ ],
+ [
+ "12c77ad0259120089f5bfb841999e9e17acf71a00ef7891366e3f286c0122790",
+ "ce7cb3c1789b119987d36ed73dd40db07099ae9eca8b8ee53f63f610728d7600",
+ "8cdcc4d90a94f8148619bc7a32d230e8bbd4416051f53da3ebda320a40904128",
+ ],
+ [
+ "db46139a816b40cc15be3b82cd799f025cfa0817552bf959386e0a264184a419",
+ "03778d7eee1973fbe8a45670bc2172a8fbc8e0627cf81ad932f7bbedc1aca177",
+ "f3382b89603501ac8a5038aeda9422af88a2557f6a831b20062518733d59382d",
+ ],
+ // The following key sets have a v_in_sqrt value of 0
+ [
+ "51a8b03750f4b34922d3588b73a556a09594b7b014d2b7cefa26bb8c5eee6d2e",
+ "e8db45df5146d3745eae81e722af5e030a9a999a3e0d588f84514b125029b81b",
+ "f859efb4704b0b4c71959b02976de3ea9746f01e5adacddddba10ee08178cd1d",
+ ],
+ [
+ "d5a46dfdf2c66a76c312e83954fc0cf54f75ff14c2d6febfeb08153e34f54cfd",
+ "08a6adca98cbee754c3cdb103b5d9f69e61b87c64339947cbbaa1a956df5b745",
+ "139b06604063d7942540fbd33de329a2f733c65a89fd68151743896744397c2b",
+ ],
+ [
+ "a06917dc2988e4b51559ab26e25fd920e8fec2f8f2fe0f4c3c725dce06de7867",
+ "868603c764dff5f6db6f963237731452c469dfa2c8c5b682cfec85fc38661415",
+ "2bdd5f3dcaeefa352f200306be3471ad90a0a0ac4b6abba44230e284a852b813",
+ ],
+ [
+ "7acad18a021a568d2abaf079d046f5eb55e081a32b00d4f6c77f8b8c9afed866",
+ "8e0f52904421469a46b2d636b9d17595573071d16ebff280fc88829b5ef8bd4f",
+ "abc0de8594713993eab06144fe9b6d7bd04227c62bda19ef984008a93161fb33",
+ ],
+ [
+ "c547b93c519a1c0b40b71fe7b08e13b38639564e9317f6b58c5f99d5ad82471a",
+ "687fe9c6fe84e94ef0f7344abdac92dfd60dabc6156c1a3eea19d0d06705b461",
+ "8b0ea3b2215bf829aedd250c629557ca646515861aa0b7fc881c50d622f9ac38",
+ ],
+ [
+ "77e48dfa107bbfdda73f50ec2c38347e4fcc9c38866adb75488a2143993a058f",
+ "7d124e12af90216f26ce3198f6b02e76faf990dd248cdb246dd80d4e1fef3d4d",
+ "128481624af3015c6226428a247514370800f212a7a06c90dfe4f1aa672d3b3e",
+ ],
+ ]
+ }
+
+ /// returns a set of private keys generated by (a fork of) the golang
+ /// implementation agl/ed25519 which is used by the mainstream obfs4
+ /// library.
+ ///
+ /// All of these fail to generate a keypair capable of using elligator2
+ /// to encode of the public key.
+ ///
+ #[cfg(feature = "elligator2")]
+ fn ntor_invalid_keys() -> [&'static str; 16] {
+ [
+ "fd0d592d67038a6253331547949d70eb7a6b6c4c4831a1bc235554765b20f845",
+ "9576aa473a2b73b174e753ada7d55bc34ae8c05c1b1d6077f9a098984df9cf52",
+ "5b26538e0663a453994cf49d5614926786a0549c0123fa9afc7be99e04afc9ea",
+ "d4e58db7420267ad43e72e1fe0dee1acb9ca547e258e22370d76c0945f4b1a1e",
+ "01b047693c6b5212607387df88d1a427d2f24bd24157058705fc090e22778a64",
+ "d2d363fad3ea3aceb8262adc1184f32c6f63911e6bc008c87f366f9c2b60753c",
+ "75f703e3095343cf5d3ccd05a3fea06f82509a26c391a0365a1f1ca5e1d5f82f",
+ "1f6986c9146f2d4b1907a812e7e7254c82e121776d58fd9c4a5f6741a326e2ef",
+ "fe8465f11b40d52dc845df0a1639398b1726409d3b52526082c3e67c52f06842",
+ "4546f8e010e0d3be7ab3e97480cecc411b054013960ff4032b3d3e5afe1565e0",
+ "fc0f99f041118fd44c685b87b143b48d6d8c820d9f07235eeb7f7a9468dc0ca0",
+ "847c1d842a2e91521d1a63e191d3d2389e54c73378130f376d013781730ace3e",
+ "7d8a740b09f477588bc9a9020b2444ee384718f889403c02f3a61e69f55e6f7f",
+ "0549080985424df8f9401be8802e8c6a4c02737c474ac76c9d3f7968a3fca40a",
+ "53cd411b966503129365517e5c1b04e9f52a248cc57599af9ed58c9ff5b774d4",
+ "015aebbe87a5e94c5d8d3179583cf46d02af06c8f99a6d4ce8a3a4b1de097cd2",
+ ]
+ }
+
+ const ENCODING_TESTS_COUNT: usize = 6;
+ struct EncodingTestCase {
+ high_y: bool,
+ point: &'static str,
+ representative: Option<&'static str>,
+ }
+
+ fn encoding_testcases() -> [EncodingTestCase; ENCODING_TESTS_COUNT] {
+ [
+ // A not encodable point with both "high_y" values
+ EncodingTestCase {
+ point: "e6f66fdf6e230c603c5e6e59a254ea1476a13eb9511b9549846781e12e52230a",
+ high_y: false,
+ representative: None,
+ },
+ EncodingTestCase {
+ point: "e6f66fdf6e230c603c5e6e59a254ea1476a13eb9511b9549846781e12e52230a",
+ high_y: true,
+ representative: None,
+ },
+ // An encodable point with both "high_y" values
+ EncodingTestCase {
+ point: "33951964003c940878063ccfd0348af42150ca16d2646f2c5856e8338377d800",
+ high_y: false,
+ representative: Some(
+ "999b591b6697d074f266192277d554dec3c24c2ef6108101f63d94f7fff3a013",
+ ),
+ },
+ EncodingTestCase {
+ point: "33951964003c940878063ccfd0348af42150ca16d2646f2c5856e8338377d800",
+ high_y: true,
+ representative: Some(
+ "bd3d2a7ed1c8a100a977f8d992e33aaa6f630d55089770ea469101d7fd73d13d",
+ // "999b591b6697d074f266192277d554dec3c24c2ef6108101f63d94f7fff3a013",
+ ),
+ },
+ // 0 with both "high_y" values
+ EncodingTestCase {
+ point: "0000000000000000000000000000000000000000000000000000000000000000",
+ high_y: false,
+ representative: Some(
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ),
+ },
+ EncodingTestCase {
+ point: "0000000000000000000000000000000000000000000000000000000000000000",
+ high_y: true,
+ representative: Some(
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ),
+ },
+ ]
+ }
+
+ const DECODING_TESTS_COUNT: usize = 7;
+ struct DecodingTestCase {
+ representative: &'static str,
+ point: &'static str,
+ }
+
+ fn decoding_testcases() -> [DecodingTestCase; DECODING_TESTS_COUNT] {
+ [
+ // A small representative with false "high_y" property
+ DecodingTestCase {
+ representative: "e73507d38bae63992b3f57aac48c0abc14509589288457995a2b4ca3490aa207",
+ point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f",
+ },
+ // A small representative with true "high_y" property
+ DecodingTestCase {
+ representative: "95a16019041dbefed9832048ede11928d90365f24a38aa7aef1b97e23954101b",
+ point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363",
+ },
+ // The last representative returning true: (p - 1) / 2
+ DecodingTestCase {
+ representative: "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
+ point: "9cdb525555555555555555555555555555555555555555555555555555555555",
+ },
+ // The first representative returning false: (p + 1) / 2
+ DecodingTestCase {
+ representative: "f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
+ point: "9cdb525555555555555555555555555555555555555555555555555555555555",
+ },
+ // A large representative with false "high_y" property
+ DecodingTestCase {
+ representative: "179f24730ded2ce3173908ec61964653b8027e383f40346c1c9b4d2bdb1db76c",
+ point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60",
+ },
+ // A large representative with true "high_y" property
+ DecodingTestCase {
+ representative: "8a2f286180c3d8630b5f5a3c7cc027a55e0d3ffb3b1b990c5c7bb4c3d1f91b6f",
+ point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843",
+ },
+ // 0
+ DecodingTestCase {
+ representative: "0000000000000000000000000000000000000000000000000000000000000000",
+ point: "0000000000000000000000000000000000000000000000000000000000000000",
+ },
+ ]
+ }
+}
+
+#[cfg(test)]
+#[cfg(feature = "elligator2")]
+mod rfc9380 {
+ use super::*;
+
+ use hex::FromHex;
+ use std::string::String;
+
+ #[test]
+ fn map_to_curve_test_go_ed25519_extra() {
+ for i in 0..CURVE25519_ELL2.len() {
+ let testcase = &CURVE25519_ELL2[i];
+
+ let u = testcase[0].must_from_be();
+ let mut clamped = u.clone();
+ clamped[31] &= 63;
+
+ // map point to curve
+ let (q_x, _) = map_fe_to_curve(&FieldElement::from_bytes(&clamped));
+
+ // check resulting point
+ assert_eq!(
+ q_x.encode_be(),
+ testcase[1],
+ "({i}) incorrect x curve25519 ELL2\n"
+ );
+ }
+ }
+
+ #[test]
+ fn map_to_curve_test_curve25519() {
+ for i in 0..curve25519_XMD_SHA512_ELL2_NU.len() {
+ let testcase = &curve25519_XMD_SHA512_ELL2_NU[i];
+ let u = FieldElement::from_bytes(&testcase.u_0.must_from_le());
+
+ // map point to curve
+ let (q_x, q_y) = map_fe_to_curve(&u);
+
+ // check resulting point
+ assert_eq!(
+ q_x.encode_le(),
+ testcase.Q_x,
+ "({i}) incorrect Q0_x curve25519 NU\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q_y.encode_le(),
+ testcase.Q_y,
+ "({i}) incorrect Q0_y curve25519 NU\n{:?}",
+ testcase
+ );
+ }
+ for i in 0..curve25519_XMD_SHA512_ELL2_RO.len() {
+ let testcase = &curve25519_XMD_SHA512_ELL2_RO[i];
+ let u0 = FieldElement::from_bytes(&testcase.u_0.must_from_le());
+ let u1 = FieldElement::from_bytes(&testcase.u_1.must_from_le());
+
+ // map points to curve
+ let (q0_x, q0_y) = map_fe_to_curve(&u0);
+ let (q1_x, q1_y) = map_fe_to_curve(&u1);
+
+ // check resulting points
+ assert_eq!(
+ q0_x.encode_le(),
+ testcase.Q0_x,
+ "({i}) incorrect Q0_x curve25519 RO\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q0_y.encode_le(),
+ testcase.Q0_y,
+ "({i}) incorrect Q0_y curve25519 RO\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q1_x.encode_le(),
+ testcase.Q1_x,
+ "({i}) incorrect Q1_x curve25519 RO\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q1_y.encode_le(),
+ testcase.Q1_y,
+ "({i}) incorrect Q1_y curve25519 RO\n{:?}",
+ testcase
+ );
+ }
+ }
+
+ #[test]
+ fn map_to_curve_test_edwards25519() {
+ for i in 0..edwards25519_XMD_SHA512_ELL2_NU.len() {
+ let testcase = &curve25519_XMD_SHA512_ELL2_NU[i];
+ let u = FieldElement::from_bytes(&testcase.u_0.must_from_le());
+
+ // map point to curve
+ let (q_x, q_y) = map_fe_to_curve(&u);
+
+ // check resulting point
+ assert_eq!(
+ q_x.encode_le(),
+ testcase.Q_x,
+ "({i}) incorrect Q0_x curve25519 NU\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q_y.encode_le(),
+ testcase.Q_y,
+ "({i}) incorrect Q0_y curve25519 NU\n{:?}",
+ testcase
+ );
+ }
+ for i in 0..edwards25519_XMD_SHA512_ELL2_RO.len() {
+ let testcase = &curve25519_XMD_SHA512_ELL2_RO[i];
+ let u0 = FieldElement::from_bytes(&testcase.u_0.must_from_le());
+ let u1 = FieldElement::from_bytes(&testcase.u_1.must_from_le());
+
+ // map points to curve
+ let (q0_x, q0_y) = map_fe_to_curve(&u0);
+ let (q1_x, q1_y) = map_fe_to_curve(&u1);
+
+ // check resulting points
+ assert_eq!(
+ q0_x.encode_le(),
+ testcase.Q0_x,
+ "({i}) incorrect Q0_x curve25519 RO\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q0_y.encode_le(),
+ testcase.Q0_y,
+ "({i}) incorrect Q0_y curve25519 RO\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q1_x.encode_le(),
+ testcase.Q1_x,
+ "({i}) incorrect Q1_x curve25519 RO\n{:?}",
+ testcase
+ );
+ assert_eq!(
+ q1_y.encode_le(),
+ testcase.Q1_y,
+ "({i}) incorrect Q1_y curve25519 RO\n{:?}",
+ testcase
+ );
+ }
+ }
+
+ /// Example test cases found in gitlab.com/yawning/edwards25519-extra
+ ///
+ /// 1. representative
+ /// 2. associated point
+ ///
+ /// These test cases need the upper two bits cleared to be properly mapped.
+ const CURVE25519_ELL2: [[&'static str; 2]; 14] = [
+ [
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ],
+ [
+ "0000000000000000000000000000000000000000000000000000000000000040",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ],
+ [
+ "0000000000000000000000000000000000000000000000000000000000000080",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ],
+ [
+ "00000000000000000000000000000000000000000000000000000000000000c0",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ],
+ [
+ "673a505e107189ee54ca93310ac42e4545e9e59050aaac6f8b5f64295c8ec02f",
+ "242ae39ef158ed60f20b89396d7d7eef5374aba15dc312a6aea6d1e57cacf85e",
+ ],
+ [
+ "922688fa428d42bc1fa8806998fbc5959ae801817e85a42a45e8ec25a0d7545a",
+ "696f341266c64bcfa7afa834f8c34b2730be11c932e08474d1a22f26ed82410b",
+ ],
+ [
+ "0d3b0eb88b74ed13d5f6a130e03c4ad607817057dc227152827c0506a538bbba",
+ "0b00df174d9fb0b6ee584d2cf05613130bad18875268c38b377e86dfefef177f",
+ ],
+ [
+ "01a3ea5658f4e00622eeacf724e0bd82068992fae66ed2b04a8599be16662ef5",
+ "7ae4c58bc647b5646c9f5ae4c2554ccbf7c6e428e7b242a574a5a9c293c21f7e",
+ ],
+ [
+ "69599ab5a829c3e9515128d368da7354a8b69fcee4e34d0a668b783b6cae550f",
+ "09024abaaef243e3b69366397e8dfc1fdc14a0ecc7cf497cbe4f328839acce69",
+ ],
+ [
+ "9172922f96d2fa41ea0daf961857056f1656ab8406db80eaeae76af58f8c9f50",
+ "beab745a2a4b4e7f1a7335c3ffcdbd85139f3a72b667a01ee3e3ae0e530b3372",
+ ],
+ [
+ "6850a20ac5b6d2fa7af7042ad5be234d3311b9fb303753dd2b610bd566983281",
+ "1287388eb2beeff706edb9cf4fcfdd35757f22541b61528570b86e8915be1530",
+ ],
+ [
+ "84417826c0e80af7cb25a73af1ba87594ff7048a26248b5757e52f2824e068f1",
+ "51acd2e8910e7d28b4993db7e97e2b995005f26736f60dcdde94bdf8cb542251",
+ ],
+ [
+ "b0fbe152849f49034d2fa00ccc7b960fad7b30b6c4f9f2713eb01c147146ad31",
+ "98508bb3590886af3be523b61c3d0ce6490bb8b27029878caec57e4c750f993d",
+ ],
+ [
+ "a0ca9ff75afae65598630b3b93560834c7f4dd29a557aa29c7becd49aeef3753",
+ "3c5fad0516bb8ec53da1c16e910c23f792b971c7e2a0ee57d57c32e3655a646b",
+ ],
+ ];
+
+ // J.4.1. curve25519_XMD:SHA-512_ELL2_RO_
+ //
+ // Random Oracle Curve25519 SHA512 Elligator2
+ //
+ // suite = curve25519_XMD:SHA-512_ELL2_RO_
+ // dst = QUUX-V01-CS02-with-curve25519_XMD:SHA-512_ELL2_RO_
+ //
+ #[allow(non_upper_case_globals)]
+ const curve25519_XMD_SHA512_ELL2_RO: [xmd_sha512_25519_ro_testcase; 5] = [
+ xmd_sha512_25519_ro_testcase {
+ u_0: "49bed021c7a3748f09fa8cdfcac044089f7829d3531066ac9e74e0994e05bc7d",
+ u_1: "5c36525b663e63389d886105cee7ed712325d5a97e60e140aba7e2ce5ae851b6",
+ Q0_x: "16b3d86e056b7970fa00165f6f48d90b619ad618791661b7b5e1ec78be10eac1",
+ Q0_y: "4ab256422d84c5120b278cbdfc4e1facc5baadffeccecf8ee9bf3946106d50ca",
+ Q1_x: "7ec29ddbf34539c40adfa98fcb39ec36368f47f30e8f888cc7e86f4d46e0c264",
+ Q1_y: "10d1abc1cae2d34c06e247f2141ba897657fb39f1080d54f09ce0af128067c74",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "6412b7485ba26d3d1b6c290a8e1435b2959f03721874939b21782df17323d160",
+ u_1: "24c7b46c1c6d9a21d32f5707be1380ab82db1054fde82865d5c9e3d968f287b2",
+ Q0_x: "71de3dadfe268872326c35ac512164850860567aea0e7325e6b91a98f86533ad",
+ Q0_y: "26a08b6e9a18084c56f2147bf515414b9b63f1522e1b6c5649f7d4b0324296ec",
+ Q1_x: "5704069021f61e41779e2ba6b932268316d6d2a6f064f997a22fef16d1eaeaca",
+ Q1_y: "50483c7540f64fb4497619c050f2c7fe55454ec0f0e79870bb44302e34232210",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "5e123990f11bbb5586613ffabdb58d47f64bb5f2fa115f8ea8df0188e0c9e1b5",
+ u_1: "5e8553eb00438a0bb1e7faa59dec6d8087f9c8011e5fb8ed9df31cb6c0d4ac19",
+ Q0_x: "7a94d45a198fb5daa381f45f2619ab279744efdd8bd8ed587fc5b65d6cea1df0",
+ Q0_y: "67d44f85d376e64bb7d713585230cdbfafc8e2676f7568e0b6ee59361116a6e1",
+ Q1_x: "30506fb7a32136694abd61b6113770270debe593027a968a01f271e146e60c18",
+ Q1_y: "7eeee0e706b40c6b5174e551426a67f975ad5a977ee2f01e8e20a6d612458c3b",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "20f481e85da7a3bf60ac0fb11ed1d0558fc6f941b3ac5469aa8b56ec883d6d7d",
+ u_1: "017d57fd257e9a78913999a23b52ca988157a81b09c5442501d07fed20869465",
+ Q0_x: "02d606e2699b918ee36f2818f2bc5013e437e673c9f9b9cdc15fd0c5ee913970",
+ Q0_y: "29e9dc92297231ef211245db9e31767996c5625dfbf92e1c8107ef887365de1e",
+ Q1_x: "38920e9b988d1ab7449c0fa9a6058192c0c797bb3d42ac345724341a1aa98745",
+ Q1_y: "24dcc1be7c4d591d307e89049fd2ed30aae8911245a9d8554bf6032e5aa40d3d",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "005fe8a7b8fef0a16c105e6cadf5a6740b3365e18692a9c05bfbb4d97f645a6a",
+ u_1: "1347edbec6a2b5d8c02e058819819bee177077c9d10a4ce165aab0fd0252261a",
+ Q0_x: "36b4df0c864c64707cbf6cf36e9ee2c09a6cb93b28313c169be29561bb904f98",
+ Q0_y: "6cd59d664fb58c66c892883cd0eb792e52055284dac3907dd756b45d15c3983d",
+ Q1_x: "3fa114783a505c0b2b2fbeef0102853c0b494e7757f2a089d0daae7ed9a0db2b",
+ Q1_y: "76c0fe7fec932aaafb8eefb42d9cbb32eb931158f469ff3050af15cfdbbeff94",
+ },
+ ];
+
+ // J.4.2. curve25519_XMD:SHA-512_ELL2_NU_
+ //
+ // Nonuniform Encoding Curve25519 SHA512 Elligator2
+ //
+ // suite: curve25519_XMD:SHA-512_ELL2_NU_
+ // dst: QUUX-V01-CS02-with-curve25519_XMD:SHA-512_ELL2_NU_
+ //
+ #[allow(non_upper_case_globals)]
+ const curve25519_XMD_SHA512_ELL2_NU: [xmd_sha512_25519_nu_testcase; 5] = [
+ xmd_sha512_25519_nu_testcase {
+ u_0: "608d892b641f0328523802a6603427c26e55e6f27e71a91a478148d45b5093cd",
+ Q_x: "51125222da5e763d97f3c10fcc92ea6860b9ccbbd2eb1285728f566721c1e65b",
+ Q_y: "343d2204f812d3dfc5304a5808c6c0d81a903a5d228b342442aa3c9ba5520a3d",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "46f5b22494bfeaa7f232cc8d054be68561af50230234d7d1d63d1d9abeca8da5",
+ Q_x: "7d56d1e08cb0ccb92baf069c18c49bb5a0dcd927eff8dcf75ca921ef7f3e6eeb",
+ Q_y: "404d9a7dc25c9c05c44ab9a94590e7c3fe2dcec74533a0b24b188a5d5dacf429",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "235fe40c443766ce7e18111c33862d66c3b33267efa50d50f9e8e5d252a40aaa",
+ Q_x: "3fbe66b9c9883d79e8407150e7c2a1c8680bee496c62fabe4619a72b3cabe90f",
+ Q_y: "08ec476147c9a0a3ff312d303dbbd076abb7551e5fce82b48ab14b433f8d0a7b",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "001e92a544463bda9bd04ddbe3d6eed248f82de32f522669efc5ddce95f46f5b",
+ Q_x: "227e0bb89de700385d19ec40e857db6e6a3e634b1c32962f370d26f84ff19683",
+ Q_y: "5f86ff3851d262727326a32c1bf7655a03665830fa7f1b8b1e5a09d85bc66e4a",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "1a68a1af9f663592291af987203393f707305c7bac9c8d63d6a729bdc553dc19",
+ Q_x: "3bcd651ee54d5f7b6013898aab251ee8ecc0688166fce6e9548d38472f6bd196",
+ Q_y: "1bb36ad9197299f111b4ef21271c41f4b7ecf5543db8bb5931307ebdb2eaa465",
+ },
+ ];
+
+ // J.5.1. edwards25519_XMD:SHA-512_ELL2_RO_
+ //
+ // Random Oracle Edwards 25519 SHA512 Elligator2
+ //
+ // suite: edwards25519_XMD:SHA-512_ELL2_RO_
+ // dst: QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_RO_
+ //
+ #[allow(non_upper_case_globals)]
+ const edwards25519_XMD_SHA512_ELL2_RO: [xmd_sha512_25519_ro_testcase; 5] = [
+ xmd_sha512_25519_ro_testcase {
+ u_0: "03fef4813c8cb5f98c6eef88fae174e6e7d5380de2b007799ac7ee712d203f3a",
+ u_1: "780bdddd137290c8f589dc687795aafae35f6b674668d92bf92ae793e6a60c75",
+ Q0_x: "6549118f65bb617b9e8b438decedc73c496eaed496806d3b2eb9ee60b88e09a7",
+ Q0_y: "7315bcc8cf47ed68048d22bad602c6680b3382a08c7c5d3f439a973fb4cf9feb",
+ Q1_x: "31dcfc5c58aa1bee6e760bf78cbe71c2bead8cebb2e397ece0f37a3da19c9ed2",
+ Q1_y: "7876d81474828d8a5928b50c82420b2bd0898d819e9550c5c82c39fc9bafa196",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "5081955c4141e4e7d02ec0e36becffaa1934df4d7a270f70679c78f9bd57c227",
+ u_1: "005bdc17a9b378b6272573a31b04361f21c371b256252ae5463119aa0b925b76",
+ Q0_x: "5c1525bd5d4b4e034512949d187c39d48e8cd84242aa4758956e4adc7d445573",
+ Q0_y: "2bf426cf7122d1a90abc7f2d108befc2ef415ce8c2d09695a7407240faa01f29",
+ Q1_x: "37b03bba828860c6b459ddad476c83e0f9285787a269df2156219b7e5c86210c",
+ Q1_y: "285ebf5412f84d0ad7bb4e136729a9ffd2195d5b8e73c0dc85110ce06958f432",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "285ebaa3be701b79871bcb6e225ecc9b0b32dff2d60424b4c50642636a78d5b3",
+ u_1: "2e253e6a0ef658fedb8e4bd6a62d1544fd6547922acb3598ec6b369760b81b31",
+ Q0_x: "3ac463dd7fddb773b069c5b2b01c0f6b340638f54ee3bd92d452fcec3015b52d",
+ Q0_y: "7b03ba1e8db9ec0b390d5c90168a6a0b7107156c994c674b61fe696cbeb46baf",
+ Q1_x: "0757e7e904f5e86d2d2f4acf7e01c63827fde2d363985aa7432106f1b3a444ec",
+ Q1_y: "50026c96930a24961e9d86aa91ea1465398ff8e42015e2ec1fa397d416f6a1c0",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "4fedd25431c41f2a606952e2945ef5e3ac905a42cf64b8b4d4a83c533bf321af",
+ u_1: "02f20716a5801b843987097a8276b6d869295b2e11253751ca72c109d37485a9",
+ Q0_x: "703e69787ea7524541933edf41f94010a201cc841c1cce60205ec38513458872",
+ Q0_y: "32bb192c4f89106466f0874f5fd56a0d6b6f101cb714777983336c159a9bec75",
+ Q1_x: "0c9077c5c31720ed9413abe59bf49ce768506128d810cb882435aa90f713ef6b",
+ Q1_y: "7d5aec5210db638c53f050597964b74d6dda4be5b54fa73041bf909ccb3826cb",
+ },
+ xmd_sha512_25519_ro_testcase {
+ u_0: "6e34e04a5106e9bd59f64aba49601bf09d23b27f7b594e56d5de06df4a4ea33b",
+ u_1: "1c1c2cb59fc053f44b86c5d5eb8c1954b64976d0302d3729ff66e84068f5fd96",
+ Q0_x: "21091b2e3f9258c7dfa075e7ae513325a94a3d8a28e1b1cb3b5b6f5d65675592",
+ Q0_y: "41a33d324c89f570e0682cdf7bdb78852295daf8084c669f2cc9692896ab5026",
+ Q1_x: "4c07ec48c373e39a23bd7954f9e9b66eeab9e5ee1279b867b3d5315aa815454f",
+ Q1_y: "67ccac7c3cb8d1381242d8d6585c57eabaddbb5dca5243a68a8aeb5477d94b3a",
+ },
+ ];
+
+ // J.5.2. edwards25519_XMD:SHA-512_ELL2_NU_
+ //
+ // Nonuniform Encoding Edwards 25519 SHA512 Elligator2
+ //
+ // suite: edwards25519_XMD:SHA-512_ELL2_NU_
+ // dst: QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_
+ //
+ #[allow(non_upper_case_globals)]
+ const edwards25519_XMD_SHA512_ELL2_NU: [xmd_sha512_25519_nu_testcase; 5] = [
+ xmd_sha512_25519_nu_testcase {
+ u_0: "7f3e7fb9428103ad7f52db32f9df32505d7b427d894c5093f7a0f0374a30641d",
+ Q_x: "42836f691d05211ebc65ef8fcf01e0fb6328ec9c4737c26050471e50803022eb",
+ Q_y: "22cb4aaa555e23bd460262d2130d6a3c9207aa8bbb85060928beb263d6d42a95",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "09cfa30ad79bd59456594a0f5d3a76f6b71c6787b04de98be5cd201a556e253b",
+ Q_x: "333e41b61c6dd43af220c1ac34a3663e1cf537f996bab50ab66e33c4bd8e4e19",
+ Q_y: "51b6f178eb08c4a782c820e306b82c6e273ab22e258d972cd0c511787b2a3443",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "475ccff99225ef90d78cc9338e9f6a6bb7b17607c0c4428937de75d33edba941",
+ Q_x: "55186c242c78e7d0ec5b6c9553f04c6aeef64e69ec2e824472394da32647cfc6",
+ Q_y: "5b9ea3c265ee42256a8f724f616307ef38496ef7eba391c08f99f3bea6fa88f0",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "049a1c8bd51bcb2aec339f387d1ff51428b88d0763a91bcdf6929814ac95d03d",
+ Q_x: "024b6e1621606dca8071aa97b43dce4040ca78284f2a527dcf5d0fbfac2b07e7",
+ Q_y: "5102353883d739bdc9f8a3af650342b171217167dcce34f8db57208ec1dfdbf2",
+ },
+ xmd_sha512_25519_nu_testcase {
+ u_0: "3cb0178a8137cefa5b79a3a57c858d7eeeaa787b2781be4a362a2f0750d24fa0",
+ Q_x: "3e6368cff6e88a58e250c54bd27d2c989ae9b3acb6067f2651ad282ab8c21cd9",
+ Q_y: "38fb39f1566ca118ae6c7af42810c0bb9767ae5960abb5a8ca792530bfb9447d",
+ },
+ ];
+
+ #[allow(non_camel_case_types, non_snake_case)]
+ #[derive(Debug)]
+ struct xmd_sha512_25519_ro_testcase {
+ u_0: &'static str,
+ u_1: &'static str,
+ // Output
+ Q0_x: &'static str,
+ Q0_y: &'static str,
+ Q1_x: &'static str,
+ Q1_y: &'static str,
+ }
+
+ #[allow(non_camel_case_types, non_snake_case)]
+ #[derive(Debug)]
+ struct xmd_sha512_25519_nu_testcase {
+ u_0: &'static str,
+ // output
+ Q_x: &'static str,
+ Q_y: &'static str,
+ }
+
+ trait FromByteString {
+ fn must_from_le(&self) -> [u8; 32];
+ fn must_from_be(&self) -> [u8; 32];
+ }
+
+ impl<'a> FromByteString for &'a str {
+ fn must_from_le(&self) -> [u8; 32] {
+ let mut u = <[u8; 32]>::from_hex(self).unwrap();
+ u.reverse();
+ u
+ }
+ fn must_from_be(&self) -> [u8; 32] {
+ <[u8; 32]>::from_hex(self).unwrap()
+ }
+ }
+
+ trait ToByteString {
+ fn encode_le(&self) -> String;
+ fn encode_be(&self) -> String;
+ }
+
+ impl ToByteString for FieldElement {
+ fn encode_le(&self) -> String {
+ let mut b = self.as_bytes();
+ b.reverse();
+ hex::encode(&b)
+ }
+
+ fn encode_be(&self) -> String {
+ hex::encode(self.as_bytes())
+ }
+ }
+
+ impl ToByteString for [u8; 32] {
+ fn encode_le(&self) -> String {
+ let mut b = self.clone();
+ b.reverse();
+ hex::encode(&b)
+ }
+
+ fn encode_be(&self) -> String {
+ hex::encode(self)
+ }
+ }
+}
diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs
index 68c9c8b8..0a4a428b 100644
--- a/curve25519-dalek/src/field.rs
+++ b/curve25519-dalek/src/field.rs
@@ -30,7 +30,7 @@ use cfg_if::cfg_if;
use subtle::Choice;
use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable;
-use subtle::ConstantTimeEq;
+use subtle::{ConstantTimeEq, ConstantTimeGreater};
use crate::backend;
use crate::constants;
@@ -217,7 +217,7 @@ impl FieldElement {
/// Raise this field element to the power (p-5)/8 = 2^252 -3.
#[rustfmt::skip] // keep alignment of explanatory comments
#[allow(clippy::let_and_return)]
- fn pow_p58(&self) -> FieldElement {
+ pub(crate) fn pow_p58(&self) -> FieldElement {
// The bits of (p-5)/8 are 101111.....11.
//
// nonzero bits of exponent
@@ -305,6 +305,18 @@ impl FieldElement {
}
}
+impl ConstantTimeGreater for FieldElement {
+ /// Test equality between two `FieldElement`s. Since the
+ /// internal representation is not canonical, the field elements
+ /// are normalized to wire format before comparison.
+ ///
+ /// If self > other return Choice(1), otherwise return Choice(0)
+ ///
+ fn ct_gt(&self, other: &FieldElement) -> Choice {
+ self.gt(other)
+ }
+}
+
#[cfg(test)]
mod test {
use crate::field::*;
diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs
index fecfe888..9792daf3 100644
--- a/curve25519-dalek/src/lib.rs
+++ b/curve25519-dalek/src/lib.rs
@@ -95,6 +95,11 @@ pub mod traits;
// Finite field arithmetic mod p = 2^255 - 19
pub(crate) mod field;
+// elligator2 implementation for encoding/decoding points to representatives and
+// vice versa.
+#[cfg(feature = "elligator2")]
+pub mod elligator2;
+
// Arithmetic backends (using u32, u64, etc) live here
#[cfg(docsrs)]
pub mod backend;
diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs
index 2be35cdc..1f235274 100644
--- a/curve25519-dalek/src/montgomery.rs
+++ b/curve25519-dalek/src/montgomery.rs
@@ -54,16 +54,18 @@ use core::{
ops::{Mul, MulAssign},
};
-use crate::constants::{APLUS2_OVER_FOUR, MONTGOMERY_A, MONTGOMERY_A_NEG};
+use crate::constants::APLUS2_OVER_FOUR;
use crate::edwards::{CompressedEdwardsY, EdwardsPoint};
use crate::field::FieldElement;
use crate::scalar::{clamp_integer, Scalar};
-
use crate::traits::Identity;
+#[cfg(feature = "elligator2")]
+use crate::elligator2;
+
use subtle::Choice;
+use subtle::ConditionallySelectable;
use subtle::ConstantTimeEq;
-use subtle::{ConditionallyNegatable, ConditionallySelectable};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
@@ -244,35 +246,12 @@ impl MontgomeryPoint {
CompressedEdwardsY(y_bytes).decompress()
}
-}
-
-/// Perform the Elligator2 mapping to a Montgomery point.
-///
-/// See
-//
-// TODO Determine how much of the hash-to-group API should be exposed after the CFRG
-// draft gets into a more polished/accepted state.
-#[allow(unused)]
-pub(crate) fn elligator_encode(r_0: &FieldElement) -> MontgomeryPoint {
- let one = FieldElement::ONE;
- let d_1 = &one + &r_0.square2(); /* 2r^2 */
- let d = &MONTGOMERY_A_NEG * &(d_1.invert()); /* A/(1+2r^2) */
-
- let d_sq = &d.square();
- let au = &MONTGOMERY_A * &d;
-
- let inner = &(d_sq + &au) + &one;
- let eps = &d * &inner; /* eps = d^3 + Ad^2 + d */
-
- let (eps_is_sq, _eps) = FieldElement::sqrt_ratio_i(&eps, &one);
-
- let zero = FieldElement::ZERO;
- let Atemp = FieldElement::conditional_select(&MONTGOMERY_A, &zero, eps_is_sq); /* 0, or A if nonsquare*/
- let mut u = &d + &Atemp; /* d, or d+A if nonsquare */
- u.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */
-
- MontgomeryPoint(u.as_bytes())
+ #[cfg(feature = "elligator2")]
+ /// This decodes an elligator2 hidden point to a curve point on Curve25519.
+ pub fn from_representative(&self) -> MontgomeryPoint {
+ elligator2::map_to_point(&self.0)
+ }
}
/// A `ProjectivePoint` holds a point on the projective line
@@ -641,20 +620,20 @@ mod test {
#[test]
#[cfg(feature = "alloc")]
+ #[cfg(feature = "elligator2")]
fn montgomery_elligator_correct() {
let bytes: Vec = (0u8..32u8).collect();
let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");
- let fe = FieldElement::from_bytes(&bits_in);
- let eg = elligator_encode(&fe);
- assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
+ let eg = elligator2::map_to_point(&bits_in);
+ assert_eq!(eg.0, ELLIGATOR_CORRECT_OUTPUT);
}
#[test]
+ #[cfg(feature = "elligator2")]
fn montgomery_elligator_zero_zero() {
let zero = [0u8; 32];
- let fe = FieldElement::from_bytes(&zero);
- let eg = elligator_encode(&fe);
- assert_eq!(eg.to_bytes(), zero);
+ let eg = elligator2::map_to_point(&zero);
+ assert_eq!(eg.0, zero);
}
}
diff --git a/ed25519-dalek/src/signing.rs b/ed25519-dalek/src/signing.rs
index 8999f50d..3d911dd3 100644
--- a/ed25519-dalek/src/signing.rs
+++ b/ed25519-dalek/src/signing.rs
@@ -774,7 +774,7 @@ impl<'d> Deserialize<'d> for SigningKey {
));
}
- SigningKey::try_from(bytes).map_err(serde::de::Error::custom)
+ Ok(SigningKey::from(bytes))
}
}
diff --git a/x25519-dalek/Cargo.toml b/x25519-dalek/Cargo.toml
index 4169c55a..33712967 100644
--- a/x25519-dalek/Cargo.toml
+++ b/x25519-dalek/Cargo.toml
@@ -35,7 +35,7 @@ rustdoc-args = [
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
"--cfg", "docsrs",
]
-features = ["getrandom", "reusable_secrets", "serde", "static_secrets"]
+features = ["getrandom", "reusable_secrets", "serde", "static_secrets", "elligator2"]
[dependencies]
curve25519-dalek = { version = "4", path = "../curve25519-dalek", default-features = false }
@@ -61,3 +61,4 @@ alloc = ["curve25519-dalek/alloc", "serde?/alloc", "zeroize?/alloc"]
precomputed-tables = ["curve25519-dalek/precomputed-tables"]
reusable_secrets = []
static_secrets = []
+elligator2 = ["curve25519-dalek/elligator2"]
diff --git a/x25519-dalek/src/x25519.rs b/x25519-dalek/src/x25519.rs
index 3b96f9cf..cb18c1b0 100644
--- a/x25519-dalek/src/x25519.rs
+++ b/x25519-dalek/src/x25519.rs
@@ -375,3 +375,137 @@ pub fn x25519(k: [u8; 32], u: [u8; 32]) -> [u8; 32] {
pub const X25519_BASEPOINT_BYTES: [u8; 32] = [
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
+
+/// [`PublicKey`] transformation to a format indistinguishable from uniform
+/// random. Requires feature `elligator2`.
+///
+/// This allows public keys to be sent over an insecure channel without
+/// revealing that an x25519 public key is being shared.
+///
+/// # Example
+#[cfg_attr(feature = "elligator2", doc = "```")]
+#[cfg_attr(not(feature = "elligator2"), doc = "```ignore")]
+/// use rand_core::OsRng;
+/// use rand_core::RngCore;
+///
+/// use x25519_dalek::x25519;
+/// use x25519_dalek::EphemeralSecret;
+/// use x25519_dalek::{PublicKey, PublicRepresentative};
+///
+/// // ~50% of points are not encodable as elligator representatives, but we
+/// // want to ensure we select a keypair that is.
+/// fn get_representable_ephemeral() -> EphemeralSecret {
+/// for i in 0_u8..255 {
+/// let secret = EphemeralSecret::random_from_rng(&mut OsRng);
+/// match Option::::from(&secret) {
+/// Some(_) => return secret,
+/// None => continue,
+/// }
+/// }
+/// panic!("we should definitely have found a key by now")
+/// }
+///
+/// // Generate Alice's key pair.
+/// let alice_secret = get_representable_ephemeral();
+/// let alice_representative = Option::::from(&alice_secret).unwrap();
+///
+/// // Generate Bob's key pair.
+/// let bob_secret = get_representable_ephemeral();
+/// let bob_representative = Option::::from(&bob_secret).unwrap();
+///
+/// // Alice and Bob should now exchange their representatives and reveal the
+/// // public key from the other person.
+/// let bob_public = PublicKey::from(&bob_representative);
+///
+/// let alice_public = PublicKey::from(&alice_representative);
+///
+/// // Once they've done so, they may generate a shared secret.
+/// let alice_shared = alice_secret.diffie_hellman(&bob_public);
+/// let bob_shared = bob_secret.diffie_hellman(&alice_public);
+///
+/// assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
+/// ```
+#[cfg(feature = "elligator2")]
+#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
+pub struct PublicRepresentative([u8; 32]);
+
+#[cfg(feature = "elligator2")]
+impl PublicRepresentative {
+ /// View this public representative as a byte array.
+ #[inline]
+ pub fn as_bytes(&self) -> &[u8; 32] {
+ &self.0
+ }
+
+ /// Extract this representative's bytes for serialization.
+ #[inline]
+ pub fn to_bytes(&self) -> [u8; 32] {
+ self.0
+ }
+}
+
+#[cfg(feature = "elligator2")]
+impl AsRef<[u8]> for PublicRepresentative {
+ /// View this shared secret key as a byte array.
+ #[inline]
+ fn as_ref(&self) -> &[u8] {
+ self.as_bytes()
+ }
+}
+
+#[cfg(feature = "elligator2")]
+impl From<[u8; 32]> for PublicRepresentative {
+ /// Build a Elligator2 Public key Representative from bytes
+ fn from(r: [u8; 32]) -> PublicRepresentative {
+ PublicRepresentative(r)
+ }
+}
+
+#[cfg(feature = "elligator2")]
+impl<'a> From<&'a [u8; 32]> for PublicRepresentative {
+ /// Build a Elligator2 Public key Representative from bytes by reference
+ fn from(r: &'a [u8; 32]) -> PublicRepresentative {
+ PublicRepresentative(*r)
+ }
+}
+
+#[cfg(feature = "elligator2")]
+impl<'a> From<&'a EphemeralSecret> for Option {
+ /// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicRepresentative`].
+ fn from(secret: &'a EphemeralSecret) -> Option {
+ let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0);
+ let res: Option<[u8; 32]> = repres;
+ Some(PublicRepresentative(res?))
+ }
+}
+
+#[cfg(feature = "reusable_secrets")]
+#[cfg(feature = "elligator2")]
+impl<'a> From<&'a ReusableSecret> for Option {
+ /// Given an x25519 [`ReusableSecret`] key, compute its corresponding [`PublicRepresentative`].
+ fn from(secret: &'a ReusableSecret) -> Option {
+ let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0);
+ let res: Option<[u8; 32]> = repres;
+ Some(PublicRepresentative(res?))
+ }
+}
+
+#[cfg(feature = "static_secrets")]
+#[cfg(feature = "elligator2")]
+impl<'a> From<&'a StaticSecret> for Option {
+ /// Given an x25519 [`StaticSecret`] key, compute its corresponding [`PublicRepresentative`].
+ fn from(secret: &'a StaticSecret) -> Option {
+ let repres = curve25519_dalek::elligator2::representative_from_privkey(&secret.0);
+ let res: Option<[u8; 32]> = repres;
+ Some(PublicRepresentative(res?))
+ }
+}
+
+#[cfg(feature = "elligator2")]
+impl<'a> From<&'a PublicRepresentative> for PublicKey {
+ /// Given an elligator2 [`PublicRepresentative`], compute its corresponding [`PublicKey`].
+ fn from(representative: &'a PublicRepresentative) -> PublicKey {
+ let point = curve25519_dalek::elligator2::map_to_point(&representative.0);
+ PublicKey(point)
+ }
+}