use super::field::Field;
pub(super) type Coordinates<F> = (<F as Field>::E, <F as Field>::E);
pub(super) trait Group {
type F: Field;
type Proj: Clone;
type Cached: Clone;
const CACHED: usize;
fn field(&self) -> &Self::F;
fn identity(&self) -> Self::Proj;
fn is_identity(&self, p: &Self::Proj) -> bool;
fn load(&self, x: &[u64]) -> Self::Proj;
fn store(&self, p: &Self::Proj, r: &mut [u64]);
fn neg(&self, p: &Self::Proj) -> Self::Proj;
fn double(&self, p: &Self::Proj) -> Self::Proj;
fn add(&self, p: &Self::Proj, q: &Self::Proj) -> Self::Proj;
fn equal(&self, p: &Self::Proj, q: &Self::Proj) -> bool;
fn cache(&self, p: &[Self::Proj]) -> Vec<Self::Cached>;
#[inline]
fn cache_affine(&self, p: &[Self::Proj]) -> Vec<Self::Cached> {
self.cache(p)
}
fn add_cached(&self, p: &Self::Proj, c: &Self::Cached) -> Self::Proj;
fn neg_cached(&self, c: &Self::Cached) -> Self::Cached;
fn store_cached(&self, c: &Self::Cached, r: &mut [u64]);
fn load_cached(&self, x: &[u64]) -> Self::Cached;
fn of_affine(&self, x: &<Self::F as Field>::E, y: &<Self::F as Field>::E) -> Self::Proj;
fn affine_all(&self, p: &[Self::Proj]) -> Vec<Option<Coordinates<Self::F>>>;
#[inline]
fn double_n(&self, p: &mut Self::Proj, n: usize) {
for _ in 0..n {
*p = self.double(p);
}
}
#[inline]
fn sub_cached(&self, p: &Self::Proj, c: &Self::Cached) -> Self::Proj {
self.add_cached(p, &self.neg_cached(c))
}
fn mul(&self, p: &Self::Proj, k: &[u64], negative: bool) -> Self::Proj {
mul(self, p, k, negative)
}
}
pub(super) fn bit_length(k: &[u64]) -> usize {
match k.iter().rposition(|&l| l != 0) {
Some(i) => 64 * i + 64 - k[i].leading_zeros() as usize,
None => 0,
}
}
#[inline(always)]
fn bits_at(k: &[u64], at: usize, n: u32) -> u64 {
let (l, s) = (at / 64, at % 64);
let low = k.get(l).copied().unwrap_or(0) >> s;
let high = if s == 0 {
0
} else {
k.get(l + 1).copied().unwrap_or(0) << (64 - s)
};
(low | high) & ((1 << n) - 1)
}
pub(super) fn window(bits: usize) -> u32 {
match bits {
0..=23 => 2,
24..=75 => 3,
76..=199 => 4,
200..=639 => 5,
640..=1279 => 6,
1280..=3071 => 7,
_ => 8,
}
}
pub(super) fn wnaf(k: &[u64], w: u32) -> Vec<i8> {
let len = bit_length(k) + 1;
let mut digits = vec![0i8; len];
let width = 1u64 << w;
let mut carry = 0;
let mut at = 0;
while at < len {
let d = carry + bits_at(k, at, w);
if d & 1 == 0 {
at += 1;
continue;
}
if d < width / 2 {
carry = 0;
digits[at] = d as i8;
} else {
carry = 1;
digits[at] = (d as i64 - width as i64) as i8;
}
at += w as usize;
}
digits
}
pub(super) fn odd_multiples<G: Group + ?Sized>(g: &G, p: &G::Proj, n: usize) -> Vec<G::Proj> {
let mut table = Vec::with_capacity(n);
table.push(p.clone());
if n > 1 {
let two = g.double(p);
for i in 1..n {
let next = g.add(&table[i - 1], &two);
table.push(next);
}
}
table
}
pub(super) fn mul<G: Group + ?Sized>(g: &G, p: &G::Proj, k: &[u64], negative: bool) -> G::Proj {
let bits = bit_length(k);
if bits == 0 || g.is_identity(p) {
return g.identity();
}
let w = window(bits);
let digits = wnaf(k, w);
let multiples = odd_multiples(g, p, 1 << (w - 2));
let table = g.cache(&multiples);
let mut i = digits.len();
while digits[i - 1] == 0 {
i -= 1;
}
i -= 1;
let mut r = multiples[(digits[i] as usize - 1) / 2].clone();
let mut zeros = 0;
while i > 0 {
i -= 1;
zeros += 1;
let d = digits[i];
if d != 0 {
g.double_n(&mut r, zeros);
zeros = 0;
r = if d > 0 {
g.add_cached(&r, &table[(d as usize - 1) / 2])
} else {
g.sub_cached(&r, &table[((-d) as usize - 1) / 2])
};
}
}
g.double_n(&mut r, zeros);
if negative { g.neg(&r) } else { r }
}
const PIPPENGER: usize = 64;
pub(super) fn multi_mul<G: Group + ?Sized>(g: &G, terms: &[(&[u64], bool, G::Proj)]) -> G::Proj {
if terms.len() < PIPPENGER {
straus(g, terms)
} else {
pippenger(g, terms)
}
}
fn straus<G: Group + ?Sized>(g: &G, terms: &[(&[u64], bool, G::Proj)]) -> G::Proj {
let mut digits = Vec::with_capacity(terms.len());
let mut all = Vec::new();
let mut start = Vec::with_capacity(terms.len());
let mut len = 0;
for (k, negative, p) in terms {
let bits = bit_length(k);
let w = window(bits);
start.push(all.len());
let base = if *negative { g.neg(p) } else { p.clone() };
all.extend(odd_multiples(g, &base, 1 << (w - 2)));
let d = wnaf(k, w);
len = len.max(d.len());
digits.push(d);
}
let table = g.cache(&all);
let mut r = g.identity();
let mut zeros = 0;
for i in (0..len).rev() {
zeros += 1;
let digit = |d: &Vec<i8>| d.get(i).copied().unwrap_or(0);
if digits.iter().all(|d| digit(d) == 0) {
continue;
}
g.double_n(&mut r, zeros);
zeros = 0;
for (t, d) in digits.iter().enumerate() {
let d = digit(d);
if d > 0 {
r = g.add_cached(&r, &table[start[t] + (d as usize - 1) / 2]);
} else if d < 0 {
r = g.sub_cached(&r, &table[start[t] + ((-d) as usize - 1) / 2]);
}
}
}
g.double_n(&mut r, zeros);
r
}
fn bucket_bits(n: usize) -> u32 {
(usize::BITS - n.leading_zeros()).saturating_sub(2).max(2)
}
#[inline(always)]
fn signed_digit(k: &[u64], at: usize, c: u32, carry: &mut u64) -> i64 {
let d = bits_at(k, at, c) + *carry;
if d > 1 << (c - 1) {
*carry = 1;
d as i64 - (1i64 << c)
} else {
*carry = 0;
d as i64
}
}
fn pippenger<G: Group + ?Sized>(g: &G, terms: &[(&[u64], bool, G::Proj)]) -> G::Proj {
let c = bucket_bits(terms.len());
let bits = terms.iter().map(|t| bit_length(t.0)).max().unwrap_or(0);
let windows = (bits + 1).div_ceil(c as usize);
let based: Vec<G::Proj> = terms
.iter()
.map(|(_, negative, p)| if *negative { g.neg(p) } else { p.clone() })
.collect();
let table = g.cache_affine(&based);
let mut digits = vec![0i32; windows * terms.len()];
for (t, (k, _, _)) in terms.iter().enumerate() {
let mut carry = 0;
for w in 0..windows {
digits[w * terms.len() + t] = signed_digit(k, w * c as usize, c, &mut carry) as i32;
}
}
let mut r = g.identity();
let mut buckets = vec![g.identity(); 1 << (c - 1)];
for w in (0..windows).rev() {
g.double_n(&mut r, c as usize);
for b in buckets.iter_mut() {
*b = g.identity();
}
for (t, entry) in table.iter().enumerate() {
let d = digits[w * terms.len() + t];
if d > 0 {
let b = (d - 1) as usize;
buckets[b] = g.add_cached(&buckets[b], entry);
} else if d < 0 {
let b = (-d - 1) as usize;
buckets[b] = g.sub_cached(&buckets[b], entry);
}
}
let mut running = g.identity();
let mut sum = g.identity();
for b in buckets.iter().rev() {
running = g.add(&running, b);
sum = g.add(&sum, &running);
}
r = g.add(&r, &sum);
}
r
}
pub struct Table {
pub(super) limbs: Vec<u64>,
pub(super) width: u32,
pub(super) bits: usize,
}
fn table_width(bits: usize) -> u32 {
match bits {
0..=63 => 4,
64..=127 => 5,
128..=191 => 6,
_ => 8,
}
}
fn cached_limbs<G: Group + ?Sized>(g: &G) -> usize {
G::CACHED * g.field().limbs() + 1
}
pub(super) fn prepare<G: Group + ?Sized>(g: &G, p: &G::Proj, bits: usize) -> Table {
let width = table_width(bits);
let windows = (bits + 1).div_ceil(width as usize);
let digits = 1usize << (width - 1);
let mut all = Vec::with_capacity(windows * digits);
let mut base = p.clone();
for _ in 0..windows {
let mut m = base.clone();
all.push(m.clone());
for _ in 1..digits {
m = g.add(&m, &base);
all.push(m.clone());
}
base = g.double(&m);
}
let size = cached_limbs(g);
let mut limbs = vec![0; all.len() * size];
for (c, r) in g.cache_affine(&all).iter().zip(limbs.chunks_mut(size)) {
g.store_cached(c, r);
}
Table { limbs, width, bits }
}
pub(super) fn prepared_mul<G: Group + ?Sized>(
g: &G,
table: &Table,
k: &[u64],
negative: bool,
) -> G::Proj {
let width = table.width;
let windows = (table.bits + 1).div_ceil(width as usize);
let digits = 1usize << (width - 1);
let size = cached_limbs(g);
let mut r = g.identity();
let mut carry = 0;
for w in 0..windows {
let d = signed_digit(k, w * width as usize, width, &mut carry);
if d != 0 {
let at = (w * digits + d.unsigned_abs() as usize - 1) * size;
let c = g.load_cached(&table.limbs[at..at + size]);
r = if d > 0 {
g.add_cached(&r, &c)
} else {
g.sub_cached(&r, &c)
};
}
}
if negative { g.neg(&r) } else { r }
}