use super::fe25519::Fe25519;
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
use subtle::Choice;
struct MontgomeryPointInternal {
x: Fe25519,
y: Fe25519,
}
fn elligator2(r: &Fe25519) -> (MontgomeryPointInternal, Choice) {
let a = Fe25519::CURVE25519_A;
let mut rr2 = r.sq2();
rr2 = rr2.add(&Fe25519::ONE);
rr2 = rr2.invert();
let mut x = a.mul(&rr2);
x = x.neg();
let x2 = x.square();
let x3 = x.mul(&x2);
let ax2 = a.mul(&x2);
let gx1 = x3.add(&x).add(&ax2);
let notsquare = gx1.is_not_square();
let negx = x.neg();
x.cmov(&negx, notsquare);
let mut x2_adj = Fe25519::ZERO;
x2_adj.cmov(&a, notsquare);
x = x.sub(&x2_adj);
let x2_new = x.square();
let x3_new = x.mul(&x2_new);
let ax2_new = a.mul(&x2_new);
let gy = x3_new.add(&x).add(&ax2_new);
let (sqrt_exists, y) = gy.sqrt();
debug_assert!(bool::from(sqrt_exists), "sqrt must exist at this point");
(MontgomeryPointInternal { x, y }, notsquare)
}
fn mont_to_ed(mont: &MontgomeryPointInternal) -> (Fe25519, Fe25519) {
let one = Fe25519::ONE;
let x_plus_one = mont.x.add(&one);
let x_minus_one = mont.x.sub(&one);
let x_plus_one_y = x_plus_one.mul(&mont.y);
let x_plus_one_y_inv = x_plus_one_y.invert();
let is_zero = x_plus_one_y_inv.is_zero();
let mut xed = mont.x.mul(&Fe25519::SQRT_AM2);
xed = xed.mul(&x_plus_one_y_inv);
xed = xed.mul(&x_plus_one);
let one_over_x_plus_one = x_plus_one_y_inv.mul(&mont.y);
let mut yed = one_over_x_plus_one.mul(&x_minus_one);
yed.cmov(&one, is_zero);
(xed, yed)
}
pub fn elligator2_to_edwards(input: &[u8; 32]) -> Option<EdwardsPoint> {
let mut s = *input;
s[31] &= 0x7f;
let r_fe = Fe25519::from_bytes(&s);
let (mont, _notsquare) = elligator2(&r_fe);
let (xed, yed) = mont_to_ed(&mont);
let final_xed = xed.abs();
let mut point_bytes = yed.to_bytes();
let xed_sign = bool::from(final_xed.is_negative()) as u8; point_bytes[31] |= xed_sign << 7;
let compressed_before_cofactor = CompressedEdwardsY(point_bytes);
let point_before_cofactor = compressed_before_cofactor.decompress()?;
let point_cleared = point_before_cofactor.mul_by_cofactor();
Some(point_cleared)
}
pub fn hash_to_curve_elligator2(uniform_bytes: &[u8; 32]) -> Option<EdwardsPoint> {
elligator2_to_edwards(uniform_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_elligator2_zero_input() {
let input = [0u8; 32];
let result = elligator2_to_edwards(&input);
assert!(result.is_some(), "Zero input should produce valid point");
}
#[test]
fn test_elligator2_nonzero_input() {
let mut input = [0u8; 32];
input[0] = 42;
let result = elligator2_to_edwards(&input);
assert!(result.is_some(), "Nonzero input should produce valid point");
}
#[test]
fn test_elligator2_sign_bit_cleared() {
let mut input1 = [0u8; 32];
input1[0] = 42;
input1[31] = 0x00;
let mut input2 = [0u8; 32];
input2[0] = 42;
input2[31] = 0x80;
let point1 = elligator2_to_edwards(&input1).unwrap();
let point2 = elligator2_to_edwards(&input2).unwrap();
assert_eq!(
point1.compress().as_bytes(),
point2.compress().as_bytes(),
"Inputs differing only in sign bit should produce same point (sign bit is cleared)"
);
}
#[test]
fn test_elligator2_deterministic() {
let mut input = [0u8; 32];
input[0] = 123;
input[15] = 45;
let point1 = elligator2_to_edwards(&input).unwrap();
let point2 = elligator2_to_edwards(&input).unwrap();
assert_eq!(
point1.compress().as_bytes(),
point2.compress().as_bytes(),
"Same input should produce same output"
);
}
}