Add project files.

This commit is contained in:
micro
2026-06-20 09:31:59 -05:00
parent 9bb3f00d86
commit 4baa4ce8c0
346 changed files with 55751 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
//! Helper functions for use with Lizard
#![allow(non_snake_case)]
use subtle::Choice;
use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable;
use subtle::ConstantTimeEq;
use super::lizard_constants;
use crate::constants;
use crate::field::FieldElement;
/// Represents a point (s,t) on the the Jacobi quartic associated
/// to the Edwards curve.
#[derive(Copy, Clone)]
#[allow(missing_docs)]
pub struct JacobiPoint {
pub S: FieldElement,
pub T: FieldElement,
}
impl JacobiPoint {
/// Elligator2 is defined in two steps: first a field element is converted
/// to a point (s,t) on the Jacobi quartic associated to the Edwards curve.
/// Then this point is mapped to a point on the Edwards curve.
/// This function computes a field element that is mapped to a given (s,t)
/// with Elligator2 if it exists.
pub(crate) fn elligator_inv(&self) -> (Choice, FieldElement) {
let mut out = FieldElement::ZERO;
// Special case: s = 0. If s is zero, either t = 1 or t = -1.
// If t=1, then sqrt(i*d) is the preimage. Otherwise it's 0.
let s_is_zero = self.S.is_zero();
let t_equals_one = self.T.ct_eq(&FieldElement::ONE);
out.conditional_assign(&lizard_constants::SQRT_ID, t_equals_one);
let mut ret = s_is_zero;
let mut done = s_is_zero;
// a := (t+1) (d+1)/(d-1)
let a = &(&self.T + &FieldElement::ONE) * &lizard_constants::DP1_OVER_DM1;
let a2 = a.square();
// y := 1/sqrt(i (s^4 - a^2)).
let s2 = self.S.square();
let s4 = s2.square();
let invSqY = &(&s4 - &a2) * &constants::SQRT_M1;
// There is no preimage if the square root of i*(s^4-a^2) does not exist.
let (sq, y) = invSqY.invsqrt();
ret |= sq;
done |= !sq;
// x := (a + sign(s)*s^2) y
let mut pms2 = s2;
pms2.conditional_negate(self.S.is_negative());
let mut x = &(&a + &pms2) * &y;
let x_is_negative = x.is_negative();
x.conditional_negate(x_is_negative);
out.conditional_assign(&x, !done);
(ret, out)
}
pub(crate) fn dual(&self) -> JacobiPoint {
JacobiPoint {
S: -(&self.S),
T: -(&self.T),
}
}
}
+49
View File
@@ -0,0 +1,49 @@
//! Constants for use in Lizard
//!
//! Could be moved into backend/serial/u??/constants.rs
#[cfg(curve25519_dalek_bits = "64")]
pub(crate) use super::u64_constants::*;
#[cfg(curve25519_dalek_bits = "32")]
pub(crate) use super::u32_constants::*;
// ------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------
#[cfg(test)]
mod test {
use super::*;
use crate::constants;
use crate::field::FieldElement;
#[test]
fn test_lizard_constants() {
let (_, sqrt_id) = FieldElement::sqrt_ratio_i(
&(&constants::SQRT_M1 * &constants::EDWARDS_D),
&FieldElement::ONE,
);
assert_eq!(sqrt_id, SQRT_ID);
assert_eq!(
&(&constants::EDWARDS_D + &FieldElement::ONE)
* &(&constants::EDWARDS_D - &FieldElement::ONE).invert(),
DP1_OVER_DM1
);
assert_eq!(
MDOUBLE_INVSQRT_A_MINUS_D,
-&(&constants::INVSQRT_A_MINUS_D + &constants::INVSQRT_A_MINUS_D)
);
assert_eq!(
MIDOUBLE_INVSQRT_A_MINUS_D,
&MDOUBLE_INVSQRT_A_MINUS_D * &constants::SQRT_M1
);
let (_, invsqrt_one_plus_d) = (&constants::EDWARDS_D + &FieldElement::ONE).invsqrt();
assert_eq!(-&invsqrt_one_plus_d, MINVSQRT_ONE_PLUS_D);
}
}
+332
View File
@@ -0,0 +1,332 @@
//! Defines additional methods on RistrettoPoint for Lizard
#![allow(non_snake_case)]
use digest::generic_array::typenum::U32;
use digest::Digest;
use crate::constants;
use crate::field::FieldElement;
use subtle::Choice;
use subtle::ConditionallySelectable;
use subtle::ConstantTimeEq;
use crate::edwards::EdwardsPoint;
use super::jacobi_quartic::JacobiPoint;
use super::lizard_constants;
use crate::ristretto::RistrettoPoint;
#[allow(unused_imports)]
use core::prelude::*;
impl RistrettoPoint {
/// Directly encode 253 bits as a RistrettoPoint, using Elligator
pub fn from_uniform_bytes_single_elligator(bytes: &[u8; 32]) -> RistrettoPoint {
RistrettoPoint::elligator_ristretto_flavor(&FieldElement::from_bytes(bytes))
}
/// Encode 16 bytes of data to a RistrettoPoint, using the Lizard method
pub fn lizard_encode<D: Digest>(data: &[u8; 16]) -> RistrettoPoint
where
D: Digest<OutputSize = U32>,
{
let mut fe_bytes: [u8; 32] = Default::default();
let digest = D::digest(data);
fe_bytes[0..32].copy_from_slice(digest.as_slice());
fe_bytes[8..24].copy_from_slice(data);
fe_bytes[0] &= 254; // make positive since Elligator on r and -r is the same
fe_bytes[31] &= 63;
let fe = FieldElement::from_bytes(&fe_bytes);
RistrettoPoint::elligator_ristretto_flavor(&fe)
}
/// Decode 16 bytes of data from a RistrettoPoint, using the Lizard method
pub fn lizard_decode<D: Digest>(&self) -> Option<[u8; 16]>
where
D: Digest<OutputSize = U32>,
{
let mut result: [u8; 16] = Default::default();
let mut h: [u8; 32] = Default::default();
let (mask, fes) = self.elligator_ristretto_flavor_inverse();
let mut n_found = 0;
for (j, fe_j) in fes.iter().enumerate() {
let mut ok = Choice::from((mask >> j) & 1);
let buf2 = fe_j.as_bytes(); // array
h.copy_from_slice(&D::digest(&buf2[8..24])); // array
h[8..24].copy_from_slice(&buf2[8..24]);
h[0] &= 254;
h[31] &= 63;
ok &= h.ct_eq(&buf2);
for i in 0..16 {
result[i] = u8::conditional_select(&result[i], &buf2[8 + i], ok);
}
n_found += ok.unwrap_u8();
}
if n_found == 1 {
Some(result)
} else {
None
}
}
/// Directly encode 253 bits as a RistrettoPoint, using Elligator
pub fn encode_253_bits(data: &[u8; 32]) -> Option<RistrettoPoint> {
if data.len() != 32 {
return None;
}
let fe = FieldElement::from_bytes(data);
let p = RistrettoPoint::elligator_ristretto_flavor(&fe);
Some(p)
}
/// Directly decode a RistrettoPoint as 253 bits, using Elligator
pub fn decode_253_bits(&self) -> (u8, [[u8; 32]; 8]) {
let mut ret = [[0u8; 32]; 8];
let (mask, fes) = self.elligator_ristretto_flavor_inverse();
for j in 0..8 {
ret[j] = fes[j].as_bytes();
}
(mask, ret)
}
/// Return the coset self + E[4], for debugging.
pub fn xcoset4(&self) -> [EdwardsPoint; 4] {
[
self.0,
self.0 + constants::EIGHT_TORSION[2],
self.0 + constants::EIGHT_TORSION[4],
self.0 + constants::EIGHT_TORSION[6],
]
}
/// Computes the at most 8 positive FieldElements f such that
/// self == elligator_ristretto_flavor(f).
/// Assumes self is even.
///
/// Returns a bitmask of which elements in fes are set.
pub fn elligator_ristretto_flavor_inverse(&self) -> (u8, [FieldElement; 8]) {
// Elligator2 computes a Point from a FieldElement in two steps: first
// it computes a (s,t) on the Jacobi quartic and then computes the
// corresponding even point on the Edwards curve.
//
// We invert in three steps. Any Ristretto point has four representatives
// as even Edwards points. For each of those even Edwards points,
// there are two points on the Jacobi quartic that map to it.
// Each of those eight points on the Jacobi quartic might have an
// Elligator2 preimage.
//
// Essentially we first loop over the four representatives of our point,
// then for each of them consider both points on the Jacobi quartic and
// check whether they have an inverse under Elligator2. We take the
// following shortcut though.
//
// We can compute two Jacobi quartic points for (x,y) and (-x,-y)
// at the same time. The four Jacobi quartic points are two of
// such pairs.
let mut mask: u8 = 0;
let jcs = self.to_jacobi_quartic_ristretto();
let mut ret = [FieldElement::ONE; 8];
for i in 0..4 {
let (ok, fe) = jcs[i].elligator_inv();
let mut tmp: u8 = 0;
ret[2 * i] = fe;
tmp.conditional_assign(&1, ok);
mask |= tmp << (2 * i);
let jc = jcs[i].dual();
let (ok, fe) = jc.elligator_inv();
let mut tmp: u8 = 0;
ret[2 * i + 1] = fe;
tmp.conditional_assign(&1, ok);
mask |= tmp << (2 * i + 1);
}
(mask, ret)
}
/// Find a point on the Jacobi quartic associated to each of the four
/// points Ristretto equivalent to p.
///
/// There is one exception: for (0,-1) there is no point on the quartic and
/// so we repeat one on the quartic equivalent to (0,1).
fn to_jacobi_quartic_ristretto(self) -> [JacobiPoint; 4] {
let x2 = self.0.X.square(); // X^2
let y2 = self.0.Y.square(); // Y^2
let y4 = y2.square(); // Y^4
let z2 = self.0.Z.square(); // Z^2
let z_min_y = &self.0.Z - &self.0.Y; // Z - Y
let z_pl_y = &self.0.Z + &self.0.Y; // Z + Y
let z2_min_y2 = &z2 - &y2; // Z^2 - Y^2
// gamma := 1/sqrt( Y^4 X^2 (Z^2 - Y^2) )
let (_, gamma) = (&(&y4 * &x2) * &z2_min_y2).invsqrt();
let den = &gamma * &y2;
let s_over_x = &den * &z_min_y;
let sp_over_xp = &den * &z_pl_y;
let s0 = &s_over_x * &self.0.X;
let s1 = &(-(&sp_over_xp)) * &self.0.X;
// t_0 := -2/sqrt(-d-1) * Z * sOverX
// t_1 := -2/sqrt(-d-1) * Z * spOverXp
let tmp = &lizard_constants::MDOUBLE_INVSQRT_A_MINUS_D * &self.0.Z;
let mut t0 = &tmp * &s_over_x;
let mut t1 = &tmp * &sp_over_xp;
// den := -1/sqrt(1+d) (Y^2 - Z^2) gamma
let den = &(&(-(&z2_min_y2)) * &lizard_constants::MINVSQRT_ONE_PLUS_D) * &gamma;
// Same as before but with the substitution (X, Y, Z) = (Y, X, i*Z)
let iz = &constants::SQRT_M1 * &self.0.Z; // iZ
let iz_min_x = &iz - &self.0.X; // iZ - X
let iz_pl_x = &iz + &self.0.X; // iZ + X
let s_over_y = &den * &iz_min_x;
let sp_over_yp = &den * &iz_pl_x;
let mut s2 = &s_over_y * &self.0.Y;
let mut s3 = &(-(&sp_over_yp)) * &self.0.Y;
// t_2 := -2/sqrt(-d-1) * i*Z * sOverY
// t_3 := -2/sqrt(-d-1) * i*Z * spOverYp
let tmp = &lizard_constants::MDOUBLE_INVSQRT_A_MINUS_D * &iz;
let mut t2 = &tmp * &s_over_y;
let mut t3 = &tmp * &sp_over_yp;
// Special case: X=0 or Y=0. Then return
//
// (0,1) (1,-2i/sqrt(-d-1) (-1,-2i/sqrt(-d-1))
//
// Note that if X=0 or Y=0, then s_i = t_i = 0.
let x_or_y_is_zero = self.0.X.is_zero() | self.0.Y.is_zero();
t0.conditional_assign(&FieldElement::ONE, x_or_y_is_zero);
t1.conditional_assign(&FieldElement::ONE, x_or_y_is_zero);
t2.conditional_assign(
&lizard_constants::MIDOUBLE_INVSQRT_A_MINUS_D,
x_or_y_is_zero,
);
t3.conditional_assign(
&lizard_constants::MIDOUBLE_INVSQRT_A_MINUS_D,
x_or_y_is_zero,
);
s2.conditional_assign(&FieldElement::ONE, x_or_y_is_zero);
s3.conditional_assign(&(-(&FieldElement::ONE)), x_or_y_is_zero);
[
JacobiPoint { S: s0, T: t0 },
JacobiPoint { S: s1, T: t1 },
JacobiPoint { S: s2, T: t2 },
JacobiPoint { S: s3, T: t3 },
]
}
}
// ------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------
#[cfg(test)]
mod test {
use sha2;
use self::sha2::Sha256;
use super::*;
use crate::ristretto::CompressedRistretto;
use rand_core::RngCore;
#[cfg(feature = "rand")]
use rand_os::OsRng;
fn test_lizard_encode_helper(data: &[u8; 16], result: &[u8; 32]) {
let p = RistrettoPoint::lizard_encode::<Sha256>(data);
let p_bytes = p.compress().to_bytes();
assert!(&p_bytes == result);
let p = CompressedRistretto::from_slice(&p_bytes)
.unwrap()
.decompress()
.unwrap();
let data_out = p.lizard_decode::<Sha256>().unwrap();
assert!(&data_out == data);
}
#[test]
fn test_lizard_encode() {
test_lizard_encode_helper(
&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
&[
0xf0, 0xb7, 0xe3, 0x44, 0x84, 0xf7, 0x4c, 0xf0, 0xf, 0x15, 0x2, 0x4b, 0x73, 0x85,
0x39, 0x73, 0x86, 0x46, 0xbb, 0xbe, 0x1e, 0x9b, 0xc7, 0x50, 0x9a, 0x67, 0x68, 0x15,
0x22, 0x7e, 0x77, 0x4f,
],
);
test_lizard_encode_helper(
&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
&[
0xcc, 0x92, 0xe8, 0x1f, 0x58, 0x5a, 0xfc, 0x5c, 0xaa, 0xc8, 0x86, 0x60, 0xd8, 0xd1,
0x7e, 0x90, 0x25, 0xa4, 0x44, 0x89, 0xa3, 0x63, 0x4, 0x21, 0x23, 0xf6, 0xaf, 0x7,
0x2, 0x15, 0x6e, 0x65,
],
);
test_lizard_encode_helper(
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
&[
0xc8, 0x30, 0x57, 0x3f, 0x8a, 0x8e, 0x77, 0x78, 0x67, 0x1f, 0x76, 0xcd, 0xc7, 0x96,
0xdc, 0xa, 0x23, 0x5c, 0xf1, 0x77, 0xf1, 0x97, 0xd9, 0xfc, 0xba, 0x6, 0xe8, 0x4e,
0x96, 0x24, 0x74, 0x44,
],
);
}
#[test]
fn test_elligator_inv() {
let mut rng = rand::thread_rng();
for i in 0..100 {
let mut fe_bytes = [0u8; 32];
if i == 0 {
// Test for first corner-case: fe = 0
fe_bytes = [0u8; 32];
} else if i == 1 {
// Test for second corner-case: fe = +sqrt(i*d)
fe_bytes = [
168, 27, 92, 74, 203, 42, 48, 117, 170, 109, 234, 14, 45, 169, 188, 205, 21,
110, 235, 115, 153, 84, 52, 117, 151, 235, 123, 244, 88, 85, 179, 5,
];
} else {
// For the rest, just generate a random field element to test.
rng.fill_bytes(&mut fe_bytes);
}
fe_bytes[0] &= 254; // positive
fe_bytes[31] &= 127; // < 2^255-19
let fe = FieldElement::from_bytes(&fe_bytes);
let pt = RistrettoPoint::elligator_ristretto_flavor(&fe);
for pt2 in &pt.xcoset4() {
let (mask, fes) = RistrettoPoint(*pt2).elligator_ristretto_flavor_inverse();
let mut found = false;
for (j, fe_j) in fes.iter().enumerate() {
if mask & (1 << j) != 0 {
assert_eq!(RistrettoPoint::elligator_ristretto_flavor(fe_j), pt);
if *fe_j == fe {
found = true;
}
}
}
assert!(found);
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
//! The Lizard method for encoding/decoding 16 bytes into Ristretto points.
#![allow(non_snake_case)]
#[cfg(curve25519_dalek_bits = "32")]
mod u32_constants;
#[cfg(curve25519_dalek_bits = "64")]
mod u64_constants;
pub mod jacobi_quartic;
pub mod lizard_constants;
pub mod lizard_ristretto;
+63
View File
@@ -0,0 +1,63 @@
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(curve25519_dalek_backend = "fiat")] {
pub use crate::backend::serial::fiat_u64::field::FieldElement51;
const fn field_element(element: [u64; 5]) -> FieldElement51 {
FieldElement51(fiat_crypto::curve25519_64::fiat_25519_tight_field_element(element))
}
} else {
pub use crate::backend::serial::u64::field::FieldElement51;
const fn field_element(element: [u64; 5]) -> FieldElement51 {
FieldElement51(element)
}
}
}
/// `= sqrt(i*d)`, where `i = +sqrt(-1)` and `d` is the Edwards curve parameter.
pub const SQRT_ID: FieldElement51 = field_element([
2298852427963285,
3837146560810661,
4413131899466403,
3883177008057528,
2352084440532925,
]);
/// `= (d+1)/(d-1)`, where `d` is the Edwards curve parameter.
pub const DP1_OVER_DM1: FieldElement51 = field_element([
2159851467815724,
1752228607624431,
1825604053920671,
1212587319275468,
253422448836237,
]);
/// `= -2/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub const MDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = field_element([
1693982333959686,
608509411481997,
2235573344831311,
947681270984193,
266558006233600,
]);
/// `= -2i/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters
/// and `i = +sqrt(-1)`.
pub const MIDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = field_element([
1608655899704280,
1999971613377227,
49908634785720,
1873700692181652,
353702208628067,
]);
/// `= -1/sqrt(1+d)`, where `d` is the Edwards curve parameters.
pub const MINVSQRT_ONE_PLUS_D: FieldElement51 = field_element([
321571956990465,
1251814006996634,
2226845496292387,
189049560751797,
2074948709371214,
]);