Skip to content

Commit 7e95845

Browse files
committed
Add inverse Elligator2 mapping to Curve25519.
1 parent b61831b commit 7e95845

File tree

1 file changed

+165
-1
lines changed

1 file changed

+165
-1
lines changed

src/montgomery.rs

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,81 @@ pub(crate) fn elligator_encode(r_0: &FieldElement) -> MontgomeryPoint {
193193
MontgomeryPoint(u.to_bytes())
194194
}
195195

196+
/// Perform the inverse Elligator2 mapping from a Montgomery point to
197+
/// field element. This algorithm is based on [Elligator:
198+
/// Elliptic-curve points indistinguishable from uniform random
199+
/// strings][elligator], Section 5.3.
200+
///
201+
/// This function is a partial right inverse of `elligator_encode`: if
202+
/// `elligator_decode(&p, sign) == Some(fe)` then `elligator_encode(&fe)
203+
/// == p`.
204+
///
205+
/// This function does _not_ operate in constant time: if `point`
206+
/// cannot be mapped to a field element, the function exits early. In
207+
/// typical usage this function is combined with rejection sampling
208+
/// and called repeatedly until a mappable point is found.
209+
///
210+
/// Note: the output field elements of this function are uniformly
211+
/// distributed among the nonnegative field elements, but only if the
212+
/// input points are also uniformly distributed among all points of
213+
/// the curve. In particular, if the inputs are only selected from
214+
/// members of the prime order group, then the outputs are
215+
/// distinguishable from random.
216+
///
217+
/// # Inputs
218+
///
219+
/// * `point`: the \\(u\\)-coordinate of a point on the curve. Not all
220+
/// points map to field elements.
221+
///
222+
/// * `v_is_negative`: true if the \\(v\\)-coordinate of the point is negative.
223+
///
224+
/// # Returns
225+
///
226+
/// Either `None`, if the point couldn't be mapped, or `Some(fe)` such
227+
/// that `elligator_encode(&fe) == point`.
228+
///
229+
/// [elligator] https://elligator.cr.yp.to/elligator-20130828.pdf
230+
///
231+
#[allow(unused)]
232+
pub(crate) fn elligator_decode(point: &MontgomeryPoint, v_is_negative: Choice) -> Option<FieldElement> {
233+
let one = FieldElement::one();
234+
let u = FieldElement::from_bytes(&point.to_bytes());
235+
let u_plus_A = &u + &MONTGOMERY_A;
236+
let uu_plus_uA = &u * &u_plus_A;
237+
238+
// Condition: u is on the curve
239+
let vv = &(&u * &uu_plus_uA) + &u;
240+
let (u_is_on_curve, _v) = FieldElement::sqrt_ratio_i(&vv, &one);
241+
if (!bool::from(u_is_on_curve)) {
242+
return None
243+
}
244+
245+
// Condition: u != A
246+
if (u == MONTGOMERY_A_NEG) {
247+
return None
248+
}
249+
250+
// Condition: -2u(u+A) is a square
251+
let minus_uu2_minus_uA2 = -&(&uu_plus_uA + &uu_plus_uA);
252+
let (is_square, _) = FieldElement::sqrt_ratio_i(&minus_uu2_minus_uA2, &one);
253+
if (!bool::from(is_square)) {
254+
return None;
255+
}
256+
257+
let mut t1 = u;
258+
let mut t2 = u_plus_A;
259+
FieldElement::conditional_swap(&mut t1, &mut t2, v_is_negative);
260+
t1.negate();
261+
t2 = &t2 + &t2;
262+
263+
// if !v_is_negative: r = sqrt(-u / 2(u + a))
264+
// if v_is_negative: r = sqrt(-(u+A) / 2u)
265+
let (r_is_square, r) = FieldElement::sqrt_ratio_i(&t1, &t2);
266+
debug_assert!(bool::from(r_is_square));
267+
268+
return Some(r);
269+
}
270+
196271
/// A `ProjectivePoint` holds a point on the projective line
197272
/// \\( \mathbb P(\mathbb F\_p) \\), which we identify with the Kummer
198273
/// line of the Montgomery curve.
@@ -356,7 +431,7 @@ mod test {
356431
use constants;
357432
use core::convert::TryInto;
358433

359-
use rand_core::OsRng;
434+
use rand_core::{OsRng, RngCore};
360435

361436
#[test]
362437
fn identity_in_different_coordinates() {
@@ -469,11 +544,100 @@ mod test {
469544
assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
470545
}
471546

547+
#[test]
548+
#[cfg(feature = "std")] // Vec
549+
fn montgomery_elligator_decode_correct() {
550+
let bytes: std::vec::Vec<u8> = (0u8..32u8).collect();
551+
let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");
552+
553+
let fe = FieldElement::from_bytes(&bits_in);
554+
let eg = MontgomeryPoint(ELLIGATOR_CORRECT_OUTPUT);
555+
let result = elligator_decode(&eg, 0.into());
556+
assert_eq!(result, Some(fe));
557+
}
558+
559+
#[test]
560+
fn montgomery_elligator_encode_decode() {
561+
for _i in 0..4096 {
562+
let mut bits = [0u8; 32];
563+
OsRng.fill_bytes(&mut bits);
564+
565+
let fe = FieldElement::from_bytes(&bits);
566+
let eg = elligator_encode(&fe);
567+
568+
// Up to four different field values may encode to a
569+
// single MontgomeryPoint. Firstly, the MontgomeryPoint
570+
// loses the v-coordinate, so it may represent two
571+
// different curve points, (u, v) and (u, -v). The
572+
// elligator_decode function is given a sign argument to
573+
// indicate which one is meant.
574+
//
575+
// Second, for each curve point (except zero), two
576+
// distinct field elements encode to it, r and -r. The
577+
// elligator_decode function returns the nonnegative one.
578+
//
579+
// We check here that one of these four values is equal to
580+
// the original input to elligator_encode.
581+
582+
let mut found = false;
583+
584+
for i in 0..=1 {
585+
let decoded = elligator_decode(&eg, i.into())
586+
.expect("Elligator decode failed");
587+
for j in &[fe, -&fe] {
588+
found |= decoded == *j;
589+
}
590+
}
591+
592+
assert!(found);
593+
}
594+
}
595+
596+
#[test]
597+
fn montgomery_elligator_decode_encode() {
598+
for i in 0..4096 {
599+
let mut bits = [0u8; 32];
600+
OsRng.fill_bytes(&mut bits);
601+
602+
let point = MontgomeryPoint(bits);
603+
604+
let result = elligator_decode(&point, ((i % 2) as u8).into());
605+
606+
if let Some(fe) = result {
607+
let encoded = elligator_encode(&fe);
608+
609+
assert_eq!(encoded, point);
610+
}
611+
}
612+
}
613+
614+
/// Test that Elligator decoding will fail on a point that is not on the curve.
615+
#[test]
616+
fn montgomery_elligator_decode_noncurve() {
617+
let one = FieldElement::one();
618+
619+
// u = 2 corresponds to a point on the twist.
620+
let two = MontgomeryPoint((&one+&one).to_bytes());
621+
622+
for i in 0..=1 {
623+
let result = elligator_decode(&two, i.into());
624+
assert_eq!(None, result);
625+
}
626+
}
627+
472628
#[test]
473629
fn montgomery_elligator_zero_zero() {
474630
let zero = [0u8; 32];
475631
let fe = FieldElement::from_bytes(&zero);
476632
let eg = elligator_encode(&fe);
477633
assert_eq!(eg.to_bytes(), zero);
478634
}
635+
636+
#[test]
637+
fn montgomery_elligator_decode_zero_zero() {
638+
let zero = [0u8; 32];
639+
let eg = MontgomeryPoint(zero);
640+
let fe = elligator_decode(&eg, 0.into()).expect("Elligator decode failed");
641+
assert_eq!(fe.to_bytes(), zero);
642+
}
479643
}

0 commit comments

Comments
 (0)