use core::arch::asm;
use core::mem::MaybeUninit;
const P1: u64 = 0x0000_0000_ffff_ffff;
const P3: u64 = 0xffff_ffff_0000_0001;
macro_rules! reduce_tail {
() => {
concat!(
"mov rax, {t0}\n",
"shl rax, 32\n",
"mov rdx, {t0}\n",
"shr rdx, 32\n",
"mov {c}, {t0}\n",
"sub {c}, rax\n",
"sbb {t0}, rdx\n",
"add {t1}, rax\n",
"adc {t2}, rdx\n",
"adc {t3}, {c}\n",
"adc {t0}, 0\n",
"mov rax, {t1}\n",
"shl rax, 32\n",
"mov rdx, {t1}\n",
"shr rdx, 32\n",
"mov {c}, {t1}\n",
"sub {c}, rax\n",
"sbb {t1}, rdx\n",
"add {t2}, rax\n",
"adc {t3}, rdx\n",
"adc {t0}, {c}\n",
"adc {t1}, 0\n",
"mov rax, {t2}\n",
"shl rax, 32\n",
"mov rdx, {t2}\n",
"shr rdx, 32\n",
"mov {c}, {t2}\n",
"sub {c}, rax\n",
"sbb {t2}, rdx\n",
"add {t3}, rax\n",
"adc {t0}, rdx\n",
"adc {t1}, {c}\n",
"adc {t2}, 0\n",
"mov rax, {t3}\n",
"shl rax, 32\n",
"mov rdx, {t3}\n",
"shr rdx, 32\n",
"mov {c}, {t3}\n",
"sub {c}, rax\n",
"sbb {t3}, rdx\n",
"add {t0}, rax\n",
"adc {t1}, rdx\n",
"adc {t2}, {c}\n",
"adc {t3}, 0\n",
"xor {c:e}, {c:e}\n",
"add {t4}, {t0}\n",
"adc {t5}, {t1}\n",
"adc {t6}, {t2}\n",
"adc {t7}, {t3}\n",
"adc {c}, 0\n",
"mov rax, {t4}\n",
"mov rdx, {t5}\n",
"mov {t2}, {t6}\n",
"mov {t3}, {t7}\n",
"mov {t0:e}, {p1}\n",
"mov {t1}, {p3}\n",
"sub rax, -1\n",
"sbb rdx, {t0}\n",
"sbb {t2}, 0\n",
"sbb {t3}, {t1}\n",
"sbb {c}, 0\n",
"cmovc rax, {t4}\n",
"cmovc rdx, {t5}\n",
"cmovc {t2}, {t6}\n",
"cmovc {t3}, {t7}\n",
)
};
}
#[inline]
pub(crate) fn has_adx() -> bool {
#[cfg(feature = "std")]
{
use core::sync::atomic::{AtomicU8, Ordering};
static CACHED: AtomicU8 = AtomicU8::new(0);
match CACHED.load(Ordering::Relaxed) {
1 => true,
2 => false,
_ => {
let have =
std::is_x86_feature_detected!("bmi2") && std::is_x86_feature_detected!("adx");
CACHED.store(u8::from(!have) + 1, Ordering::Relaxed);
have
}
}
}
#[cfg(not(feature = "std"))]
{
cfg!(all(target_feature = "bmi2", target_feature = "adx"))
}
}
#[inline]
fn has_avx2() -> bool {
#[cfg(feature = "std")]
{
use core::sync::atomic::{AtomicU8, Ordering};
static CACHED: AtomicU8 = AtomicU8::new(0);
match CACHED.load(Ordering::Relaxed) {
1 => true,
2 => false,
_ => {
let have = std::is_x86_feature_detected!("avx2");
CACHED.store(u8::from(!have) + 1, Ordering::Relaxed);
have
}
}
}
#[cfg(not(feature = "std"))]
{
cfg!(target_feature = "avx2")
}
}
#[inline]
pub(crate) fn scan<const W: usize>(entries: &[[u64; W]], masks: &[u64]) -> [u64; W] {
let n = entries.len().min(masks.len());
let (entries, masks) = (&entries[..n], &masks[..n]);
if W == 8 && has_avx2() {
unsafe {
let entries8 = core::slice::from_raw_parts(entries.as_ptr().cast::<[u64; 8]>(), n);
let r = scan_avx2::<8>(entries8, masks);
core::ptr::read(&r as *const [u64; 8] as *const [u64; W])
}
} else {
unsafe { scan_sse2(entries, masks) }
}
}
#[inline]
#[target_feature(enable = "sse2")]
fn scan_sse2<const W: usize>(entries: &[[u64; W]], masks: &[u64]) -> [u64; W] {
use core::arch::x86_64::*;
const { assert!(W.is_multiple_of(2) && W <= 18) };
let mut acc = [0u64; W];
let mut lanes = [_mm_setzero_si128(); 9];
for (entry, &mask) in entries.iter().zip(masks) {
let m = _mm_set1_epi64x(mask as i64);
for (j, lane) in lanes.iter_mut().enumerate().take(W / 2) {
let v = unsafe { _mm_loadu_si128(entry.as_ptr().add(2 * j).cast()) };
*lane = _mm_or_si128(*lane, _mm_and_si128(v, m));
}
}
for (j, lane) in lanes.iter().enumerate().take(W / 2) {
unsafe { _mm_storeu_si128(acc.as_mut_ptr().add(2 * j).cast(), *lane) };
}
acc
}
#[target_feature(enable = "avx2")]
unsafe fn scan_avx2<const W: usize>(entries: &[[u64; W]], masks: &[u64]) -> [u64; W] {
use core::arch::x86_64::*;
const { assert!(W.is_multiple_of(4) && W <= 12) };
let mut acc = [0u64; W];
let mut lanes = [_mm256_setzero_si256(); 3];
for (entry, &mask) in entries.iter().zip(masks) {
let m = _mm256_set1_epi64x(mask as i64);
for (j, lane) in lanes.iter_mut().enumerate().take(W / 4) {
let v = unsafe { _mm256_loadu_si256(entry.as_ptr().add(4 * j).cast()) };
*lane = _mm256_or_si256(*lane, _mm256_and_si256(v, m));
}
}
for (j, lane) in lanes.iter().enumerate().take(W / 4) {
unsafe { _mm256_storeu_si256(acc.as_mut_ptr().add(4 * j).cast(), *lane) };
}
acc
}
#[inline]
pub(crate) fn scan_affine(
entries: &[crate::nist::point::AffinePoint<crate::p256::P256>],
masks: &[u64],
) -> crate::nist::point::AffinePoint<crate::p256::P256> {
use crate::nist::point::AffinePoint;
use crate::p256::Fp;
const _: () = assert!(core::mem::size_of::<AffinePoint<crate::p256::P256>>() == 64);
let words: &[[u64; 8]] =
unsafe { core::slice::from_raw_parts(entries.as_ptr().cast(), entries.len()) };
let w = scan(words, masks);
AffinePoint {
x: Fp([w[0], w[1], w[2], w[3]]),
y: Fp([w[4], w[5], w[6], w[7]]),
}
}
#[inline]
pub(crate) fn scan_point(
entries: &[crate::nist::point::Point<crate::p256::P256>],
masks: &[u64],
) -> [[u64; 4]; 3] {
use crate::nist::point::Point;
const _: () = assert!(core::mem::size_of::<Point<crate::p256::P256>>() == 96);
let words: &[[u64; 12]] =
unsafe { core::slice::from_raw_parts(entries.as_ptr().cast(), entries.len()) };
let w = scan(words, masks);
[
[w[0], w[1], w[2], w[3]],
[w[4], w[5], w[6], w[7]],
[w[8], w[9], w[10], w[11]],
]
}
#[inline(always)]
fn slots<const N: usize>(values: &[&[u64; 4]]) -> [MaybeUninit<u64>; N] {
let mut buf = [const { MaybeUninit::uninit() }; N];
for (i, v) in values.iter().enumerate() {
for (j, w) in v.iter().enumerate() {
buf[4 * i + j].write(*w);
}
}
buf
}
#[inline(always)]
unsafe fn read<const N: usize, const K: usize>(
buf: &[MaybeUninit<u64>; N],
first: usize,
) -> [[u64; 4]; K] {
let mut out = [[0u64; 4]; K];
for (k, value) in out.iter_mut().enumerate() {
for (j, w) in value.iter_mut().enumerate() {
*w = unsafe { buf[4 * (first + k) + j].assume_init() };
}
}
out
}
const _: () = {
use super::p256_x86_point as f;
assert!(f::DOUBLE_Y3 == f::DOUBLE_X3 + 1 && f::DOUBLE_Z3 == f::DOUBLE_X3 + 2);
assert!(
f::DOUBLE_IN_PLACE_Y == f::DOUBLE_IN_PLACE_X + 1
&& f::DOUBLE_IN_PLACE_Z == f::DOUBLE_IN_PLACE_X + 2
);
assert!(f::ADD_Y3 == f::ADD_X3 + 1 && f::ADD_Z3 == f::ADD_X3 + 2);
assert!(f::ADD_H == f::ADD_X3 + 3 && f::ADD_R == f::ADD_H + 1);
assert!(f::ADD_AFFINE_Y3 == f::ADD_AFFINE_X3 + 1 && f::ADD_AFFINE_Z3 == f::ADD_AFFINE_X3 + 2);
assert!(f::ADD_AFFINE_H == f::ADD_AFFINE_X3 + 3 && f::ADD_AFFINE_R == f::ADD_AFFINE_H + 1);
};
pub(crate) struct Ladder<const N: usize>([MaybeUninit<u64>; N]);
impl<const N: usize> Ladder<N> {
#[inline(always)]
pub(crate) fn new() -> Self {
Self([const { MaybeUninit::uninit() }; N])
}
}
impl<const N: usize> core::ops::Deref for Ladder<N> {
type Target = [MaybeUninit<u64>; N];
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const N: usize> core::ops::DerefMut for Ladder<N> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<const N: usize> Drop for Ladder<N> {
fn drop(&mut self) {
for w in self.0.iter_mut() {
unsafe { core::ptr::write_volatile(w, MaybeUninit::new(0)) };
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
#[cfg(all(test, feature = "std"))]
ladder_probe::record(self.0.iter().all(|w| {
unsafe { w.assume_init() == 0 }
}));
}
}
#[cfg(all(test, feature = "std"))]
pub(crate) mod ladder_probe {
use core::cell::Cell;
std::thread_local! {
static DROPS: Cell<usize> = const { Cell::new(0) };
static DIRTY: Cell<usize> = const { Cell::new(0) };
}
pub(crate) fn record(clear: bool) {
DROPS.with(|c| c.set(c.get() + 1));
if !clear {
DIRTY.with(|c| c.set(c.get() + 1));
}
}
pub(crate) fn reset() {
DROPS.with(|c| c.set(0));
DIRTY.with(|c| c.set(0));
}
pub(crate) fn snapshot() -> (usize, usize) {
(DROPS.with(Cell::get), DIRTY.with(Cell::get))
}
}
pub(crate) struct Secret<T>(T);
impl<T> Secret<T> {
#[inline(always)]
pub(crate) fn new(value: T) -> Self {
Self(value)
}
}
impl<T> core::ops::Deref for Secret<T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &T {
&self.0
}
}
impl<T> core::ops::DerefMut for Secret<T> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> Drop for Secret<T> {
fn drop(&mut self) {
let p = core::ptr::addr_of_mut!(self.0).cast::<u8>();
for i in 0..core::mem::size_of::<T>() {
unsafe { core::ptr::write_volatile(p.add(i), 0) };
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
#[cfg(all(test, feature = "std"))]
secret_probe::record(
(0..core::mem::size_of::<T>())
.all(|i| unsafe { core::ptr::read_volatile(p.add(i)) } == 0),
);
}
}
#[cfg(all(test, feature = "std"))]
pub(crate) mod secret_probe {
use core::cell::Cell;
std::thread_local! {
static DROPS: Cell<usize> = const { Cell::new(0) };
static DIRTY: Cell<usize> = const { Cell::new(0) };
}
pub(crate) fn record(clear: bool) {
DROPS.with(|c| c.set(c.get() + 1));
if !clear {
DIRTY.with(|c| c.set(c.get() + 1));
}
}
pub(crate) fn reset() {
DROPS.with(|c| c.set(0));
DIRTY.with(|c| c.set(0));
}
pub(crate) fn snapshot() -> (usize, usize) {
(DROPS.with(Cell::get), DIRTY.with(Cell::get))
}
}
#[inline(always)]
pub(crate) fn point_double(x: &[u64; 4], y: &[u64; 4], z: &[u64; 4]) -> Option<[[u64; 4]; 3]> {
if !has_adx() {
return None;
}
let mut buf = slots::<60>(&[x, y, z]);
unsafe {
super::p256_x86_point::double_adx(&mut buf);
Some(read(&buf, super::p256_x86_point::DOUBLE_X3))
}
}
#[inline(always)]
#[allow(clippy::type_complexity)]
pub(crate) fn point_add(
p: [&[u64; 4]; 3],
q: [&[u64; 4]; 3],
) -> Option<([[u64; 4]; 3], [u64; 4], [u64; 4])> {
if !has_adx() {
return None;
}
let mut buf = slots::<96>(&[p[0], p[1], p[2], q[0], q[1], q[2]]);
unsafe {
super::p256_x86_point::add_adx(&mut buf);
let [h, r] = read(&buf, super::p256_x86_point::ADD_H);
Some((read(&buf, super::p256_x86_point::ADD_X3), h, r))
}
}
#[inline(always)]
#[allow(clippy::type_complexity)]
pub(crate) fn point_add_affine(
p: [&[u64; 4]; 3],
q: [&[u64; 4]; 2],
) -> Option<([[u64; 4]; 3], [u64; 4], [u64; 4])> {
if !has_adx() {
return None;
}
let mut buf = slots::<80>(&[p[0], p[1], p[2], q[0], q[1]]);
unsafe {
super::p256_x86_point::add_affine_adx(&mut buf);
let [h, r] = read(&buf, super::p256_x86_point::ADD_AFFINE_H);
Some((read(&buf, super::p256_x86_point::ADD_AFFINE_X3), h, r))
}
}
pub(crate) const LADDER: usize = 96;
pub(crate) fn mul_scalar(
p: &crate::nist::point::Point<crate::p256::P256>,
scalar: &<crate::p256::P256 as crate::nist::point::Curve>::Scalar,
) -> Option<crate::nist::point::Point<crate::p256::P256>> {
use super::gentable::{digit_count, signed_digits_w, Window, WIDTH};
use crate::nist::arith::Field;
use crate::nist::point::Point;
use crate::p256::Fp;
if !has_adx() {
return None;
}
let secret_bytes = Secret::new(scalar.to_bytes());
let bytes = secret_bytes.as_ref();
let digits = Secret::new(signed_digits_w(bytes, WIDTH));
let n = digit_count(bytes.len(), WIDTH);
let window = Window::new(p);
let mut entry = Secret::new(Point::<crate::p256::P256>::identity());
let mut buf = Ladder::<LADDER>::new();
let put = |buf: &mut [MaybeUninit<u64>; LADDER], slot: usize, q: &Point<crate::p256::P256>| {
for (k, c) in [q.x(), q.y(), q.z()].iter().enumerate() {
for (j, w) in c.0.iter().enumerate() {
buf[4 * (slot + k) + j].write(*w);
}
}
};
*entry = window.select(digits[n - 1]);
put(&mut buf, super::p256_x86_point::DOUBLE_IN_PLACE_X, &entry);
let doublings = |buf: &mut [MaybeUninit<u64>; LADDER]| {
let ip: &mut [MaybeUninit<u64>; 48] = (&mut buf[..48])
.try_into()
.expect("the doubling's slots are a prefix of the buffer");
for _ in 0..WIDTH {
unsafe { super::p256_x86_point::double_in_place_adx(ip) };
}
};
for i in (1..n - 1).rev() {
*entry = window.select(digits[i]);
doublings(&mut buf);
unsafe { ladder_add(&mut buf, &entry) };
}
doublings(&mut buf);
let [x, y, z] = unsafe { read::<LADDER, 3>(&buf, super::p256_x86_point::DOUBLE_IN_PLACE_X) };
let acc = Point::from_coordinates(Fp(x), Fp(y), Fp(z));
*entry = window.select(digits[0]);
Some(acc.add(&entry))
}
#[cfg_attr(not(feature = "std"), allow(dead_code))]
pub(crate) const TABLE: usize = 80;
#[cfg(feature = "std")]
pub(crate) fn table_mul<const E: usize>(
windows: &crate::nist::gentable::Windows<crate::p256::P256, E>,
scalar: &<crate::p256::P256 as crate::nist::point::Curve>::Scalar,
) -> Option<crate::nist::point::Point<crate::p256::P256>> {
use super::gentable::{digit_count, signed_digits_w, width, window};
use crate::nist::arith::Field;
use crate::nist::point::{Curve, Point};
use crate::p256::{Fp, P256};
if !has_adx() {
return None;
}
let w = width::<E>();
let secret_bytes = Secret::new(scalar.to_bytes());
let digits = Secret::new(signed_digits_w(secret_bytes.as_ref(), w));
let n = digit_count(secret_bytes.as_ref().len(), w);
let first_complete = (0..n)
.find(|&i| w * (i + 1) >= P256::ORDER_BITS)
.unwrap_or(n);
let mut buf = Ladder::<TABLE>::new();
let put = |buf: &mut [MaybeUninit<u64>; TABLE], slot: usize, words: &[u64; 4]| {
for (j, x) in words.iter().enumerate() {
buf[4 * slot + j].write(*x);
}
};
let identity = Point::<P256>::identity();
put(&mut buf, 0, &identity.x().0);
put(&mut buf, 1, &identity.y().0);
put(&mut buf, 2, &identity.z().0);
let mut xy = Secret::new(([0u64; 4], [0u64; 4]));
for (i, &digit) in digits.iter().enumerate().take(first_complete) {
let (x, y) = window(windows, i).select(digit);
let coords: &mut ([u64; 4], [u64; 4]) = &mut xy;
coords.0 = x.0;
coords.1 = y.0;
unsafe { table_add(&mut buf, &coords.0, &coords.1, digit) };
}
let [x, y, z] = unsafe { read::<TABLE, 3>(&buf, 0) };
let mut acc = Point::from_coordinates(Fp(x), Fp(y), Fp(z));
for (i, &digit) in digits.iter().enumerate().take(n).skip(first_complete) {
acc = window(windows, i).add_to(&acc, digit, true);
}
Some(acc)
}
#[inline(always)]
pub(crate) unsafe fn ladder_add(
buf: &mut [MaybeUninit<u64>; LADDER],
entry: &crate::nist::point::Point<crate::p256::P256>,
) {
use crate::nist::arith::{select, Field};
use crate::p256::Fp;
for (k, c) in [entry.x(), entry.y(), entry.z()].iter().enumerate() {
for (j, w) in c.0.iter().enumerate() {
buf[4 * (3 + k) + j].write(*w);
}
}
unsafe { super::p256_x86_point::add_adx(buf) };
let [ax, ay, az, ex, ey, ez, sx, sy, sz] = unsafe { read::<LADDER, 9>(buf, 0) };
let [entry_inf, acc_inf] = core::hint::black_box([Fp(ez).zero_mask(), Fp(az).zero_mask()]);
for (k, (a, (e, s))) in [ax, ay, az]
.iter()
.zip([ex, ey, ez].iter().zip([sx, sy, sz].iter()))
.enumerate()
{
let r = select(acc_inf, *e, select(entry_inf, *a, *s));
for (j, w) in r.iter().enumerate() {
buf[4 * k + j].write(*w);
}
}
}
#[cfg_attr(not(feature = "std"), allow(dead_code))]
#[inline(always)]
pub(crate) unsafe fn table_add(
buf: &mut [MaybeUninit<u64>; TABLE],
x: &[u64; 4],
y: &[u64; 4],
digit: i8,
) {
use crate::nist::arith::{select, Field};
use crate::p256::Fp;
let put = |buf: &mut [MaybeUninit<u64>; TABLE], slot: usize, w: &[u64; 4]| {
for (j, v) in w.iter().enumerate() {
buf[4 * slot + j].write(*v);
}
};
put(buf, 3, x);
put(buf, 4, y);
unsafe { super::p256_x86_point::add_affine_adx(buf) };
let [ax, ay, az, _, _, sx, sy, sz] = unsafe { read::<TABLE, 8>(buf, 0) };
let keep = ((digit as u8 as u64).wrapping_sub(1) >> 63).wrapping_neg();
let [acc_inf, keep] = core::hint::black_box([Fp(az).zero_mask(), keep]);
let lifted = [*x, *y, Fp::ONE.0];
for (k, (a, (l, s))) in [ax, ay, az]
.iter()
.zip(lifted.iter().zip([sx, sy, sz].iter()))
.enumerate()
{
put(buf, k, &select(keep, *a, select(acc_inf, *l, *s)));
}
}
#[inline(always)]
pub(crate) fn mul(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] {
if has_adx() {
unsafe { mul_adx(a, b) }
} else {
mul_plain(a, b)
}
}
#[inline(always)]
pub(crate) fn square(a: &[u64; 4]) -> [u64; 4] {
if has_adx() {
unsafe { square_adx(a) }
} else {
square_plain(a)
}
}
#[inline]
pub unsafe fn mul_adx(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] {
let (r0, r1, r2, r3): (u64, u64, u64, u64);
unsafe {
asm!(
".p2align 6",
"mov rdx, qword ptr [{b}]",
"mulx {t1}, {t0}, qword ptr [{a}]",
"mulx {t2}, rax, qword ptr [{a} + 8]",
"add {t1}, rax",
"mulx {t3}, rax, qword ptr [{a} + 16]",
"adc {t2}, rax",
"mulx {t4}, rax, qword ptr [{a} + 24]",
"adc {t3}, rax",
"adc {t4}, 0",
"xor {t5:e}, {t5:e}",
"mov rax, {t0}",
"shl rax, 32",
"mov {c}, {t0}",
"shr {c}, 32",
"mov rdx, {t0}",
"sub rdx, rax",
"sbb {t0}, {c}",
"add {t1}, rax",
"adc {t2}, {c}",
"adc {t3}, rdx",
"adc {t4}, {t0}",
"adc {t5}, 0",
"mov rdx, qword ptr [{b} + 8]",
"xor {t0:e}, {t0:e}",
"mulx {c}, rax, qword ptr [{a}]",
"adcx {t1}, rax",
"adox {t2}, {c}",
"mulx {c}, rax, qword ptr [{a} + 8]",
"adcx {t2}, rax",
"adox {t3}, {c}",
"mulx {c}, rax, qword ptr [{a} + 16]",
"adcx {t3}, rax",
"adox {t4}, {c}",
"mulx {c}, rax, qword ptr [{a} + 24]",
"adcx {t4}, rax",
"adox {t5}, {c}",
"mov eax, 0",
"adcx {t5}, rax",
"adox {t0}, rax",
"adcx {t0}, rax",
"mov rax, {t1}",
"shl rax, 32",
"mov {c}, {t1}",
"shr {c}, 32",
"mov rdx, {t1}",
"sub rdx, rax",
"sbb {t1}, {c}",
"add {t2}, rax",
"adc {t3}, {c}",
"adc {t4}, rdx",
"adc {t5}, {t1}",
"adc {t0}, 0",
"mov rdx, qword ptr [{b} + 16]",
"xor {t1:e}, {t1:e}",
"mulx {c}, rax, qword ptr [{a}]",
"adcx {t2}, rax",
"adox {t3}, {c}",
"mulx {c}, rax, qword ptr [{a} + 8]",
"adcx {t3}, rax",
"adox {t4}, {c}",
"mulx {c}, rax, qword ptr [{a} + 16]",
"adcx {t4}, rax",
"adox {t5}, {c}",
"mulx {c}, rax, qword ptr [{a} + 24]",
"adcx {t5}, rax",
"adox {t0}, {c}",
"mov eax, 0",
"adcx {t0}, rax",
"adox {t1}, rax",
"adcx {t1}, rax",
"mov rax, {t2}",
"shl rax, 32",
"mov {c}, {t2}",
"shr {c}, 32",
"mov rdx, {t2}",
"sub rdx, rax",
"sbb {t2}, {c}",
"add {t3}, rax",
"adc {t4}, {c}",
"adc {t5}, rdx",
"adc {t0}, {t2}",
"adc {t1}, 0",
"mov rdx, qword ptr [{b} + 24]",
"xor {t2:e}, {t2:e}",
"mulx {c}, rax, qword ptr [{a}]",
"adcx {t3}, rax",
"adox {t4}, {c}",
"mulx {c}, rax, qword ptr [{a} + 8]",
"adcx {t4}, rax",
"adox {t5}, {c}",
"mulx {c}, rax, qword ptr [{a} + 16]",
"adcx {t5}, rax",
"adox {t0}, {c}",
"mulx {c}, rax, qword ptr [{a} + 24]",
"adcx {t0}, rax",
"adox {t1}, {c}",
"mov eax, 0",
"adcx {t1}, rax",
"adox {t2}, rax",
"adcx {t2}, rax",
"mov rax, {t3}",
"shl rax, 32",
"mov {c}, {t3}",
"shr {c}, 32",
"mov rdx, {t3}",
"sub rdx, rax",
"sbb {t3}, {c}",
"add {t4}, rax",
"adc {t5}, {c}",
"adc {t0}, rdx",
"adc {t1}, {t3}",
"adc {t2}, 0",
"mov rax, {t4}",
"mov rdx, {t5}",
"mov {c}, {t0}",
"mov {t3}, {t1}",
"mov {a:e}, {p1}",
"mov {b}, {p3}",
"sub rax, -1",
"sbb rdx, {a}",
"sbb {c}, 0",
"sbb {t3}, {b}",
"sbb {t2}, 0",
"cmovc rax, {t4}",
"cmovc rdx, {t5}",
"cmovc {c}, {t0}",
"cmovc {t3}, {t1}",
a = inout(reg) a.as_ptr() => _,
b = inout(reg) b.as_ptr() => _,
p1 = const P1,
p3 = const P3,
t0 = out(reg) _,
t1 = out(reg) _,
t2 = out(reg) _,
t3 = out(reg) r3,
t4 = out(reg) _,
t5 = out(reg) _,
c = out(reg) r2,
out("rax") r0,
out("rdx") r1,
options(pure, readonly, nostack),
);
}
[r0, r1, r2, r3]
}
#[inline]
pub unsafe fn square_adx(a: &[u64; 4]) -> [u64; 4] {
let (r0, r1, r2, r3): (u64, u64, u64, u64);
unsafe {
asm!(
".p2align 6",
"mov rdx, qword ptr [{a}]",
"mulx {t2}, {t1}, qword ptr [{a} + 8]",
"mulx {t3}, rax, qword ptr [{a} + 16]",
"add {t2}, rax",
"mulx {t4}, rax, qword ptr [{a} + 24]",
"adc {t3}, rax",
"adc {t4}, 0",
"mov rdx, qword ptr [{a} + 8]",
"xor {t5:e}, {t5:e}",
"mulx {c}, rax, qword ptr [{a} + 16]",
"adcx {t3}, rax",
"adox {t4}, {c}",
"mulx {c}, rax, qword ptr [{a} + 24]",
"adcx {t4}, rax",
"adox {t5}, {c}",
"mov eax, 0",
"adcx {t5}, rax",
"mov rdx, qword ptr [{a} + 16]",
"mulx {t6}, rax, qword ptr [{a} + 24]",
"add {t5}, rax",
"adc {t6}, 0",
"xor {t7:e}, {t7:e}",
"add {t1}, {t1}",
"adc {t2}, {t2}",
"adc {t3}, {t3}",
"adc {t4}, {t4}",
"adc {t5}, {t5}",
"adc {t6}, {t6}",
"adc {t7}, 0",
"mov rdx, qword ptr [{a}]",
"mulx {c}, {t0}, rdx",
"add {t1}, {c}",
"mov rdx, qword ptr [{a} + 8]",
"mulx {c}, rax, rdx",
"adc {t2}, rax",
"adc {t3}, {c}",
"mov rdx, qword ptr [{a} + 16]",
"mulx {c}, rax, rdx",
"adc {t4}, rax",
"adc {t5}, {c}",
"mov rdx, qword ptr [{a} + 24]",
"mulx {c}, rax, rdx",
"adc {t6}, rax",
"adc {t7}, {c}",
reduce_tail!(),
a = in(reg) a.as_ptr(),
p1 = const P1,
p3 = const P3,
t0 = out(reg) _,
t1 = out(reg) _,
t2 = out(reg) r2,
t3 = out(reg) r3,
t4 = out(reg) _,
t5 = out(reg) _,
t6 = out(reg) _,
t7 = out(reg) _,
c = out(reg) _,
out("rax") r0,
out("rdx") r1,
options(pure, readonly, nostack),
);
}
[r0, r1, r2, r3]
}
#[inline]
pub(crate) fn mul_plain(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] {
let (r0, r1, r2, r3): (u64, u64, u64, u64);
unsafe {
asm!(
".p2align 6",
"mov rax, qword ptr [{a}]",
"mul qword ptr [{b}]",
"mov {t0}, rax",
"mov {t1}, rdx",
"mov rax, qword ptr [{a} + 8]",
"mul qword ptr [{b}]",
"add {t1}, rax",
"adc rdx, 0",
"mov {t2}, rdx",
"mov rax, qword ptr [{a} + 16]",
"mul qword ptr [{b}]",
"add {t2}, rax",
"adc rdx, 0",
"mov {t3}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul qword ptr [{b}]",
"add {t3}, rax",
"adc rdx, 0",
"mov {t4}, rdx",
"mov rax, qword ptr [{a}]",
"mul qword ptr [{b} + 8]",
"add {t1}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 8]",
"mul qword ptr [{b} + 8]",
"add {t2}, {c}",
"adc rdx, 0",
"add {t2}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 16]",
"mul qword ptr [{b} + 8]",
"add {t3}, {c}",
"adc rdx, 0",
"add {t3}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul qword ptr [{b} + 8]",
"add {t4}, {c}",
"adc rdx, 0",
"add {t4}, rax",
"adc rdx, 0",
"mov {t5}, rdx",
"mov rax, qword ptr [{a}]",
"mul qword ptr [{b} + 16]",
"add {t2}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 8]",
"mul qword ptr [{b} + 16]",
"add {t3}, {c}",
"adc rdx, 0",
"add {t3}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 16]",
"mul qword ptr [{b} + 16]",
"add {t4}, {c}",
"adc rdx, 0",
"add {t4}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul qword ptr [{b} + 16]",
"add {t5}, {c}",
"adc rdx, 0",
"add {t5}, rax",
"adc rdx, 0",
"mov {t6}, rdx",
"mov rax, qword ptr [{a}]",
"mul qword ptr [{b} + 24]",
"add {t3}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 8]",
"mul qword ptr [{b} + 24]",
"add {t4}, {c}",
"adc rdx, 0",
"add {t4}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 16]",
"mul qword ptr [{b} + 24]",
"add {t5}, {c}",
"adc rdx, 0",
"add {t5}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul qword ptr [{b} + 24]",
"add {t6}, {c}",
"adc rdx, 0",
"add {t6}, rax",
"adc rdx, 0",
"mov {t7}, rdx",
reduce_tail!(),
a = in(reg) a.as_ptr(),
b = in(reg) b.as_ptr(),
p1 = const P1,
p3 = const P3,
t0 = out(reg) _,
t1 = out(reg) _,
t2 = out(reg) r2,
t3 = out(reg) r3,
t4 = out(reg) _,
t5 = out(reg) _,
t6 = out(reg) _,
t7 = out(reg) _,
c = out(reg) _,
out("rax") r0,
out("rdx") r1,
options(pure, readonly, nostack),
);
}
[r0, r1, r2, r3]
}
#[inline]
pub(crate) fn square_plain(a: &[u64; 4]) -> [u64; 4] {
let (r0, r1, r2, r3): (u64, u64, u64, u64);
unsafe {
asm!(
".p2align 6",
"mov rax, qword ptr [{a} + 8]",
"mul qword ptr [{a}]",
"mov {t1}, rax",
"mov {t2}, rdx",
"mov rax, qword ptr [{a} + 16]",
"mul qword ptr [{a}]",
"add {t2}, rax",
"adc rdx, 0",
"mov {t3}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul qword ptr [{a}]",
"add {t3}, rax",
"adc rdx, 0",
"mov {t4}, rdx",
"mov rax, qword ptr [{a} + 16]",
"mul qword ptr [{a} + 8]",
"add {t3}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul qword ptr [{a} + 8]",
"add {t4}, {c}",
"adc rdx, 0",
"add {t4}, rax",
"adc rdx, 0",
"mov {t5}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul qword ptr [{a} + 16]",
"add {t5}, rax",
"adc rdx, 0",
"mov {t6}, rdx",
"xor {t7:e}, {t7:e}",
"add {t1}, {t1}",
"adc {t2}, {t2}",
"adc {t3}, {t3}",
"adc {t4}, {t4}",
"adc {t5}, {t5}",
"adc {t6}, {t6}",
"adc {t7}, 0",
"mov rax, qword ptr [{a}]",
"mul rax",
"mov {t0}, rax",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 8]",
"mul rax",
"add {t1}, {c}",
"adc {t2}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 16]",
"mul rax",
"add {t3}, {c}",
"adc {t4}, rax",
"adc rdx, 0",
"mov {c}, rdx",
"mov rax, qword ptr [{a} + 24]",
"mul rax",
"add {t5}, {c}",
"adc {t6}, rax",
"adc {t7}, rdx",
reduce_tail!(),
a = in(reg) a.as_ptr(),
p1 = const P1,
p3 = const P3,
t0 = out(reg) _,
t1 = out(reg) _,
t2 = out(reg) r2,
t3 = out(reg) r3,
t4 = out(reg) _,
t5 = out(reg) _,
t6 = out(reg) _,
t7 = out(reg) _,
c = out(reg) _,
out("rax") r0,
out("rdx") r1,
options(pure, readonly, nostack),
);
}
[r0, r1, r2, r3]
}
#[inline]
pub(crate) fn add(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] {
let (r0, r1, r2, r3): (u64, u64, u64, u64);
unsafe {
asm!(
".p2align 6",
"mov {t0}, qword ptr [{a}]",
"mov {t1}, qword ptr [{a} + 8]",
"mov {t2}, qword ptr [{a} + 16]",
"mov {t3}, qword ptr [{a} + 24]",
"xor {c:e}, {c:e}",
"add {t0}, qword ptr [{b}]",
"adc {t1}, qword ptr [{b} + 8]",
"adc {t2}, qword ptr [{b} + 16]",
"adc {t3}, qword ptr [{b} + 24]",
"adc {c}, 0",
"mov {s0}, {t0}",
"mov {s1}, {t1}",
"mov {s2}, {t2}",
"mov {s3}, {t3}",
"mov {k1:e}, {p1}",
"mov {k3}, {p3}",
"sub {s0}, -1",
"sbb {s1}, {k1}",
"sbb {s2}, 0",
"sbb {s3}, {k3}",
"sbb {c}, 0",
"cmovc {s0}, {t0}",
"cmovc {s1}, {t1}",
"cmovc {s2}, {t2}",
"cmovc {s3}, {t3}",
a = in(reg) a.as_ptr(),
b = in(reg) b.as_ptr(),
p1 = const P1,
p3 = const P3,
t0 = out(reg) _,
t1 = out(reg) _,
t2 = out(reg) _,
t3 = out(reg) _,
c = out(reg) _,
k1 = out(reg) _,
k3 = out(reg) _,
s0 = out(reg) r0,
s1 = out(reg) r1,
s2 = out(reg) r2,
s3 = out(reg) r3,
options(pure, readonly, nostack),
);
}
[r0, r1, r2, r3]
}
#[inline]
pub(crate) fn sub(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] {
let (r0, r1, r2, r3): (u64, u64, u64, u64);
unsafe {
asm!(
".p2align 6",
"mov {t0}, qword ptr [{a}]",
"mov {t1}, qword ptr [{a} + 8]",
"mov {t2}, qword ptr [{a} + 16]",
"mov {t3}, qword ptr [{a} + 24]",
"sub {t0}, qword ptr [{b}]",
"sbb {t1}, qword ptr [{b} + 8]",
"sbb {t2}, qword ptr [{b} + 16]",
"sbb {t3}, qword ptr [{b} + 24]",
"sbb {m}, {m}",
"mov {k1}, {m}",
"shr {k1}, 32",
"mov {k3}, {p3}",
"and {k3}, {m}",
"add {t0}, {m}",
"adc {t1}, {k1}",
"adc {t2}, 0",
"adc {t3}, {k3}",
a = in(reg) a.as_ptr(),
b = in(reg) b.as_ptr(),
p3 = const P3,
m = out(reg) _,
k1 = out(reg) _,
k3 = out(reg) _,
t0 = out(reg) r0,
t1 = out(reg) r1,
t2 = out(reg) r2,
t3 = out(reg) r3,
options(pure, readonly, nostack),
);
}
[r0, r1, r2, r3]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nist::p256_field::{self as rust, P};
fn next(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn operand(state: &mut u64, structured: bool) -> [u64; 4] {
loop {
let mut x = [0u64; 4];
let r = next(state);
for (i, limb) in x.iter_mut().enumerate() {
*limb = if structured {
match (r >> (2 * i)) & 3 {
0 => 0,
1 => u64::MAX,
2 => next(state),
_ => 1 << (next(state) % 64),
}
} else {
next(state)
};
}
let mut borrow = 0u64;
for i in 0..4 {
let (d, b1) = x[i].overflowing_sub(P[i]);
let (_, b2) = d.overflowing_sub(borrow);
borrow = u64::from(b1 | b2);
}
if borrow == 1 {
return x;
}
}
}
fn check(a: [u64; 4], b: [u64; 4], adx: bool) {
let m = rust::mul(a, b);
assert_eq!(mul_plain(&a, &b), m, "mul_plain {a:x?} {b:x?}");
let s = rust::square(a);
assert_eq!(square_plain(&a), s, "square_plain {a:x?}");
if adx {
unsafe {
assert_eq!(mul_adx(&a, &b), m, "mul_adx {a:x?} {b:x?}");
assert_eq!(square_adx(&a), s, "square_adx {a:x?}");
}
}
assert_eq!(mul(&a, &b), m);
assert_eq!(square(&a), s);
assert_eq!(add(&a, &b), rust::add(a, b), "add {a:x?} {b:x?}");
assert_eq!(sub(&a, &b), rust::sub(a, b), "sub {a:x?} {b:x?}");
}
#[test]
fn the_small_multiples_take_every_path_through_their_fold() {
let times = |k: u64, a: [u64; 4]| -> [u64; 4] {
let mut buf = [const { MaybeUninit::uninit() }; 8];
for (i, w) in a.iter().enumerate() {
buf[i].write(*w);
}
unsafe {
use crate::nist::p256_x86_point as f;
let out = match k {
3 => {
f::times3_adx(&mut buf);
f::TIMES3_OUT
}
4 => {
f::times4_adx(&mut buf);
f::TIMES4_OUT
}
_ => {
f::times8_adx(&mut buf);
f::TIMES8_OUT
}
};
read::<8, 1>(&buf, out)[0]
}
};
let below_p = |x: &[u64; 4]| {
(0..4)
.rev()
.find(|&i| x[i] != P[i])
.is_some_and(|i| x[i] < P[i])
};
let mut checked = [0usize; 9];
let mut inputs = std::vec::Vec::new();
for k in [3u64, 4, 8] {
for q in 1..k {
for (d, high) in (0..256u64).flat_map(|d| [(d, false), (d, true)]) {
let mut t = [d, 0, 0, (q << 32).wrapping_neg(), q];
if high {
t[3] = t[3].wrapping_add(2 * q);
}
let mut rem = 0u128;
let mut a = [0u64; 5];
for i in (0..5).rev() {
let cur = (rem << 64) | t[i] as u128;
a[i] = (cur / k as u128) as u64;
rem = cur % k as u128;
}
let a4 = [a[0], a[1], a[2], a[3]];
if rem == 0 && a[4] == 0 && below_p(&a4) {
inputs.push((k, a4));
checked[k as usize] += 1;
}
}
}
}
assert!(
checked[3] > 0 && checked[4] > 0 && checked[8] > 0,
"{checked:?}"
);
let mut state = 0x7175_u64;
for _ in 0..20_000 {
for k in [3u64, 4, 8] {
inputs.push((k, operand(&mut state, false)));
}
}
for (k, a) in [3u64, 4, 8].iter().flat_map(|k| {
[[0u64; 4], [1, 0, 0, 0], [P[0] - 1, P[1], P[2], P[3]]].map(move |a| (*k, a))
}) {
inputs.push((k, a));
}
for (k, a) in inputs {
let mut want = a;
for _ in 1..k {
want = rust::add(want, a);
}
assert_eq!(times(k, a), want, "{k} * {a:x?}");
}
}
#[test]
fn the_fused_operations_agree_with_repeated_additions() {
if !has_adx() {
return;
}
let cms = |cm: u64, dm: u64, a: [u64; 4], b: [u64; 4]| -> [u64; 4] {
let mut buf = [const { MaybeUninit::uninit() }; 12];
for i in 0..4 {
buf[i].write(a[i]);
buf[4 + i].write(b[i]);
}
unsafe {
use crate::nist::p256_x86_point as f;
let out = match (cm, dm) {
(3, 8) => {
f::cms_3_8_adx(&mut buf);
f::CMS_3_8_OUT
}
(4, 1) => {
f::cms_4_1_adx(&mut buf);
f::CMS_4_1_OUT
}
_ => {
f::cms_12_9_adx(&mut buf);
f::CMS_12_9_OUT
}
};
read::<12, 1>(&buf, out)[0]
}
};
let want = |cm: u64, dm: u64, a: [u64; 4], b: [u64; 4]| -> [u64; 4] {
let mut r = [0u64; 4];
for _ in 0..cm {
r = rust::add(r, a);
}
for _ in 0..dm {
r = rust::sub(r, b);
}
r
};
let mut checked = 0usize;
let mut sides = [0usize; 2];
for (cm, dm) in [(3u64, 8u64), (4, 1), (12, 9)] {
for (a, b, carries) in crate::nist::fold_edges::cases(&P, cm, dm) {
let (a, b) = ([a[0], a[1], a[2], a[3]], [b[0], b[1], b[2], b[3]]);
assert_eq!(
cms(cm, dm, a, b),
want(cm, dm, a, b),
"{cm} {a:x?} - {dm} {b:x?}"
);
sides[usize::from(carries)] += 1;
}
}
assert!(sides[0] > 20 && sides[1] > 20, "{sides:?}");
let pm1 = [P[0] - 1, P[1], P[2], P[3]];
let edges = [
[0u64; 4],
[1, 0, 0, 0],
[2, 0, 0, 0],
pm1,
[P[0] - 2, P[1], P[2], P[3]],
];
let mut state = 0xc35_u64;
let mut pairs: std::vec::Vec<([u64; 4], [u64; 4])> = std::vec::Vec::new();
for a in edges {
for b in edges {
pairs.push((a, b));
}
}
for i in 0..1 << 18 {
let s = i % 2 == 1;
pairs.push((operand(&mut state, s), operand(&mut state, s)));
}
for (a, b) in pairs {
for (cm, dm) in [(3u64, 8u64), (4, 1), (12, 9)] {
assert_eq!(
cms(cm, dm, a, b),
want(cm, dm, a, b),
"{cm} {a:x?} - {dm} {b:x?}"
);
}
checked += 1;
}
assert!(checked > 1 << 18);
}
#[test]
fn the_doubling_in_place_is_the_formula() {
use crate::nist::point::Point;
use crate::p256::{Fp, P256};
if !has_adx() {
return;
}
let mut state = 0x2d_u64;
let mut p = Point::<P256>::generator().double().double();
for i in 0..2000 {
let q = if i % 2 == 0 {
p = p.add(&Point::generator());
p
} else {
let s = i % 4 == 1;
let c = [
operand(&mut state, s),
operand(&mut state, s),
operand(&mut state, s),
];
Point::from_coordinates(Fp(c[0]), Fp(c[1]), Fp(c[2]))
};
let mut buf = [const { MaybeUninit::uninit() }; 48];
for (k, c) in [q.x(), q.y(), q.z()].iter().enumerate() {
for (j, w) in c.0.iter().enumerate() {
buf[4 * k + j].write(*w);
}
}
for _ in 0..2 {
unsafe { crate::nist::p256_x86_point::double_in_place_adx(&mut buf) };
}
let got: [[u64; 4]; 3] =
unsafe { read(&buf, crate::nist::p256_x86_point::DOUBLE_IN_PLACE_X) };
let want = q.double_formula().double_formula();
assert_eq!(got, [want.x().0, want.y().0, want.z().0], "{i}");
}
}
fn scalars(
count: usize,
) -> std::vec::Vec<<crate::p256::P256 as crate::nist::point::Curve>::Scalar> {
use crate::nist::arith::Field;
use crate::nist::point::Curve;
type S = <crate::p256::P256 as Curve>::Scalar;
let mut out = std::vec![S::ZERO];
let (mut small, mut minus) = (S::ZERO, S::ZERO);
for _ in 0..40 {
small = small.add(&S::ONE);
minus = minus.sub(&S::ONE);
out.push(small);
out.push(minus);
}
let mut state = 0x5ca1_u64;
for _ in 0..count {
let mut bytes = S::zero_bytes();
for chunk in bytes.as_mut().chunks_mut(8) {
chunk.copy_from_slice(&next(&mut state).to_le_bytes()[..chunk.len()]);
}
out.push(S::from_bytes_reduced(&bytes));
}
out
}
#[test]
fn the_buffer_multiplications_agree_with_the_generic_ones() {
use crate::nist::gentable::HasGeneratorTable;
use crate::nist::point::Point;
use crate::p256::P256;
if !has_adx() {
return;
}
let words = |p: &Point<P256>| (p.x().0, p.y().0, p.z().0);
let g = Point::<P256>::generator();
let points = [g, g.mul_scalar_generic(&scalars(1)[81]), Point::identity()];
let ks = scalars(1000);
for p in &points {
for k in &ks {
let got = mul_scalar(p, k).expect("BMI2 and ADX");
assert_eq!(words(&got), words(&p.mul_scalar_generic(k)));
}
}
for k in &ks {
let got = crate::p256::table_mul_adx_for_tests(k).expect("BMI2 and ADX");
assert_eq!(words(&got), words(&P256::table_mul_generic_for_tests(k)));
}
}
#[test]
fn the_buffer_multiplications_wipe_their_buffers() {
use super::ladder_probe as probe;
use super::secret_probe;
use crate::nist::point::Point;
use crate::p256::P256;
if !has_adx() {
std::println!("no BMI2 and ADX; the buffer multiplications were not run");
return;
}
let k = &scalars(1)[81];
probe::reset();
secret_probe::reset();
mul_scalar(&Point::<P256>::generator(), k).expect("BMI2 and ADX");
assert_eq!(probe::snapshot(), (1, 0), "mul_scalar");
assert_eq!(secret_probe::snapshot(), (3, 0), "mul_scalar's temporaries");
probe::reset();
secret_probe::reset();
crate::p256::table_mul_adx_for_tests(k).expect("BMI2 and ADX");
assert_eq!(probe::snapshot(), (1, 0), "table_mul");
assert_eq!(secret_probe::snapshot(), (3, 0), "table_mul's temporaries");
}
#[test]
fn both_instruction_sequences_agree_with_the_rust() {
let adx = std::is_x86_feature_detected!("bmi2") && std::is_x86_feature_detected!("adx");
assert_eq!(has_adx(), adx);
std::println!(
"p256 x86-64 field: mulx/adx {}",
if adx { "selected" } else { "unavailable" }
);
let one = [1u64, 0, 0, 0];
let pm1 = [P[0] - 1, P[1], P[2], P[3]];
let r = [1u64, 0xffff_ffff_0000_0000, u64::MAX, 0x0000_0000_ffff_fffe];
let edges = [
[0u64; 4],
one,
pm1,
[P[0] - 2, P[1], P[2], P[3]],
r,
[u64::MAX, u64::MAX, u64::MAX, 0xffff_ffff_0000_0000],
[0, 0, 0, 1 << 63],
[u64::MAX, u64::MAX, u64::MAX, (1 << 63) - 1],
];
for a in edges {
for b in edges {
check(a, b, adx);
}
}
let mut state = 0x86;
for i in 0..1 << 20 {
let structured = i % 2 == 1;
let a = operand(&mut state, structured);
let b = operand(&mut state, structured);
check(a, b, adx);
}
}
}