use super::base_table::ED25519_BASE_TABLE;
use super::field::{Fe, Field, ScalarInt};
use crate::ct::{Choice, ConditionallySelectable, ConstantTimeLess};
#[derive(Clone, Copy, Debug)]
pub(crate) struct Point {
pub(crate) x: Fe,
pub(crate) y: Fe,
pub(crate) z: Fe,
pub(crate) t: Fe,
}
#[inline]
fn table_point(entry: &[u64; 15], one: Fe) -> Point {
Point {
x: Fe([entry[0], entry[1], entry[2], entry[3], entry[4]]),
y: Fe([entry[5], entry[6], entry[7], entry[8], entry[9]]),
z: one,
t: Fe([entry[10], entry[11], entry[12], entry[13], entry[14]]),
}
}
impl Field {
#[cfg(any(test, feature = "hazmat-edwards25519", feature = "ristretto255"))]
pub(crate) fn base(&self) -> Point {
self.decode(&super::field::BASE_ENC)
.expect("valid base point")
}
pub(crate) fn decode(&self, enc: &[u8; 32]) -> Option<Point> {
let sign = (enc[31] >> 7) & 1;
let mut yb = *enc;
yb[31] &= 0x7f;
let yval = ScalarInt::from_le_bytes(&yb);
if !bool::from(yval.ct_lt(&self.p)) {
return None;
}
let y = Fe::from_bytes(&yb);
let yy = self.sq(y);
let u = self.sub(yy, self.one);
let v = self.add(self.mul(self.d, yy), self.one);
let (was_square, mut x) = self.sqrt_ratio_i(u, v);
if !bool::from(was_square) {
return None;
}
if bool::from(x.is_zero()) && sign == 1 {
return None;
}
if x.is_negative().unwrap_u8() != sign {
x = self.neg(x);
}
let t = self.mul(x, y);
Some(Point {
x,
y,
z: self.one,
t,
})
}
pub(crate) fn encode(&self, p: &Point) -> [u8; 32] {
let zinv = self.inv(p.z);
let x = self.mul(p.x, zinv);
let y = self.mul(p.y, zinv);
let mut out = y.to_bytes();
out[31] |= x.is_negative().unwrap_u8() << 7;
out
}
#[cfg(any(feature = "hazmat-edwards25519", feature = "ristretto255"))]
pub(crate) fn to_affine_bytes(&self, p: &Point) -> ([u8; 32], [u8; 32]) {
let zinv = self.inv(p.z);
let x = self.mul(p.x, zinv);
let y = self.mul(p.y, zinv);
(x.to_bytes(), y.to_bytes())
}
pub(crate) fn identity(&self) -> Point {
Point {
x: Fe::ZERO,
y: self.one,
z: self.one,
t: Fe::ZERO,
}
}
pub(crate) fn point_add(&self, p: &Point, q: &Point) -> Point {
let aa = self.mul(self.sub(p.y, p.x), self.sub(q.y, q.x));
let bb = self.mul(self.add(p.y, p.x), self.add(q.y, q.x));
let cc = self.mul(self.mul(p.t, self.d2), q.t);
let dd = self.mul(self.add(p.z, p.z), q.z);
let e = self.sub(bb, aa);
let ff = self.sub(dd, cc);
let g = self.add(dd, cc);
let h = self.add(bb, aa);
Point {
x: self.mul(e, ff),
y: self.mul(g, h),
t: self.mul(e, h),
z: self.mul(ff, g),
}
}
pub(crate) fn point_double(&self, p: &Point) -> Point {
let a = self.sq(p.x);
let b = self.sq(p.y);
let zz = self.sq(p.z);
let c = self.add(zz, zz);
let d = self.neg(a);
let e = self.sub(self.sub(self.sq(self.add(p.x, p.y)), a), b);
let g = self.add(d, b);
let ff = self.sub(g, c);
let h = self.sub(d, b);
Point {
x: self.mul(e, ff),
y: self.mul(g, h),
t: self.mul(e, h),
z: self.mul(ff, g),
}
}
pub(crate) fn point_negate(&self, p: &Point) -> Point {
Point {
x: self.neg(p.x),
y: p.y,
z: p.z,
t: self.neg(p.t),
}
}
#[cfg(any(test, feature = "hazmat-edwards25519", feature = "ristretto255"))]
pub(crate) fn scalar_mult(&self, scalar: &[u8; 32], p: &Point) -> Point {
let mut table = [self.identity(); 16];
table[1] = *p;
for i in 2..16 {
table[i] = self.point_add(&table[i - 1], p);
}
let mut acc = self.identity();
let mut i = 64;
while i > 0 {
i -= 1;
acc = self.point_double(&acc);
acc = self.point_double(&acc);
acc = self.point_double(&acc);
acc = self.point_double(&acc);
let byte = scalar[i / 2];
let digit = (if i % 2 == 1 { byte >> 4 } else { byte & 0xf }) as usize;
let mut sel = table[0];
for (j, entry) in table.iter().enumerate() {
sel = point_select(&sel, entry, Choice::from((j == digit) as u8));
}
acc = self.point_add(&acc, &sel);
}
acc
}
pub(crate) fn mul_base(&self, scalar: &[u8; 32]) -> Point {
let id = self.identity();
let mut acc = id;
for (i, window) in ED25519_BASE_TABLE.iter().enumerate() {
let byte = scalar[i / 2];
let digit = (if i % 2 == 1 { byte >> 4 } else { byte & 0xf }) as usize;
let mut sel = id;
for (j, entry) in window.iter().enumerate() {
let cand = table_point(entry, self.one);
sel = point_select(&sel, &cand, Choice::from((j + 1 == digit) as u8));
}
acc = self.point_add(&acc, &sel);
}
acc
}
pub(crate) fn mul_base_vartime(&self, scalar: &[u8; 32]) -> Point {
let mut acc = self.identity();
for (i, window) in ED25519_BASE_TABLE.iter().enumerate() {
let byte = scalar[i / 2];
let digit = (if i % 2 == 1 { byte >> 4 } else { byte & 0xf }) as usize;
if digit != 0 {
let cand = table_point(&window[digit - 1], self.one);
acc = self.point_add(&acc, &cand);
}
}
acc
}
pub(crate) fn scalar_mult_vartime(&self, scalar: &[u8; 32], p: &Point) -> Point {
let p2 = self.point_double(p);
let mut odd = [*p; 8];
for i in 1..8 {
odd[i] = self.point_add(&odd[i - 1], &p2);
}
let naf = wnaf5(scalar);
let mut top = None;
for i in (0..naf.len()).rev() {
if naf[i] != 0 {
top = Some(i);
break;
}
}
let Some(top) = top else {
return self.identity();
};
let mut acc = self.identity();
for i in (0..=top).rev() {
acc = self.point_double(&acc);
let d = naf[i];
if d > 0 {
acc = self.point_add(&acc, &odd[(d as usize) / 2]);
} else if d < 0 {
acc = self.point_add(&acc, &self.point_negate(&odd[(-d as usize) / 2]));
}
}
acc
}
pub(crate) fn point_ct_eq(&self, p: &Point, q: &Point) -> Choice {
let x1z2 = self.mul(p.x, q.z);
let x2z1 = self.mul(q.x, p.z);
let y1z2 = self.mul(p.y, q.z);
let y2z1 = self.mul(q.y, p.z);
self.ct_eq(x1z2, x2z1) & self.ct_eq(y1z2, y2z1)
}
}
fn wnaf5(scalar: &[u8; 32]) -> [i8; 261] {
let mut naf = [0i8; 261];
let mut x = [0u64; 5];
for (i, limb) in x.iter_mut().take(4).enumerate() {
let mut b = [0u8; 8];
b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
*limb = u64::from_le_bytes(b);
}
let width = 1u64 << 5;
let window_mask = width - 1;
let mut pos = 0;
let mut carry = 0u64;
while pos < 256 {
let idx = pos / 64;
let bit = pos % 64;
let bit_buf = if bit < 64 - 5 {
x[idx] >> bit
} else {
(x[idx] >> bit) | (x[idx + 1] << (64 - bit))
};
let window = carry + (bit_buf & window_mask);
if window & 1 == 0 {
pos += 1;
continue;
}
if window < width / 2 {
carry = 0;
naf[pos] = window as i8;
} else {
carry = 1;
naf[pos] = (window as i8).wrapping_sub(width as i8);
}
pos += 5;
}
if carry != 0 {
naf[pos] = 1;
}
naf
}
pub(crate) fn point_select(a: &Point, b: &Point, c: Choice) -> Point {
Point {
x: Fe::conditional_select(&b.x, &a.x, c),
y: Fe::conditional_select(&b.y, &a.y, c),
z: Fe::conditional_select(&b.z, &a.z, c),
t: Fe::conditional_select(&b.t, &a.t, c),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base_table_matches_computed() {
let f = Field::new();
let mut base = f.base();
for window in ED25519_BASE_TABLE.iter() {
let mut acc = base;
for (j, entry) in window.iter().enumerate() {
if j > 0 {
acc = f.point_add(&acc, &base);
}
let e = table_point(entry, f.one);
assert!(bool::from(f.ct_eq(f.mul(e.x, acc.z), acc.x)), "x mismatch");
assert!(bool::from(f.ct_eq(f.mul(e.y, acc.z), acc.y)), "y mismatch");
assert!(bool::from(f.ct_eq(f.mul(e.t, acc.z), acc.t)), "t mismatch");
}
base = f.point_double(&f.point_double(&f.point_double(&f.point_double(&base))));
}
}
#[test]
fn mul_base_matches_generic_scalar_mult() {
use crate::hash::Sha256;
use crate::rng::{HmacDrbg, RngCore};
let f = Field::new();
let b = f.base();
let check = |s: &[u8; 32]| {
let comb = f.encode(&f.mul_base(s));
assert_eq!(
comb,
f.encode(&f.scalar_mult(s, &b)),
"comb/ladder mismatch"
);
assert_eq!(
comb,
f.encode(&f.mul_base_vartime(s)),
"vartime comb mismatch"
);
assert_eq!(
comb,
f.encode(&f.scalar_mult_vartime(s, &b)),
"vartime wNAF mismatch"
);
};
let int_bytes = |v: &ScalarInt| {
let mut out = [0u8; 32];
v.write_le_bytes(&mut out);
out
};
let mut edges = [[0u8; 32]; 8];
edges[1][0] = 1;
edges[2][0] = 2;
edges[3] = int_bytes(&f.l.wrapping_sub(&ScalarInt::ONE));
edges[4] = int_bytes(&f.l);
edges[5] = int_bytes(&f.l.wrapping_add(&ScalarInt::ONE));
edges[6][31] = 0x80;
edges[7] = [0xff; 32];
for s in &edges {
check(s);
}
let mut rng = HmacDrbg::<Sha256>::new(b"ed25519-comb-differential", b"nonce", &[]);
for _ in 0..32 {
let mut s = [0u8; 32];
rng.fill_bytes(&mut s);
check(&s);
}
}
#[test]
#[ignore = "table generator; emits Rust source on stdout"]
#[cfg(feature = "std")]
fn gen_ed25519_base_table() {
use std::{print, println};
let f = Field::new();
let mut base = f.base();
println!("pub(crate) static ED25519_BASE_TABLE: [[[u64; 15]; 15]; 64] = [");
for _ in 0..64 {
println!(" [");
let mut acc = base;
for j in 1..=15u32 {
if j > 1 {
acc = f.point_add(&acc, &base);
}
let zinv = f.inv(acc.z);
let x = Fe::from_bytes(&f.mul(acc.x, zinv).to_bytes());
let y = Fe::from_bytes(&f.mul(acc.y, zinv).to_bytes());
let t = Fe::from_bytes(&f.mul(x, y).to_bytes());
print!(" [");
for l in x.0.iter().chain(y.0.iter()).chain(t.0.iter()) {
print!("0x{l:013x}, ");
}
println!("],");
}
println!(" ],");
base = f.point_double(&f.point_double(&f.point_double(&f.point_double(&base))));
}
println!("];");
}
}