use ic_core::ct::Choice;
use super::{basepoint, signed_digits, AffineNiels, Point};
const DIGITS: usize = 64;
const ENTRIES: usize = 8;
const TABLES: usize = DIGITS / 2;
#[cfg(feature = "std")]
struct Window([AffineNiels; ENTRIES]);
#[cfg(feature = "std")]
impl Window {
fn new(base: &Point) -> 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()))
}
fn select(&self, digit: i8) -> AffineNiels {
let negative = Choice::from_u8((digit as u8) >> 7);
let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u8;
let mut out = AffineNiels::IDENTITY;
for (i, entry) in self.0.iter().enumerate() {
let hit = Choice::from_u8(u8::from(magnitude == (i as u8 + 1)));
out.cmov(entry, hit);
}
out.conditional_negate(negative);
out
}
}
#[cfg(feature = "std")]
use std::sync::OnceLock;
#[cfg(feature = "std")]
#[allow(clippy::declare_interior_mutable_const)]
const NO_WINDOW: OnceLock<Window> = OnceLock::new();
#[cfg(feature = "std")]
static WINDOWS: [OnceLock<Window>; TABLES] = [NO_WINDOW; TABLES];
#[cfg(feature = "std")]
#[allow(clippy::declare_interior_mutable_const)]
const NO_POINT: OnceLock<AffineNiels> = OnceLock::new();
#[cfg(feature = "std")]
static ODD: [OnceLock<AffineNiels>; 64] = [NO_POINT; 64];
#[cfg(feature = "std")]
static BUILT: OnceLock<()> = OnceLock::new();
#[cfg(feature = "std")]
pub fn prepare() {
BUILT.get_or_init(|| {
let mut base = basepoint();
for (i, slot) in WINDOWS.iter().enumerate() {
if i > 0 {
for _ in 0..8 {
base = base.double();
}
}
slot.get_or_init(|| Window::new(&base));
}
let b = basepoint();
let twice = b.double();
let mut multiple = b;
for (i, slot) in ODD.iter().enumerate() {
if i > 0 {
multiple = multiple.add(&twice);
}
slot.get_or_init(|| multiple.to_affine_niels());
}
});
}
#[cfg(feature = "std")]
fn window(i: usize) -> &'static Window {
WINDOWS[i].get_or_init(|| {
let mut base = basepoint();
for _ in 0..8 * i {
base = base.double();
}
Window::new(&base)
})
}
#[cfg(feature = "std")]
pub fn mul(scalar: &[u8; 32]) -> Point {
prepare();
let digits = signed_digits(scalar);
let mut acc = Point::IDENTITY;
for i in (1..DIGITS).step_by(2) {
acc = acc
.add_affine_niels(&window(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(i / 2).select(digits[i]))
.to_extended();
}
acc
}
#[cfg(feature = "std")]
pub(super) fn odd_multiple(i: usize) -> &'static AffineNiels {
ODD[i].get_or_init(|| {
let b = basepoint();
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(s);
let slow = basepoint().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"
);
}
}