diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index 5e68e888..d9627451 100644 --- a/curve25519-dalek/Cargo.toml +++ b/curve25519-dalek/Cargo.toml @@ -27,7 +27,7 @@ rustdoc-args = [ "--html-in-header", "docs/assets/rustdoc-include-katex-header.html", "--cfg", "docsrs", ] -features = ["serde", "rand_core", "elligator2", "digest", "legacy_compatibility", "group-bits"] +features = ["serde", "rand_core", "digest", "legacy_compatibility", "group-bits"] [dev-dependencies] sha2 = { version = "0.10", default-features = false } diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index 97695c38..755c8c60 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -268,4 +268,24 @@ impl FieldElement2625 { fiat_25519_carry(&mut output.0, &output_loose); output } + + /// 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/fiat_u64/field.rs b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs index 2a022e23..0e4eedc7 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs @@ -259,4 +259,24 @@ impl FieldElement51 { fiat_25519_carry(&mut output.0, &output_loose); output } + + /// 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 f1978f98..5ad9229a 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -103,8 +103,6 @@ use core::ops::{Mul, MulAssign}; use cfg_if::cfg_if; -#[cfg(feature = "elligator2")] -use crate::elligator2::{map_fe_to_edwards, MASK_UNSET_BYTE}; #[cfg(feature = "digest")] use digest::{generic_array::typenum::U64, Digest}; @@ -590,6 +588,8 @@ impl EdwardsPoint { where D: Digest + Default, { + use crate::elligator2::Legacy; + let mut hash = D::new(); hash.update(bytes); let h = hash.finalize(); @@ -598,52 +598,16 @@ impl EdwardsPoint { let sign_bit = (res[31] & 0x80) >> 7; - let fe1 = MontgomeryPoint::map_to_point_unbounded(&res); + // rfc9380 should always result in a valid point since no field elements + // are invalid. so unwrap should be safe. + #[allow(clippy::unwrap_used)] + let fe1 = MontgomeryPoint::from_representative::(&res).unwrap(); let E1_opt = fe1.to_edwards(sign_bit); E1_opt .expect("Montgomery conversion to Edwards point in Elligator failed") .mul_by_cofactor() } - - #[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 - 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(feature = "elligator2")] - fn from_xy(x: &FieldElement, y: &FieldElement) -> EdwardsPoint { - 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 index 18b3b926..f8540fce 100644 --- a/curve25519-dalek/src/elligator2.rs +++ b/curve25519-dalek/src/elligator2.rs @@ -1,12 +1,135 @@ // -*- mode: rust; -*- -//! Functions mapping curve points to representative values -//! (indistinguishable from random), and back again. -use crate::constants::{MONTGOMERY_A, MONTGOMERY_A_NEG}; +//! Functions mapping curve points to representative (random) values +//! and back again. +//! +//! ## Usage +//! +//! ```rust ignore +//! use rand::RngCore; +//! use curve25519_dalek::elligator2::{RFC9380, MapToPointVariant}; +//! +//! type A = RFC9380; +//! +//! let mut rng = rand::thread_rng(); +//! let mut privkey = [0_u8;32]; +//! rng.fill_bytes(&mut privkey); +//! let tweak = rng.next_u32() as u8; +//! +//! let public_key = A::mul_base_clamped(privkey); +//! // only about half of points are representable, so this will fail ~50% of the time. +//! let representative = A::to_representative(&privkey, tweak) +//! .expect("non representable point :(" ); +//! +//! // The representative gets distributed in place of the public key, +//! // it can then be converted back into the curve25519 point. +//! let public_key1 = A::from_representative(&representative).unwrap(); +//! +//! # let p = public_key.to_montgomery().to_bytes(); +//! # let p1 = public_key1.to_montgomery().to_bytes(); +//! # assert_eq!(hex::encode(&p), hex::encode(&p1)); +//! ``` +//! +//! The elligator2 transforms can also be applied to [`MontgomeryPoint`] and +//! [`EdwardsPoint`] objects themselves. +//! +//! ```rust +//! use rand::RngCore; +//! use curve25519_dalek::{MontgomeryPoint, EdwardsPoint, elligator2::{RFC9380, Randomized, MapToPointVariant}}; +//! +//! // Montgomery Points can be mapped to and from elligator representatives +//! // using any algorithm variant. +//! let tweak = rand::thread_rng().next_u32() as u8; +//! let mont_point = MontgomeryPoint::default(); // example point known to be representable +//! let r = mont_point.to_representative::(tweak).unwrap(); +//! +//! _ = MontgomeryPoint::from_representative::(&r).unwrap(); +//! +//! // Edwards Points can be transformed as well. +//! let edw_point = EdwardsPoint::default(); // example point known to be representable +//! let r = edw_point.to_representative::(tweak).unwrap(); +//! +//! _ = EdwardsPoint::from_representative::(&r).unwrap(); +//! ``` +//! +//! ### Generating Representable Points. +//! +//! As only about 50% of points are actually representable using elligator2. In +//! order to guarantee that generated points are representable we can just try +//! in a loop as the probability of overall success (given a proper source of +//! randomness) over `N` trials is approximately `P_success = 1 - (0.5)^N`. +//! +//! ```rust +//! use rand::{RngCore, CryptoRng}; +//! use curve25519_dalek::elligator2::{MapToPointVariant, Randomized}; +//! +//! type A = Randomized; +//! const RETRY_LIMIT: usize = 64; +//! +//! pub fn key_from_rng(mut csprng: R) -> ([u8;32], u8) { +//! let mut private = [0_u8;32]; +//! csprng.fill_bytes(&mut private); +//! +//! // The tweak only needs generated once as it doesn't affect the overall +//! // validity of the elligator2 representative. +//! let tweak = csprng.next_u32() as u8; +//! +//! let mut repres: Option<[u8; 32]> = +//! A::to_representative(&private, tweak).into(); +//! +//! for _ in 0..RETRY_LIMIT { +//! if repres.is_some() { +//! return (private, tweak) +//! } +//! csprng.fill_bytes(&mut private); +//! repres = A::to_representative(&private, tweak).into(); +//! } +//! +//! panic!("failed to generate representable secret, bad RNG provided"); +//! } +//! +//! let mut rng = rand::thread_rng(); +//! let k = key_from_rng(&mut rng); +//! ``` +//! +//! ### Which variant is right for me? +//! +//! As the variants are not equivalent and do not generate the same 1) public key +//! given a private key (`mul_base_clamped`) 2) representative given a private +//! key (`to_representative`), the variant you choose will depend on your use case. +//! +//! The major difference in use case depends on +//! whether you need to convert public keys to randomized representatives, and +//! those representatives need to be **indistinguishable from uniform random**. +//! If this is the case, use [`Randomized`]. +//! If this does not describe your use case, for example, you are interested in +//! using this implementation for something like `Hash2Curve`, you should instead +//! use [`RFC9380`]. +//! +//! +//! If you are unsure, you will likely want to use the [`RFC9380`] variant. +//! +//! ## Security +//! +//! As with the backing curve25519-dalek crate, all operations are implemented +//! using constant-time logic (no secret-dependent branches, +//! no secret-dependent memory accesses), unless specifically marked as being +//! variable-time code. +//! +//! The [`Randomized`] variant provides a stronger guarantee of being +//! indistinguishable from uniform randomness, both through statistical analysis +//! and computational transformations. This is comes at the cost of compatability +//! with the `Hash2Curve` RFC as the algorithms defined by RFC 9380 and implemented +//! in the [`RFC9380`] variant can be computationally transformed such that they +//! are distinguishable from unifrom random points of the field. +//! + +use crate::constants::{MONTGOMERY_A, MONTGOMERY_A_NEG, SQRT_M1}; use crate::field::FieldElement; use crate::montgomery::MontgomeryPoint; use crate::EdwardsPoint; +use cfg_if::cfg_if; use subtle::{ Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption, @@ -23,7 +146,238 @@ pub(crate) const DIVIDE_MINUS_P_1_2_BYTES: [u8; 32] = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, ]; -/// Gets a public representative for a key pair using the private key. +/// Common interface for the different ways to compute the elligator2 forward +/// and reverse transformations. +pub trait MapToPointVariant { + /// Takes a public representative and returns an Edwards curve field element + /// if one exists. + fn from_representative(representative: &[u8; 32]) -> CtOption; + + /// Takes a private key value and gets a byte representation of the public representative. + /// + /// The `tweak` parameter is used to adjust the computed representative making + /// it computationally indistinguishable from uniform random. If this property + /// is not required then the provided tweak value does not matter. + fn to_representative(point: &[u8; 32], tweak: u8) -> CtOption<[u8; 32]>; + + /// Provides direct access to the scalar base multiplication that will produce + /// a public key point from a private key point. + fn mul_base_clamped(bytes: [u8; 32]) -> EdwardsPoint { + EdwardsPoint::mul_base_clamped(bytes) + } +} + +/// Converts between a point on elliptic curve E (Curve25519) and an element of +/// the finite field F over which E is defined. See section 6.7.1 of +/// [RFC 9380 specification](https://datatracker.ietf.org/doc/rfc9380/). +/// +/// We add randomness to the top two bits of the generated representative as the +/// generated values always 0 in those two bits. Similarly we clear the top two +/// bits of a given representative FieldElement before mapping it to the curve. +pub struct RFC9380; + +impl MapToPointVariant for RFC9380 { + fn from_representative(representative: &[u8; 32]) -> CtOption { + let mut r = *representative; + r[31] &= MASK_UNSET_BYTE; + let representative = FieldElement::from_bytes(&r); + let (x, y) = map_fe_to_edwards(&representative); + let point = EdwardsPoint { + X: x, + Y: y, + Z: FieldElement::ONE, + T: &x * &y, + }; + CtOption::new(point, Choice::from(1)) + } + + fn to_representative(point: &[u8; 32], tweak: u8) -> CtOption<[u8; 32]> { + let pubkey = EdwardsPoint::mul_base_clamped(*point).to_montgomery(); + let v_in_sqrt = v_in_sqrt(point); + let p: Option<[u8; 32]> = point_to_representative(&pubkey, v_in_sqrt.into()).into(); + match p { + None => CtOption::new([0u8; 32], Choice::from(0)), + Some(mut a) => { + a[31] |= MASK_SET_BYTE & tweak; + CtOption::new(a, Choice::from(1)) + } + } + } +} + +/// Converts between a point on elliptic curve E (Curve25519) and an element of +/// the finite field F over which E is defined. Ensures that generated field +/// elements are indistinguishable from uniform random at the cost of compatability +/// with RFC 9380. +/// +/// Differs from [`RFC9380`] in the implementation of the `to_representative` function +/// as RFC9380 misses a computational distinguisher that would allow an attacker to +/// distinguish the representative from random bytes. +pub struct Randomized; + +impl MapToPointVariant for Randomized { + fn from_representative(representative: &[u8; 32]) -> CtOption { + RFC9380::from_representative(representative) + } + + fn to_representative(point: &[u8; 32], tweak: u8) -> CtOption<[u8; 32]> { + let u = EdwardsPoint::mul_base_clamped_dirty(*point); + representative_from_pubkey(&u, tweak) + } + + fn mul_base_clamped(bytes: [u8; 32]) -> EdwardsPoint { + EdwardsPoint::mul_base_clamped_dirty(bytes) + } +} + +#[cfg(feature = "digest")] +/// Converts between a point on elliptic curve E (Curve25519) and an element of +/// the finite field F over which E is defined. Supports older implementations +/// with a common implementation bug (Signal, Kleshni-C). +/// +/// In contrast to the [`RFC9380`] variant, `Legacy` does NOT assume that input values are always +/// going to be the least-square-root representation of the field element. +/// This is divergent from the specifications for both elligator2 and RFC 9380, +/// however, some older implementations miss this detail. This allows us to be +/// compatible with those alternate implementations if necessary, since the +/// resulting point will be different for inputs with either of the +/// high-order two bits set. The kleshni C and Signal implementations are examples +/// of libraries that don't always use the least square root. +/// +/// In general this mode should NOT be used unless there is a very specific +/// reason to do so. +/// +// We return the LSR for to_representative values. This is here purely for testing +// compatability and ensuring that we understand the subtle differences that can +// influence proper implementation. +pub struct Legacy; + +#[cfg(feature = "digest")] +impl MapToPointVariant for Legacy { + fn from_representative(representative: &[u8; 32]) -> CtOption { + let representative = FieldElement::from_bytes(representative); + let (x, y) = map_fe_to_edwards(&representative); + let point = EdwardsPoint { + X: x, + Y: y, + Z: FieldElement::ONE, + T: &x * &y, + }; + CtOption::new(point, Choice::from(1)) + } + + fn to_representative(point: &[u8; 32], _tweak: u8) -> CtOption<[u8; 32]> { + let pubkey = EdwardsPoint::mul_base_clamped(*point); + let v_in_sqrt = v_in_sqrt_pubkey_edwards(&pubkey); + point_to_representative(&MontgomeryPoint(*point), v_in_sqrt.into()) + } +} + +// =========================================================================== +// Montgomery and Edwards Interfaces +// =========================================================================== + +impl MontgomeryPoint { + #[cfg(feature = "elligator2")] + /// 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. It is assumed that input values are always going to be the + /// least-square-root representation of the field element in allignment + /// with both the elligator2 specification and RFC9380. + /// + /// The input r and output P are elements of the field F. Note that + /// the output P is a point on the Montgomery curve and as such it's byte + /// representation is distinguishable from uniform random. + /// + /// Input: + /// * r -> 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 mut clamped = *r; + clamped[31] &= MASK_UNSET_BYTE; + let r_0 = FieldElement::from_bytes(&clamped); + let (p, _) = map_fe_to_montgomery(&r_0); + MontgomeryPoint(p.as_bytes()) + } + + /// Maps a representative to a curve point. + /// + /// This function is the inverse of `to_representative`. + pub fn from_representative(representative: &[u8; 32]) -> Option { + let b: Option = T::from_representative(representative).into(); + b.map(|x| x.to_montgomery()) + } + + /// Mapts a point on the curve to a representative. + pub fn to_representative(&self, tweak: u8) -> Option<[u8; 32]> { + T::to_representative(&self.0, tweak).into() + } +} + +impl EdwardsPoint { + #[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 r 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: + /// * r -> an element of field F. + /// + /// Output: + /// * P - a point on the Edwards elliptic curve. + /// + /// See + 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(feature = "elligator2")] + fn from_xy(x: &FieldElement, y: &FieldElement) -> EdwardsPoint { + let z = FieldElement::ONE; + let t = x * y; + + EdwardsPoint { + X: *x, + Y: *y, + Z: z, + T: t, + } + } + + /// Maps a representative to a curve point. + /// + /// This function is the inverse of `to_representative`. + pub fn from_representative(representative: &[u8; 32]) -> Option { + T::from_representative(representative).into() + } + + /// Mapts a point on the curve to a representative. + pub fn to_representative(&self, tweak: u8) -> Option<[u8; 32]> { + T::to_representative(&self.to_montgomery().0, tweak).into() + } +} + +// =========================================================================== +// Randomized implementation +// =========================================================================== + +/// Gets a public representative for a key pair using the private key (RFC 9380). /// /// The `tweak` parameter is used to adjust the computed representative making /// it computationally indistinguishable from uniform random. If this property @@ -46,21 +400,128 @@ pub(crate) const DIVIDE_MINUS_P_1_2_BYTES: [u8; 32] = [ /// ``` /// /// For a more in-depth explanation see: -/// https://github.com/agl/ed25519/issues/27 -/// https://www.bamsoftware.com/papers/fep-flaws/ +/// +/// pub fn representative_from_privkey(privkey: &[u8; 32], tweak: u8) -> Option<[u8; 32]> { - let pubkey = EdwardsPoint::mul_base_clamped(*privkey).to_montgomery(); - let v_in_sqrt = v_in_sqrt(privkey); - let p: Option<[u8; 32]> = point_to_representative(&pubkey, v_in_sqrt.into()).into(); - match p { - None => None, - Some(mut a) => { - a[31] |= MASK_SET_BYTE & tweak; - Some(a) - } + RFC9380::to_representative(privkey, tweak).into() +} + +cfg_if! { + if #[cfg(curve25519_dalek_bits = "64")] { + /// Low order point Edwards x-coordinate `sqrt((sqrt(d + 1) + 1) / d)`. + const LOW_ORDER_POINT_X: FieldElement = FieldElement::from_limbs([ + 0x00014646c545d14a, + 0x0006027cbc471bd4, + 0x0003792aed7064f1, + 0x0005147499cc991c, + 0x0001fd5b9a006394, + ]); + /// Low order point Edwards y-coordinate `-lop_x * sqrtm1` + const LOW_ORDER_POINT_Y: FieldElement = FieldElement::from_limbs([ + 0x0007b2c28f95e826, + 0x0006513e9868b604, + 0x0006b37f57c263bf, + 0x0004589c99e36982, + 0x00005fc536d88023, + ]); + const FE_MINUS_TWO: FieldElement = FieldElement::from_limbs([ + 2251799813685227, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, + ]); + } + else if #[cfg(curve25519_dalek_bits = "32")] { + const LOW_ORDER_POINT_X: FieldElement = FieldElement::from_limbs([ + 0x0145d14a, 0x005191b1, 0x00471bd4, 0x01809f2f, 0x017064f1, + 0x00de4abb, 0x01cc991c, 0x01451d26, 0x02006394, 0x007f56e6 + ]); + const LOW_ORDER_POINT_Y: FieldElement = FieldElement::from_limbs([ + 0x0395e826, 0x01ecb0a3, 0x0068b604, 0x01944fa6, 0x03c263bf, + 0x01acdfd5, 0x01e36982, 0x01162726, 0x02d88023, 0x0017f14d + ]); + const FE_MINUS_TWO: FieldElement = FieldElement::from_limbs([ + 0x03ffffeb, 0x01ffffff, 0x03ffffff, 0x01ffffff, 0x03ffffff, + 0x01ffffff, 0x03ffffff, 0x01ffffff, 0x03ffffff, 0x01ffffff + ]); + } +} + +#[allow(clippy::identity_op)] +fn select_low_order_point(a: &FieldElement, b: &FieldElement, cofactor: u8) -> FieldElement { + // bit 0 + let c0 = !Choice::from((cofactor >> 1) & 1); + let out = FieldElement::conditional_select(b, &FieldElement::ZERO, c0); + + // bit 1 + let c1 = !Choice::from((cofactor >> 0) & 1); + let mut out = FieldElement::conditional_select(a, &out, c1); + + // bit 2 + let c2 = Choice::from((cofactor >> 2) & 1); + out.conditional_negate(c2); + out +} + +#[cfg(feature = "elligator2")] +impl EdwardsPoint { + /// Multiply the basepoint by `clamp_integer(bytes)`. For a description of clamping, see + /// [`clamp_integer`]. This variant integrates a low order point into the resulting + /// value in order to prevent a computational distinguisher attack that would allow + /// an adversary to check if representatives are always points or order L when multiplied + /// with the base point. + /// + /// > A scalar multiplication with the base point, even one that does not clamp the scalar, will always yield a point of order L. That is, for all s: `L × (s × B) = zero`. + /// > A random Elligator2 representative however will only map to a point of order L 12.5% of the time. + /// + /// [`clamp_integer`]: crate::scalar::clamp_integer + pub fn mul_base_clamped_dirty(privkey: [u8; 32]) -> Self { + let lop_x = select_low_order_point(&LOW_ORDER_POINT_X, &SQRT_M1, privkey[0]); + let (cofactor, _) = privkey[0].overflowing_add(2); + let lop_y = select_low_order_point(&LOW_ORDER_POINT_Y, &FieldElement::ONE, cofactor); + let lop_t = &lop_x * &lop_y; + + let low_order_point = EdwardsPoint { + X: lop_x, + Y: lop_y, + Z: FieldElement::ONE, + T: lop_t, + }; + + // add the low order point to the public key + EdwardsPoint::mul_base_clamped(privkey) + low_order_point } } +/// Gets the pulic representative using the ['EdwardsPoint'] public key. +fn representative_from_pubkey(pubkey: &EdwardsPoint, tweak: u8) -> CtOption<[u8; 32]> { + let mut t1 = FieldElement::from_bytes(&pubkey.to_montgomery().0); + + // -2 * u * (u + A) + let t2 = &t1 + &MONTGOMERY_A; + let t3 = &(&t1 * &t2) * &FE_MINUS_TWO; + + let (is_sq, mut t3) = FieldElement::sqrt_ratio_i(&FieldElement::ONE, &t3); + + t1 = FieldElement::conditional_select(&t1, &t2, Choice::from(tweak & 1)); + t3 = &t1 * &t3; + t1 = &t3 + &t3; + + let tmp: u8 = t1.as_bytes()[0] & 1; + t3.conditional_negate(Choice::from(tmp)); + + // get representative and pad with bits from the tweak. + let mut representative = t3.as_bytes(); + representative[31] |= tweak & MASK_SET_BYTE; + + CtOption::new(representative, is_sq) +} + +// =========================================================================== +// RFC9380 (and Legacy) implementation +// =========================================================================== + /// 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. /// @@ -82,7 +543,7 @@ pub fn representative_from_privkey(privkey: &[u8; 32], tweak: u8) -> Option<[u8; /// # Inputs /// /// * `point`: the \\(u\\)-coordinate of a point on the curve. Not all -/// points map to field elements. +/// points map to field elements. /// /// * `v_in_sqrt`: true if the \\(v\\)-coordinate of the point is negative. /// @@ -94,13 +555,15 @@ pub fn representative_from_privkey(privkey: &[u8; 32], tweak: u8) -> Option<[u8; /// [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]> { +pub(crate) 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 a_neg = -a; let is_encodable = is_encodable(a); @@ -136,7 +599,7 @@ fn is_encodable(u: &FieldElement) -> Choice { 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(); + chi = -χ let chi_bytes = chi.as_bytes(); @@ -183,7 +646,7 @@ pub(crate) fn high_y(d: &FieldElement) -> Choice { // [`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 { +pub(crate) fn v_in_sqrt(key_input: &[u8; 32]) -> Choice { let mut masked_pk = *key_input; masked_pk[0] &= 0xf8; masked_pk[31] &= 0x7f; @@ -195,7 +658,7 @@ pub fn v_in_sqrt(key_input: &[u8; 32]) -> Choice { /// 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 { +pub(crate) 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)) @@ -217,16 +680,15 @@ pub fn v_in_sqrt_pubkey_edwards(pubkey: &EdwardsPoint) -> Choice { divide_minus_p_1_2.ct_gt(&v) } -// ---------------------------------------------------------------------------- -// ---------------------------------------------------------------------------- +// ============================================================================ +// ============================================================================ fn map_to_curve_parts( r: &FieldElement, ) -> (FieldElement, FieldElement, FieldElement, FieldElement) { let zero = FieldElement::ZERO; let one = FieldElement::ONE; - let mut minus_one = FieldElement::ONE; - minus_one.negate(); + let minus_one = -&FieldElement::ONE; // Exceptional case 2u^2 == -1 let mut tv1 = r.square2(); @@ -297,1053 +759,28 @@ pub(crate) fn map_fe_to_edwards(r: &FieldElement) -> (FieldElement, FieldElement (&xn * &(xd.invert()), &yn * &(yd.invert())) } -// ------------------------------------------------------------------------ +// ======================================================================== // Tests -// ------------------------------------------------------------------------ +// ======================================================================== #[cfg(test)] #[cfg(feature = "elligator2")] #[cfg(feature = "alloc")] -mod test { - use super::*; - - use hex::FromHex; - - //////////////////////////////////////////////////////////// - // 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]>::from_hex(testcase.point).expect("failed to decode hex point"); - - let edw_point = MontgomeryPoint(point) - .to_edwards(testcase.high_y as u8) - .expect("failed to convert point to edwards"); - 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.expect("checked, is some"), - hex::encode(repres.expect("failed to get representative from point")), - "[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.expect("this should not fail")) - ); - } - } - } - - #[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]>::from_hex(vector[0]).expect("failed to decode hex privatekey"); - let true_repres = vector[2]; - - let repres_res = representative_from_privkey(&privkey, 0u8); - assert!( - Into::::into(repres_res.is_some()), - "failed to get representative when we should have gotten one :(" - ); - let repres = repres_res.expect("failed to get representative from pubkey"); - - 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]>::from_hex(testcase.representative) - .expect("failed to decode hex representative"); - - let point = MontgomeryPoint::map_to_point(&repres); - assert_eq!( - testcase.point, - hex::encode(point.to_bytes()), - "[good case] kleshni ({i}) bad representative from point" - ); - - let point_from_unbounded = MontgomeryPoint::map_to_point_unbounded(&repres); - assert_eq!( - testcase.non_lsr_point, - hex::encode(point_from_unbounded.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]>::from_hex(vector[2]).expect("failed to decode hex representative"); - - // ensure that the representative can be reversed to recover the - // original public key. - let pubkey = MontgomeryPoint::map_to_point(&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]>::from_hex(key).expect("failed to decode hex privkey"); - - // ensure that the representative can be reversed to recover the - // original public key. - let res: Option<[u8; 32]> = representative_from_privkey(&privkey, 0u8); - assert!( - res.is_none(), - "[bad case] agl/ed25519 ({i}) expected None, got Some({})", - hex::encode(res.expect("this shouldn't happen")) - ); - } - } - - /// 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 = 10; - 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", - ), - }, - // A not encodable point with both "high_y" values - EncodingTestCase { - point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", - high_y: false, - representative: Some( - "d660db8cf212d31ce8c6f7139e69b9ac47fd81c7c0bfcb93e364b2d424e24813", - ), - }, - EncodingTestCase { - point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", - high_y: true, - representative: Some( - "489a2e0f6955e08f1ae6eb8dcdbc0f867a87a96a02d2dfd2aca21d8b536f0f1b", - ), - }, - // A not encodable point with both "high_y" values - EncodingTestCase { - point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", - high_y: false, - representative: Some( - "63d0d79e7f3c279cf4a0a5c3833fd85aa1f2c004c4e466f3a3844b3c2e06e410", - ), - }, - EncodingTestCase { - point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", - high_y: true, - representative: Some( - "0f03b41c86aeb49acf2f76b39cc90a55a0b140b7290f1c9e032591ddcb074537", - ), - }, - ] - } - - const DECODING_TESTS_COUNT: usize = 7; - struct DecodingTestCase { - representative: &'static str, - /// if we only allow least-square-root values as the representative and - /// clear the high order two bits (effectively) ensuring that the - /// representative value is less than `2^254 - 10`, this is the point - /// that we should receive. - point: &'static str, - /// if we allow unbounded values to be used directly as representatives, - /// not only least-square-root values, this is the point we should receive. - non_lsr_point: &'static str, - } - - fn decoding_testcases() -> [DecodingTestCase; DECODING_TESTS_COUNT] { - [ - // A small representative with false "high_y" property - DecodingTestCase { - representative: "e73507d38bae63992b3f57aac48c0abc14509589288457995a2b4ca3490aa207", - point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", - non_lsr_point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", - }, - // A small representative with true "high_y" property - DecodingTestCase { - representative: "95a16019041dbefed9832048ede11928d90365f24a38aa7aef1b97e23954101b", - point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", - non_lsr_point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", - }, - // The last representative returning true: (p - 1) / 2 - DecodingTestCase { - representative: "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", - point: "9cdb525555555555555555555555555555555555555555555555555555555555", - non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", - }, - // The first representative returning false: (p + 1) / 2 - DecodingTestCase { - representative: "f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", - point: "9cdb525555555555555555555555555555555555555555555555555555555555", - non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", - }, - // 0 - DecodingTestCase { - representative: "0000000000000000000000000000000000000000000000000000000000000000", - point: "0000000000000000000000000000000000000000000000000000000000000000", - non_lsr_point: "0000000000000000000000000000000000000000000000000000000000000000", - }, - // These two tests are not least-square-root representations. - - // A large representative with false "high_y" property - DecodingTestCase { - representative: "179f24730ded2ce3173908ec61964653b8027e383f40346c1c9b4d2bdb1db76c", - point: "e6e5355e0482e952cc951f13db26316ab111ae9edb58c45428a984ce7042d349", - non_lsr_point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", - }, - // A large representative with true "high_y" property - DecodingTestCase { - representative: "8a2f286180c3d8630b5f5a3c7cc027a55e0d3ffb3b1b990c5c7bb4c3d1f91b6f", - point: "27e222fec324b0293842a59a63b8201b0f97b1dd599ebcd478a896b7261aff3e", - non_lsr_point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", - }, - ] - } -} +mod compatibility; #[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, testcase) in CURVE25519_ELL2.iter().enumerate() { - let u = testcase[0].must_from_be(); - let mut clamped = u; - clamped[31] &= 63; - - // map point to curve - let (q_x, _) = map_fe_to_montgomery(&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, testcase) in curve25519_XMD_SHA512_ELL2_NU.iter().enumerate() { - let u = FieldElement::from_bytes(&testcase.u_0.must_from_le()); - - // map point to curve - let (q_x, q_y) = map_fe_to_montgomery(&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, testcase) in curve25519_XMD_SHA512_ELL2_RO.iter().enumerate() { - 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_montgomery(&u0); - let (q1_x, q1_y) = map_fe_to_montgomery(&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, testcase) in edwards25519_XMD_SHA512_ELL2_NU.iter().enumerate() { - let u = FieldElement::from_bytes(&testcase.u_0.must_from_le()); - let (q_x, q_y) = map_fe_to_edwards(&u); - - // check resulting point - assert_eq!( - q_x.encode_le(), - testcase.Q_x, - "({i}) incorrect Q0_x edwards25519 NU\n{:?}", - testcase - ); - assert_eq!( - q_y.encode_le(), - testcase.Q_y, - "({i}) incorrect Q0_y edwards25519 NU\n{:?}", - testcase - ); - } - for (i, testcase) in edwards25519_XMD_SHA512_ELL2_RO.iter().enumerate() { - 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_edwards(&u0); - let (q1_x, q1_y) = map_fe_to_edwards(&u1); - - // check resulting points - assert_eq!( - q0_x.encode_le(), - testcase.Q0_x, - "({i}) incorrect Q0_x edwards25519 RO\n{:?}", - testcase - ); - assert_eq!( - q0_y.encode_le(), - testcase.Q0_y, - "({i}) incorrect Q0_y edwards25519 RO\n{:?}", - testcase - ); - assert_eq!( - q1_x.encode_le(), - testcase.Q1_x, - "({i}) incorrect Q1_x edwards25519 RO\n{:?}", - testcase - ); - assert_eq!( - q1_y.encode_le(), - testcase.Q1_y, - "({i}) incorrect Q1_y edwards25519 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: [[&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).expect("failed to unhex"); - u.reverse(); - u - } - fn must_from_be(&self) -> [u8; 32] { - <[u8; 32]>::from_hex(self).expect("failed to unhex from be") - } - } - - 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; - b.reverse(); - hex::encode(b) - } - - fn encode_be(&self) -> String { - hex::encode(self) - } - } -} +mod rfc9380; #[cfg(test)] #[cfg(feature = "elligator2")] -mod randomness { - use super::*; - - use kolmogorov_smirnov as ks; - use rand::{thread_rng, RngCore}; - use rand_distr::{Binomial, Distribution}; - use std::vec::Vec; +mod randomness; - struct BitCounts { - counts: [[u64; 100]; 32 * 8], - entries: usize, - } - - impl BitCounts { - fn new() -> Self { - BitCounts { - counts: [[0u64; 100]; 256], - entries: 0, - } - } - fn entry(&mut self, arr: &[u8; 32]) { - for (i, arr_byte) in arr.iter().enumerate() { - for j in 0..8 { - self.counts[i * 8 + j][self.entries % 100] += ((arr_byte >> j) & 0x01) as u64; - } - } - self.entries += 1; - } - fn outliers(&self) -> Vec { - let mut rng = thread_rng(); - let binomial_100 = - Binomial::new(100, 0.5).expect("failed to build binomial distribution"); - let expected_dist: [u64; 100] = binomial_100 - .sample_iter(&mut rng) - .take(100) - .collect::>() - .try_into() - .expect("failed to build example binomial expected distribution"); - // this is a high confidence, but we want to avoid this test failing - // due to statistical variability on repeat runs. - let confidence = 0.95; - let mut outlier_indices = vec![]; - - for (n, bit_trial) in self.counts.iter().enumerate() { - let result = ks::test(bit_trial, &expected_dist, confidence); - // require reject_probability == 1.0 to avoid statistical variability on re-runs - if result.is_rejected && result.reject_probability == 1.0 { - // samples definitely not from same distribution - outlier_indices.push(n); - println!( - "{n}, {} {} {} {}", - result.statistic, - result.reject_probability, - result.critical_value, - result.confidence - ); - if n == 255 { - println!("{:?}\n{:?}", bit_trial, expected_dist); - } - } - } - outlier_indices - } - } - - #[test] - /// If we use a "random" value for the tweak the high order bits will be - /// set such that the representative is: - /// 1) unchanged w.r.t. the public key value derived from the representative - /// i.e) it still derives the same public key value from the (clamped) - /// representative that we get from the private key. - /// 2) statistically indistinguishable from uniform random. - /// 4) computationally indistinguishable from random strings for the `a ?= sqrt(a^2)` test. - /// - /// (To see this test fail change `rng.next_u32() as u8` to `0u8`) - fn bitwise_entropy() { - const ITERATIONS: usize = 10000; - // number of iterations - let mut i = 0usize; - let mut rng = thread_rng(); - let mut privkey = [0u8; 32]; - - // count of occurences of a 1 per bit in the representative - let mut bitcounts = BitCounts::new(); - - while i < ITERATIONS { - rng.fill_bytes(&mut privkey); - let alice_representative = - match representative_from_privkey(&privkey, rng.next_u32() as u8) { - None => continue, - Some(r) => r, - }; - - bitcounts.entry(&alice_representative); - - let pub_from_repr = MontgomeryPoint::map_to_point(&alice_representative); - let pub_from_priv = EdwardsPoint::mul_base_clamped(privkey).to_montgomery(); - assert_eq!( - hex::encode(pub_from_priv.as_bytes()), - hex::encode(pub_from_repr.as_bytes()), - "failed pubkey match at iteration {i}" - ); - - i += 1; - } - - let outliers = bitcounts.outliers(); - assert!(outliers.is_empty(), "bad bits: {:?}", outliers); - } - - /// TLDR: The sqrt_ratio_i function is canonical so this library does not - /// suffer from the describbed computational distinguisher. - /// - /// The specific issue that this is testing for can be described as: - /// ```txt - /// An instantiation of Elligator is parameterized by what might be called - /// a “canonical” square root function, one with the property that - /// `√a2 = √(−a)2` for all field elements `a`. That is, we designate just - /// over half the field elements as “non-negative,” and the image of the - /// square root function consists of exactly those elements. A convenient - /// definition of “non-negative” for Curve25519, suggested by its authors, - /// is the lower half of the field, the elements `{0, 1, …, (q − 1) / 2}`. - /// When there are two options for a square root, take the smaller of the two. - /// ``` - /// - /// Any Elligator implementation that does not do this canonicalization of - /// the final square root, and instead it maps a given input systematically - /// to either its negative or non-negative root is vulnerable to the - /// following computational distinguisher. - /// - /// ```txt - /// [An adversary could] observe a representative, interpret it as a field - /// element, square it, then take the square root using the same - /// non-canonical square root algorithm. With representatives produced by - /// an affected version of [the elligator2 implementation], the output of - /// the square-then-root operation would always match the input. With - /// random strings, the output would match only half the time. - /// ``` - /// - /// For a more in-depth explanation see: - /// https://github.com/agl/ed25519/issues/27 - /// https://www.bamsoftware.com/papers/fep-flaws/ - #[test] - fn test_canonical() { - const ITERATIONS: usize = 10000; - // number of iterations - let mut i = 0usize; - let mut rng = thread_rng(); - let mut privkey = [0u8; 32]; - - // number of times the representative (interpreted as a point) squared, then square_rooted, - // equals the original representative. Should happen w/ 50% probability. - let mut squares_equal = 0usize; - - while i < ITERATIONS { - rng.fill_bytes(&mut privkey); - let alice_representative = match representative_from_privkey(&privkey, 0u8) { - None => continue, - Some(r) => r, - }; - - if is_canonical(&alice_representative) { - squares_equal += 1; - } - i += 1; - } - - let expected_range = 4500..5500; // if truly binomial n=10000, p=0.5 then this should "always" pass (> 10x std dev) - assert!( - expected_range.contains(&squares_equal), - "squares_equal: {squares_equal} is not in [4500:5500]" - ); - } +#[cfg(test)] +#[cfg(feature = "elligator2")] +mod subgroup; - fn is_canonical(repres: &[u8; 32]) -> bool { - let r_fe = FieldElement::from_bytes(repres); - let (ok, r_fe_prime) = FieldElement::sqrt_ratio_i(&r_fe.square(), &FieldElement::ONE); - (r_fe.ct_eq(&r_fe_prime) & ok).into() - } -} +#[cfg(test)] +#[cfg(feature = "elligator2")] +#[cfg(feature = "digest")] +mod legacy; diff --git a/curve25519-dalek/src/elligator2/compatibility.rs b/curve25519-dalek/src/elligator2/compatibility.rs new file mode 100644 index 00000000..8ba7a735 --- /dev/null +++ b/curve25519-dalek/src/elligator2/compatibility.rs @@ -0,0 +1,257 @@ +use super::*; + +use hex::FromHex; + +//////////////////////////////////////////////////////////// +// Ntor tests // +//////////////////////////////////////////////////////////// + +#[test] +#[cfg(feature = "elligator2")] +fn pubkey_from_repres() { + // testcases from golang agl/ed25519 + for (i, vector) in ntor_valid_test_vectors().iter().enumerate() { + let true_pubkey = vector[2]; + let repres = <[u8; 32]>::from_hex(vector[3]).expect("failed to decode hex representative"); + + // ensure that the representative can be reversed to recover the + // original public key. + let pubkey = MontgomeryPoint::map_to_point(&repres); + assert_eq!( + true_pubkey, + hex::encode(pubkey.to_bytes()), + "[good case] agl/ed25519 ({i}) bad pubkey from true representative" + ); + } +} + +#[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]>::from_hex(vector[0]).expect("failed to decode hex privatekey"); + let true_repres = vector[3]; + + let pubkey_clean = EdwardsPoint::mul_base_clamped(privkey) + .to_montgomery() + .to_bytes(); + assert_eq!( + hex::encode(pubkey_clean), + vector[1], + "[good case] agl/ed25519 ({i}) incorrect clean pubkey from privkey" + ); + + let pubkey = Randomized::mul_base_clamped(privkey); + assert_eq!( + hex::encode(pubkey.to_montgomery().to_bytes()), + vector[2], + "[good case] agl/ed25519 ({i}) incorrect pubkey from privkey" + ); + + let r1 = Randomized::to_representative(&privkey, 0u8) + .expect("[good case] agl/ed25519 ({i}) failed to get representative from privkey"); + + let repres = representative_from_pubkey(&pubkey, 0u8) + .expect("[good case] agl/ed25519 ({i}) failed to get representative from pubkey"); + + assert_eq!(hex::encode(r1), hex::encode(repres)); + assert_eq!( + true_repres, + hex::encode(repres), + "[good case] agl/ed25519 ({i}) incorrect representative from privkey" + ); + } +} + +#[test] +#[cfg(feature = "elligator2")] +fn non_representable_points() { + for (i, key) in ntor_invalid_keys().iter().enumerate() { + let privkey = <[u8; 32]>::from_hex(key).expect("failed to decode hex privkey"); + + // ensure that the representative can be reversed to recover the + // original public key. + let res: Option<[u8; 32]> = MontgomeryPoint(privkey).to_representative::(0u8); + assert!( + res.is_none(), + "[bad case] agl/ed25519 ({i}) expected None, got Some({})", + hex::encode(res.expect("this shouldn't happen")) + ); + } +} + +/// 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 when using normal mul_base_clamped +/// 2) the associated public key point w/ cofactor creates using mul_base_clamped_dirty +/// 3) the successfully created representative from the dirty publicKey +/// +/// 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; 4]; 21] { + [ + [ + "b531f4243aa4a013f0f87a2eaaec47807844a2f375d40b774e824d37a196b2b6", + "aa2d8f1c47f717d96edbf503ff0d9fa782849c486ad8a7c15b2bf4a793902c35", + "04a47c7903661acf03cd71e5400c8b96650bb3620d2ae91b674713ca5f2ac673", + "f30130eb1c192cda48a503932c8751232fd784ab7792acba499807e78084a909", + ], + [ + "d63f245d00c57683e8f3f7174b2601aa89319d39823e42aa388fb4f349b8df16", + "97ed90e92c02c5a488d4d9589174867d0d640c460caf4c61833f6c6cb5f5f874", + "3741b60e375affc17d0dba931bb0f5f4540c31c67a979c3399596103283dd27d", + "3a50a823f482a7af1ac898446850dd643b9a68b530df4cebe7d9f108ebe8933b", + ], + [ + "2b6b4888ec2d23748f708627c1a9260b0f10dd2fbc941e44cd578d69760d9873", + "79a9d72800606a520b580bdba40882fa78f13f61a4a402ef615cf01ef5875450", + "d8db37c0fafe81757adcf4f580daff6f68b200a4a38965a4a71ced905e67ee26", + "ed3b270440c2c9d6630bd2b92fd07ede4b8ac3881a286bc9d5f225e35e1d5a31", + ], + [ + "9228b2e3a95462b6d9f32cc326c3c55107972e9cb426301d33861bec15049b65", + "995ac596a4c4f6da1a7ae3cc5ef9dcfb74443717a68dc03972882c9ba7db1823", + "9d8ef0820c3ed0b0c7d29e178eaf31431ca4ee1e97cdab08ea6c85946a3e5e6b", + "b918077e9e1283c0cc44055a28b97ea131e8517983ed033d60e99b818f55fe00", + ], + [ + "21af4b82c709c6dbf0a1aa28b7a045ea6a20448ec36c9f5f6eadf60c9a71ee66", + "f8ac0dd56d7c9f7d681a8fec38675a39e37e2b9780995ce6cdc1f4da25356860", + "41b49d99d7490aae2c774893d2f24147c353f4179ef318c1954899eb93ba7316", + "e1d2992fce97a72d40483f940aa1156d34eea4e5789336155d718f0d74eb9e27", + ], + [ + "465fd0ac29e17ecadf922676a99a40e59ef20ed7d964663fce6d09cfe247599e", + "5246cdd46625f43e590699a8c0f8740af173190036875c163fc9b430c72b301e", + "9b85e75bfe129adf7e28d2ac1a8a2a186259c91e9afe5b60497f59fe48a61363", + "cfa4592dc98b4625c70bcc5f5b39611043436e4d8818e977ae479e2321b4e731", + ], + [ + "f0bfc68ccc5cde3aac026b8668055c06bb286950197cf8df5b8546c2a55f8676", + "51f046dc5d42d9a73fc12d9b3720723915f58b8b617584ae469bd081118c784d", + "51f046dc5d42d9a73fc12d9b3720723915f58b8b617584ae469bd081118c784d", + "0cb6b5eb789ec03c3561b6574554f1a3e9ceb265cf92e8f1edf03e445641e13a", + ], + [ + "3d4269b3aa052fa291db18183ebd9e05d0230faa7f474eaa28c7cebb191b552c", + "22b0fa046129a0b3a1ddaacb7b62edd952d239aec38ea8c89bf00e15fe859547", + "af0c7b6703225e5ff0260e2cec7c79ded10506c76f1fd86b6134b0b41481f05f", + "932f7c251dfc76ca8fc893068ac7f76c82f311830bbc39a7d69ab1dc75adb419", + ], + [ + "2507de9b7116c8e38b8d9d4a9787c7ad58a5ac82c7cafca8f1f20ed13d41c07e", + "a2a75aa84a1e31a332a49ef20767c0b4b7f2fc81784b720a6f39a666b3d28e53", + "769d48797268911011619509485d5f68649bc56c208057de42188b7f4b6e4a40", + "4b0946387189f8eb0d51ac2ca1156297cc69f3e7d681da2353af7b61055f6c04", + ], + [ + "ab675f987fa1ff564d9532b94103d382424c804e16f33278d5daf8460cafc12f", + "d186d70dd4a861fe0b13bf5bdd43a28ffd5457128d47aee934b8efd4fa163972", + "f3875d1c9118a39467f774837d5b73f26245add28adface6c3831d0b16311600", + "55dc20e8bdfc4d13a6a3f1c49859f35248d1bda1d0db26efc02ccf407ec5670c", + ], + [ + "7f74e1ee4b024dfa1d0922f3b848891ca0bda69760a72354ee38c9a340293511", + "1616f968954f044a8033d0639ef9eaa22658a8deb9d3bd75c4f900b8439ac053", + "9511eb90311692dba1df5cabe16835545d8105a3a51bb1a4e2e3e77625789e59", + "bab2454581ba3c31d61525e341c6e45dae64d63ddeb18ec7246104225d9aad27", + ], + [ + "38af5f517bc769ef83371332d83c438b9a15cafb94718c2d417a2b5595919325", + "ba6d043159102150818942f0bfca69377198ce93d7df638814b4a84df74e7e3f", + "ba6d043159102150818942f0bfca69377198ce93d7df638814b4a84df74e7e3f", + "b3c91ba8db89652d7c93568e141479644b27fbefbb9f8ebd66697c2f3b40fc12", + ], + [ + "84b40c80c5c44f91e7165328f84db2507c8fd0fb58cd939705565dc60f98d4ab", + "7cfb1e927d172cfd0fecf3616bd3077a1e485f8487f5f792a44af24602aeee78", + "0796354388c13f2f43263a04791be6f341c0827dca22cf602e3711aeff3c270d", + "33c419bdec3cbc5bf10811f53c8745e7832d38b0fd4f4975150989e763431702", + ], + [ + "fcb6c9a0acbea273445cc1c84c5fe486ba88f76ff6197e4d66732ac1b7905e0e", + "a865fcb0d433c9205079046ddff9e3add7b3d5b85eabf77570aef6221625845a", + "82d52b90191fa82e6125352d0df011f40d1c52c936d8472c80ab5f39be4dc275", + "a500286f77a44da3954cd2d28bc6ceb698fe9b5f59296494cdba2223ed82781d", + ], + [ + "3a89d10d54534bc595f95b7266ea95a7509a13e35eaede55be49931c73e29f51", + "9a2fd476dbc15148a72d2915223142060ebee6e8fce2a110bc00977d618f4354", + "6d027e1197c78eeed729cdf0a08b25f6c2a36d726dcf2dc7592cbf22a64c4667", + "26591aa301c6b90b41bb2d7d227b7513446a2281138135b606b2ccfd67073d28", + ], + [ + "5ad1beed45793f7e7fd2b59b1d3c64441087c31f5edbc71d5adacf3e87f3b409", + "69947803a666ae9746282ec985bb140473bd2cca8e7e806e6b7c63dba12abf09", + "e562faef7f4846624819ed16de66bfbc5cee8c7200d2579fa81ee00e457d0c41", + "304f86219a3b6f8045038baa1d51271c69b4594582b05409fb08310d15893830", + ], + [ + "248c40bf9a05281c5c42d884c37d916c4dface070f936d293f30a1d05bce5caa", + "ea506f676538b2aa4c60092026453ecb37a3562295be551d134cb334063aad4a", + "27934d458e6c253b0211a6d5ffead34a7f18264c9744c8d860a5af02b486960e", + "3fb9e265959e9ce605f662d983cf5c7606be10cf95dcf1eb96931d7d189fcd1f", + ], + [ + "88d60d17395bb4df69205cc2750bb2e83f8646aa56acaaeccdaec527e085bcc2", + "ae62dc36e3f8bcc7c436cb8adb1b931af32bbe1501d048dc50d9947399f0ac0c", + "ae62dc36e3f8bcc7c436cb8adb1b931af32bbe1501d048dc50d9947399f0ac0c", + "1e8c4ba9a54eccd2bd9020c8333033857187eab8e17aa40f46e22acaca07d31e", + ], + [ + "39e952a23697e05cf624e82c36c36bb51d546ffef5aa4fc4d7b695a51cae499a", + "8d04ce07d2251923e7c871df78ad6eb56db6c0c8b4b11864c2078f91dce17c1f", + "94d13ad7430c8d95f9687b7f030bd22079471f02c96938fabe1d8eac13ad0e3b", + "f33d05d0a975fcd9777db0e78a4c74ec161d90026c29dfc2bdaf79d1a24d6c0d", + ], + [ + "d6111d6215c5e799b92331c0e2984f25de42e9c02e37d53abad8b7cb2a9c5e9f", + "8fb6b77c034c1b96eca447a1192e54e8efd14944595f2d8f37e0bb13b9537e59", + "0bce86adc6fee70bb5ecb91c56afe59dd0cdf7a92bf6fa75347a48fa91bcb35c", + "ff8143b7f3dd11e594ba558c0bc67116676376637971c03394d707a642f1fc25", + ], + [ + "d08d3be3602c52c45df91f7b8b16132f779e8336f88a4e6ad320874eaabf60a2", + "04dd9e5f7311cb8ba72487effab05b8ecbd89f3c7f2e18733d2bda22a4ebd00e", + "04dd9e5f7311cb8ba72487effab05b8ecbd89f3c7f2e18733d2bda22a4ebd00e", + "80c34dd01d6832dbb2589120b905f302c8a7adf5b7aac0418c9c6ebb0fad4e35", + ], + // The following key sets have a v_in_sqrt value of 0 + ] +} + +/// 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] { + [ + "e3457a03d99b91ab2860470a9501e03f30f4f91c9655c5d2700e43fc07262f3f", + "f73f18d545882268e7ca793f717d175acb8c628af8de8445b0c5a0c4cca7fab8", + "3e0b46dc90be39c8abc65231caabfa935ee055f83c4055b89fc189715c10c9fa", + "8d2a6143e68ad022f544e13a21954a0615eb544e072399f31a4535c6bb5a4343", + "42e01260c570a1be859ecce34b029acf600b7ef520e864a7c75cd122e4eb650b", + "270eece8fa7e73fd071c4e7deefcd4f58553fa2572ef9b750605a48dfea959c0", + "5f4dd2d5e3a4d3288a61b5f91f10e89b16ed1ae4496a06b15ae1b0fbd2b8c283", + "01f7ca332f44b4467e4b336c16fb59dae75e2a81eef1f54a3a6352ab7c8cdb3c", + "c3f3c1a6f5521a796f96889bbc36977eb9b80a479dee2213bdad4f5615a8877f", + "222a8a00c74e1386c5ce7817c26d63e9c4bad0cd4799275cda4f5ca098f8fe64", + "c54a7937d97d33c0782dcf5585db41f1bf69e0013bc0d8123e77468f1c0caec7", + "9088b66f4b128156c2a600d26ee9c5468d22505b061351c2578067a83184f9a5", + "7d5cc59a6b9ec4e0c5c8cf05e02809a77c3c21f78a035489c1f11defdceaf159", + "53aa1fa8d06555851e24b6a0827d591d37a8eb434661014844048375fd2c3447", + "d7781aae42b8d8b07146d92e575092ad94d7ab2d3a71f6303a3fee1436e61a29", + "8d470a34b3b9e6f12348d70a60fb2eaaf9e920bc9ca157bc65c51fc7294c6333", + ] +} diff --git a/curve25519-dalek/src/elligator2/legacy.rs b/curve25519-dalek/src/elligator2/legacy.rs new file mode 100644 index 00000000..7513e43d --- /dev/null +++ b/curve25519-dalek/src/elligator2/legacy.rs @@ -0,0 +1,230 @@ +use crate::{elligator2::*, MontgomeryPoint}; + +use hex::{self, FromHex}; + +#[test] +#[cfg(feature = "elligator2")] +fn repres_from_pubkey_kleshni() { + // testcases from kleshni + for (i, testcase) in encoding_testcases().iter().enumerate() { + // the testcases for kleshni have public key values encoded as montgomery + // points with high_y as the sign bit + let pubkey = <[u8; 32]>::from_hex(testcase.point).expect("failed to decode hex point"); + + let edw_point = MontgomeryPoint(pubkey) + .to_edwards(testcase.high_y as u8) + .expect("failed to convert point to edwards"); + let v_in_sqrt = v_in_sqrt_pubkey_edwards(&edw_point); + let repres = point_to_representative(&MontgomeryPoint(pubkey), v_in_sqrt.into()); + + if testcase.representative.is_some() { + let r = repres.expect("failed to get representative from point"); + + // make sure that we did in fact get a representative + assert_eq!( + testcase + .representative + .expect("checked, is some - this should never fail"), + hex::encode(r), + "[good case] kleshni ({i}) bad representative from privkey", + ); + + // make sure that the representative we got is the correct representative + let p = Legacy::from_representative(&r) + .expect("failed to get pubkey from valid representative"); + + // make sure that the public key derived from the representative matches + // the public key derived from the privatekey. + assert_eq!( + hex::encode(pubkey), + hex::encode(p.to_montgomery().to_bytes()), + "[good case] kleshni ({i}) pubkey from repres doesn't match pubkey from privkey" + ); + } else { + // We expect the provided private key NOT to map to a representative + assert!( + >::into(repres.is_none()), + "[good case] kleshni ({i}) expected none got repres {}", + hex::encode(repres.expect("this should not fail")) + ); + } + } +} + +#[test] +#[cfg(feature = "elligator2")] +fn pubkey_from_repres() { + // testcases from kleshni + for (i, testcase) in decoding_testcases().iter().enumerate() { + let repres = <[u8; 32]>::from_hex(testcase.representative) + .expect("failed to decode hex representative"); + + let point = MontgomeryPoint::map_to_point(&repres); + assert_eq!( + testcase.point, + hex::encode(point.to_bytes()), + "[good case] kleshni ({i}) bad representative from point" + ); + + let point_from_unbounded = MontgomeryPoint::from_representative::(&repres) + .expect("expected point, failed"); + assert_eq!( + testcase.non_lsr_point, + hex::encode(point_from_unbounded.to_bytes()), + "[good case] kleshni ({i}) bad representative from point" + ); + } +} + +const ENCODING_TESTS_COUNT: usize = 10; +struct EncodingTestCase { + /// sign value of the Montgomery point representation of the public key point + high_y: bool, + /// publkic key value, byte encoding of a Montgomery point + point: &'static str, + /// representative value associated with the provided point, if one exists. + representative: Option<&'static str>, +} + +/// In these testcases the `point` is the montgomery representation of the public +/// key value. We do not need the private key value to check these tests as we can +/// convert from public key to representative, and for some of the examples we may +/// not know what the associated private key would be as we manually swap the sign +/// ('high_y`) value for the public key point. +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", + ), + }, + // 0 with both "high_y" values + EncodingTestCase { + point: "0000000000000000000000000000000000000000000000000000000000000000", + high_y: false, + representative: Some( + "0000000000000000000000000000000000000000000000000000000000000000", + ), + }, + EncodingTestCase { + point: "0000000000000000000000000000000000000000000000000000000000000000", + high_y: true, + representative: Some( + "0000000000000000000000000000000000000000000000000000000000000000", + ), + }, + // A not encodable point with both "high_y" values + EncodingTestCase { + point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", + high_y: false, + representative: Some( + "d660db8cf212d31ce8c6f7139e69b9ac47fd81c7c0bfcb93e364b2d424e24813", + ), + }, + EncodingTestCase { + point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", + high_y: true, + representative: Some( + "489a2e0f6955e08f1ae6eb8dcdbc0f867a87a96a02d2dfd2aca21d8b536f0f1b", + ), + }, + // A not encodable point with both "high_y" values + EncodingTestCase { + point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", + high_y: false, + representative: Some( + "63d0d79e7f3c279cf4a0a5c3833fd85aa1f2c004c4e466f3a3844b3c2e06e410", + ), + }, + EncodingTestCase { + point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", + high_y: true, + representative: Some( + "0f03b41c86aeb49acf2f76b39cc90a55a0b140b7290f1c9e032591ddcb074537", + ), + }, + ] +} + +const DECODING_TESTS_COUNT: usize = 7; +struct DecodingTestCase { + representative: &'static str, + /// if we only allow least-square-root values as the representative and + /// clear the high order two bits (effectively) ensuring that the + /// representative value is less than `2^254 - 10`, this is the point + /// that we should receive. + point: &'static str, + /// if we allow unbounded values to be used directly as representatives, + /// not only least-square-root values, this is the point we should receive. + non_lsr_point: &'static str, +} + +fn decoding_testcases() -> [DecodingTestCase; DECODING_TESTS_COUNT] { + [ + // A small representative with false "high_y" property + DecodingTestCase { + representative: "e73507d38bae63992b3f57aac48c0abc14509589288457995a2b4ca3490aa207", + point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", + non_lsr_point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", + }, + // A small representative with true "high_y" property + DecodingTestCase { + representative: "95a16019041dbefed9832048ede11928d90365f24a38aa7aef1b97e23954101b", + point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", + non_lsr_point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", + }, + // The last representative returning true: (p - 1) / 2 + DecodingTestCase { + representative: "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + point: "9cdb525555555555555555555555555555555555555555555555555555555555", + non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", + }, + // The first representative returning false: (p + 1) / 2 + DecodingTestCase { + representative: "f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + point: "9cdb525555555555555555555555555555555555555555555555555555555555", + non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", + }, + // 0 + DecodingTestCase { + representative: "0000000000000000000000000000000000000000000000000000000000000000", + point: "0000000000000000000000000000000000000000000000000000000000000000", + non_lsr_point: "0000000000000000000000000000000000000000000000000000000000000000", + }, + // These two tests are not least-square-root representations. + + // A large representative with false "high_y" property + DecodingTestCase { + representative: "179f24730ded2ce3173908ec61964653b8027e383f40346c1c9b4d2bdb1db76c", + point: "e6e5355e0482e952cc951f13db26316ab111ae9edb58c45428a984ce7042d349", + non_lsr_point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", + }, + // A large representative with true "high_y" property + DecodingTestCase { + representative: "8a2f286180c3d8630b5f5a3c7cc027a55e0d3ffb3b1b990c5c7bb4c3d1f91b6f", + point: "27e222fec324b0293842a59a63b8201b0f97b1dd599ebcd478a896b7261aff3e", + non_lsr_point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", + }, + ] +} diff --git a/curve25519-dalek/src/elligator2/randomness.rs b/curve25519-dalek/src/elligator2/randomness.rs new file mode 100644 index 00000000..c28bcaaf --- /dev/null +++ b/curve25519-dalek/src/elligator2/randomness.rs @@ -0,0 +1,179 @@ +use super::*; + +use kolmogorov_smirnov as ks; +use rand::{thread_rng, RngCore}; +use rand_distr::{Binomial, Distribution}; +use std::vec::Vec; + +struct BitCounts { + counts: [[u64; 100]; 32 * 8], + entries: usize, +} + +impl BitCounts { + fn new() -> Self { + BitCounts { + counts: [[0u64; 100]; 256], + entries: 0, + } + } + fn entry(&mut self, arr: &[u8; 32]) { + for (i, arr_byte) in arr.iter().enumerate() { + for j in 0..8 { + self.counts[i * 8 + j][self.entries % 100] += ((arr_byte >> j) & 0x01) as u64; + } + } + self.entries += 1; + } + fn outliers(&self) -> Vec { + let mut rng = thread_rng(); + let binomial_100 = Binomial::new(100, 0.5).expect("failed to build binomial distribution"); + let expected_dist: [u64; 100] = binomial_100 + .sample_iter(&mut rng) + .take(100) + .collect::>() + .try_into() + .expect("failed to build example binomial expected distribution"); + // this is a high confidence, but we want to avoid this test failing + // due to statistical variability on repeat runs. + let confidence = 0.95; + let mut outlier_indices = vec![]; + + for (n, bit_trial) in self.counts.iter().enumerate() { + let result = ks::test(bit_trial, &expected_dist, confidence); + // require reject_probability == 1.0 to avoid statistical variability on re-runs + if result.is_rejected && result.reject_probability == 1.0 { + // samples definitely not from same distribution + outlier_indices.push(n); + println!( + "{n}, {} {} {} {}", + result.statistic, + result.reject_probability, + result.critical_value, + result.confidence + ); + if n == 255 { + println!("{:?}\n{:?}", bit_trial, expected_dist); + } + } + } + outlier_indices + } +} + +#[test] +/// If we use a "random" value for the tweak the high order bits will be +/// set such that the representative is: +/// 1) unchanged w.r.t. the public key value derived from the representative +/// i.e) it still derives the same public key value from the (clamped) +/// representative that we get from the private key. +/// 2) statistically indistinguishable from uniform random. +/// 4) computationally indistinguishable from random strings for the `a ?= sqrt(a^2)` test. +/// +/// (To see this test fail change `rng.next_u32() as u8` to `0u8`) +fn bitwise_entropy() { + const ITERATIONS: usize = 10000; + // number of iterations + let mut i = 0usize; + let mut rng = thread_rng(); + let mut privkey = [0u8; 32]; + + // count of occurences of a 1 per bit in the representative + let mut bitcounts = BitCounts::new(); + + while i < ITERATIONS { + rng.fill_bytes(&mut privkey); + let alice_representative = + match Randomized::to_representative(&privkey, rng.next_u32() as u8).into() { + None => continue, + Some(r) => r, + }; + + bitcounts.entry(&alice_representative); + + let pub_from_repr = + MontgomeryPoint::from_representative::(&alice_representative) + .expect("failed pub from repres"); + let pub_from_priv = Randomized::mul_base_clamped(privkey).to_montgomery(); + assert_eq!( + hex::encode(pub_from_priv.as_bytes()), + hex::encode(pub_from_repr.as_bytes()), + "failed pubkey match at iteration {i}" + ); + + i += 1; + } + + let outliers = bitcounts.outliers(); + assert!(outliers.is_empty(), "bad bits: {:?}", outliers); +} + +/// TLDR: The sqrt_ratio_i function is canonical so this library does not +/// suffer from the describbed computational distinguisher. +/// +/// The specific issue that this is testing for can be described as: +/// ```txt +/// An instantiation of Elligator is parameterized by what might be called +/// a “canonical” square root function, one with the property that +/// `√a2 = √(−a)2` for all field elements `a`. That is, we designate just +/// over half the field elements as “non-negative,” and the image of the +/// square root function consists of exactly those elements. A convenient +/// definition of “non-negative” for Curve25519, suggested by its authors, +/// is the lower half of the field, the elements `{0, 1, …, (q − 1) / 2}`. +/// When there are two options for a square root, take the smaller of the two. +/// ``` +/// +/// Any Elligator implementation that does not do this canonicalization of +/// the final square root, and instead it maps a given input systematically +/// to either its negative or non-negative root is vulnerable to the +/// following computational distinguisher. +/// +/// ```txt +/// [An adversary could] observe a representative, interpret it as a field +/// element, square it, then take the square root using the same +/// non-canonical square root algorithm. With representatives produced by +/// an affected version of [the elligator2 implementation], the output of +/// the square-then-root operation would always match the input. With +/// random strings, the output would match only half the time. +/// ``` +/// +/// For a more in-depth explanation see: +/// https://github.com/agl/ed25519/issues/27 +/// https://www.bamsoftware.com/papers/fep-flaws/ +#[test] +fn test_canonical() { + const ITERATIONS: usize = 10000; + // number of iterations + let mut i = 0usize; + let mut rng = thread_rng(); + let mut privkey = [0u8; 32]; + + // number of times the representative (interpreted as a point) squared, then square_rooted, + // equals the original representative. Should happen w/ 50% probability. + let mut squares_equal = 0usize; + + while i < ITERATIONS { + rng.fill_bytes(&mut privkey); + let alice_representative = match Randomized::to_representative(&privkey, 0u8).into() { + None => continue, + Some(r) => r, + }; + + if is_canonical(&alice_representative) { + squares_equal += 1; + } + i += 1; + } + + let expected_range = 4500..5500; // if truly binomial n=10000, p=0.5 then this should "always" pass (> 10x std dev) + assert!( + expected_range.contains(&squares_equal), + "squares_equal: {squares_equal} is not in [4500:5500]" + ); +} + +fn is_canonical(repres: &[u8; 32]) -> bool { + let r_fe = FieldElement::from_bytes(repres); + let (ok, r_fe_prime) = FieldElement::sqrt_ratio_i(&r_fe.square(), &FieldElement::ONE); + (r_fe.ct_eq(&r_fe_prime) & ok).into() +} diff --git a/curve25519-dalek/src/elligator2/rfc9380.rs b/curve25519-dalek/src/elligator2/rfc9380.rs new file mode 100644 index 00000000..ea623127 --- /dev/null +++ b/curve25519-dalek/src/elligator2/rfc9380.rs @@ -0,0 +1,442 @@ +use super::*; + +use hex::FromHex; +use std::string::String; + +#[test] +fn map_to_curve_test_go_ed25519_extra() { + for (i, testcase) in CURVE25519_ELL2.iter().enumerate() { + let u = testcase[0].must_from_be(); + let mut clamped = u; + clamped[31] &= 63; + + // map point to curve + let (q_x, _) = map_fe_to_montgomery(&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, testcase) in curve25519_XMD_SHA512_ELL2_NU.iter().enumerate() { + let u = FieldElement::from_bytes(&testcase.u_0.must_from_le()); + + // map point to curve + let (q_x, q_y) = map_fe_to_montgomery(&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, testcase) in curve25519_XMD_SHA512_ELL2_RO.iter().enumerate() { + 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_montgomery(&u0); + let (q1_x, q1_y) = map_fe_to_montgomery(&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, testcase) in edwards25519_XMD_SHA512_ELL2_NU.iter().enumerate() { + let u = FieldElement::from_bytes(&testcase.u_0.must_from_le()); + let (q_x, q_y) = map_fe_to_edwards(&u); + + // check resulting point + assert_eq!( + q_x.encode_le(), + testcase.Q_x, + "({i}) incorrect Q0_x edwards25519 NU\n{:?}", + testcase + ); + assert_eq!( + q_y.encode_le(), + testcase.Q_y, + "({i}) incorrect Q0_y edwards25519 NU\n{:?}", + testcase + ); + } + for (i, testcase) in edwards25519_XMD_SHA512_ELL2_RO.iter().enumerate() { + 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_edwards(&u0); + let (q1_x, q1_y) = map_fe_to_edwards(&u1); + + // check resulting points + assert_eq!( + q0_x.encode_le(), + testcase.Q0_x, + "({i}) incorrect Q0_x edwards25519 RO\n{:?}", + testcase + ); + assert_eq!( + q0_y.encode_le(), + testcase.Q0_y, + "({i}) incorrect Q0_y edwards25519 RO\n{:?}", + testcase + ); + assert_eq!( + q1_x.encode_le(), + testcase.Q1_x, + "({i}) incorrect Q1_x edwards25519 RO\n{:?}", + testcase + ); + assert_eq!( + q1_y.encode_le(), + testcase.Q1_y, + "({i}) incorrect Q1_y edwards25519 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: [[&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).expect("failed to unhex"); + u.reverse(); + u + } + fn must_from_be(&self) -> [u8; 32] { + <[u8; 32]>::from_hex(self).expect("failed to unhex from be") + } +} + +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; + b.reverse(); + hex::encode(b) + } + + fn encode_be(&self) -> String { + hex::encode(self) + } +} diff --git a/curve25519-dalek/src/elligator2/subgroup.rs b/curve25519-dalek/src/elligator2/subgroup.rs new file mode 100644 index 00000000..fb4d9652 --- /dev/null +++ b/curve25519-dalek/src/elligator2/subgroup.rs @@ -0,0 +1,133 @@ +use super::*; +use crate::{MontgomeryPoint, Scalar}; + +use rand::{thread_rng, Rng}; + +#[test] +#[cfg(feature = "elligator2")] +/// pubkey_subgroup_check2 tests that Elligator representatives produced by +/// NewKeypair map to public keys that are not always on the prime-order subgroup +/// of Curve25519. (And incidentally that Elligator representatives agree with +/// the public key stored in the Keypair.) +/// +/// See discussion under "Step 2" at https://elligator.org/key-exchange. +// We will test the public keys that comes out of NewKeypair by +// multiplying each one by L, the order of the prime-order subgroup of +// Curve25519, then checking the order of the resulting point. The error +// condition we are checking for specifically is output points always +// having order 1, which means that public keys are always on the +// prime-order subgroup of Curve25519, which would make Elligator +// representatives distinguishable from random. More generally, we want +// to ensure that all possible output points of low order are covered. +// +// We have to do some contortions to conform to the interfaces we use. +// We do scalar multiplication by L using Edwards coordinates, rather +// than the Montgomery coordinates output by Keypair.Public and +// Representative.ToPublic, because the Montgomery-based +// crypto/curve25519.X25519 clamps the scalar to be a multiple of 8, +// which would not allow us to use the scalar we need. The Edwards-based +// ScalarMult only accepts scalars that are strictly less than L; we +// work around this by multiplying the point by L - 1, then adding the +// point once to the product. +fn pubkey_subgroup_check() { + // This is the same as scMinusOne in filippo.io/edwards25519. + // https://github.com/FiloSottile/edwards25519/blob/v1.0.0/scalar.go#L34 + let scalar_order_minus1 = Scalar::from_canonical_bytes([ + 236_u8, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, + ]) + .unwrap(); + + // Returns a new edwards25519.Point that is v multiplied by the subgroup order. + let scalar_mult_order = |v: &EdwardsPoint| -> EdwardsPoint { + // v * (L - 1) + v => v * L + let p = v * scalar_order_minus1; + p + v + }; + + // Generates a new Keypair using, and returns the public key representative + // along, with its public key as a newly allocated edwards25519.Point. + let generate = || -> ([u8; 32], EdwardsPoint) { + for _ in 0..63 { + let y_sk = thread_rng().gen::<[u8; 32]>(); + let y_sk_tweak = thread_rng().gen::(); + + let y_repr_bytes = match Randomized::to_representative(&y_sk, y_sk_tweak).into() { + Some(r) => r, + None => continue, + }; + let y_pk = Randomized::mul_base_clamped(y_sk); + + assert_eq!( + MontgomeryPoint::from_representative::(&y_repr_bytes) + .expect("failed to re-derive point from representative"), + y_pk.to_montgomery() + ); + + return (y_repr_bytes, y_pk); + } + panic!("failed to generate a valid keypair"); + }; + + // These are all the points of low order that may result from + // multiplying an Elligator-mapped point by L. We will test that all of + // them are covered. + let low_order_points = [ + "0100000000000000000000000000000000000000000000000000000000000000", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000080", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + ]; + let mut counts = [0; 8]; + + // Assuming a uniform distribution of representatives, the probability + // that a specific low-order point will not be covered after n trials is + // (7/8)^n. The probability that *any* of the 8 low-order points will + // remain uncovered after n trials is at most 8 times that, 8*(7/8)^n. + // We must do at least log((1e-12)/8)/log(7/8) = 222.50 trials, in the + // worst case, to ensure a false error rate of less than 1 in a + // trillion. In practice, we keep track of the number of covered points + // and break the loop when it reaches 8, so when representatives are + // actually uniform we will usually run much fewer iterations. + let mut num_covered = 0; + for _ in 0..255 { + let (repr, pk) = generate(); + let v = scalar_mult_order(&pk); + + let b = v.compress().to_bytes(); + let b_str = hex::encode(b); + let index = match low_order_points.iter().position(|x| x == &b_str) { + Some(idx) => idx, + None => { + panic!( + "map({})*order yielded unexpected point {:}", + hex::encode(repr), + b_str + ); + } + }; + + counts[index] += 1; + if counts[index] == 1 { + // We just covered a new point for the first time. + num_covered += 1; + if num_covered == low_order_points.len() { + break; + } + } + } + let mut failed = false; + for count in counts.iter() { + if *count == 0 { + failed = true; + } + } + + if failed { + panic!("not all low order points were covered") + } +} diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index d0a3087e..59f40292 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -60,9 +60,6 @@ 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; @@ -246,64 +243,6 @@ impl MontgomeryPoint { CompressedEdwardsY(y_bytes).decompress() } - - #[cfg(feature = "elligator2")] - /// 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 unbounded variant does NOT assume that input values are always - /// going to be the least-square-root representation of the field element. - /// This is divergent from both the elligator2 specification and RFC9380, - /// however, some implementations miss this detail. This allows us to be - /// compatible with those alternate implementations if necessary, since the - /// resulting point will be different for inputs with either of the - /// high-order two bits set. - /// - /// The input u and output P are elements of the field F. Note that - /// the output P is a point on the Montgomery 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 Montgomery elliptic curve. - /// - /// See - pub fn map_to_point_unbounded(r: &[u8; 32]) -> MontgomeryPoint { - let r_0 = FieldElement::from_bytes(r); - let (p, _) = elligator2::map_fe_to_montgomery(&r_0); - MontgomeryPoint(p.as_bytes()) - } - - #[cfg(feature = "elligator2")] - /// 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. It is assumed that input values are always going to be the - /// least-square-root representation of the field element in allignment - /// with both the elligator2 specification and RFC9380. - /// - /// The input u and output P are elements of the field F. Note that - /// the output P is a point on the Montgomery 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 Montgomery elliptic curve. - /// - /// See - pub fn map_to_point(r: &[u8; 32]) -> MontgomeryPoint { - let mut clamped = *r; - clamped[31] &= elligator2::MASK_UNSET_BYTE; - let r_0 = FieldElement::from_bytes(&clamped); - let (p, _) = elligator2::map_fe_to_montgomery(&r_0); - MontgomeryPoint(p.as_bytes()) - } } /// A `ProjectivePoint` holds a point on the projective line @@ -664,7 +603,9 @@ mod test { } } + #[cfg(test)] #[cfg(feature = "alloc")] + #[cfg(feature = "elligator2")] const ELLIGATOR_CORRECT_OUTPUT: [u8; 32] = [ 0x5f, 0x35, 0x20, 0x00, 0x1c, 0x6c, 0x99, 0x36, 0xa3, 0x12, 0x06, 0xaf, 0xe7, 0xc7, 0xac, 0x22, 0x4e, 0x88, 0x61, 0x61, 0x9b, 0xf9, 0x88, 0x72, 0x44, 0x49, 0x15, 0x89, 0x9d, 0x95, @@ -673,7 +614,7 @@ mod test { #[test] #[cfg(feature = "alloc")] - #[cfg(feature = "elligator2")] + #[cfg(feature = "digest")] fn montgomery_elligator_correct() { let bytes: Vec = (0u8..32u8).collect(); let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken"); diff --git a/x25519-dalek/Cargo.toml b/x25519-dalek/Cargo.toml index 33712967..4169c55a 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", "elligator2"] +features = ["getrandom", "reusable_secrets", "serde", "static_secrets"] [dependencies] curve25519-dalek = { version = "4", path = "../curve25519-dalek", default-features = false } @@ -61,4 +61,3 @@ 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 244889e2..3b96f9cf 100644 --- a/x25519-dalek/src/x25519.rs +++ b/x25519-dalek/src/x25519.rs @@ -72,7 +72,7 @@ impl AsRef<[u8]> for PublicKey { /// secret is used at most once. #[cfg_attr(feature = "zeroize", derive(Zeroize))] #[cfg_attr(feature = "zeroize", zeroize(drop))] -pub struct EphemeralSecret(pub(crate) [u8; 32], pub(crate) u8); +pub struct EphemeralSecret(pub(crate) [u8; 32]); impl EphemeralSecret { /// Perform a Diffie-Hellman key agreement between `self` and @@ -94,10 +94,8 @@ impl EphemeralSecret { pub fn random_from_rng(mut csprng: T) -> Self { // The secret key is random bytes. Clamping is done later. let mut bytes = [0u8; 32]; - let mut tweak = [0u8; 1]; csprng.fill_bytes(&mut bytes); - csprng.fill_bytes(&mut tweak); - EphemeralSecret(bytes, tweak[0]) + EphemeralSecret(bytes) } /// Generate a new [`EphemeralSecret`]. @@ -136,7 +134,7 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey { #[cfg_attr(feature = "zeroize", derive(Zeroize))] #[cfg_attr(feature = "zeroize", zeroize(drop))] #[derive(Clone)] -pub struct ReusableSecret(pub(crate) [u8; 32], pub(crate) u8); +pub struct ReusableSecret(pub(crate) [u8; 32]); #[cfg(feature = "reusable_secrets")] impl ReusableSecret { @@ -159,10 +157,8 @@ impl ReusableSecret { pub fn random_from_rng(mut csprng: T) -> Self { // The secret key is random bytes. Clamping is done later. let mut bytes = [0u8; 32]; - let mut tweak = [0u8; 1]; csprng.fill_bytes(&mut bytes); - csprng.fill_bytes(&mut tweak); - ReusableSecret(bytes, tweak[0]) + ReusableSecret(bytes) } /// Generate a new [`ReusableSecret`]. @@ -199,7 +195,7 @@ impl<'a> From<&'a ReusableSecret> for PublicKey { #[cfg_attr(feature = "zeroize", derive(Zeroize))] #[cfg_attr(feature = "zeroize", zeroize(drop))] #[derive(Clone)] -pub struct StaticSecret([u8; 32], u8); +pub struct StaticSecret([u8; 32]); #[cfg(feature = "static_secrets")] impl StaticSecret { @@ -222,10 +218,8 @@ impl StaticSecret { pub fn random_from_rng(mut csprng: T) -> Self { // The secret key is random bytes. Clamping is done later. let mut bytes = [0u8; 32]; - let mut tweak = [0u8; 1]; csprng.fill_bytes(&mut bytes); - csprng.fill_bytes(&mut tweak); - StaticSecret(bytes, tweak[0]) + StaticSecret(bytes) } /// Generate a new [`StaticSecret`]. @@ -251,7 +245,7 @@ impl StaticSecret { impl From<[u8; 32]> for StaticSecret { /// Load a secret key from a byte array. fn from(bytes: [u8; 32]) -> StaticSecret { - StaticSecret(bytes, 0u8) + StaticSecret(bytes) } } @@ -381,138 +375,3 @@ 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, secret.1); - 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, secret.1); - 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, secret.1); - 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 = MontgomeryPoint::map_to_point(&representative.0); - PublicKey(point) - } -} -