use core::arch::asm;
use ic_core::ct::Choice;
#[macro_use]
mod text;
#[cfg(any(feature = "std", feature = "ct-probes"))]
pub mod edwards;
mod inv;
mod slot {
pub const X1: usize = 0;
pub const XM: usize = 4;
pub const ZM: usize = 8;
pub const XN: usize = 12;
pub const ZN: usize = 16;
pub const LEN: usize = 76;
}
#[inline(always)]
pub fn ladder_step(ws: &mut [u64; slot::LEN], swap: u64) {
unsafe {
asm!(
fe_sub_once!(192, 32, 64), fe_add_once!(224, 96, 128), fe_sub_once!(256, 96, 128), fe_add_once!(160, 32, 64), "test rsi, rsi\n",
fe_cmovnz!(288, 224, 160), fe_cmovnz!(320, 256, 192), fe_mul5!(352, 192, 224), fe_mul5!(544, 160, 256), fe_sqr!(448, 320), fe_sqr!(416, 288), fe_sub5_fold!(480, 352, 544), fe_add5_fold!(512, 352, 544), fe_sqr!(480, 480), fe_sqr!(32, 512), fe_sub_once!(544, 416, 448), fe_mul121666_add!(576, 544, 448), fe_mul!(96, 416, 448), fe_mul!(128, 544, 576), fe_mul!(64, 480, 0), in("rdi") ws.as_mut_ptr(),
inout("rsi") swap => _,
out("rax") _,
out("rcx") _,
out("rdx") _,
out("r8") _,
out("r9") _,
out("r10") _,
out("r11") _,
out("r12") _,
out("r13") _,
out("r14") _,
out("r15") _,
options(nostack),
);
}
}
#[inline(never)]
pub fn ladder_double(ws: &mut [u64; slot::LEN]) {
unsafe {
asm!(
".p2align 6",
fe_add_once!(288, 96, 128), fe_sub_once!(320, 96, 128), fe_sqr!(448, 320), fe_sqr!(416, 288), fe_sub_once!(544, 416, 448), fe_mul121666_add!(576, 544, 448), fe_mul!(96, 416, 448), fe_mul!(128, 544, 576), in("rdi") ws.as_mut_ptr(),
out("rax") _,
out("rcx") _,
out("rdx") _,
out("rsi") _,
out("r8") _,
out("r9") _,
out("r10") _,
out("r11") _,
out("r12") _,
out("r13") _,
out("r14") _,
out("r15") _,
options(nostack),
);
}
}
pub fn x25519_ladder(scalar: &[u8; 32], u: &[u8; 32]) -> [u8; 32] {
use ic_core::Zeroize;
let mut ws = [0u64; slot::LEN];
let put = |ws: &mut [u64; slot::LEN], at: usize, v: Fe64| {
ws[at..at + 4].copy_from_slice(&v.0);
};
let get = |ws: &[u64; slot::LEN], at: usize| -> Fe64 {
let mut v = [0u64; 4];
v.copy_from_slice(&ws[at..at + 4]);
Fe64(v)
};
let x1 = Fe64::from_bytes(u);
put(&mut ws, slot::X1, x1);
put(&mut ws, slot::XM, x1);
put(&mut ws, slot::ZM, Fe64::ONE);
put(&mut ws, slot::XN, x1);
put(&mut ws, slot::ZN, Fe64::ONE);
ladder_double(&mut ws);
let mut prev = 1u64;
for pos in (3..254).rev() {
let bit = u64::from((scalar[pos / 8] >> (pos & 7)) & 1);
ladder_step(&mut ws, bit ^ prev);
prev = bit;
}
let m = mask(prev);
let x = Fe64::select_mask(&get(&ws, slot::XN), &get(&ws, slot::XM), m);
let z = Fe64::select_mask(&get(&ws, slot::ZN), &get(&ws, slot::ZM), m);
put(&mut ws, slot::XN, x);
put(&mut ws, slot::ZN, z);
ladder_double(&mut ws);
ladder_double(&mut ws);
ladder_double(&mut ws);
let (x, z) = (get(&ws, slot::XN), get(&ws, slot::ZN));
ws.zeroize();
x.mul(&z.invert()).to_bytes()
}
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct Fe64(pub(crate) [u64; 4]);
#[inline]
#[must_use]
pub fn available() -> bool {
ic_core::cpu::has_bmi2_adx()
}
#[inline(always)]
pub fn mask(bit: u64) -> u64 {
let mut m = bit;
unsafe {
asm!("neg {m}", m = inout(reg) m, options(pure, nomem, nostack));
}
m
}
#[inline(always)]
const fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) {
let t = (a as u128)
.wrapping_add(b as u128)
.wrapping_add(carry as u128);
(t as u64, (t >> 64) as u64)
}
#[inline(always)]
const fn fold(w: [u64; 4], c: u64) -> [u64; 4] {
let (w0, k) = adc(w[0], c.wrapping_mul(38), 0);
let (w1, k) = adc(w[1], 0, k);
let (w2, k) = adc(w[2], 0, k);
let (w3, k) = adc(w[3], 0, k);
let (w0, _) = adc(w0, k.wrapping_mul(38), 0);
[w0, w1, w2, w3]
}
impl Fe64 {
pub const ZERO: Fe64 = Fe64([0; 4]);
pub const ONE: Fe64 = Fe64([1, 0, 0, 0]);
pub const fn from_limbs51(l: [u64; 5]) -> Fe64 {
let mut acc = (l[0] as u128).wrapping_add((l[1] as u128) << 51);
let w0 = acc as u64;
acc >>= 64;
acc = acc.wrapping_add((l[2] as u128) << 38);
let w1 = acc as u64;
acc >>= 64;
acc = acc.wrapping_add((l[3] as u128) << 25);
let w2 = acc as u64;
acc >>= 64;
acc = acc.wrapping_add((l[4] as u128) << 12);
let w3 = acc as u64;
Fe64(fold([w0, w1, w2, w3], (acc >> 64) as u64))
}
pub fn from_bytes(bytes: &[u8; 32]) -> Fe64 {
let load = |i: usize| -> u64 {
let mut v = [0u8; 8];
v.copy_from_slice(&bytes[i * 8..i * 8 + 8]);
u64::from_le_bytes(v)
};
Fe64([load(0), load(1), load(2), load(3) & (u64::MAX >> 1)])
}
pub fn invert(&self) -> Fe64 {
Fe64(inv::invert(self.canonical()))
}
pub fn to_bytes(self) -> [u8; 32] {
let r = self.canonical();
let mut out = [0u8; 32];
for (chunk, limb) in out.chunks_exact_mut(8).zip(r) {
chunk.copy_from_slice(&limb.to_le_bytes());
}
out
}
fn canonical(self) -> [u64; 4] {
let w = self.0;
let top = w[3] >> 63;
let (x0, k) = adc(w[0], top.wrapping_mul(19), 0);
let (x1, k) = adc(w[1], 0, k);
let (x2, k) = adc(w[2], 0, k);
let (x3, _) = adc(w[3] & (u64::MAX >> 1), 0, k);
let (y0, k) = adc(x0, 19, 0);
let (y1, k) = adc(x1, 0, k);
let (y2, k) = adc(x2, 0, k);
let (y3, _) = adc(x3, 0, k);
let ge = mask(y3 >> 63);
let y3 = y3 & (u64::MAX >> 1);
[
(y0 & ge) | (x0 & !ge),
(y1 & ge) | (x1 & !ge),
(y2 & ge) | (x2 & !ge),
(y3 & ge) | (x3 & !ge),
]
}
#[inline(always)]
pub fn add(&self, other: &Fe64) -> Fe64 {
let [mut r0, mut r1, mut r2, mut r3] = self.0;
let [b0, b1, b2, b3] = other.0;
unsafe {
asm!(
".p2align 6",
"add {r0}, {b0}",
"adc {r1}, {b1}",
"adc {r2}, {b2}",
"adc {r3}, {b3}",
"sbb {t}, {t}",
"and {t}, 38",
"add {r0}, {t}",
"adc {r1}, 0",
"adc {r2}, 0",
"adc {r3}, 0",
"sbb {t}, {t}",
"and {t}, 38",
"add {r0}, {t}",
r0 = inout(reg) r0,
r1 = inout(reg) r1,
r2 = inout(reg) r2,
r3 = inout(reg) r3,
b0 = in(reg) b0,
b1 = in(reg) b1,
b2 = in(reg) b2,
b3 = in(reg) b3,
t = out(reg) _,
options(pure, nomem, nostack),
);
}
Fe64([r0, r1, r2, r3])
}
#[inline(always)]
pub fn sub(&self, other: &Fe64) -> Fe64 {
let [mut r0, mut r1, mut r2, mut r3] = self.0;
let [b0, b1, b2, b3] = other.0;
unsafe {
asm!(
".p2align 6",
"sub {r0}, {b0}",
"sbb {r1}, {b1}",
"sbb {r2}, {b2}",
"sbb {r3}, {b3}",
"sbb {t}, {t}",
"and {t}, 38",
"sub {r0}, {t}",
"sbb {r1}, 0",
"sbb {r2}, 0",
"sbb {r3}, 0",
"sbb {t}, {t}",
"and {t}, 38",
"sub {r0}, {t}",
r0 = inout(reg) r0,
r1 = inout(reg) r1,
r2 = inout(reg) r2,
r3 = inout(reg) r3,
b0 = in(reg) b0,
b1 = in(reg) b1,
b2 = in(reg) b2,
b3 = in(reg) b3,
t = out(reg) _,
options(pure, nomem, nostack),
);
}
Fe64([r0, r1, r2, r3])
}
#[inline(always)]
pub fn neg(&self) -> Fe64 {
Fe64::ZERO.sub(self)
}
#[inline(always)]
pub fn mul(&self, other: &Fe64) -> Fe64 {
let (r0, r1, r2, r3);
unsafe {
asm!(
".p2align 6",
"mov rdx, qword ptr [rdi]",
"mulx r9, r8, qword ptr [rsi]",
"mulx r10, rax, qword ptr [rsi + 8]",
"add r9, rax",
"mulx r11, rax, qword ptr [rsi + 16]",
"adc r10, rax",
"mulx r12, rax, qword ptr [rsi + 24]",
"adc r11, rax",
"adc r12, 0",
"mov rdx, qword ptr [rdi + 8]",
"xor r13d, r13d",
"mulx rcx, rax, qword ptr [rsi]",
"adcx r9, rax",
"adox r10, rcx",
"mulx rcx, rax, qword ptr [rsi + 8]",
"adcx r10, rax",
"adox r11, rcx",
"mulx rcx, rax, qword ptr [rsi + 16]",
"adcx r11, rax",
"adox r12, rcx",
"mulx rcx, rax, qword ptr [rsi + 24]",
"adcx r12, rax",
"adox rcx, r13",
"adcx r13, rcx",
"mov rdx, qword ptr [rdi + 16]",
"xor r14d, r14d",
"mulx rcx, rax, qword ptr [rsi]",
"adcx r10, rax",
"adox r11, rcx",
"mulx rcx, rax, qword ptr [rsi + 8]",
"adcx r11, rax",
"adox r12, rcx",
"mulx rcx, rax, qword ptr [rsi + 16]",
"adcx r12, rax",
"adox r13, rcx",
"mulx rcx, rax, qword ptr [rsi + 24]",
"adcx r13, rax",
"adox rcx, r14",
"adcx r14, rcx",
"mov rdx, qword ptr [rdi + 24]",
"xor r15d, r15d",
"mulx rcx, rax, qword ptr [rsi]",
"adcx r11, rax",
"adox r12, rcx",
"mulx rcx, rax, qword ptr [rsi + 8]",
"adcx r12, rax",
"adox r13, rcx",
"mulx rcx, rax, qword ptr [rsi + 16]",
"adcx r13, rax",
"adox r14, rcx",
"mulx rcx, rax, qword ptr [rsi + 24]",
"adcx r14, rax",
"adox rcx, r15",
"adcx r15, rcx",
"mov edx, 38",
"xor esi, esi",
"mulx rcx, rax, r12",
"adcx r8, rax",
"adox r9, rcx",
"mulx rcx, rax, r13",
"adcx r9, rax",
"adox r10, rcx",
"mulx rcx, rax, r14",
"adcx r10, rax",
"adox r11, rcx",
"mulx r12, rax, r15",
"adcx r11, rax",
"adox r12, rsi",
"adcx r12, rsi",
"shld r12, r11, 1",
"btr r11, 63",
"imul r12, r12, 19",
"add r8, r12",
"adc r9, rsi",
"adc r10, rsi",
"adc r11, rsi",
inout("rsi") self.0.as_ptr() => _,
inout("rdi") other.0.as_ptr() => _,
out("rax") _,
out("rcx") _,
out("rdx") _,
out("r8") r0,
out("r9") r1,
out("r10") r2,
out("r11") r3,
out("r12") _,
out("r13") _,
out("r14") _,
out("r15") _,
options(pure, readonly, nostack),
);
}
Fe64([r0, r1, r2, r3])
}
#[inline(always)]
pub fn square(&self) -> Fe64 {
let (r0, r1, r2, r3);
unsafe {
asm!(
".p2align 6",
"mov rdx, qword ptr [rsi]",
"mulx r10, r9, qword ptr [rsi + 8]",
"mulx r11, rax, qword ptr [rsi + 16]",
"add r10, rax",
"mulx r12, rax, qword ptr [rsi + 24]",
"adc r11, rax",
"adc r12, 0",
"mov rdx, qword ptr [rsi + 8]",
"xor edi, edi",
"mulx rcx, rax, qword ptr [rsi + 16]",
"adcx r11, rax",
"adox r12, rcx",
"mulx r13, rax, qword ptr [rsi + 24]",
"adcx r12, rax",
"adox r13, rdi",
"adcx r13, rdi",
"mov rdx, qword ptr [rsi + 16]",
"mulx r14, rax, qword ptr [rsi + 24]",
"add r13, rax",
"adc r14, rdi",
"mov rdx, qword ptr [rsi]",
"mulx rcx, r8, rdx",
"xor r15d, r15d",
"adcx r9, r9",
"adox r9, rcx",
"mov rdx, qword ptr [rsi + 8]",
"mulx rcx, rax, rdx",
"adcx r10, r10",
"adox r10, rax",
"adcx r11, r11",
"adox r11, rcx",
"mov rdx, qword ptr [rsi + 16]",
"mulx rcx, rax, rdx",
"adcx r12, r12",
"adox r12, rax",
"adcx r13, r13",
"adox r13, rcx",
"mov rdx, qword ptr [rsi + 24]",
"mulx rcx, rax, rdx",
"adcx r14, r14",
"adox r14, rax",
"adcx r15, r15",
"adox r15, rcx",
"mov edx, 38",
"xor edi, edi",
"mulx rcx, rax, r12",
"adcx r8, rax",
"adox r9, rcx",
"mulx rcx, rax, r13",
"adcx r9, rax",
"adox r10, rcx",
"mulx rcx, rax, r14",
"adcx r10, rax",
"adox r11, rcx",
"mulx r12, rax, r15",
"adcx r11, rax",
"adox r12, rdi",
"adcx r12, rdi",
"shld r12, r11, 1",
"btr r11, 63",
"imul r12, r12, 19",
"add r8, r12",
"adc r9, rdi",
"adc r10, rdi",
"adc r11, rdi",
inout("rsi") self.0.as_ptr() => _,
out("rdi") _,
out("rax") _,
out("rcx") _,
out("rdx") _,
out("r8") r0,
out("r9") r1,
out("r10") r2,
out("r11") r3,
out("r12") _,
out("r13") _,
out("r14") _,
out("r15") _,
options(pure, readonly, nostack),
);
}
Fe64([r0, r1, r2, r3])
}
#[inline(always)]
pub fn mul121666(&self) -> Fe64 {
let [a0, a1, a2, a3] = self.0;
let (r0, r1, r2, r3);
unsafe {
asm!(
".p2align 6",
"mulx {h0}, {r0}, {a0}",
"mulx {h1}, {r1}, {a1}",
"add {r1}, {h0}",
"mulx {h0}, {r2}, {a2}",
"adc {r2}, {h1}",
"mulx {h1}, {r3}, {a3}",
"adc {r3}, {h0}",
"adc {h1}, 0",
"imul {h1}, {h1}, 38",
"add {r0}, {h1}",
"adc {r1}, 0",
"adc {r2}, 0",
"adc {r3}, 0",
"sbb {h1}, {h1}",
"and {h1}, 38",
"add {r0}, {h1}",
a0 = in(reg) a0,
a1 = in(reg) a1,
a2 = in(reg) a2,
a3 = in(reg) a3,
r0 = out(reg) r0,
r1 = out(reg) r1,
r2 = out(reg) r2,
r3 = out(reg) r3,
h0 = out(reg) _,
h1 = out(reg) _,
in("rdx") 121_666u64,
options(pure, nomem, nostack),
);
}
Fe64([r0, r1, r2, r3])
}
#[inline(always)]
pub fn select_mask(a: &Fe64, b: &Fe64, mask: u64) -> Fe64 {
Fe64(core::array::from_fn(|i| {
a.0[i] ^ (mask & (a.0[i] ^ b.0[i]))
}))
}
#[inline(always)]
pub fn cmov(a: &mut Fe64, b: &Fe64, choice: Choice) {
let mask = (choice.unwrap_u8() as u64).wrapping_neg();
for i in 0..4 {
a.0[i] ^= mask & (a.0[i] ^ b.0[i]);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::Fe;
fn skip() -> bool {
if available() {
return false;
}
std::eprintln!("fe64: CPU lacks BMI2/ADX; four-limb field not exercised");
true
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 >> 12;
self.0 ^= self.0 << 25;
self.0 ^= self.0 >> 27;
self.0.wrapping_mul(0x2545_f491_4f6c_dd1d)
}
fn limb(&mut self) -> u64 {
let r = self.next();
match r % 8 {
0 => u64::MAX,
1 => 0,
2 => 1u64 << (self.next() % 64),
3 => u64::MAX - (self.next() % 64),
_ => self.next(),
}
}
fn fe(&mut self) -> Fe64 {
Fe64([self.limb(), self.limb(), self.limb(), self.limb()])
}
}
fn reference(x: &Fe64) -> Fe {
let mut bytes = [0u8; 32];
for (chunk, limb) in bytes.chunks_exact_mut(8).zip(x.0) {
chunk.copy_from_slice(&limb.to_le_bytes());
}
let top = bytes[31] >> 7;
let lo = Fe::from_bytes(&bytes);
lo.add(&Fe::from_u64(19 * top as u64))
}
fn edges() -> std::vec::Vec<Fe64> {
const M: u64 = u64::MAX;
let p = [M - 18, M, M, M >> 1];
std::vec![
Fe64([0, 0, 0, 0]),
Fe64([1, 0, 0, 0]),
Fe64([p[0] - 1, p[1], p[2], p[3]]),
Fe64(p),
Fe64([p[0] + 1, p[1], p[2], p[3]]),
Fe64([M - 38, M, M, M]),
Fe64([M, M, M, M >> 1]),
Fe64([0, 0, 0, 1 << 63]),
Fe64([M, M, M, M]),
Fe64([M - 37, M, M, M]),
Fe64([M - 36, M, M, M]),
Fe64([38, 0, 0, 0]),
Fe64([19, 0, 0, 1 << 63]),
Fe64([M, 0, M, 0]),
Fe64([0, M, 0, M]),
]
}
#[test]
fn this_machine_runs_the_four_limb_field() {
if std::env::var_os("IC_EC_NO_ADX_OK").is_none() {
assert!(
available(),
"this CPU has no BMI2/ADX, so the four-limb field is untested"
);
}
}
#[test]
fn encoding_agrees_with_the_reference() {
if skip() {
return;
}
let mut rng = Rng(0x0123_4567_89ab_cdef);
let mut cases = edges();
for _ in 0..20_000 {
cases.push(rng.fe());
}
for x in &cases {
assert_eq!(x.to_bytes(), reference(x).to_bytes(), "to_bytes {x:x?}");
let mut b = [0u8; 32];
for (chunk, limb) in b.chunks_exact_mut(8).zip(x.0) {
chunk.copy_from_slice(&limb.to_le_bytes());
}
assert_eq!(
Fe64::from_bytes(&b).to_bytes(),
Fe::from_bytes(&b).to_bytes(),
"from_bytes {b:02x?}"
);
}
}
#[test]
fn every_operation_agrees_with_the_five_limb_field() {
if skip() {
return;
}
let check = |x: &Fe64, y: &Fe64| {
let (rx, ry) = (reference(x), reference(y));
assert_eq!(
x.mul(y).to_bytes(),
rx.mul(&ry).to_bytes(),
"mul {x:x?} {y:x?}"
);
assert_eq!(
x.add(y).to_bytes(),
rx.add(&ry).to_bytes(),
"add {x:x?} {y:x?}"
);
assert_eq!(
x.sub(y).to_bytes(),
rx.sub(&ry).to_bytes(),
"sub {x:x?} {y:x?}"
);
};
let check1 = |x: &Fe64| {
let rx = reference(x);
assert_eq!(
x.square().to_bytes(),
rx.square().to_bytes(),
"square {x:x?}"
);
assert_eq!(x.neg().to_bytes(), rx.neg().to_bytes(), "neg {x:x?}");
assert_eq!(
x.mul121666().to_bytes(),
rx.mul121666().to_bytes(),
"mul121666 {x:x?}"
);
};
let edges = edges();
for x in &edges {
check1(x);
for y in &edges {
check(x, y);
}
}
let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
let n = if cfg!(debug_assertions) {
200_000
} else {
2_000_000
};
for _ in 0..n {
let (x, y) = (rng.fe(), rng.fe());
check(&x, &y);
check1(&x);
}
}
#[test]
fn the_text_kernels_agree_with_the_five_limb_field() {
if skip() {
return;
}
fn run(a: &Fe64, b: &Fe64) -> [u64; 48] {
let mut ws = [0u64; 48];
ws[0..4].copy_from_slice(&a.0);
ws[4..8].copy_from_slice(&b.0);
unsafe {
asm!(
fe_sqr!(64, 0), fe_mul5!(96, 0, 32), fe_mul5!(136, 32, 32), fe_add5_fold!(176, 96, 136), fe_sub5_fold!(208, 96, 136), fe_sub5_fold!(240, 136, 96), fe_add_once!(272, 64, 176), fe_sub_once!(304, 64, 208), fe_mul121666_add!(336, 0, 32), in("rdi") ws.as_mut_ptr(),
out("rax") _,
out("rcx") _,
out("rdx") _,
out("rsi") _,
out("r8") _,
out("r9") _,
out("r10") _,
out("r11") _,
out("r12") _,
out("r13") _,
out("r14") _,
out("r15") _,
options(nostack),
);
}
ws
}
let four = |ws: &[u64; 48], at: usize| {
reference(&Fe64([ws[at], ws[at + 1], ws[at + 2], ws[at + 3]]))
};
let five = |ws: &[u64; 48], at: usize| {
four(ws, at).add(&Fe::from_u64(38).mul(&Fe::from_u64(ws[at + 4])))
};
let check = |a: &Fe64, b: &Fe64| {
let ws = run(a, b);
let (ra, rb) = (reference(a), reference(b));
let (ab, bb) = (ra.mul(&rb), rb.square());
let sq = ra.square();
let want = [
(64, false, sq),
(96, true, ab),
(136, true, bb),
(176, false, ab.add(&bb)),
(208, false, ab.sub(&bb)),
(240, false, bb.sub(&ab)),
(272, false, sq.add(&ab.add(&bb))),
(304, false, sq.sub(&ab.sub(&bb))),
(336, false, ra.mul121666().add(&rb)),
];
for (offset, wide, value) in want {
let at = offset / 8;
let got = if wide { five(&ws, at) } else { four(&ws, at) };
assert_eq!(
got.to_bytes(),
value.to_bytes(),
"slot {offset}, {a:x?} {b:x?}"
);
}
assert!(ws[96 / 8 + 4] <= 38 && ws[136 / 8 + 4] <= 38);
};
let edges = edges();
for a in &edges {
for b in &edges {
check(a, b);
}
}
let mut rng = Rng(0x5851_f42d_4c95_7f2d);
let n = if cfg!(debug_assertions) {
100_000
} else {
1_000_000
};
for _ in 0..n {
check(&rng.fe(), &rng.fe());
}
}
#[test]
fn chained_operations_agree() {
if skip() {
return;
}
let mut rng = Rng(0x2545_f491_4f6c_dd1d);
let (mut x, mut y) = (rng.fe(), rng.fe());
let (mut rx, mut ry) = (reference(&x), reference(&y));
for i in 0..200_000 {
match i % 5 {
0 => {
x = x.mul(&y);
rx = rx.mul(&ry);
}
1 => {
y = y.square();
ry = ry.square();
}
2 => {
x = x.sub(&y);
rx = rx.sub(&ry);
}
3 => {
y = y.add(&x).mul121666();
ry = ry.add(&rx).mul121666();
}
_ => {
let z = rng.fe();
x = x.add(&z);
rx = rx.add(&reference(&z));
}
}
assert_eq!(x.to_bytes(), rx.to_bytes(), "step {i}");
assert_eq!(y.to_bytes(), ry.to_bytes(), "step {i}");
}
}
#[test]
fn inversion_agrees_with_fermat() {
if skip() {
return;
}
let mut cases = edges();
let mut rng = Rng(0x0bad_cafe_dead_beef);
let n = if cfg!(debug_assertions) {
5_000
} else {
100_000
};
for _ in 0..n {
cases.push(rng.fe());
}
for x in &cases {
let got = x.invert();
let want = reference(x).invert();
assert_eq!(got.to_bytes(), want.to_bytes(), "invert {x:x?}");
let one = x.mul(&got).to_bytes();
let zero = bool::from(ic_core::ct::is_zero(&x.to_bytes()));
let mut expect = [0u8; 32];
expect[0] = u8::from(!zero);
assert_eq!(one, expect, "x * x^-1 for {x:x?}");
}
}
#[test]
fn conversion_from_five_limbs_is_exact() {
let mut rng = Rng(0xdead_beef_cafe_f00d);
for _ in 0..10_000 {
let l: [u64; 5] = core::array::from_fn(|_| rng.next() >> 12);
assert_eq!(
Fe64::from_limbs51(l).to_bytes(),
Fe::from_limbs51(l).to_bytes(),
"{l:x?}"
);
}
}
#[test]
#[ignore = "diagnostic"]
fn where_x25519_spends_its_time() {
use std::time::Instant;
if skip() {
return;
}
let time = |label: &str, n: u32, f: &mut dyn FnMut()| {
let mut best = f64::INFINITY;
for _ in 0..9 {
let s = Instant::now();
for _ in 0..n {
f();
}
best = best.min(s.elapsed().as_secs_f64() / n as f64 * 1e9);
}
std::println!("{label:<32} {best:>9.1} ns");
};
let mut ws = [7u64; slot::LEN];
time("251 ladder steps", 2000, &mut || {
for i in 0..251u64 {
ladder_step(core::hint::black_box(&mut ws), i & 1);
}
});
time("ladder_double", 200_000, &mut || {
ladder_double(core::hint::black_box(&mut ws));
});
let x = Fe64([1, 2, 3, 4]);
time("invert", 20_000, &mut || {
core::hint::black_box(core::hint::black_box(x).invert());
});
time("100 mul", 20_000, &mut || {
let mut y = x;
for _ in 0..100 {
y = y.mul(&x);
}
core::hint::black_box(y);
});
time("100 square", 20_000, &mut || {
let mut y = x;
for _ in 0..100 {
y = y.square();
}
core::hint::black_box(y);
});
fn all_steps(scalar: &[u8; 32], u: &[u8; 32]) -> [u8; 32] {
let mut ws = [0u64; slot::LEN];
let x1 = Fe64::from_bytes(u);
ws[slot::X1..slot::X1 + 4].copy_from_slice(&x1.0);
ws[slot::XM..slot::XM + 4].copy_from_slice(&x1.0);
ws[slot::ZM] = 1;
ws[slot::XN] = 1;
let mut prev = 0u64;
for pos in (0..255).rev() {
let bit = u64::from((scalar[pos / 8] >> (pos & 7)) & 1);
ladder_step(&mut ws, bit ^ prev);
prev = bit;
}
let m = mask(prev);
let g = |at: usize| Fe64([ws[at], ws[at + 1], ws[at + 2], ws[at + 3]]);
let x = Fe64::select_mask(&g(slot::XN), &g(slot::XM), m);
let z = Fe64::select_mask(&g(slot::ZN), &g(slot::ZM), m);
x.mul(&z.invert()).to_bytes()
}
let mut k = [0x5au8; 32];
k[0] &= 248;
k[31] = (k[31] & 127) | 64;
let u = [9u8; 32];
assert_eq!(all_steps(&k, &u), x25519_ladder(&k, &u));
let mut best = [f64::INFINITY; 2];
for _ in 0..40 {
for (i, f) in [
all_steps as fn(&[u8; 32], &[u8; 32]) -> [u8; 32],
x25519_ladder,
]
.into_iter()
.enumerate()
{
let s = Instant::now();
for _ in 0..100 {
core::hint::black_box(f(core::hint::black_box(&k), &u));
}
best[i] = best[i].min(s.elapsed().as_secs_f64() / 100.0 * 1e9);
}
}
std::println!("{:<32} {:>9.1} ns", "x25519, 255 steps", best[0]);
std::println!("{:<32} {:>9.1} ns", "x25519_ladder", best[1]);
}
#[test]
fn select_and_cmov_are_conditional() {
let (x, y) = (Fe64([1, 2, 3, 4]), Fe64([5, 6, 7, 8]));
assert_eq!(Fe64::select_mask(&x, &y, mask(0)).0, x.0);
assert_eq!(Fe64::select_mask(&x, &y, mask(1)).0, y.0);
let mut c = x;
Fe64::cmov(&mut c, &y, Choice::FALSE);
assert_eq!(c.0, x.0);
Fe64::cmov(&mut c, &y, Choice::TRUE);
assert_eq!(c.0, y.0);
}
}