use ic_core::ct::Choice;
use ic_core::Zeroize;
use super::arith::{masks_for, select_ct, Field};
use super::gentable::{
comb_span, digit_count, signed_digits_w, AffineWindow, ENTRIES, TEETH, WIDTH,
};
use super::ifma_kernels as k;
use super::point::{Curve, Point};
#[derive(Clone, Copy)]
pub(crate) struct Ifma(());
impl Ifma {
#[inline]
pub(crate) fn get() -> Option<Ifma> {
if crate::avx512::ifma() {
Some(Ifma(()))
} else {
None
}
}
}
#[repr(C, align(32))]
#[derive(Clone, Copy)]
pub struct Lanes<const L: usize>([[u64; 4]; L]);
#[repr(C, align(32))]
pub(crate) struct Spill<const N: usize>([u64; N]);
pub(crate) trait Vector: Copy {
fn zero() -> Self;
fn set(&mut self, lane: usize, limbs: &[u64]);
fn get(&self, lane: usize, out: &mut [u64]);
fn words_mut(&mut self) -> &mut [u64];
fn ptr(&self) -> *const u64;
fn mut_ptr(&mut self) -> *mut u64;
unsafe fn scan(w: &[Self; ENTRIES], masks: &[u64; ENTRIES], out: &mut Self);
}
impl<const L: usize> Vector for Lanes<L> {
fn zero() -> Self {
Lanes([[0; 4]; L])
}
fn set(&mut self, lane: usize, limbs: &[u64]) {
for (row, x) in self.0.iter_mut().zip(limbs) {
row[lane] = *x;
}
}
fn get(&self, lane: usize, out: &mut [u64]) {
for (row, x) in self.0.iter().zip(out.iter_mut()) {
*x = row[lane];
}
}
fn words_mut(&mut self) -> &mut [u64] {
self.0.as_flattened_mut()
}
fn ptr(&self) -> *const u64 {
self.0.as_ptr().cast()
}
fn mut_ptr(&mut self) -> *mut u64 {
self.0.as_mut_ptr().cast()
}
unsafe fn scan(w: &[Self; ENTRIES], masks: &[u64; ENTRIES], out: &mut Self) {
unsafe { scan_avx2(w, masks, out) }
}
}
#[target_feature(enable = "avx2")]
unsafe fn scan_avx2<const L: usize>(
w: &[Lanes<L>; ENTRIES],
masks: &[u64; ENTRIES],
out: &mut Lanes<L>,
) {
use core::arch::x86_64::*;
let mut acc = [_mm256_setzero_si256(); L];
for (entry, &m) in w.iter().zip(masks) {
let mv = _mm256_set1_epi64x(m as i64);
for (a, row) in acc.iter_mut().zip(entry.0.iter()) {
let x = unsafe { _mm256_load_si256(row.as_ptr().cast()) };
*a = _mm256_or_si256(*a, _mm256_and_si256(x, mv));
}
}
for (row, a) in out.0.iter_mut().zip(acc) {
unsafe { _mm256_store_si256(row.as_mut_ptr().cast(), a) };
}
}
pub(crate) trait Scratch {
fn zero() -> Self;
fn mut_ptr(&mut self) -> *mut u64;
fn words_mut(&mut self) -> &mut [u64];
}
impl<const N: usize> Scratch for Spill<N> {
fn zero() -> Self {
Spill([0; N])
}
fn mut_ptr(&mut self) -> *mut u64 {
self.0.as_mut_ptr()
}
fn words_mut(&mut self) -> &mut [u64] {
&mut self.0
}
}
pub(crate) type Kernel = unsafe fn(*const u64, *const u64, *const u64, *mut u64, *mut u64);
pub(crate) unsafe trait LaneCurve: Curve {
type V: Vector;
type S: Scratch;
const LIMBS: usize;
const DOUBLE: Kernel;
const ADD: Kernel;
const MADD: Kernel;
#[cfg(feature = "std")]
const MADD4: Kernel;
const FROM_FP: Kernel;
const TO_FP: Kernel;
#[cfg(feature = "std")]
const TO_FP4: Kernel;
fn to_limbs(x: &Self::Field, out: &mut [u64]);
fn from_limbs(limbs: &[u64]) -> Self::Field;
}
type Masks = [u64; 3];
const NONE: Masks = [0; 3];
struct Pair<V: Vector, const N: usize> {
v: [[V; N]; 2],
cur: usize,
}
impl<V: Vector, const N: usize> Pair<V, N> {
fn new() -> Self {
Pair {
v: [[V::zero(); N]; 2],
cur: 0,
}
}
fn cur(&self) -> &[V; N] {
&self.v[self.cur & 1]
}
fn step(&mut self) -> (*const u64, *mut u64) {
let i = self.cur & 1;
let base = self.v.as_mut_ptr();
self.cur ^= 1;
(
base.wrapping_add(i).cast_const().cast(),
base.wrapping_add(i ^ 1).cast(),
)
}
fn wipe(&mut self) {
for v in self.v.iter_mut().flatten() {
v.words_mut().zeroize();
}
}
}
#[inline(always)]
unsafe fn run<C: LaneCurve>(
_: Ifma,
f: Kernel,
a: *const u64,
b: *const u64,
m: &[u64],
o: *mut u64,
s: &mut C::S,
) {
unsafe { f(a, b, m.as_ptr(), o, s.mut_ptr()) }
}
struct Cleared(Ifma);
impl Drop for Cleared {
fn drop(&mut self) {
clear_vectors(self.0);
#[cfg(all(test, feature = "std"))]
cleared_probe::record();
}
}
#[cfg(all(test, feature = "std"))]
pub(crate) mod cleared_probe {
use core::cell::Cell;
std::thread_local! {
static DROPS: Cell<usize> = const { Cell::new(0) };
}
pub(crate) fn record() {
DROPS.with(|c| c.set(c.get() + 1));
}
pub(crate) fn take() -> usize {
DROPS.with(|c| c.replace(0))
}
}
#[inline(always)]
fn clear_vectors(_: Ifma) {
unsafe { clear_vectors_unchecked() }
}
#[inline(always)]
unsafe fn clear_vectors_unchecked() {
unsafe {
core::arch::asm!(
"vzeroall",
out("xmm0") _, out("xmm1") _, out("xmm2") _, out("xmm3") _,
out("xmm4") _, out("xmm5") _, out("xmm6") _, out("xmm7") _,
out("xmm8") _, out("xmm9") _, out("xmm10") _, out("xmm11") _,
out("xmm12") _, out("xmm13") _, out("xmm14") _, out("xmm15") _,
options(nomem, nostack, preserves_flags),
);
}
}
fn load<C: LaneCurve>(t: Ifma, p: &Point<C>, s: &mut C::S) -> C::V {
let mut j = C::V::zero();
let mut limbs = [0u64; 8];
for (lane, x) in [p.x(), p.y(), p.z()].iter().enumerate() {
C::to_limbs(x, &mut limbs);
j.set(lane, &limbs[..C::LIMBS]);
}
let mut o = C::V::zero();
unsafe {
run::<C>(
t,
C::FROM_FP,
j.ptr(),
core::ptr::null(),
&NONE,
o.mut_ptr(),
s,
)
};
j.words_mut().zeroize();
o
}
fn store<C: LaneCurve>(t: Ifma, v: &C::V, s: &mut C::S) -> Point<C> {
let mut o = C::V::zero();
unsafe {
run::<C>(
t,
C::TO_FP,
v.ptr(),
core::ptr::null(),
&NONE,
o.mut_ptr(),
s,
)
};
let mut limbs = [0u64; 8];
let mut c = [C::Field::ZERO; 3];
for (lane, x) in c.iter_mut().enumerate() {
o.get(lane, &mut limbs[..C::LIMBS]);
*x = C::from_limbs(&limbs[..C::LIMBS]);
}
o.words_mut().zeroize();
limbs.zeroize();
Point::from_coordinates(c[0], c[1], c[2])
}
unsafe fn select<V: Vector>(w: &[V; ENTRIES], digit: i8, out: &mut V) -> (u64, u64) {
let negative = u64::from((digit as u8) >> 7);
let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u32;
let masks = masks_for::<ENTRIES>(magnitude, 1);
unsafe { V::scan(w, &masks, out) };
(negative.wrapping_neg(), zero_mask(digit))
}
fn zero_mask(digit: i8) -> u64 {
(u64::from(digit as u8).wrapping_sub(1) >> 63).wrapping_neg()
}
fn doublings<C: LaneCurve>(t: Ifma, acc: &mut Pair<C::V, 1>, s: &mut C::S) {
for _ in 0..WIDTH {
let (a, o) = acc.step();
unsafe { run::<C>(t, C::DOUBLE, a, core::ptr::null(), &NONE, o, s) };
}
}
pub(crate) fn mul_scalar<C: LaneCurve>(t: Ifma, p: &Point<C>, scalar: &C::Scalar) -> Point<C> {
let _cleared = Cleared(t);
let bytes = scalar.to_bytes();
let digits = signed_digits_w(bytes.as_ref(), WIDTH);
let n = digit_count(bytes.as_ref().len(), WIDTH);
let mut s = C::S::zero();
let base = load(t, p, &mut s);
let mut w = [C::V::zero(); ENTRIES];
w[0] = base;
for j in 1..ENTRIES / 2 {
let (done, rest) = w.split_at_mut(2 * j - 1);
unsafe {
run::<C>(
t,
C::DOUBLE,
done[j - 1].ptr(),
core::ptr::null(),
&NONE,
rest[0].mut_ptr(),
&mut s,
);
run::<C>(
t,
C::ADD,
rest[0].ptr(),
base.ptr(),
&NONE,
rest[1].mut_ptr(),
&mut s,
);
}
}
let (done, rest) = w.split_at_mut(ENTRIES - 1);
unsafe {
run::<C>(
t,
C::DOUBLE,
done[ENTRIES / 2 - 1].ptr(),
core::ptr::null(),
&NONE,
rest[0].mut_ptr(),
&mut s,
);
}
let mut acc = Pair::<C::V, 1>::new();
let mut e = C::V::zero();
let (neg, zero) = unsafe { select(&w, digits[n - 1], &mut e) };
let (a, o) = acc.step();
unsafe { run::<C>(t, C::ADD, a, e.ptr(), &[neg, zero, u64::MAX], o, &mut s) };
let mut acc_inf = zero;
for i in (1..n - 1).rev() {
let (neg, zero) = unsafe { select(&w, digits[i], &mut e) };
doublings::<C>(t, &mut acc, &mut s);
let (a, o) = acc.step();
unsafe { run::<C>(t, C::ADD, a, e.ptr(), &[neg, zero, acc_inf], o, &mut s) };
acc_inf &= zero;
}
doublings::<C>(t, &mut acc, &mut s);
let mut sum = store(t, &acc.cur()[0], &mut s);
Point::cmov(
&mut sum,
&Point::identity(),
Choice::from_u8((acc_inf & 1) as u8),
);
let (neg, _) = unsafe { select(&w, digits[0], &mut e) };
let mut entry = store(t, &e, &mut s);
entry.conditional_negate(Choice::from_u8((neg & 1) as u8));
let mut out = sum.add(&entry);
Point::cmov(&mut out, &Point::identity(), p.is_identity());
e.words_mut().zeroize();
for v in w.iter_mut() {
v.words_mut().zeroize();
}
acc.wipe();
s.words_mut().zeroize();
out
}
fn operand<C: LaneCurve>(x: &C::Field, y: &C::Field, q: &mut C::V) {
let mut limbs = [0u64; 8];
C::to_limbs(x, &mut limbs);
q.set(0, &limbs[..C::LIMBS]);
C::to_limbs(y, &mut limbs);
q.set(1, &limbs[..C::LIMBS]);
limbs.zeroize();
}
pub(crate) fn comb_mul<C: LaneCurve>(
t: Ifma,
windows: &[AffineWindow<C, ENTRIES>; TEETH],
scalar: &C::Scalar,
) -> Point<C> {
let _cleared = Cleared(t);
let bytes = scalar.to_bytes();
let digits = signed_digits_w(bytes.as_ref(), WIDTH);
let n = digit_count(C::SCALAR_BYTES, WIDTH);
let m = comb_span::<C>();
let last = (0..m)
.rev()
.find(|&j| WIDTH * (n - j) >= C::ORDER_BITS)
.unwrap_or(0);
let mut s = C::S::zero();
let mut acc = Pair::<C::V, 1>::new();
let mut q = C::V::zero();
let mut acc_inf = u64::MAX;
for j in (last + 1..m).rev() {
if j + 1 < m {
doublings::<C>(t, &mut acc, &mut s);
}
for (c, window) in windows.iter().enumerate() {
let i = c * m + j;
if i >= n {
continue;
}
let (x, y) = window.select(digits[i]);
operand::<C>(&x, &y, &mut q);
let zero = zero_mask(digits[i]);
let (a, o) = acc.step();
unsafe { run::<C>(t, C::MADD, a, q.ptr(), &[0, zero, acc_inf], o, &mut s) };
acc_inf &= zero;
}
}
if last + 1 < m {
doublings::<C>(t, &mut acc, &mut s);
}
let mut p = store(t, &acc.cur()[0], &mut s);
Point::cmov(
&mut p,
&Point::identity(),
Choice::from_u8((acc_inf & 1) as u8),
);
for j in (0..=last).rev() {
if j < last {
for _ in 0..WIDTH {
p = p.double();
}
}
for (c, window) in windows.iter().enumerate() {
let i = c * m + j;
if i < n {
p = window.add_to(&p, digits[i], true);
}
}
}
q.words_mut().zeroize();
acc.wipe();
s.words_mut().zeroize();
p
}
#[cfg(feature = "std")]
pub(crate) fn table_mul<C: LaneCurve, const E: usize>(
t: Ifma,
windows: &super::gentable::Windows<C, E>,
scalar: &C::Scalar,
) -> Point<C> {
let _cleared = Cleared(t);
let window = |i| super::gentable::window(windows, i);
let width = super::gentable::width::<E>();
let bytes = scalar.to_bytes();
let digits = signed_digits_w(bytes.as_ref(), width);
let n = digit_count(bytes.as_ref().len(), width);
let first_complete = (0..n)
.find(|&i| width * (i + 1) >= C::ORDER_BITS)
.unwrap_or(n);
let mut s = C::S::zero();
let mut acc = Pair::<C::V, 3>::new();
let mut q = [C::V::zero(); 2];
let mut acc_inf = [u64::MAX; 4];
let mut limbs = [0u64; 8];
for start in (0..first_complete).step_by(4) {
let mut masks = [[0u64; 4]; 3];
for (lane, inf) in acc_inf.iter_mut().enumerate() {
let i = start + lane;
let keep = if i < first_complete {
let (x, y) = window(i).select(digits[i]);
C::to_limbs(&x, &mut limbs);
q[0].set(lane, &limbs[..C::LIMBS]);
C::to_limbs(&y, &mut limbs);
q[1].set(lane, &limbs[..C::LIMBS]);
zero_mask(digits[i])
} else {
u64::MAX
};
masks[1][lane] = keep;
masks[2][lane] = *inf;
*inf &= keep;
}
let (a, o) = acc.step();
unsafe {
run::<C>(
t,
C::MADD4,
a,
q.as_ptr().cast(),
masks.as_flattened(),
o,
&mut s,
)
};
}
let mut f = [C::V::zero(); 3];
unsafe {
run::<C>(
t,
C::TO_FP4,
acc.cur().as_ptr().cast(),
core::ptr::null(),
&NONE,
f.as_mut_ptr().cast(),
&mut s,
)
};
let mut parts = [Point::<C>::identity(); 4];
for (lane, (p, inf)) in parts.iter_mut().zip(acc_inf).enumerate() {
let mut c = [C::Field::ZERO; 3];
for (x, v) in c.iter_mut().zip(f.iter()) {
v.get(lane, &mut limbs[..C::LIMBS]);
*x = C::from_limbs(&limbs[..C::LIMBS]);
}
*p = Point::from_coordinates(c[0], c[1], c[2]);
Point::cmov(p, &Point::identity(), Choice::from_u8((inf & 1) as u8));
}
let mut sum = parts[0]
.add_unequal(&parts[1])
.add_unequal(&parts[2].add_unequal(&parts[3]));
for (i, digit) in digits.iter().enumerate().take(n).skip(first_complete) {
sum = window(i).add_to(&sum, *digit, true);
}
for v in q.iter_mut().chain(f.iter_mut()) {
v.words_mut().zeroize();
}
acc.wipe();
limbs.zeroize();
s.words_mut().zeroize();
sum
}
#[inline(always)]
fn pack52(l: &[u64; 5]) -> [u64; 5] {
let mut out = [0u64; 5];
let mut acc = u128::from(l[0]) + (u128::from(l[1]) << 52);
out[0] = acc as u64;
acc = (acc >> 64) + (u128::from(l[2]) << 40);
out[1] = acc as u64;
acc = (acc >> 64) + (u128::from(l[3]) << 28);
out[2] = acc as u64;
acc = (acc >> 64) + (u128::from(l[4]) << 16);
out[3] = acc as u64;
out[4] = (acc >> 64) as u64;
out
}
#[inline(always)]
fn pack48(l: &[u64; 8]) -> [u64; 7] {
let mut out = [0u64; 7];
let mut acc = u128::from(l[0]) + (u128::from(l[1]) << 48);
out[0] = acc as u64;
acc = (acc >> 64) + (u128::from(l[2]) << 32);
out[1] = acc as u64;
acc = (acc >> 64) + (u128::from(l[3]) << 16);
out[2] = acc as u64;
acc = (acc >> 64) + u128::from(l[4]) + (u128::from(l[5]) << 48);
out[3] = acc as u64;
acc = (acc >> 64) + (u128::from(l[6]) << 32);
out[4] = acc as u64;
acc = (acc >> 64) + (u128::from(l[7]) << 16);
out[5] = acc as u64;
out[6] = (acc >> 64) as u64;
out
}
#[inline(always)]
fn reduce<const W: usize, const TIMES: usize>(mut v: [u64; W], p: &[u64]) -> [u64; W] {
for _ in 0..TIMES {
let mut d = [0u64; W];
let mut borrow = 0u64;
for (i, (di, vi)) in d.iter_mut().zip(v.iter()).enumerate() {
let pi = if i + 1 < W { p[i] } else { 0 };
let (x, b1) = vi.overflowing_sub(pi);
let (x, b2) = x.overflowing_sub(borrow);
*di = x;
borrow = u64::from(b1 | b2);
}
v = select_ct(borrow.wrapping_sub(1), d, v);
}
v
}
fn to_limbs_impl(w: &[u64], out: &mut [u64], limbs: usize, width: usize) {
let mask = (1u64 << width) - 1;
for (k, o) in out.iter_mut().enumerate().take(limbs) {
let bit = width * k;
let (word, shift) = (bit / 64, bit % 64);
let lo = w[word] >> shift;
let hi = match w.get(word + 1) {
Some(next) if shift + width > 64 => next << (64 - shift),
_ => 0,
};
*o = if k + 1 == limbs {
lo | hi
} else {
(lo | hi) & mask
};
}
}
macro_rules! lane_curve {
($curve:ty, $field:path, $p:expr, $words:literal, $limbs:expr, $width:literal, $spill:expr,
$subtractions:expr, $pack:path,
$double:path, $add:path, $madd:path, $madd4:path, $from_fp:path, $to_fp:path, $to_fp4:path) => {
unsafe impl LaneCurve for $curve {
type V = Lanes<{ $limbs }>;
type S = Spill<{ $spill }>;
const LIMBS: usize = $limbs;
const DOUBLE: Kernel = $double;
const ADD: Kernel = $add;
const MADD: Kernel = $madd;
#[cfg(feature = "std")]
const MADD4: Kernel = $madd4;
const FROM_FP: Kernel = $from_fp;
const TO_FP: Kernel = $to_fp;
#[cfg(feature = "std")]
const TO_FP4: Kernel = $to_fp4;
fn to_limbs(x: &Self::Field, out: &mut [u64]) {
to_limbs_impl(&x.0, out, $limbs, $width);
}
fn from_limbs(limbs: &[u64]) -> Self::Field {
let mut l = [0u64; $limbs];
l.copy_from_slice(&limbs[..$limbs]);
let v = reduce::<{ $words + 1 }, { $subtractions }>($pack(&l), &$p);
l.zeroize();
let mut out = [0u64; $words];
out.copy_from_slice(&v[..$words]);
$field(out)
}
}
};
}
lane_curve!(
crate::p256::P256,
crate::p256::Fp,
crate::nist::p256_field::P,
4,
k::P256_LIMBS,
52,
k::P256_SPILL_WORDS,
k::P256_TO_FP_SUBTRACTIONS,
pack52,
k::p256_double,
k::p256_add,
k::p256_madd,
k::p256_madd4,
k::p256_from_fp,
k::p256_to_fp,
k::p256_to_fp4
);
lane_curve!(
crate::p384::P384,
crate::p384::Fp,
crate::p384::P,
6,
k::P384_LIMBS,
48,
k::P384_SPILL_WORDS,
k::P384_TO_FP_SUBTRACTIONS,
pack48,
k::p384_double,
k::p384_add,
k::p384_madd,
k::p384_madd4,
k::p384_from_fp,
k::p384_to_fp,
k::p384_to_fp4
);
#[cfg(feature = "ct-probes")]
pub mod probes {
use super::*;
pub use super::Lanes;
pub type P256 = Lanes<{ k::P256_LIMBS }>;
pub type P384 = Lanes<{ k::P384_LIMBS }>;
pub const P256_SPILL: usize = k::P256_SPILL_WORDS;
pub const P384_SPILL: usize = k::P384_SPILL_WORDS;
macro_rules! kernels {
($($name:ident = $kernel:path, $v:ty, $spill:expr;)*) => {$(
#[inline(always)]
pub unsafe fn $name(a: &[$v; 3], b: &[$v; 2], m: &[u64; 12], o: &mut [$v; 3], t: &mut [u64; $spill]) {
unsafe { $kernel(a.as_ptr().cast(), b.as_ptr().cast(), m.as_ptr(), o.as_mut_ptr().cast(), t.as_mut_ptr()) }
unsafe { clear_vectors_unchecked() }
}
)*};
}
kernels! {
p256_double = k::p256_double, P256, P256_SPILL;
p256_add = k::p256_add, P256, P256_SPILL;
p256_madd = k::p256_madd, P256, P256_SPILL;
p256_madd4 = k::p256_madd4, P256, P256_SPILL;
p256_from_fp = k::p256_from_fp, P256, P256_SPILL;
p256_to_fp = k::p256_to_fp, P256, P256_SPILL;
p256_to_fp4 = k::p256_to_fp4, P256, P256_SPILL;
p384_double = k::p384_double, P384, P384_SPILL;
p384_add = k::p384_add, P384, P384_SPILL;
p384_madd = k::p384_madd, P384, P384_SPILL;
p384_madd4 = k::p384_madd4, P384, P384_SPILL;
p384_from_fp = k::p384_from_fp, P384, P384_SPILL;
p384_to_fp = k::p384_to_fp, P384, P384_SPILL;
p384_to_fp4 = k::p384_to_fp4, P384, P384_SPILL;
}
#[inline(always)]
pub unsafe fn p256_select(w: &[P256; ENTRIES], digit: i8, out: &mut P256) -> [u64; 2] {
let (neg, zero) = unsafe { select(w, digit, out) };
[neg, zero]
}
#[inline(always)]
pub unsafe fn p384_select(w: &[P384; ENTRIES], digit: i8, out: &mut P384) -> [u64; 2] {
let (neg, zero) = unsafe { select(w, digit, out) };
[neg, zero]
}
#[inline(always)]
pub fn p256_from_limbs(limbs: &[u64; 5]) -> [u64; 4] {
<crate::p256::P256 as LaneCurve>::from_limbs(limbs).0
}
#[inline(always)]
pub fn p384_from_limbs(limbs: &[u64; 8]) -> [u64; 6] {
<crate::p384::P384 as LaneCurve>::from_limbs(limbs).0
}
}
#[cfg(test)]
mod tests;