Skip to content

Remove Legacy Elligator2 Implementation #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: elligator2-ntor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions curve25519-dalek/src/backend/serial/fiat_u32/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,24 +268,4 @@ 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)
}
}
20 changes: 0 additions & 20 deletions curve25519-dalek/src/backend/serial/fiat_u64/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,24 +259,4 @@ 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)
}
}
20 changes: 0 additions & 20 deletions curve25519-dalek/src/backend/serial/u32/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,24 +601,4 @@ impl FieldElement2625 {
}
FieldElement2625::reduce(coeffs)
}

/// Returns 1 if self is greater than the other and 0 otherwise
// implementation based on C libgmp -> mpn_sub_n
pub(crate) fn gt(&self, other: &Self) -> Choice {
let mut _ul = 0_u32;
let mut _vl = 0_u32;
let mut _rl = 0_u32;

let mut cy = 0_u32;
for i in 0..10 {
_ul = self.0[i];
_vl = other.0[i];

let (_sl, _cy1) = _ul.overflowing_sub(_vl);
let (_rl, _cy2) = _sl.overflowing_sub(cy);
cy = _cy1 as u32 | _cy2 as u32;
}

Choice::from((cy != 0_u32) as u8)
}
}
20 changes: 0 additions & 20 deletions curve25519-dalek/src/backend/serial/u64/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,24 +572,4 @@ impl FieldElement51 {

square
}

/// Returns 1 if self is greater than the other and 0 otherwise
// implementation based on C libgmp -> mpn_sub_n
pub(crate) fn gt(&self, other: &Self) -> Choice {
let mut _ul = 0_u64;
let mut _vl = 0_u64;
let mut _rl = 0_u64;

let mut cy = 0_u64;
for i in 0..5 {
_ul = self.0[i];
_vl = other.0[i];

let (_sl, _cy1) = _ul.overflowing_sub(_vl);
let (_rl, _cy2) = _sl.overflowing_sub(cy);
cy = _cy1 as u64 | _cy2 as u64;
}

Choice::from((cy != 0_u64) as u8)
}
}
30 changes: 27 additions & 3 deletions curve25519-dalek/src/edwards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ impl EdwardsPoint {
where
D: Digest<OutputSize = U64> + Default,
{
use crate::elligator2::Legacy;
use crate::elligator2::RFC9380;

let mut hash = D::new();
hash.update(bytes);
Expand All @@ -601,7 +601,7 @@ impl EdwardsPoint {
// 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::<Legacy>(&res).unwrap();
let fe1 = MontgomeryPoint::from_representative::<RFC9380>(&res).unwrap();
let E1_opt = fe1.to_edwards(sign_bit);

E1_opt
Expand Down Expand Up @@ -2276,17 +2276,29 @@ mod test {
"f06fc939bc10551a0fd415aebf107ef0b9c4ee1ef9a164157bdd089127782617",
"785b2a6a00a5579cc9da1ff997ce8339b6f9fb46c6f10cf7a12ff2986341a6e0",
],
// Non Least-Square-Root representative values. (i.e. representative > 2^254-10 )
]
}

#[cfg(all(feature = "alloc", feature = "digest"))]
fn test_vectors_non_lsr() -> Vec<Vec<&'static str>> {
// Non Least-Square-Root representative values. (i.e. representative > 2^254-10 )
vec![
vec![
// input
"84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9",
// output
"aaa72cb973e1a958646d3b11a0d7b03642972ac4306361e137eddf2dd1e935a6",
// non-least square representative (i.e. legacy implementation) here for reference
"93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453",
],
vec![
"48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c",
"80c8813513cd260d5438188f17d5490b5052b465b259d6741ae36b9e137d9d24",
"ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966",
],
vec![
"80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095",
"027edfc3be233b12ccfe57be905ec1cc1af97dcd5543e6d49f8f4d74a2dc4324",
"57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47",
],
]
Expand All @@ -2307,5 +2319,17 @@ mod test {
"signal map_to_curve failed for test {n}"
);
}

