Skip to content

Commit

Permalink
Add inverse Elligator2 mapping to Curve25519.
Browse files Browse the repository at this point in the history
  • Loading branch information
lealanko committed Jun 20, 2021
1 parent b61831b commit 7e95845
Showing 1 changed file with 165 additions and 1 deletion.
166 changes: 165 additions & 1 deletion src/montgomery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,81 @@ pub(crate) fn elligator_encode(r_0: &FieldElement) -> MontgomeryPoint {
MontgomeryPoint(u.to_bytes())
}

/// Perform the inverse Elligator2 mapping from a Montgomery point to
/// field element. This algorithm is based on [Elligator:
/// Elliptic-curve points indistinguishable from uniform random
/// strings][elligator], Section 5.3.
///
/// This function is a partial right inverse of `elligator_encode`: if
/// `elligator_decode(&p, sign) == Some(fe)` then `elligator_encode(&fe)
/// == p`.
///
/// This function does _not_ operate in constant time: if `point`
/// cannot be mapped to a field element, the function exits early. In
/// typical usage this function is combined with rejection sampling
/// and called repeatedly until a mappable point is found.
///
/// Note: the output field elements of this function are uniformly
/// distributed among the nonnegative field elements, but only if the
/// input points are also uniformly distributed among all points of
/// the curve. In particular, if the inputs are only selected from
/// members of the prime order group, then the outputs are
/// distinguishable from random.
///
/// # Inputs
///
/// * `point`: the \\(u\\)-coordinate of a point on the curve. Not all
/// points map to field elements.
///
/// * `v_is_negative`: true if the \\(v\\)-coordinate of the point is negative.
///
/// # Returns
///
/// Either `None`, if the point couldn't be mapped, or `Some(fe)` such
/// that `elligator_encode(&fe) == point`.
///
/// [elligator] https://elligator.cr.yp.to/elligator-20130828.pdf
///
#[allow(unused)]
pub(crate) fn elligator_decode(point: &MontgomeryPoint, v_is_negative: Choice) -> Option<FieldElement> {
let one = FieldElement::one();
let u = FieldElement::from_bytes(&point.to_bytes());
let u_plus_A = &u + &MONTGOMERY_A;
let uu_plus_uA = &u * &u_plus_A;

// Condition: u is on the curve
let vv = &(&u * &uu_plus_uA) + &u;
let (u_is_on_curve, _v) = FieldElement::sqrt_ratio_i(&vv, &one);
if (!bool::from(u_is_on_curve)) {
return None
}

// Condition: u != A
if (u == MONTGOMERY_A_NEG) {
return None
}

// Condition: -2u(u+A) is a square
let minus_uu2_minus_uA2 = -&(&uu_plus_uA + &uu_plus_uA);
let (is_square, _) = FieldElement::sqrt_ratio_i(&minus_uu2_minus_uA2, &one);
if (!bool::from(is_square)) {
return None;
}

let mut t1 = u;
let mut t2 = u_plus_A;
FieldElement::conditional_swap(&mut t1, &mut t2, v_is_negative);
t1.negate();
t2 = &t2 + &t2;

// if !v_is_negative: r = sqrt(-u / 2(u + a))
// if v_is_negative: r = sqrt(-(u+A) / 2u)
let (r_is_square, r) = FieldElement::sqrt_ratio_i(&t1, &t2);
debug_assert!(bool::from(r_is_square));

return Some(r);
}

/// A `ProjectivePoint` holds a point on the projective line
/// \\( \mathbb P(\mathbb F\_p) \\), which we identify with the Kummer
/// line of the Montgomery curve.
Expand Down Expand Up @@ -356,7 +431,7 @@ mod test {
use constants;
use core::convert::TryInto;

use rand_core::OsRng;
use rand_core::{OsRng, RngCore};

#[test]
fn identity_in_different_coordinates() {
Expand Down Expand Up @@ -469,11 +544,100 @@ mod test {
assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
}

#[test]
#[cfg(feature = "std")] // Vec
fn montgomery_elligator_decode_correct() {
let bytes: std::vec::Vec<u8> = (0u8..32u8).collect();
let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");

let fe = FieldElement::from_bytes(&bits_in);
let eg = MontgomeryPoint(ELLIGATOR_CORRECT_OUTPUT);
let result = elligator_decode(&eg, 0.into());
assert_eq!(result, Some(fe));
}

#[test]
fn montgomery_elligator_encode_decode() {
for _i in 0..4096 {
let mut bits = [0u8; 32];
OsRng.fill_bytes(&mut bits);

let fe = FieldElement::from_bytes(&bits);
let eg = elligator_encode(&fe);

// Up to four different field values may encode to a
// single MontgomeryPoint. Firstly, the MontgomeryPoint
// loses the v-coordinate, so it may represent two
// different curve points, (u, v) and (u, -v). The
// elligator_decode function is given a sign argument to
// indicate which one is meant.
//
// Second, for each curve point (except zero), two
// distinct field elements encode to it, r and -r. The
// elligator_decode function returns the nonnegative one.
//
// We check here that one of these four values is equal to
// the original input to elligator_encode.

let mut found = false;

for i in 0..=1 {
let decoded = elligator_decode(&eg, i.into())
.expect("Elligator decode failed");
for j in &[fe, -&fe] {
found |= decoded == *j;
}
}

assert!(found);
}
}

#[test]
fn montgomery_elligator_decode_encode() {
for i in 0..4096 {
let mut bits = [0u8; 32];
OsRng.fill_bytes(&mut bits);

let point = MontgomeryPoint(bits);

let result = elligator_decode(&point, ((i % 2) as u8).into());

if let Some(fe) = result {
let encoded = elligator_encode(&fe);

assert_eq!(encoded, point);
}
}
}

/// Test that Elligator decoding will fail on a point that is not on the curve.
#[test]
fn montgomery_elligator_decode_noncurve() {
let one = FieldElement::one();

// u = 2 corresponds to a point on the twist.
let two = MontgomeryPoint((&one+&one).to_bytes());

for i in 0..=1 {
let result = elligator_decode(&two, i.into());
assert_eq!(None, result);
}
}

#[test]
fn montgomery_elligator_zero_zero() {
let zero = [0u8; 32];
let fe = FieldElement::from_bytes(&zero);
let eg = elligator_encode(&fe);
assert_eq!(eg.to_bytes(), zero);
}

#[test]
fn montgomery_elligator_decode_zero_zero() {
let zero = [0u8; 32];
let eg = MontgomeryPoint(zero);
let fe = elligator_decode(&eg, 0.into()).expect("Elligator decode failed");
assert_eq!(fe.to_bytes(), zero);
}
}

0 comments on commit 7e95845

Please sign in to comment.