use ic_core::ct::Choice;
use super::arith::masks_for;
#[cfg(feature = "std")]
use super::arith::Field;
#[cfg(feature = "std")]
use super::point::AffinePoint;
use super::point::Curve;
use super::point::Point;
pub(super) const WIDTH: usize = 5;
const ENTRIES: usize = 1 << (WIDTH - 1);
pub(super) struct Window<C: Curve>([Point<C>; ENTRIES]);
impl<C: Curve> Window<C> {
pub(super) fn new(base: &Point<C>) -> Self {
Self(multiples(base))
}
pub(super) fn select(&self, digit: i8) -> Point<C> {
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 masks = masks_for::<ENTRIES>(magnitude as u32, 1);
let mut out = match C::scan_point(&self.0, &masks) {
Some([x, y, z]) => Point::from_coordinates(x, y, z),
None => {
let mut out = Point::zero();
for (entry, mask) in self.0.iter().zip(masks) {
Point::or_masked(&mut out, entry, mask);
}
out
}
};
out.conditional_negate(negative);
out
}
}
fn multiples<C: Curve, const E: usize>(base: &Point<C>) -> [Point<C>; E] {
let mut entries = [Point::identity(); E];
entries[0] = *base;
for i in 1..E {
entries[i] = if i % 2 == 1 {
entries[i / 2].double()
} else {
entries[i - 1].add_unequal(base)
};
}
entries
}
#[inline]
#[cfg(feature = "std")]
fn equal(a: u8, b: u8) -> Choice {
Choice::from_u8((((a ^ b) as u32).wrapping_sub(1) >> 31) as u8)
}
#[cfg(feature = "std")]
pub struct AffineWindow<C: Curve, const E: usize>([AffinePoint<C>; E]);
#[cfg(feature = "std")]
impl<C: Curve, const E: usize> AffineWindow<C, E> {
fn new(base: &Point<C>) -> Self {
let points: [Point<C>; E] = multiples(base);
let mut prefix = [C::Field::ONE; E];
let mut acc = C::Field::ONE;
for (p, point) in prefix.iter_mut().zip(points.iter()) {
*p = acc;
acc = acc.mul(&point.z());
}
let mut inv = acc.invert();
let mut entries = [AffinePoint {
x: C::Field::ZERO,
y: C::Field::ZERO,
}; E];
for i in (0..E).rev() {
let z_inv = inv.mul(&prefix[i]);
inv = inv.mul(&points[i].z());
let z_inv2 = z_inv.square();
entries[i] = AffinePoint {
x: points[i].x().mul(&z_inv2),
y: points[i].y().mul(&z_inv2.mul(&z_inv)),
};
}
Self(entries)
}
fn select(&self, digit: i8) -> (C::Field, C::Field) {
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 masks = masks_for::<E>(magnitude as u32, 1);
let (x, mut y) = match C::scan_affine(&self.0, &masks) {
Some(p) => (p.x, p.y),
None => {
let mut x = C::Field::ZERO;
let mut y = C::Field::ZERO;
for (entry, mask) in self.0.iter().zip(masks) {
C::Field::or_masked(&mut x, &entry.x, mask);
C::Field::or_masked(&mut y, &entry.y, mask);
}
(x, y)
}
};
let ny = y.neg();
C::Field::cmov(&mut y, &ny, negative);
(x, y)
}
fn add_to(&self, acc: &Point<C>, digit: i8, complete: bool) -> Point<C> {
let (x, y) = self.select(digit);
let zero = ((digit as u8 as u64).wrapping_sub(1) >> 63).wrapping_neg();
if complete {
let mut out = acc.add(&Point::from_affine(&AffinePoint { x, y }));
Point::cmov(&mut out, acc, equal(digit as u8, 0));
out
} else {
acc.add_affine_unequal_or_keep(&x, &y, zero)
}
}
fn add_to_vartime(&self, acc: &Point<C>, digit: i8) -> Point<C> {
if digit == 0 {
return *acc;
}
let entry = &self.0[digit.unsigned_abs() as usize - 1];
if digit > 0 {
acc.add_affine_vartime(&entry.x, &entry.y)
} else {
acc.add_affine_vartime(&entry.x, &entry.y.neg())
}
}
}
#[cfg(feature = "std")]
pub type Windows<C, const E: usize> = [std::sync::OnceLock<AffineWindow<C, E>>];
#[cfg(feature = "std")]
const fn width<const E: usize>() -> usize {
E.trailing_zeros() as usize + 1
}
#[cfg(feature = "std")]
pub fn prepare<C: Curve, const E: usize>(windows: &Windows<C, E>, built: &std::sync::OnceLock<()>) {
built.get_or_init(|| {
let mut base = Point::<C>::generator();
for (i, slot) in windows.iter().enumerate() {
if i > 0 {
for _ in 0..width::<E>() {
base = base.double();
}
}
slot.get_or_init(|| AffineWindow::new(&base));
}
});
}
#[cfg(feature = "std")]
fn window<C: Curve, const E: usize>(windows: &Windows<C, E>, i: usize) -> &AffineWindow<C, E> {
windows[i].get_or_init(|| {
let mut base = Point::<C>::generator();
for _ in 0..width::<E>() * i {
base = base.double();
}
AffineWindow::new(&base)
})
}
#[cfg(feature = "std")]
pub fn mul<C: Curve, const E: usize>(
windows: &Windows<C, E>,
built: &std::sync::OnceLock<()>,
scalar: &C::Scalar,
) -> Point<C> {
prepare(windows, built);
let w = width::<E>();
let bytes = scalar.to_bytes();
let digits = signed_digits_w(bytes.as_ref(), w);
let n = digit_count(bytes.as_ref().len(), w);
debug_assert!(n <= windows.len());
let mut acc = Point::identity();
for (i, digit) in digits.iter().take(n).enumerate() {
let complete = w * (i + 1) >= C::ORDER_BITS;
acc = window(windows, i).add_to(&acc, *digit, complete);
}
acc
}
#[cfg(feature = "std")]
pub fn mul_vartime<C: Curve, const E: usize>(
windows: &Windows<C, E>,
built: &std::sync::OnceLock<()>,
scalar: &C::Scalar,
) -> Point<C> {
prepare(windows, built);
let w = width::<E>();
let bytes = scalar.to_bytes();
let digits = signed_digits_w(bytes.as_ref(), w);
let n = digit_count(bytes.as_ref().len(), w);
let mut acc = Point::identity();
for (i, digit) in digits.iter().take(n).enumerate() {
acc = window(windows, i).add_to_vartime(&acc, *digit);
}
acc
}
pub(crate) const fn digit_count(len: usize, w: usize) -> usize {
8 * len / w + 1
}
const MAX_DIGITS_W: usize = 8 * 66 / 4 + 1;
pub(crate) fn signed_digits_w(bytes: &[u8], w: usize) -> [i8; MAX_DIGITS_W] {
let mut limbs = [0u64; 10];
for (i, byte) in bytes.iter().rev().enumerate() {
limbs[i / 8] |= (*byte as u64) << (8 * (i % 8));
}
let n = digit_count(bytes.len(), w);
let mask = (1u64 << w) - 1;
let half = 1i16 << (w - 1);
let mut digits = [0i8; MAX_DIGITS_W];
let mut carry = 0i16;
for (i, digit) in digits.iter_mut().take(n).enumerate() {
let bit = w * i;
let (word, shift) = (bit / 64, bit % 64);
let lo = limbs[word] >> shift;
let hi = if shift + w > 64 {
limbs[word + 1] << (64 - shift)
} else {
0
};
let v = ((lo | hi) & mask) as i16 + carry;
if i + 1 == n {
*digit = v as i8;
} else {
carry = (v + half) >> w;
*digit = (v - (carry << w)) as i8;
}
}
digits
}
pub trait HasGeneratorTable: Curve + Sized + 'static {
fn mul_generator(scalar: &Self::Scalar) -> super::point::Point<Self>;
fn mul_generator_vartime(scalar: &Self::Scalar) -> super::point::Point<Self>;
fn prepare_generator_table();
}
macro_rules! generator_table_for {
($curve:ty, $entries:literal) => {
#[cfg(feature = "std")]
fn generator_table() -> (
&'static $crate::nist::gentable::Windows<$curve, $entries>,
&'static std::sync::OnceLock<()>,
) {
const USED: usize = $crate::nist::gentable::digit_count(
<$curve as $crate::nist::point::Curve>::SCALAR_BYTES,
($entries as usize).trailing_zeros() as usize + 1,
);
#[allow(clippy::declare_interior_mutable_const)]
const EMPTY: std::sync::OnceLock<
$crate::nist::gentable::AffineWindow<$curve, $entries>,
> = std::sync::OnceLock::new();
static WINDOWS: [std::sync::OnceLock<
$crate::nist::gentable::AffineWindow<$curve, $entries>,
>; USED] = [EMPTY; USED];
static BUILT: std::sync::OnceLock<()> = std::sync::OnceLock::new();
(&WINDOWS, &BUILT)
}
impl $crate::nist::gentable::HasGeneratorTable for $curve {
#[cfg(feature = "std")]
fn mul_generator(
scalar: &<Self as $crate::nist::point::Curve>::Scalar,
) -> $crate::nist::point::Point<Self> {
let (windows, built) = generator_table();
$crate::nist::gentable::mul(windows, built, scalar)
}
#[cfg(not(feature = "std"))]
fn mul_generator(
scalar: &<Self as $crate::nist::point::Curve>::Scalar,
) -> $crate::nist::point::Point<Self> {
$crate::nist::point::Point::<Self>::generator().mul_scalar(scalar)
}
#[cfg(feature = "std")]
fn mul_generator_vartime(
scalar: &<Self as $crate::nist::point::Curve>::Scalar,
) -> $crate::nist::point::Point<Self> {
let (windows, built) = generator_table();
$crate::nist::gentable::mul_vartime(windows, built, scalar)
}
#[cfg(not(feature = "std"))]
fn mul_generator_vartime(
scalar: &<Self as $crate::nist::point::Curve>::Scalar,
) -> $crate::nist::point::Point<Self> {
$crate::nist::point::Point::<Self>::generator().mul_scalar_vartime(scalar)
}
fn prepare_generator_table() {
#[cfg(feature = "std")]
{
let (windows, built) = generator_table();
$crate::nist::gentable::prepare(windows, built);
}
}
}
};
}
pub(crate) use generator_table_for;
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn signed_digits_sum_to_the_scalar() {
let mut cases: std::vec::Vec<[u8; 15]> = std::vec![[0; 15], [0xff; 15], [0x80; 15]];
let mut x = [0u8; 15];
for i in 0..200u32 {
for (j, b) in x.iter_mut().enumerate() {
*b = (i.wrapping_mul(2654435761) >> (j % 24)) as u8 ^ (j as u8).wrapping_mul(37);
}
cases.push(x);
}
for w in 4..=6 {
for bytes in &cases {
let digits = signed_digits_w(bytes, w);
let n = digit_count(bytes.len(), w);
let half = 1i128 << (w - 1);
let mut sum = 0i128;
for (i, d) in digits.iter().take(n).enumerate() {
let d = *d as i128;
if i + 1 == n {
assert!(0 <= d && d <= half, "top digit {d} at width {w}");
} else {
assert!(-half <= d && d < half, "digit {d} at width {w}");
}
sum += d << (w * i);
}
assert!(digits[n..].iter().all(|d| *d == 0));
let want = bytes.iter().fold(0i128, |acc, b| (acc << 8) | *b as i128);
assert_eq!(sum, want, "width {w}");
}
}
}
}