use super::arith::Field;
use super::gentable::HasGeneratorTable;
use super::point::{wnaf_vartime, AffinePoint, Curve, Point};
pub(crate) const G_WIDTH: u32 = 8;
pub const G_ODD: usize = 1 << (G_WIDTH - 2);
pub(crate) const Q_WIDTH: u32 = 5;
pub(crate) const Q_ODD: usize = 1 << (Q_WIDTH - 2);
pub(crate) fn odd_multiples_affine_vartime<C: Curve, const K: usize>(
base: &Point<C>,
) -> [AffinePoint<C>; K] {
let twice = base.double();
let mut points = [*base; K];
for i in 1..K {
points[i] = points[i - 1].add_vartime(&twice);
}
normalize_vartime(&points)
}
fn normalize_vartime<C: Curve, const K: usize>(points: &[Point<C>; K]) -> [AffinePoint<C>; K] {
let mut prefix = [C::Field::ONE; K];
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 out = [AffinePoint {
x: C::Field::ZERO,
y: C::Field::ZERO,
}; K];
for i in (0..K).rev() {
let z_inv = inv.mul(&prefix[i]);
inv = inv.mul(&points[i].z());
let z_inv2 = z_inv.square();
out[i] = AffinePoint {
x: points[i].x().mul(&z_inv2),
y: points[i].y().mul(&z_inv2.mul(&z_inv)),
};
}
out
}
pub(crate) fn mul_double_vartime<C: HasGeneratorTable>(
a: &C::Scalar,
p: &Point<C>,
b: &C::Scalar,
) -> Point<C> {
if bool::from(p.is_identity()) {
return Point::mul_generator_vartime(a);
}
let g = C::odd_generator_multiples();
let q: [AffinePoint<C>; Q_ODD] = odd_multiples_affine_vartime(p);
let (a_bytes, b_bytes) = (a.to_bytes(), b.to_bytes());
let (g_naf, g_len) = wnaf_vartime(a_bytes.as_ref(), G_WIDTH);
let (q_naf, q_len) = wnaf_vartime(b_bytes.as_ref(), Q_WIDTH);
let len = g_len.max(q_len);
let (g_naf, q_naf) = (&g_naf[..len], &q_naf[..len]);
if let Some(r) = C::mul_double_vartime_fast(g, &q, g_naf, q_naf) {
return r;
}
joint_vartime(g, &q, g_naf, q_naf)
}
pub(crate) fn joint_vartime<C: Curve>(
g: &[AffinePoint<C>],
q: &[AffinePoint<C>],
g_naf: &[i8],
q_naf: &[i8],
) -> Point<C> {
debug_assert_eq!(g_naf.len(), q_naf.len());
let mut acc = Point::identity();
let mut started = false;
for i in (0..g_naf.len()).rev() {
if started {
acc = acc.double();
}
for (naf, table) in [(g_naf, g), (q_naf, q)] {
let digit = naf[i];
if digit != 0 {
let entry = &table[(digit.unsigned_abs() >> 1) as usize];
let y = if digit > 0 { entry.y } else { entry.y.neg() };
acc = acc.add_affine_vartime(&entry.x, &y);
started = true;
}
}
}
acc
}
#[cfg(test)]
mod tests {
use super::*;
use crate::p256::P256;
use crate::p384::P384;
use crate::p521::P521;
fn scalars<C: Curve>(count: usize) -> std::vec::Vec<C::Scalar> {
let one = C::Scalar::ONE;
let minus_one = C::Scalar::ZERO.sub(&one);
let two = one.add(&one);
let mut out = std::vec![C::Scalar::ZERO, one, two, minus_one, minus_one.sub(&one)];
let mut p = one;
for k in 0..C::ORDER_BITS {
if k % 37 == 0 || k + 3 >= C::ORDER_BITS {
out.push(p);
out.push(p.sub(&one));
out.push(p.neg());
}
p = p.add(&p);
}
let mut state = 0x9e37_79b9_7f4a_7c15u64;
let mut bytes = std::vec![0u8; C::SCALAR_BYTES];
let target = out.len() + count;
while out.len() < target {
for b in bytes.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
out.push(C::scalar_reduce_slice(&bytes));
}
out
}
fn reference<C: HasGeneratorTable>(a: &C::Scalar, q: &Point<C>, b: &C::Scalar) -> Point<C> {
Point::mul_generator(a).add(&q.mul_scalar(b))
}
fn agrees<C: HasGeneratorTable>(count: usize) {
let ks = scalars::<C>(count);
let g = Point::<C>::generator();
let mut points = std::vec![g, g.neg(), g.double(), g.double().neg()];
for k in ks.iter().skip(ks.len() - 3) {
points.push(Point::mul_generator(k));
}
for (i, q) in points.iter().enumerate() {
let offset = (i * count) % ks.len();
for (j, a) in ks.iter().enumerate().cycle().skip(offset).take(count) {
let b_list = [
*a,
a.neg(),
ks[(j * 7 + i) % ks.len()],
ks[(j + 1) % ks.len()],
];
for b in b_list {
let want = reference::<C>(a, q, &b);
let got = mul_double_vartime::<C>(a, q, &b);
assert!(bool::from(got.ct_eq(&want)), "{} a={a:?} b={b:?}", C::NAME);
let old = Point::<C>::mul_double_vartime(&g, a, q, &b);
assert!(bool::from(old.ct_eq(&want)), "old chain");
let gt = C::odd_generator_multiples();
let qt: [AffinePoint<C>; Q_ODD] = odd_multiples_affine_vartime(q);
let (ab, bb) = (a.to_bytes(), b.to_bytes());
let (gn, gl) = wnaf_vartime(ab.as_ref(), G_WIDTH);
let (qn, ql) = wnaf_vartime(bb.as_ref(), Q_WIDTH);
let len = gl.max(ql);
let generic = joint_vartime(gt, &qt, &gn[..len], &qn[..len]);
assert!(bool::from(generic.ct_eq(&want)), "generic chain");
}
}
}
for a in ks.iter().take(8) {
let id = Point::<C>::identity();
let got = mul_double_vartime::<C>(a, &id, &ks[3]);
assert!(bool::from(got.ct_eq(&Point::mul_generator(a))));
}
}
#[test]
fn the_joint_chain_agrees_with_the_constant_time_arithmetic() {
agrees::<P256>(40);
agrees::<P384>(16);
agrees::<P521>(6);
}
#[test]
fn the_generator_table_holds_odd_multiples() {
fn check<C: HasGeneratorTable>() {
let t = C::odd_generator_multiples();
let g = Point::<C>::generator();
let mut want = g;
let twice = g.double();
for (k, entry) in t.iter().enumerate() {
assert!(
bool::from(Point::from_affine(entry).ct_eq(&want)),
"{} entry {k}",
C::NAME
);
want = want.add(&twice);
}
}
check::<P256>();
check::<P384>();
check::<P521>();
}
#[test]
fn the_carry_recoding_is_the_subtraction_recoding() {
use crate::nist::point::wnaf_by_subtraction;
let mut state = 0x2545_f491_4f6c_dd1du64;
for len in [32usize, 48, 66, 5, 1] {
let mut cases = std::vec![
std::vec![0u8; len],
std::vec![0xff; len],
std::vec![0x80; len]
];
for i in 0..len * 8 {
let mut b = std::vec![0u8; len];
b[len - 1 - i / 8] = 1 << (i % 8);
cases.push(b.clone());
for (j, x) in b.iter_mut().enumerate() {
let lo = (len - 1 - j) * 8;
for bit in 0..8 {
if lo + bit <= i {
*x |= 1 << bit;
}
}
}
cases.push(b);
}
for _ in 0..300 {
let mut b = std::vec![0u8; len];
for x in b.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*x = (state >> 24) as u8;
}
cases.push(b);
}
for bytes in &cases {
for w in 2..=8 {
let (got, got_len) = wnaf_vartime(bytes, w);
let (want, want_len) = wnaf_by_subtraction(bytes, w);
assert_eq!(got_len, want_len, "{bytes:02x?} width {w}");
assert!(got == want, "{bytes:02x?} width {w}");
}
}
}
}
#[test]
fn the_non_adjacent_forms_sum_to_the_scalar() {
fn check<C: Curve>() {
for k in scalars::<C>(64) {
let bytes = k.to_bytes();
for w in [Q_WIDTH, G_WIDTH] {
let (naf, len) = wnaf_vartime(bytes.as_ref(), w);
let bound = (1i16 << (w - 1)) - 1;
let mut last: Option<usize> = None;
let mut sum = C::Scalar::ZERO;
for i in (0..len).rev() {
sum = sum.add(&sum);
let d = naf[i] as i16;
if d != 0 {
assert!(d % 2 != 0 && -bound <= d && d <= bound, "digit {d}");
if let Some(j) = last {
assert!(j - i >= w as usize, "digits {j} and {i} at width {w}");
}
last = Some(i);
let mut m = C::Scalar::ZERO;
for _ in 0..d.unsigned_abs() {
m = m.add(&C::Scalar::ONE);
}
sum = if d > 0 { sum.add(&m) } else { sum.sub(&m) };
}
}
assert!(naf[len..].iter().all(|d| *d == 0));
assert!(sum == k, "{} width {w}", C::NAME);
}
}
}
check::<P256>();
check::<P384>();
}
}