#![allow(clippy::needless_range_loop)]
use super::basepoint_table::{self, window};
use super::{odd_multiples, signed_digits, wnaf, AffineNiels, Ext, Niels};
use crate::fe64::edwards::{self as e, slot, Ws, Ws2};
use crate::fe64::Fe64;
use ic_core::Zeroize;
fn put(ws: &mut Ws, at: usize, v: &Fe64) {
ws.0[at..at + 4].copy_from_slice(&v.0);
}
fn get(ws: &Ws, at: usize) -> Fe64 {
let mut v = [0u64; 4];
v.copy_from_slice(&ws.0[at..at + 4]);
Fe64(v)
}
fn put_affine(ws: &mut Ws, n: &AffineNiels<Fe64>) {
put(ws, slot::YPX, &n.ypx);
put(ws, slot::YMX, &n.ymx);
put(ws, slot::T2D, &n.t2d);
}
fn put_niels(ws: &mut Ws, n: &Niels<Fe64>) {
put(ws, slot::YPX, &n.ypx);
put(ws, slot::YMX, &n.ymx);
put(ws, slot::NZ, &n.z);
put(ws, slot::T2D, &n.t2d);
}
fn get_ext(ws: &Ws) -> Ext<Fe64> {
Ext {
x: get(ws, slot::X),
y: get(ws, slot::Y),
z: get(ws, slot::Z),
t: get(ws, slot::T),
}
}
fn identity() -> Ws {
let mut ws = Ws([0; 64]);
put(&mut ws, slot::Y, &Fe64::ONE);
put(&mut ws, slot::Z, &Fe64::ONE);
ws
}
pub(super) fn mul_base(scalar: &[u8; 32]) -> Ext<Fe64> {
basepoint_table::prepare::<Fe64>();
let digits = signed_digits(scalar);
let mut two = Ws2([0; 128]);
for half in [0, 64] {
two.0[half + slot::Y..half + slot::Y + 4].copy_from_slice(&Fe64::ONE.0);
two.0[half + slot::Z..half + slot::Z + 4].copy_from_slice(&Fe64::ONE.0);
}
for j in 0..32 {
let odd = window::<Fe64>(j).select(digits[2 * j + 1]);
let even = window::<Fe64>(j).select(digits[2 * j]);
for (half, n) in [(0, &odd), (64, &even)] {
two.0[half + slot::YPX..half + slot::YPX + 4].copy_from_slice(&n.ypx.0);
two.0[half + slot::YMX..half + slot::YMX + 4].copy_from_slice(&n.ymx.0);
two.0[half + slot::T2D..half + slot::T2D + 4].copy_from_slice(&n.t2d.0);
}
e::madd_ext2(&mut two);
}
let mut ws = Ws([0; 64]);
ws.0.copy_from_slice(&two.0[..64]);
let mut even = Ws([0; 64]);
even.0.copy_from_slice(&two.0[64..]);
let even = get_ext(&even).to_niels();
for _ in 0..3 {
e::dbl(&mut ws);
e::to_proj(&mut ws);
}
e::dbl(&mut ws);
e::to_ext(&mut ws);
put_niels(&mut ws, &even);
e::nadd(&mut ws);
e::to_ext(&mut ws);
let out = get_ext(&ws);
ws.0.zeroize();
two.0.zeroize();
out
}
pub(super) fn double_mul(a: &Ext<Fe64>, k: &[u8; 32], s: &[u8; 32]) -> Ext<Fe64> {
basepoint_table::prepare::<Fe64>();
let odd_a = odd_multiples(a);
let naf_a = wnaf(k, 5);
let naf_b = wnaf(s, 8);
let mut i = 257;
while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
i -= 1;
}
let mut ws = identity();
loop {
e::dbl(&mut ws);
if naf_a[i] != 0 {
e::to_ext(&mut ws);
put_niels(&mut ws, &odd_a[(naf_a[i].unsigned_abs() as usize) / 2]);
if naf_a[i] > 0 {
e::nadd(&mut ws);
} else {
e::nsub(&mut ws);
}
}
if naf_b[i] != 0 {
e::to_ext(&mut ws);
put_affine(
&mut ws,
basepoint_table::odd_multiple::<Fe64>((naf_b[i].unsigned_abs() as usize) / 2),
);
if naf_b[i] > 0 {
e::madd(&mut ws);
} else {
e::msub(&mut ws);
}
}
if i == 0 {
e::to_ext(&mut ws);
return get_ext(&ws);
}
e::to_proj(&mut ws);
i -= 1;
}
}
#[cfg(test)]
mod tests {
use super::super::basepoint_table::mul_generic;
use super::super::{basepoint, double_scalar_mul_vartime_generic, Completed};
use super::*;
fn skip() -> bool {
!crate::fe64::available()
}
fn sample_points(n: usize) -> std::vec::Vec<Ext<Fe64>> {
let mut out = std::vec::Vec::new();
let mut p = basepoint::<Fe64>();
for _ in 0..n {
out.push(p);
p = p.double().add(&basepoint::<Fe64>());
}
out
}
fn put_ext(ws: &mut Ws, p: &Ext<Fe64>) {
put(ws, slot::X, &p.x);
put(ws, slot::Y, &p.y);
put(ws, slot::Z, &p.z);
put(ws, slot::T, &p.t);
}
fn get_completed(ws: &Ws) -> Completed<Fe64> {
Completed {
x: get(ws, 16),
y: get(ws, 20),
z: get(ws, 24),
t: get(ws, 28),
}
}
#[test]
#[ignore = "diagnostic"]
fn where_signing_spends_its_time() {
use std::time::Instant;
basepoint_table::prepare::<Fe64>();
let n = 20_000u32;
let time = |label: &str, f: &mut dyn FnMut()| {
let mut best = f64::INFINITY;
for _ in 0..7 {
let s = Instant::now();
for _ in 0..n {
f();
}
best = best.min(s.elapsed().as_secs_f64() / n as f64 * 1e9);
}
std::println!("{label:<32} {best:>9.1} ns");
};
let digits = signed_digits(&[0x5au8; 32]);
time("64 selects", &mut || {
for i in 0..64 {
core::hint::black_box(
window::<Fe64>(i / 2).select(core::hint::black_box(digits[i])),
);
}
});
let mut two = Ws2([7; 128]);
time("32 madd_ext2", &mut || {
for _ in 0..32 {
e::madd_ext2(&mut two);
}
});
let mut one = Ws([7; 64]);
time("64 madd + to_ext", &mut || {
for _ in 0..64 {
e::madd(&mut one);
e::to_ext(&mut one);
}
});
time("mul_base", &mut || {
core::hint::black_box(mul_base(core::hint::black_box(&[0x5au8; 32])));
});
time("mul_generic", &mut || {
core::hint::black_box(mul_generic::<Fe64>(core::hint::black_box(&[0x5au8; 32])));
});
}
#[test]
fn each_block_is_the_formula_it_transcribes() {
if skip() {
return;
}
let pts = sample_points(12);
let mut checked = 0;
for p in &pts {
let mut ws = Ws([0; 64]);
put_ext(&mut ws, p);
e::dbl(&mut ws);
let want = p.to_projective().double();
let got = get_completed(&ws);
assert!(got.to_extended().eq_projective(&want.to_extended()), "dbl");
e::to_ext(&mut ws);
assert!(get_ext(&ws).eq_projective(&want.to_extended()), "to_ext");
put_ext(&mut ws, p);
e::dbl(&mut ws);
e::to_proj(&mut ws);
let proj = want.to_projective();
assert_eq!(get(&ws, slot::X).to_bytes(), proj.x.to_bytes(), "to_proj");
assert_eq!(get(&ws, slot::Z).to_bytes(), proj.z.to_bytes(), "to_proj");
for q in &pts {
let an = q.to_affine_niels();
let n = q.to_niels();
type Case = (&'static str, fn(&mut Ws), Completed<Fe64>, bool);
let cases: [Case; 4] = [
("madd", e::madd, p.add_affine_niels(&an), true),
("msub", e::msub, p.sub_affine_niels(&an), true),
("nadd", e::nadd, p.add_niels(&n), false),
("nsub", e::nsub, p.sub_niels(&n), false),
];
for (name, f, want, affine) in cases {
put_ext(&mut ws, p);
if affine {
put_affine(&mut ws, &an);
} else {
put_niels(&mut ws, &n);
}
f(&mut ws);
let got = get_completed(&ws);
assert!(
got.to_extended().eq_projective(&want.to_extended()),
"{name}"
);
checked += 1;
}
}
}
assert_eq!(checked, 12 * 12 * 4);
}
#[test]
fn the_block_paths_agree_with_the_generic_ones() {
use ic_core::traits::internal::Digest;
if skip() {
return;
}
let mut checked = 0;
for i in 0u32..64 {
let mut s = ic_hash::Sha256::digest(&i.to_le_bytes());
s[31] &= 0x7f;
if i == 0 {
s = [0; 32];
}
if i == 1 {
s = [0xff; 32];
s[31] = 0x7f;
}
let fast = mul_base(&s);
let slow = mul_generic::<Fe64>(&s);
let portable = mul_generic::<crate::field::Fe>(&s);
assert_eq!(fast.compress(), slow.compress(), "mul_base {i}");
assert_eq!(fast.compress(), portable.compress(), "mul_base {i}");
let mut k = ic_hash::Sha256::digest(&(i + 1000).to_le_bytes());
k[31] &= 0x7f;
let a = mul_base(&k);
let fast = double_mul(&a, &k, &s);
let slow = double_scalar_mul_vartime_generic(&a, &k, &s);
let portable =
double_scalar_mul_vartime_generic(&a.convert::<crate::field::Fe>(), &k, &s);
assert_eq!(fast.compress(), slow.compress(), "double_mul {i}");
assert_eq!(fast.compress(), portable.compress(), "double_mul {i}");
checked += 1;
}
assert_eq!(checked, 64);
}
}