use std::sync::OnceLock;
use super::{basepoint, signed_digits, AffineNiels, EdField, Ext};
const DIGITS: usize = 64;
const ENTRIES: usize = 8;
const TABLES: usize = DIGITS / 2;
#[repr(transparent)]
pub(crate) struct Window<F>([AffineNiels<F>; ENTRIES]);
impl<F: EdField> Window<F> {
#[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
pub(super) fn entries(&self) -> &[AffineNiels<F>; ENTRIES] {
&self.0
}
fn new(base: &Ext<F>) -> Self {
let mut multiples = [*base; ENTRIES];
for i in 1..ENTRIES {
multiples[i] = multiples[i - 1].add(base);
}
Self(core::array::from_fn(|i| multiples[i].to_affine_niels()))
}
pub(super) fn select(&self, digit: i8) -> AffineNiels<F> {
let negative = u64::from((digit as u8) >> 7);
let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u64;
let mut out = AffineNiels::IDENTITY;
for (i, entry) in self.0.iter().enumerate() {
let hit = (magnitude ^ (i as u64 + 1)).wrapping_sub(1) >> 63;
out.cmov(entry, hit);
}
out.conditional_negate(negative);
out
}
}
pub(crate) trait TableField: EdField {
fn windows() -> &'static [OnceLock<Window<Self>>; TABLES];
fn odd() -> &'static [OnceLock<AffineNiels<Self>>; 64];
fn built() -> &'static OnceLock<()>;
fn mul_base(scalar: &[u8; 32]) -> Ext<Self> {
mul_generic::<Self>(scalar)
}
fn double_mul(a: &Ext<Self>, k: &[u8; 32], s: &[u8; 32]) -> Ext<Self> {
super::double_scalar_mul_vartime_generic(a, k, s)
}
}
macro_rules! tables {
($f:ty $(, $fast:item)*) => {
impl TableField for $f {
$($fast)*
fn windows() -> &'static [OnceLock<Window<Self>>; TABLES] {
#[allow(clippy::declare_interior_mutable_const)]
const NO_WINDOW: OnceLock<Window<$f>> = OnceLock::new();
static WINDOWS: [OnceLock<Window<$f>>; TABLES] = [NO_WINDOW; TABLES];
&WINDOWS
}
fn odd() -> &'static [OnceLock<AffineNiels<Self>>; 64] {
#[allow(clippy::declare_interior_mutable_const)]
const NO_POINT: OnceLock<AffineNiels<$f>> = OnceLock::new();
static ODD: [OnceLock<AffineNiels<$f>>; 64] = [NO_POINT; 64];
&ODD
}
fn built() -> &'static OnceLock<()> {
static BUILT: OnceLock<()> = OnceLock::new();
&BUILT
}
}
};
}
tables!(crate::field::Fe);
#[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
tables!(
crate::fe64::Fe64,
fn mul_base(scalar: &[u8; 32]) -> Ext<Self> {
match crate::f51x4::Ifma::get() {
Some(t) => super::vector::mul_base(t, scalar),
None => super::fast::mul_base(scalar),
}
},
fn double_mul(a: &Ext<Self>, k: &[u8; 32], s: &[u8; 32]) -> Ext<Self> {
match crate::f51x4::Ifma::get() {
Some(t) => super::vector::double_mul(t, a, k, s),
None => super::fast::double_mul(a, k, s),
}
}
);
pub fn prepare<F: TableField>() {
F::built().get_or_init(|| {
let mut base = basepoint::<F>();
for (i, slot) in F::windows().iter().enumerate() {
if i > 0 {
for _ in 0..8 {
base = base.double();
}
}
slot.get_or_init(|| Window::new(&base));
}
let b = basepoint::<F>();
let twice = b.double();
let mut multiple = b;
for (i, slot) in F::odd().iter().enumerate() {
if i > 0 {
multiple = multiple.add(&twice);
}
slot.get_or_init(|| multiple.to_affine_niels());
}
});
}
pub(super) fn window<F: TableField>(i: usize) -> &'static Window<F> {
F::windows()[i].get_or_init(|| {
let mut base = basepoint::<F>();
for _ in 0..8 * i {
base = base.double();
}
Window::new(&base)
})
}
pub fn mul<F: TableField>(scalar: &[u8; 32]) -> Ext<F> {
F::mul_base(scalar)
}
pub fn mul_generic<F: TableField>(scalar: &[u8; 32]) -> Ext<F> {
prepare::<F>();
let digits = signed_digits(scalar);
let mut acc = Ext::IDENTITY;
for i in (1..DIGITS).step_by(2) {
acc = acc
.add_affine_niels(&window::<F>(i / 2).select(digits[i]))
.to_extended();
}
for _ in 0..4 {
acc = acc.double();
}
for i in (0..DIGITS).step_by(2) {
acc = acc
.add_affine_niels(&window::<F>(i / 2).select(digits[i]))
.to_extended();
}
acc
}
pub(super) fn odd_multiple<F: TableField>(i: usize) -> &'static AffineNiels<F> {
F::odd()[i].get_or_init(|| {
let b = basepoint::<F>();
let twice = b.double();
let mut multiple = b;
for _ in 0..i {
multiple = multiple.add(&twice);
}
multiple.to_affine_niels()
})
}
#[cfg(test)]
mod tests {
use super::*;
use ic_core::traits::SignatureScheme as _;
#[cfg(feature = "std")]
#[test]
fn the_table_agrees_with_bitwise_multiplication() {
let mut scalars: std::vec::Vec<[u8; 32]> = std::vec![
[0u8; 32],
[1u8; 32],
{
let mut s = [0x88u8; 32];
s[31] = 0x08;
s
},
{
let mut s = [0x99u8; 32];
s[31] = 0x09;
s
},
{
let mut s = [0xffu8; 32];
s[31] = 127;
s
},
];
let mut one = [0u8; 32];
one[0] = 1;
scalars.push(one);
let mut clamped = [0x5au8; 32];
clamped[0] &= 248;
clamped[31] &= 127;
clamped[31] |= 64;
scalars.push(clamped);
let mut checked = 0;
for s in &scalars {
let fast = mul::<crate::field::Fe>(s);
let slow = basepoint::<crate::field::Fe>().mul_scalar(s);
assert_eq!(
fast.compress(),
slow.compress(),
"table and bitwise multiplication differ for {s:02x?}"
);
checked += 1;
}
assert_eq!(checked, 7, "the comparison did not run");
}
#[test]
fn the_signed_digits_represent_the_scalar() {
let mut top = [0xffu8; 32];
top[31] = 127;
let mut eights = [0x88u8; 32];
eights[31] = 0x08;
let mut nines = [0x99u8; 32];
nines[31] = 0x09;
for raw in [[0u8; 32], [1u8; 32], eights, nines, top] {
let digits = signed_digits(&raw);
for d in digits {
assert!((-8..=8).contains(&d), "digit {d} out of range");
}
const M: u64 = 1_000_000_007;
let mut from_digits = 0u64;
let mut power = 1u64;
for d in digits {
let term = ((d as i64).rem_euclid(M as i64)) as u64;
from_digits = (from_digits + term * power) % M;
power = power * 16 % M;
}
let mut from_bytes = 0u64;
let mut p = 1u64;
for byte in raw {
from_bytes = (from_bytes + (byte as u64) * p) % M;
p = p * 256 % M;
}
assert_eq!(from_digits, from_bytes, "recoding changed the value");
}
}
#[cfg(feature = "std")]
#[test]
fn rfc8032_still_passes_through_the_table() {
let seed = [
0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec,
0x2c, 0xc4, 0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19, 0x70, 0x3b, 0xac, 0x03,
0x1c, 0xae, 0x7f, 0x60,
];
let mut pk = [0u8; 32];
super::super::Ed25519::public_key(&seed, &mut pk).unwrap();
assert_eq!(
ic_core::codec::hex(&pk),
"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
);
}
}