use crate::bignum::Uint;
pub(crate) type Fe = Uint<4>;
pub(crate) const P_LIMBS: [u64; 4] = [
0xFFFF_FFFF_FFFF_FFFF,
0x0000_0000_FFFF_FFFF,
0x0000_0000_0000_0000,
0xFFFF_FFFF_0000_0001,
];
const K_LIMBS: [u64; 4] = [
0x0000_0000_0000_0001,
0xFFFF_FFFF_0000_0000,
0xFFFF_FFFF_FFFF_FFFF,
0x0000_0000_FFFF_FFFE,
];
#[inline]
fn mask_from_bit(bit: u64) -> u64 {
0u64.wrapping_sub(bit & 1)
}
#[inline]
fn sub_p_mask(r: &[u64; 4], hi: u64) -> ([u64; 4], u64) {
let mut out = [0u64; 4];
let mut borrow: u128 = 0;
let mut i = 0;
while i < 4 {
let tmp = (r[i] as u128).wrapping_sub(P_LIMBS[i] as u128 + borrow);
out[i] = tmp as u64;
borrow = (tmp >> 64) & 1;
i += 1;
}
let mask = mask_from_bit(hi | (borrow as u64 ^ 1));
(out, mask)
}
#[inline]
fn select(a: &[u64; 4], b: &[u64; 4], mask: u64) -> [u64; 4] {
let mut out = [0u64; 4];
let mut i = 0;
while i < 4 {
out[i] = (a[i] & !mask) | (b[i] & mask);
i += 1;
}
out
}
#[inline]
fn reduce_once(r: [u64; 4], hi: u64) -> [u64; 4] {
let (diff, mask) = sub_p_mask(&r, hi);
select(&r, &diff, mask)
}
#[inline]
fn add_mul_k(r: &[u64; 4], m: u64) -> ([u64; 4], u64) {
let mut out = [0u64; 4];
let mut carry: u128 = 0;
let mut i = 0;
while i < 4 {
let acc = (r[i] as u128) + (m as u128) * (K_LIMBS[i] as u128) + carry;
out[i] = acc as u64;
carry = acc >> 64;
i += 1;
}
(out, carry as u64)
}
#[inline]
fn reduce512(t: &[u64; 8]) -> [u64; 4] {
let a0 = (t[0] & 0xFFFF_FFFF) as i64;
let a1 = (t[0] >> 32) as i64;
let a2 = (t[1] & 0xFFFF_FFFF) as i64;
let a3 = (t[1] >> 32) as i64;
let a4 = (t[2] & 0xFFFF_FFFF) as i64;
let a5 = (t[2] >> 32) as i64;
let a6 = (t[3] & 0xFFFF_FFFF) as i64;
let a7 = (t[3] >> 32) as i64;
let a8 = (t[4] & 0xFFFF_FFFF) as i64;
let a9 = (t[4] >> 32) as i64;
let a10 = (t[5] & 0xFFFF_FFFF) as i64;
let a11 = (t[5] >> 32) as i64;
let a12 = (t[6] & 0xFFFF_FFFF) as i64;
let a13 = (t[6] >> 32) as i64;
let a14 = (t[7] & 0xFFFF_FFFF) as i64;
let a15 = (t[7] >> 32) as i64;
let w0 = a0 + a8 + a9 - a11 - a12 - a13 - a14;
let w1 = a1 + a9 + a10 - a12 - a13 - a14 - a15;
let w2 = a2 + a10 + a11 - a13 - a14 - a15;
let w3 = a3 + 2 * (a11 + a12) + a13 - a15 - a8 - a9;
let w4 = a4 + 2 * (a12 + a13) + a14 - a9 - a10;
let w5 = a5 + 2 * (a13 + a14) + a15 - a10 - a11;
let w6 = a6 + a13 + 3 * a14 + 2 * a15 - a8 - a9;
let w7 = a7 + a8 + 3 * a15 - a10 - a11 - a12 - a13;
const P8: [u64; 4] = [
0xFFFF_FFFF_FFFF_FFF8,
0x0000_0007_FFFF_FFFF,
0,
0xFFFF_FFF8_0000_0008,
];
const P8_TOP: u64 = 7;
let mut r = [0u64; 4];
let acc = (w0 as i128) + ((w1 as i128) << 32) + (P8[0] as i128);
r[0] = acc as u64;
let acc = (w2 as i128) + ((w3 as i128) << 32) + (P8[1] as i128) + (acc >> 64);
r[1] = acc as u64;
let acc = (w4 as i128) + ((w5 as i128) << 32) + (P8[2] as i128) + (acc >> 64);
r[2] = acc as u64;
let acc = (w6 as i128) + ((w7 as i128) << 32) + (P8[3] as i128) + (acc >> 64);
r[3] = acc as u64;
let hi = ((acc >> 64) as u64).wrapping_add(P8_TOP);
let (r, c1) = add_mul_k(&r, hi);
reduce_once(r, c1)
}
#[inline]
fn mul_wide(a: &[u64; 4], b: &[u64; 4]) -> [u64; 8] {
let mut t = [0u64; 8];
let mut i = 0;
while i < 4 {
let mut carry: u128 = 0;
let mut j = 0;
while j < 4 {
let acc = (t[i + j] as u128) + (a[i] as u128) * (b[j] as u128) + carry;
t[i + j] = acc as u64;
carry = acc >> 64;
j += 1;
}
t[i + 4] = carry as u64;
i += 1;
}
t
}
#[inline]
fn sqr_wide(a: &[u64; 4]) -> [u64; 8] {
let mut t = [0u64; 8];
let mut carry: u128 = 0;
let mut j = 1;
while j < 4 {
let acc = (t[j] as u128) + (a[0] as u128) * (a[j] as u128) + carry;
t[j] = acc as u64;
carry = acc >> 64;
j += 1;
}
t[4] = carry as u64;
carry = 0;
let mut j = 2;
while j < 4 {
let acc = (t[1 + j] as u128) + (a[1] as u128) * (a[j] as u128) + carry;
t[1 + j] = acc as u64;
carry = acc >> 64;
j += 1;
}
t[5] = carry as u64;
let acc = (t[5] as u128) + (a[2] as u128) * (a[3] as u128);
t[5] = acc as u64;
t[6] = (acc >> 64) as u64;
let mut top = 0u64;
let mut i = 0;
while i < 8 {
let v = t[i];
t[i] = (v << 1) | top;
top = v >> 63;
i += 1;
}
let mut carry: u128 = 0;
let mut i = 0;
while i < 4 {
let sq = (a[i] as u128) * (a[i] as u128);
let acc = (t[2 * i] as u128) + (sq & 0xFFFF_FFFF_FFFF_FFFF) + carry;
t[2 * i] = acc as u64;
carry = acc >> 64;
let acc = (t[2 * i + 1] as u128) + (sq >> 64) + carry;
t[2 * i + 1] = acc as u64;
carry = acc >> 64;
i += 1;
}
t
}
#[inline]
pub(crate) fn add(a: &Fe, b: &Fe) -> Fe {
let a = a.as_limbs();
let b = b.as_limbs();
let mut r = [0u64; 4];
let mut carry: u128 = 0;
let mut i = 0;
while i < 4 {
let acc = (a[i] as u128) + (b[i] as u128) + carry;
r[i] = acc as u64;
carry = acc >> 64;
i += 1;
}
Fe::from_limbs(reduce_once(r, carry as u64))
}
#[inline]
pub(crate) fn sub(a: &Fe, b: &Fe) -> Fe {
let a = a.as_limbs();
let b = b.as_limbs();
let mut r = [0u64; 4];
let mut borrow: u128 = 0;
let mut i = 0;
while i < 4 {
let tmp = (a[i] as u128).wrapping_sub(b[i] as u128 + borrow);
r[i] = tmp as u64;
borrow = (tmp >> 64) & 1;
i += 1;
}
let mask = mask_from_bit(borrow as u64);
let mut out = [0u64; 4];
let mut carry: u128 = 0;
let mut j = 0;
while j < 4 {
let acc = (r[j] as u128) + ((P_LIMBS[j] & mask) as u128) + carry;
out[j] = acc as u64;
carry = acc >> 64;
j += 1;
}
Fe::from_limbs(out)
}
#[inline]
pub(crate) fn negate(a: &Fe) -> Fe {
let a = a.as_limbs();
let mut r = [0u64; 4];
let mut borrow: u128 = 0;
let mut i = 0;
while i < 4 {
let tmp = (P_LIMBS[i] as u128).wrapping_sub(a[i] as u128 + borrow);
r[i] = tmp as u64;
borrow = (tmp >> 64) & 1;
i += 1;
}
let or = a[0] | a[1] | a[2] | a[3];
let nonzero_bit = (or | or.wrapping_neg()) >> 63;
let zero_mask = mask_from_bit(nonzero_bit ^ 1);
Fe::from_limbs(select(&r, &[0u64; 4], zero_mask))
}
#[inline]
pub(crate) fn mul(a: &Fe, b: &Fe) -> Fe {
Fe::from_limbs(reduce512(&mul_wide(a.as_limbs(), b.as_limbs())))
}
#[inline]
pub(crate) fn square(a: &Fe) -> Fe {
Fe::from_limbs(reduce512(&sqr_wide(a.as_limbs())))
}
#[inline]
fn sqn(a: &Fe, n: u32) -> Fe {
let mut acc = *a;
let mut i = 0;
while i < n {
acc = square(&acc);
i += 1;
}
acc
}
pub(crate) fn invert(a: &Fe) -> Fe {
let x1 = *a;
let x2 = mul(&square(&x1), &x1);
let x4 = mul(&sqn(&x2, 2), &x2);
let x8 = mul(&sqn(&x4, 4), &x4);
let x16 = mul(&sqn(&x8, 8), &x8);
let x32 = mul(&sqn(&x16, 16), &x16);
let mut acc = sqn(&x32, 32);
acc = mul(&acc, &x1);
acc = mul(&sqn(&acc, 128), &x32);
acc = mul(&sqn(&acc, 32), &x32);
acc = mul(&sqn(&acc, 16), &x16);
acc = mul(&sqn(&acc, 8), &x8);
acc = mul(&sqn(&acc, 4), &x4);
acc = mul(&sqn(&acc, 2), &x2);
mul(&sqn(&acc, 2), &x1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bignum::MontModulus;
fn p() -> Fe {
Fe::from_limbs(P_LIMBS)
}
struct Oracle {
fp: MontModulus<4>,
}
impl Oracle {
fn new() -> Self {
Oracle {
fp: MontModulus::new(p()),
}
}
fn add(&self, a: &Fe, b: &Fe) -> Fe {
self.fp.add_mod(a, b)
}
fn sub(&self, a: &Fe, b: &Fe) -> Fe {
self.fp.sub_mod(a, b)
}
fn mul(&self, a: &Fe, b: &Fe) -> Fe {
self.fp.mul_mod(a, b)
}
fn negate(&self, a: &Fe) -> Fe {
self.fp.sub_mod(&Fe::ZERO, a)
}
fn invert(&self, a: &Fe) -> Fe {
let p_minus_2 = p().wrapping_sub(&Fe::from_u64(2));
self.fp.pow(a, &p_minus_2)
}
}
struct SplitMix64(u64);
impl SplitMix64 {
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
}
fn rand_fe(rng: &mut SplitMix64) -> Fe {
let limbs = [
rng.next_u64(),
rng.next_u64(),
rng.next_u64(),
rng.next_u64(),
];
Fe::from_limbs(limbs).reduce(&p())
}
fn edge_cases() -> [Fe; 14] {
let prime = p();
let p_minus_1 = prime.wrapping_sub(&Fe::ONE);
let p_minus_2 = prime.wrapping_sub(&Fe::from_u64(2));
let all_ones = Fe::from_limbs([u64::MAX; 4]).reduce(&prime);
[
Fe::ZERO,
Fe::ONE,
Fe::from_u64(2),
Fe::from_u64(7),
p_minus_1,
p_minus_2,
all_ones,
Fe::from_limbs([0, 1 << 32, 0, 0]),
Fe::from_limbs([0, 0, 0, 1]),
Fe::from_limbs([0, 0, 0, 1 << 32]),
Fe::from_limbs(K_LIMBS),
Fe::from_limbs([0, K_LIMBS[1], K_LIMBS[2], K_LIMBS[3]]),
Fe::from_limbs([0xFFFF_FFFF, 0xFFFF_FFFF_0000_0000, 0xFFFF_FFFF, 0]),
Fe::from_limbs([0, 0, u64::MAX, 0xFFFF_FFFF]),
]
}
fn bytes(a: &Fe) -> [u8; 32] {
let mut out = [0u8; 32];
a.write_be_bytes(&mut out);
out
}
fn check_pair(g: &Oracle, a: &Fe, b: &Fe) {
assert_eq!(
bytes(&g.add(a, b)),
bytes(&add(a, b)),
"add mismatch: a={:x?} b={:x?}",
a.as_limbs(),
b.as_limbs()
);
assert_eq!(
bytes(&g.sub(a, b)),
bytes(&sub(a, b)),
"sub mismatch: a={:x?} b={:x?}",
a.as_limbs(),
b.as_limbs()
);
assert_eq!(
bytes(&g.mul(a, b)),
bytes(&mul(a, b)),
"mul mismatch: a={:x?} b={:x?}",
a.as_limbs(),
b.as_limbs()
);
}
fn check_unary(g: &Oracle, a: &Fe) {
assert_eq!(
bytes(&g.mul(a, a)),
bytes(&square(a)),
"square mismatch: a={:x?}",
a.as_limbs()
);
assert_eq!(
bytes(&g.negate(a)),
bytes(&negate(a)),
"negate mismatch: a={:x?}",
a.as_limbs()
);
assert_eq!(
bytes(&g.invert(a)),
bytes(&invert(a)),
"invert mismatch: a={:x?}",
a.as_limbs()
);
}
#[test]
fn native_matches_generic_edge_cases() {
let g = Oracle::new();
let cases = edge_cases();
for a in &cases {
check_unary(&g, a);
for b in &cases {
check_pair(&g, a, b);
}
}
}
#[test]
fn native_matches_generic_random_batch() {
let g = Oracle::new();
let mut rng = SplitMix64(0x0123_4567_89AB_CDEF);
for _ in 0..100_000 {
let a = rand_fe(&mut rng);
let b = rand_fe(&mut rng);
check_pair(&g, &a, &b);
assert_eq!(bytes(&g.mul(&a, &a)), bytes(&square(&a)));
}
}
#[test]
fn native_matches_generic_random_unary() {
let g = Oracle::new();
let mut rng = SplitMix64(0xDEAD_BEEF_CAFE_F00D);
for _ in 0..2_000 {
let a = rand_fe(&mut rng);
check_unary(&g, &a);
}
}
#[test]
fn invert_roundtrips_and_handles_zero() {
let mut rng = SplitMix64(0xA5A5_5A5A_F0F0_0F0F);
for _ in 0..1_000 {
let a = rand_fe(&mut rng);
if bool::from(a.is_zero()) {
continue;
}
assert_eq!(bytes(&mul(&a, &invert(&a))), bytes(&Fe::ONE));
}
assert_eq!(bytes(&invert(&Fe::ZERO)), bytes(&Fe::ZERO));
}
#[test]
#[ignore = "manual microbenchmark"]
#[cfg(feature = "std")]
fn microbench_field_ops() {
use std::println;
use std::time::Instant;
let g = Oracle::new();
let mut rng = SplitMix64(42);
let mut a = rand_fe(&mut rng);
let b = rand_fe(&mut rng);
let am = g.fp.to_mont(&a);
let bm = g.fp.to_mont(&b);
const N: u32 = 2_000_000;
let t = Instant::now();
let mut x = a;
for _ in 0..N {
x = mul(&x, &b);
}
core::hint::black_box(&x);
println!("native mul: {:?}/op", t.elapsed() / N);
let t = Instant::now();
let mut x = am;
for _ in 0..N {
x = g.fp.mont_mul(&x, &bm);
}
core::hint::black_box(&x);
println!("mont_mul: {:?}/op", t.elapsed() / N);
let t = Instant::now();
let mut x = a;
for _ in 0..N {
x = square(&x);
}
core::hint::black_box(&x);
println!("native sqr: {:?}/op", t.elapsed() / N);
let t = Instant::now();
let mut x = am;
for _ in 0..N {
x = g.fp.mont_sqr(&x);
}
core::hint::black_box(&x);
println!("mont_sqr: {:?}/op", t.elapsed() / N);
let t = Instant::now();
let mut x = a;
for _ in 0..N {
x = add(&x, &b);
}
core::hint::black_box(&x);
println!("native add: {:?}/op", t.elapsed() / N);
let t = Instant::now();
let mut x = a;
for _ in 0..N {
x = g.fp.add_mod(&x, &b);
}
core::hint::black_box(&x);
println!("generic add: {:?}/op", t.elapsed() / N);
let t = Instant::now();
let mut w = mul_wide(a.as_limbs(), b.as_limbs());
for _ in 0..N {
w = mul_wide(&[w[0], w[1], w[2], w[3]], &[w[4], w[5], w[6], w[7]]);
}
core::hint::black_box(&w);
println!("mul_wide: {:?}/op", t.elapsed() / N);
let t = Instant::now();
let mut r = *a.as_limbs();
for _ in 0..N {
r = reduce512(&[r[0], r[1], r[2], r[3], r[0], r[1], r[2], r[3]]);
}
core::hint::black_box(&r);
println!("reduce512: {:?}/op", t.elapsed() / N);
let t = Instant::now();
for _ in 0..2000 {
a = invert(&a);
}
core::hint::black_box(&a);
println!("native inv: {:?}/op", t.elapsed() / 2000);
let t = Instant::now();
for _ in 0..2000 {
a = g.invert(&a);
}
core::hint::black_box(&a);
println!("generic inv: {:?}/op", t.elapsed() / 2000);
}
#[test]
fn solinas_extreme_word_patterns() {
let g = Oracle::new();
let prime = p();
let patterns = [
Fe::from_limbs([u64::MAX, 0, u64::MAX, 0]).reduce(&prime),
Fe::from_limbs([0, u64::MAX, 0, u64::MAX]).reduce(&prime),
Fe::from_limbs([
0xFFFF_FFFF_0000_0000,
0xFFFF_FFFF,
0xFFFF_FFFF_0000_0000,
0xFFFF_FFFF,
])
.reduce(&prime),
prime.wrapping_sub(&Fe::ONE),
Fe::from_limbs([u64::MAX; 4]).reduce(&prime),
];
for a in &patterns {
for b in &patterns {
check_pair(&g, a, b);
}
check_unary(&g, a);
}
}
}