Skip to content

Commit

Permalink
elligator
Browse files Browse the repository at this point in the history
  • Loading branch information
vincenthz committed Sep 9, 2024
1 parent 376c73a commit bcdf30a
Show file tree
Hide file tree
Showing 5 changed files with 475 additions and 5 deletions.
32 changes: 32 additions & 0 deletions src/curve25519/fe/fe64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ impl Fe {
0x2B8324804FC1D,
]);

/// Field element constant for -1.
pub(crate) const M1: Fe = Fe([
2251799813685228,
2251799813685247,
2251799813685247,
2251799813685247,
2251799813685247,
]);

/// Field element constant for D
pub const D: Fe = Fe([
0x34DCA135978A3,
Expand All @@ -68,6 +77,18 @@ impl Fe {
0x6738CC7407977,
0x2406D9DC56DFF,
]);

/// Field element for Montgomery curve value 486662
pub(crate) const MONTGOMERY_A: Fe = Fe([486662, 0, 0, 0, 0]);

/// Field element for Montgomery curve value -486662
pub(crate) const MONTGOMERY_A_NEG: Fe = Fe([
2251799813198567,
2251799813685247,
2251799813685247,
2251799813685247,
2251799813685247,
]);
}

#[inline]
Expand Down Expand Up @@ -394,6 +415,11 @@ impl Fe {
CtEqual::ct_ne(&self.to_bytes(), &[0; 32]).into()
}

/// Check that the field element is 'negative'
pub fn is_negative_ct(&self) -> Choice {
(self.to_packed()[0] & 1).ct_ne(0)
}