for (n, vector) in test_vectors_non_lsr().iter().enumerate() {
let input = hex::decode(vector[0]).expect("failed to decode hex input");
let output = hex::decode(vector[1]).expect("failed to decode hex output");

let point = EdwardsPoint::nonspec_map_to_curve::<sha2::Sha512>(&input);
assert_eq!(
hex::encode(point.compress().to_bytes()),
hex::encode(&output[..]),
"signal map_to_curve failed for test {n}"
);
}
}
}
99 changes: 30 additions & 69 deletions curve25519-dalek/src/elligator2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! ## Usage
//!
//! ```rust ignore
//! ```rust no_run
//! use rand::RngCore;
//! use curve25519_dalek::elligator2::{RFC9380, MapToPointVariant};
//!
Expand Down Expand Up @@ -33,23 +33,39 @@
//! The elligator2 transforms can also be applied to [`MontgomeryPoint`] and
//! [`EdwardsPoint`] objects themselves.
//!
//! ```rust
//! ```rust no_run
//! # use hex::FromHex;
//! 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 mont_point = MontgomeryPoint::default(); // example private key point known to be representable
//! let r = mont_point.to_representative::<RFC9380>(tweak).unwrap();
//! let pubkey = RFC9380::mul_base_clamped(mont_point.to_bytes()).to_montgomery();
//!
//! let derived_pubkey = MontgomeryPoint::from_representative::<RFC9380>(&r).unwrap();
//! assert_eq!(pubkey, derived_pubkey);
//!
//! // Points in byte format can be transformed
//!
//! // (example known representable point)
//! let mut point = <[u8;32]>::from_hex("00deadbeef00deadbeef00deadbeef00deadbeef00deadbeef00deadbeef0124").unwrap();
//! let tweak = rand::thread_rng().next_u32() as u8;
//! let r = RFC9380::to_representative(&point, tweak).unwrap();
//! let pubkey = RFC9380::mul_base_clamped(point).to_montgomery();
//!
//! _ = MontgomeryPoint::from_representative::<RFC9380>(&r).unwrap();
//! let derived_pubkey = MontgomeryPoint::from_representative::<RFC9380>(&r).unwrap();
//! assert_eq!(pubkey, derived_pubkey);
//!
//! // Edwards Points can be transformed as well.
//! // Edwards Points can be transformed as well. This example uses the Randomized variant.
//! let edw_point = EdwardsPoint::default(); // example point known to be representable
//! let r = edw_point.to_representative::<Randomized>(tweak).unwrap();
//! let pubkey = Randomized::mul_base_clamped(edw_point.to_montgomery().to_bytes());
//!
//! _ = EdwardsPoint::from_representative::<Randomized>(&r).unwrap();
//! let derived_pubkey = EdwardsPoint::from_representative::<Randomized>(&r).unwrap();
//! assert_eq!(pubkey, derived_pubkey);
//! ```
//!
//! ### Generating Representable Points.
Expand Down Expand Up @@ -130,22 +146,15 @@ use crate::montgomery::MontgomeryPoint;
use crate::EdwardsPoint;

use cfg_if::cfg_if;
use subtle::{
Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater,
CtOption,
};
use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption};

/// bitmask for a single byte when clearing the high order two bits of a representative
pub(crate) const MASK_UNSET_BYTE: u8 = 0x3f;
/// bitmask for a single byte when setting the high order two bits of a representative
pub(crate) const MASK_SET_BYTE: u8 = 0xc0;

/// (p - 1) / 2 = 2^254 - 10
pub(crate) const DIVIDE_MINUS_P_1_2_BYTES: [u8; 32] = [
0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
];

///
/// Common interface for the different ways to compute the elligator2 forward
/// and reverse transformations.
pub trait MapToPointVariant {
Expand Down Expand Up @@ -230,49 +239,6 @@ impl MapToPointVariant for Randomized {
}
}

#[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<EdwardsPoint> {
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
// ===========================================================================
Expand Down Expand Up @@ -559,8 +525,6 @@ 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 a_neg = -a;
Expand All @@ -583,7 +547,9 @@ pub(crate) fn point_to_representative(
let mut b = FieldElement::conditional_select(&r1, &r0, Choice::from(v_in_sqrt as u8));

// If root > (p - 1) / 2, root := -root
let negate = divide_minus_p_1_2.ct_gt(&b);
// let negate = divide_minus_p_1_2.ct_gt(&b);
// let negate = divide_minus_p_1_2.mpn_sub_n(&b);
let negate = b.is_negative();
FieldElement::conditional_negate(&mut b, negate);

CtOption::new(b.as_bytes(), is_encodable)
Expand Down Expand Up @@ -659,8 +625,6 @@ pub(crate) 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(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))
let (_, sqrt_minus_a_plus_2) = FieldElement::sqrt_ratio_i(
&(&MONTGOMERY_A_NEG - &(&FieldElement::ONE + &FieldElement::ONE)),
Expand All @@ -677,7 +641,9 @@ pub(crate) fn v_in_sqrt_pubkey_edwards(pubkey: &EdwardsPoint) -> Choice {
let v = &(&t0 * &inv1) * &(&pubkey.Z * &sqrt_minus_a_plus_2);

// is v <= (q-1)/2 ?
divide_minus_p_1_2.ct_gt(&v)
// divide_minus_p_1_2.ct_gt(&v)
// divide_minus_p_1_2.mpn_sub_n(&v)
!v.is_negative()
}

// ============================================================================
Expand Down Expand Up @@ -779,8 +745,3 @@ mod randomness;
#[cfg(test)]
#[cfg(feature = "elligator2")]
mod subgroup;

#[cfg(test)]
#[cfg(feature = "elligator2")]
#[cfg(feature = "digest")]
mod legacy;
15 changes: 14 additions & 1 deletion curve25519-dalek/src/elligator2/compatibility.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
use super::*;

use hex::FromHex;

////////////////////////////////////////////////////////////
// Ntor tests //
////////////////////////////////////////////////////////////

#[test]
#[cfg(feature = "elligator2")]
fn ident_is_representable() {
let m = MontgomeryPoint::default();
let repres = m
.to_representative::<RFC9380>(0u8)
.expect("should succeed, I think");
let pubkey = RFC9380::mul_base_clamped(m.to_bytes()).to_montgomery();

// let orig = MontgomeryPoint::from_representative::<RFC9380>(&out).expect("should prob also work");
let orig = MontgomeryPoint::map_to_point(&repres);
assert_eq!(&pubkey, &orig);
}

#[test]
#[cfg(feature = "elligator2")]
fn pubkey_from_repres() {
Expand Down
Loading
Loading