/// Check that the field element is 'negative'
pub fn is_negative(&self) -> bool {
(self.to_packed()[0] & 1) != 0
Expand All @@ -406,6 +432,12 @@ impl Fe {
pub(crate) fn maybe_set(&mut self, rhs: &Fe, do_swap: Choice) {
ct_array64_maybe_set(&mut self.0, &rhs.0, do_swap);
}

pub(crate) fn choice(self, other: Self, choice: Choice) -> Self {
let mut ret = self.0.clone();
ct_array64_maybe_set(&mut ret, &other.0, choice);
Self(ret)
}
}

#[cfg(test)]
Expand Down
57 changes: 57 additions & 0 deletions src/curve25519/fe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,37 @@ pub use fe32::*;
#[cfg(not(any(any(target_arch = "arm"), feature = "force-32bits")))]
pub use fe64::*;

use crate::constant_time::{Choice, CtEqual};

impl Fe {
/// Raise a field element to (p-5) / 8 = 2^252-3
pub fn pow_252_3(&self) -> Fe {
let z1 = self;
let z2 = z1.square();
let z8 = z2.square_repeatdly(2);
let z9 = z1 * &z8;
let z11 = &z2 * &z9;
let z22 = z11.square();
let z_5_0 = &z9 * &z22;
let z_10_5 = z_5_0.square_repeatdly(5);
let z_10_0 = &z_10_5 * &z_5_0;
let z_20_10 = z_10_0.square_repeatdly(10);
let z_20_0 = &z_20_10 * &z_10_0;
let z_40_20 = z_20_0.square_repeatdly(20);
let z_40_0 = &z_40_20 * &z_20_0;
let z_50_10 = z_40_0.square_repeatdly(10);
let z_50_0 = &z_50_10 * &z_10_0;
let z_100_50 = z_50_0.square_repeatdly(50);
let z_100_0 = &z_100_50 * &z_50_0;
let z_200_100 = z_100_0.square_repeatdly(100);
let z_200_0 = &z_200_100 * &z_100_0;
let z_250_50 = z_200_0.square_repeatdly(50);
let z_250_0 = &z_250_50 * &z_50_0;
let z_250_2 = z_250_0.square_repeatdly(2);
let z_252_0 = &z_250_2 * &z1;
z_252_0
}

/// Raise a field element to 2^255-23
pub fn pow25523(&self) -> Fe {
let z2 = self.square();
Expand Down Expand Up @@ -83,6 +113,33 @@ impl Fe {

z_255_21
}

pub(crate) fn sqrt_ratio_i(u: &Self, v: &Self) -> (Choice, Fe) {
let v3 = &v.square() * &v;
let v7 = &v3.square() * &v;
let mut r = &(u * &v3) * &(u * &v7).pow_252_3();
let check = v * &r.square();

let i = &Fe::SQRTM1;

let correct_sign_sqrt = check.ct_eq(u);
let flipped_sign_sqrt = check.ct_eq(&(-u));
let flipped_sign_sqrt_i = check.ct_eq(&(&(-u) * i));

let r_prime = &Fe::SQRTM1 * &r;
Fe::maybe_set(&mut r, &r_prime, flipped_sign_sqrt | flipped_sign_sqrt_i);

// Choose the nonnegative square root.
let r_is_negative = r.is_negative_ct();
let mut r_neg = r.clone();
r_neg.negate_mut();

Fe::maybe_set(&mut r, &r_neg, r_is_negative);

let was_nonzero_square = correct_sign_sqrt | flipped_sign_sqrt;

(was_nonzero_square, r)
}
}

#[cfg(test)]
Expand Down
142 changes: 141 additions & 1 deletion src/curve25519/ge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,46 @@ impl GeAffine {
}

pub fn from_bytes(s: &[u8; 32]) -> Option<Self> {
// See RFC8032 5.3.1 decoding process
// See RFC8032 5.1.3 decoding process
//
// y (255 bits) | sign(x) (1 bit) = s
// let u = y^2 - 1
// v = d * y^2 + 1
// x = u * v^3 * (u * v^7)^((p-5)/8)

// recover x_sign from the bytes by reading the 255 bits
let x_0 = s[31] >> 7;
// recover y by clearing the highest bit (side effect of from_bytes)
let y = Fe::from_bytes(s);

// recover x
let y2 = y.square();
let u = &y2 - &Fe::ONE;
let v = &(&y2 * &Fe::D) + &Fe::ONE;

let (is_valid_y_coord, mut x) = Fe::sqrt_ratio_i(&u, &v);

if is_valid_y_coord.is_false() {
return None;
}

let sign_bit = x_0.ct_eq(1);

let mut x_neg = x.clone();
x_neg.negate_mut();

x.maybe_set(&x_neg, sign_bit);

let Self { x: x2, y: y2 } = Self::from_bytes2(s).unwrap();

//assert_eq!(y.to_bytes(), y2.to_bytes(), "y mismatch");
//assert_eq!(x.to_bytes(), x2.to_bytes(), "x mismatch");

Some(Self { x: x2, y: y2 })
}

pub fn from_bytes2(s: &[u8; 32]) -> Option<Self> {
// See RFC8032 5.1.3 decoding process
//
// y (255 bits) | sign(x) (1 bit) = s
// let u = y^2 - 1
Expand Down Expand Up @@ -374,6 +413,84 @@ impl Ge {

h
}

fn mul_cofactor(self) -> Self {
let mut r: Self;
let mut s = self.to_partial();
for _ in 0..2 {
r = s.double_full();
s = r.to_partial();
}
s.double_full()
}

fn from_raw_hash(hash: &[u8; 32]) -> Option<Self> {
let fe = Fe::from_bytes(hash);
std::println!("fe {:x?}", fe.to_bytes());
let m1 = elligator_encode(&fe);
std::println!("m1 {:x?}", m1.0);
let e = m1.to_edwards(0);
std::println!("e: {:x?}", e.clone().map(|x| x.to_bytes()));
e.map(|p| p.mul_cofactor())
}

pub fn from_hash<H: FnOnce(&[u8]) -> [u8; 64]>(bytes: &[u8], hash: H) -> Option<Self> {
use core::convert::TryFrom;
let h = hash(bytes);
let h32 = <&[u8; 32]>::try_from(&h[0..32]).unwrap();
Self::from_raw_hash(h32)
}
}

pub struct MontgomeryPoint([u8; 32]);

impl MontgomeryPoint {
fn to_edwards(&self, sign: u8) -> Option<Ge> {
let u = Fe::from_bytes(&self.0);
if u == Fe::M1 {
return None;
}
let y = &(&u - &Fe::ONE) * &(&u + &Fe::ONE).invert();

let mut y_bytes = y.to_bytes();
println!("y_bytes[31] {:b}", &y_bytes[31]);
y_bytes[31] ^= sign << 7;
println!("y_bytes[31] {:b}", &y_bytes[31]);
println!("y_bytes {:x?}", &y_bytes);

Ge::from_bytes(&y_bytes)
}
}

/// Get the Elligator2 mapping to a MontgomeryPoint
///
/// <https://datatracker.ietf.org/doc/html/rfc9380#section-6.7.1>
fn elligator_encode(r_0: &Fe) -> MontgomeryPoint {
let r_0_sq = r_0.square();
let r_0_sq_2 = &r_0_sq + &r_0_sq;
let d_1 = &Fe::ONE + &r_0_sq_2;

let d = &Fe::MONTGOMERY_A_NEG * &(d_1.invert()); /* A/(1+2r^2) */
std::println!("d {:x?}", d.to_bytes());

let d_sq = &d.square();
let au = &Fe::MONTGOMERY_A * &d;

let inner = &(d_sq + &au) + &Fe::ONE;
let eps = &d * &inner; /* eps = d^3 + Ad^2 + d */

std::println!("eps {:x?}", eps.to_bytes());

let (eps_is_sq, _eps) = Fe::sqrt_ratio_i(&eps, &Fe::ONE);

let a_or_zero = Fe::choice(Fe::MONTGOMERY_A, Fe::ZERO, eps_is_sq); /* 0, or A if nonsquare*/
let mut u = &d + &a_or_zero;
let mut u_neg = u.clone();
u_neg.negate_mut();

Fe::maybe_set(&mut u, &u_neg, eps_is_sq.negate()); /* d, or -d-A if nonsquare */

MontgomeryPoint(u.to_bytes())
}

impl Add<&GeCached> for &Ge {
Expand Down Expand Up @@ -530,3 +647,26 @@ impl GePrecomp {
t
}
}

#[cfg(test)]
mod tests {
use super::Ge;
use crate::hashing::sha512;

#[test]
fn elligator() {
let input = [
0x21, 0x4f, 0x30, 0x6e, 0x15, 0x76, 0xf5, 0xa7, 0x57, 0x76, 0x36, 0xfe, 0x30, 0x3c,
0xa2, 0xc6, 0x25, 0xb5, 0x33, 0x31, 0x9f, 0x52, 0x44, 0x2b, 0x22, 0xa9, 0xfa, 0x3b,
0x7e, 0xde, 0x80, 0x9f,
];
let output = [
0xc9, 0x5b, 0xec, 0xf0, 0xf9, 0x35, 0x95, 0x17, 0x46, 0x33, 0xb9, 0xd4, 0xd6, 0xbb,
0xbe, 0xb8, 0x8e, 0x16, 0xfa, 0x25, 0x71, 0x76, 0xf8, 0x77, 0xce, 0x42, 0x6e, 0x14,
0x24, 0x62, 0x60, 0xd2,
];
let point = Ge::from_hash(&input, |v| sha512(v)).unwrap();
let output_got = point.to_bytes();
assert_eq!(output, output_got);
}
}
Loading

0 comments on commit bcdf30a

Please sign in to comment.