const M62: u64 = u64::MAX >> 2;
type Signed62<const N: usize> = [i64; N];
#[derive(Clone, Copy)]
pub(crate) struct ModInfo<const N: usize> {
modulus: Signed62<N>,
modulus_inv62: u64,
}
impl<const N: usize> ModInfo<N> {
pub(crate) const fn new<const W: usize>(m: [u64; W]) -> Self {
assert!(62 * N > 64 * W && 62 * (N - 1) < 64 * W);
let modulus = to_signed62::<W, N>(m);
let mut inv = m[0];
let mut i = 0;
while i < 6 {
inv = inv.wrapping_mul(2u64.wrapping_sub(m[0].wrapping_mul(inv)));
i += 1;
}
ModInfo {
modulus,
modulus_inv62: inv & M62,
}
}
}
const fn to_signed62<const W: usize, const N: usize>(a: [u64; W]) -> Signed62<N> {
let mut out = [0i64; N];
let mut i = 0;
while i < N {
let bit = 62 * i;
let (word, off) = (bit / 64, bit % 64);
let mut v = 0u64;
if word < W {
v = a[word] >> off;
if off > 2 && word + 1 < W {
v |= a[word + 1] << (64 - off);
}
}
out[i] = if i + 1 < N {
(v & M62) as i64
} else {
v as i64
};
i += 1;
}
out
}
#[inline(always)]
fn from_signed62<const N: usize, const W: usize>(a: &Signed62<N>) -> [u64; W] {
let mut out = [0u64; W];
for (i, &limb) in a.iter().enumerate() {
let bit = 62 * i;
let (word, off) = (bit / 64, bit % 64);
let v = limb as u64;
if word < W {
out[word] |= v << off;
}
if off > 2 && word + 1 < W {
out[word + 1] |= v >> (64 - off);
}
}
out
}
struct Trans {
u: i64,
v: i64,
q: i64,
r: i64,
}
struct Steps {
delta: i64,
f: u64,
g: u64,
u: u64,
v: u64,
q: u64,
r: u64,
}
impl Steps {
#[inline(always)]
fn trans(&self) -> Trans {
Trans {
u: self.u as i64,
v: self.v as i64,
q: self.q as i64,
r: self.r as i64,
}
}
}
#[inline(always)]
fn hdstep(s: &mut Steps) {
let c1 = core::hint::black_box((s.delta >> 63) as u64);
let mut mask1 = c1;
let c2 = core::hint::black_box(s.g & 1);
let mask2 = c2.wrapping_neg();
let x = (s.f ^ mask1).wrapping_sub(mask1);
let y = (s.u ^ mask1).wrapping_sub(mask1);
let z = (s.v ^ mask1).wrapping_sub(mask1);
s.g = s.g.wrapping_add(x & mask2);
s.q = s.q.wrapping_add(y & mask2);
s.r = s.r.wrapping_add(z & mask2);
mask1 &= mask2;
s.delta = (s.delta ^ mask1 as i64).wrapping_sub(1);
s.f = s.f.wrapping_add(s.g & mask1);
s.u = s.u.wrapping_add(s.q & mask1);
s.v = s.v.wrapping_add(s.r & mask1);
s.g >>= 1;
s.u <<= 1;
s.v <<= 1;
}
#[inline(always)]
fn divstep(s: &mut Steps) {
let c1 = core::hint::black_box((s.delta.wrapping_neg() >> 63) as u64);
let c2 = core::hint::black_box(s.g & 1);
let odd = c2.wrapping_neg();
let swap = c1 & odd;
let x = (s.f ^ swap).wrapping_sub(swap);
let y = (s.u ^ swap).wrapping_sub(swap);
let z = (s.v ^ swap).wrapping_sub(swap);
s.g = s.g.wrapping_add(x & odd);
s.q = s.q.wrapping_add(y & odd);
s.r = s.r.wrapping_add(z & odd);
s.f = s.f.wrapping_add(s.g & swap);
s.u = s.u.wrapping_add(s.q & swap);
s.v = s.v.wrapping_add(s.r & swap);
s.delta = (s.delta ^ swap as i64)
.wrapping_sub(swap as i64)
.wrapping_add(1);
s.g >>= 1;
s.u <<= 1;
s.v <<= 1;
}
macro_rules! steps {
($f:ident, $s:ident, 1) => {
$f(&mut $s);
};
($f:ident, $s:ident, 2) => {
steps!($f, $s, 1);
steps!($f, $s, 1);
};
($f:ident, $s:ident, 4) => {
steps!($f, $s, 2);
steps!($f, $s, 2);
};
($f:ident, $s:ident, 8) => {
steps!($f, $s, 4);
steps!($f, $s, 4);
};
($f:ident, $s:ident, 16) => {
steps!($f, $s, 8);
steps!($f, $s, 8);
};
($f:ident, $s:ident, 32) => {
steps!($f, $s, 16);
steps!($f, $s, 16);
};
}
#[inline(always)]
fn divsteps_59(zeta: i64, f0: u64, g0: u64) -> (i64, Trans) {
let mut s = Steps {
delta: zeta,
f: f0,
g: g0,
u: 8,
v: 0,
q: 0,
r: 8,
};
steps!(hdstep, s, 32);
steps!(hdstep, s, 16);
steps!(hdstep, s, 8);
steps!(hdstep, s, 2);
steps!(hdstep, s, 1);
(s.delta, s.trans())
}
#[inline(always)]
fn divsteps_62(delta: i64, f0: u64, g0: u64) -> (i64, Trans) {
let mut s = Steps {
delta,
f: f0,
g: g0,
u: 1,
v: 0,
q: 0,
r: 1,
};
steps!(divstep, s, 32);
steps!(divstep, s, 16);
steps!(divstep, s, 8);
steps!(divstep, s, 4);
steps!(divstep, s, 2);
(s.delta, s.trans())
}
#[inline(always)]
fn m(a: i64, b: i64) -> i128 {
a as i128 * b as i128
}
#[inline(always)]
fn update_de<const N: usize>(
d: &mut Signed62<N>,
e: &mut Signed62<N>,
t: &Trans,
info: &ModInfo<N>,
) {
let (u, v, q, r) = (t.u, t.v, t.q, t.r);
let md_mod = &info.modulus;
let sd = d[N - 1] >> 63;
let se = e[N - 1] >> 63;
let mut md = (u & sd).wrapping_add(v & se);
let mut me = (q & sd).wrapping_add(r & se);
let mut cd = m(u, d[0]) + m(v, e[0]);
let mut ce = m(q, d[0]) + m(r, e[0]);
md = md.wrapping_sub(
(info
.modulus_inv62
.wrapping_mul(cd as u64)
.wrapping_add(md as u64)
& M62) as i64,
);
me = me.wrapping_sub(
(info
.modulus_inv62
.wrapping_mul(ce as u64)
.wrapping_add(me as u64)
& M62) as i64,
);
cd += m(md_mod[0], md);
ce += m(md_mod[0], me);
debug_assert!(cd as u64 & M62 == 0 && ce as u64 & M62 == 0);
cd >>= 62;
ce >>= 62;
for i in 1..N {
cd += m(u, d[i]) + m(v, e[i]);
ce += m(q, d[i]) + m(r, e[i]);
if md_mod[i] != 0 {
cd += m(md_mod[i], md);
ce += m(md_mod[i], me);
}
d[i - 1] = (cd as u64 & M62) as i64;
cd >>= 62;
e[i - 1] = (ce as u64 & M62) as i64;
ce >>= 62;
}
d[N - 1] = cd as i64;
e[N - 1] = ce as i64;
}
#[inline(always)]
fn update_fg<const N: usize>(f: &mut Signed62<N>, g: &mut Signed62<N>, t: &Trans) {
let (u, v, q, r) = (t.u, t.v, t.q, t.r);
let mut cf = m(u, f[0]) + m(v, g[0]);
let mut cg = m(q, f[0]) + m(r, g[0]);
debug_assert!(cf as u64 & M62 == 0 && cg as u64 & M62 == 0);
cf >>= 62;
cg >>= 62;
for i in 1..N {
cf += m(u, f[i]) + m(v, g[i]);
cg += m(q, f[i]) + m(r, g[i]);
f[i - 1] = (cf as u64 & M62) as i64;
cf >>= 62;
g[i - 1] = (cg as u64 & M62) as i64;
cg >>= 62;
}
f[N - 1] = cf as i64;
g[N - 1] = cg as i64;
}
#[inline(always)]
fn normalize<const N: usize>(r: &mut Signed62<N>, sign: i64, info: &ModInfo<N>) {
let md = &info.modulus;
let m62 = M62 as i64;
let cond_add = core::hint::black_box(r[N - 1] >> 63);
for (x, &mi) in r.iter_mut().zip(md) {
*x += mi & cond_add;
}
let cond_negate = core::hint::black_box(sign >> 63);
for x in r.iter_mut() {
*x = (*x ^ cond_negate) - cond_negate;
}
for i in 0..N - 1 {
r[i + 1] += r[i] >> 62;
r[i] &= m62;
}
let cond_add = core::hint::black_box(r[N - 1] >> 63);
for (x, &mi) in r.iter_mut().zip(md) {
*x += mi & cond_add;
}
for i in 0..N - 1 {
r[i + 1] += r[i] >> 62;
r[i] &= m62;
}
}
pub(crate) trait Modulus<const N: usize> {
const INFO: ModInfo<N>;
}
struct State<const N: usize> {
delta: i64,
d: Signed62<N>,
e: Signed62<N>,
f: Signed62<N>,
g: Signed62<N>,
}
impl<const N: usize> State<N> {
#[inline(always)]
fn new(delta: i64, x: Signed62<N>, info: &ModInfo<N>) -> Self {
let mut e = [0; N];
e[0] = 1;
State {
delta,
d: [0; N],
e,
f: info.modulus,
g: x,
}
}
#[inline(always)]
fn apply(&mut self, delta: i64, t: &Trans, info: &ModInfo<N>) {
self.delta = delta;
update_de(&mut self.d, &mut self.e, t, info);
update_fg(&mut self.f, &mut self.g, t);
}
#[inline(always)]
fn finish<const W: usize>(mut self, info: &ModInfo<N>) -> [u64; W] {
normalize(&mut self.d, self.f[N - 1], info);
from_signed62(&self.d)
}
}
#[inline(never)]
fn batch_256<M: Modulus<5>>(s: &mut State<5>) {
let (zeta, t) = divsteps_59(s.delta, s.f[0] as u64, s.g[0] as u64);
s.apply(zeta, &t, &M::INFO);
}
pub(crate) fn invert<M: Modulus<5>>(x: &[u64; 4]) -> [u64; 4] {
let mut s = State::new(-1, to_signed62::<4, 5>(*x), &M::INFO);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
batch_256::<M>(&mut s);
#[cfg(test)]
debug_assert!(s.g == [0; 5], "g did not reach zero");
s.finish(&M::INFO)
}
const BATCHES_384: usize = 18;
const _: () = assert!(62 * BATCHES_384 >= (49 * 384 + 80) / 17);
#[inline(never)]
fn batch_384<M: Modulus<7>>(s: &mut State<7>) {
let (delta, t) = divsteps_62(s.delta, s.f[0] as u64, s.g[0] as u64);
s.apply(delta, &t, &M::INFO);
}
pub(crate) fn invert_384<M: Modulus<7>>(x: &[u64; 6]) -> [u64; 6] {
let mut s = State::new(1, to_signed62::<6, 7>(*x), &M::INFO);
const _: () = assert!(BATCHES_384 == 18);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
batch_384::<M>(&mut s);
#[cfg(test)]
debug_assert!(s.g == [0; 7], "g did not reach zero");
s.finish(&M::INFO)
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::type_complexity)]
fn moduli() -> [([u64; 4], fn(&[u64; 4]) -> [u64; 4]); 2] {
[
(crate::nist::p256_field::P, invert::<crate::p256::FpModulus>),
(
<crate::p256::P256 as crate::nist::point::Curve>::Scalar::MODULUS,
invert::<crate::p256::FnModulus>,
),
]
}
fn mulmod<const W: usize>(a: [u64; W], b: [u64; W], m: [u64; W]) -> [u64; W] {
let mut p = std::vec![0u64; 2 * W];
for i in 0..W {
let mut carry = 0u128;
for j in 0..W {
let t = p[i + j] as u128 + a[i] as u128 * b[j] as u128 + carry;
p[i + j] = t as u64;
carry = t >> 64;
}
p[i + W] = carry as u64;
}
let mut r = std::vec![0u64; W + 1];
for bit in (0..128 * W).rev() {
for k in (1..W + 1).rev() {
r[k] = r[k] << 1 | r[k - 1] >> 63;
}
r[0] = r[0] << 1 | (p[bit / 64] >> (bit % 64)) & 1;
let mut ge = r[W] != 0;
if !ge {
ge = true;
for k in (0..W).rev() {
if r[k] != m[k] {
ge = r[k] > m[k];
break;
}
}
}
if ge {
let mut borrow = 0u128;
for k in 0..W + 1 {
let mk = if k < W { m[k] } else { 0 };
let t = (r[k] as u128).wrapping_sub(mk as u128).wrapping_sub(borrow);
r[k] = t as u64;
borrow = (t >> 127) & 1;
}
}
}
let mut out = [0u64; W];
out.copy_from_slice(&r[..W]);
out
}
#[test]
fn inverses_are_inverses() {
let mut state = 0x05af_e9cd_u64;
let mut next = || {
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)
};
for (m, invert) in moduli() {
assert_eq!(invert(&[0; 4]), [0; 4]);
let below = |x: [u64; 4]| {
(0..4)
.rev()
.find(|&k| x[k] != m[k])
.is_some_and(|k| x[k] < m[k])
};
let mut inputs = std::vec![
[1u64, 0, 0, 0],
[2, 0, 0, 0],
[m[0] - 1, m[1], m[2], m[3]],
[m[0] - 2, m[1], m[2], m[3]],
[0, 0, 0, 1 << 63],
[u64::MAX, u64::MAX, u64::MAX, m[3] - 1],
];
while inputs.len() < 100_000 {
let x = [next(), next(), next(), next()];
if below(x) {
inputs.push(x);
}
}
for (i, x) in inputs.iter().enumerate() {
let inv = invert(x);
assert!(below(inv), "{x:x?}");
if i < 2_000 {
assert_eq!(mulmod(*x, inv, m), [1, 0, 0, 0], "{x:x?}");
} else {
assert_eq!(invert(&inv), *x, "{x:x?}");
}
}
}
}
#[test]
fn the_batch_is_the_papers_divstep() {
let mut state = 0x62_u64;
let mut next = || {
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)
};
for round in 0..20_000 {
let f0 = (next() & M62) | 1;
let g0 = match round % 4 {
0 => next() & M62,
1 => f0,
2 => 0,
_ => (next() & M62) & !((1 << (round % 40)) - 1),
};
let delta0 = (round % 141) as i64 - 70;
let (mut delta, mut f, mut g) = (delta0, f0 as i128, g0 as i128);
for _ in 0..62 {
if delta > 0 && g & 1 == 1 {
(delta, f, g) = (1 - delta, g, (g - f) / 2);
} else {
(delta, g) = (1 + delta, (g + (g & 1) * f) / 2);
}
}
let (got, t) = divsteps_62(delta0, f0, g0);
assert_eq!(got, delta, "{f0:x} {g0:x} {delta0}");
let (f0, g0) = (f0 as i128, g0 as i128);
assert_eq!(t.u as i128 * f0 + t.v as i128 * g0, f << 62, "f");
assert_eq!(t.q as i128 * f0 + t.r as i128 * g0, g << 62, "g");
assert!(t.u.unsigned_abs() + t.v.unsigned_abs() <= 1 << 62);
assert!(t.q.unsigned_abs() + t.r.unsigned_abs() <= 1 << 62);
}
}
#[test]
fn the_256_batch_is_the_half_delta_divstep() {
let mut state = 0x59_u64;
let mut next = || {
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)
};
for round in 0..20_000 {
let f0 = (next() & M62) | 1;
let g0 = match round % 4 {
0 => next() & M62,
1 => f0,
2 => 0,
_ => (next() & M62) & !((1 << (round % 40)) - 1),
};
let zeta0 = (round % 141) as i64 - 70;
let (mut zeta, mut f, mut g) = (zeta0, f0 as i128, g0 as i128);
for _ in 0..59 {
if zeta < 0 && g & 1 == 1 {
(zeta, f, g) = (-zeta - 2, g, (g - f) / 2);
} else {
(zeta, g) = (zeta - 1, (g + (g & 1) * f) / 2);
}
}
let (got, t) = divsteps_59(zeta0, f0, g0);
assert_eq!(got, zeta, "{f0:x} {g0:x} {zeta0}");
let (f0, g0) = (f0 as i128, g0 as i128);
assert_eq!(t.u as i128 * f0 + t.v as i128 * g0, f << 62, "f");
assert_eq!(t.q as i128 * f0 + t.r as i128 * g0, g << 62, "g");
assert!(t.u.unsigned_abs() + t.v.unsigned_abs() <= 1 << 62);
assert!(t.q.unsigned_abs() + t.r.unsigned_abs() <= 1 << 62);
}
}
#[test]
fn limbs_round_trip() {
let a6 = [
0x0123_4567_89ab_cdef,
u64::MAX,
0,
0x8000_0000_0000_0001,
0xdead_beef_0bad_f00d,
0x0fff_ffff_ffff_ffff,
];
assert_eq!(from_signed62::<7, 6>(&to_signed62::<6, 7>(a6)), a6);
let a4 = [a6[0], a6[1], a6[2], a6[3]];
assert_eq!(from_signed62::<5, 4>(&to_signed62::<4, 5>(a4)), a4);
for limb in to_signed62::<6, 7>(a6) {
assert!((0..1 << 62).contains(&limb));
}
}
#[test]
fn inverses_are_inverses_at_384_bits() {
let m = crate::p384::Fp::MODULUS;
let invert_384 = invert_384::<crate::p384::FpModulus>;
assert_eq!(invert_384(&[0; 6]), [0; 6]);
let below = |x: [u64; 6]| {
(0..6)
.rev()
.find(|&k| x[k] != m[k])
.is_some_and(|k| x[k] < m[k])
};
let mut state = 0x0384_u64;
let mut next = || {
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)
};
let mut inputs = std::vec![
[1u64, 0, 0, 0, 0, 0],
[2, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0],
[m[0] - 1, m[1], m[2], m[3], m[4], m[5]],
[m[0] - 2, m[1], m[2], m[3], m[4], m[5]],
[0, 0, 0, 0, 0, 1 << 63],
[u64::MAX, u64::MAX, u64::MAX, u64::MAX, u64::MAX, m[5] - 1],
[
m[0] >> 1 | m[1] << 63,
m[1] >> 1 | m[2] << 63,
m[2] >> 1 | m[3] << 63,
m[3] >> 1 | m[4] << 63,
m[4] >> 1 | m[5] << 63,
m[5] >> 1,
],
];
let h = inputs[7];
inputs.push([h[0] + 1, h[1], h[2], h[3], h[4], h[5]]);
for k in 0..384 {
let mut x = [0u64; 6];
x[k / 64] = 1 << (k % 64);
if below(x) {
inputs.push(x);
}
}
while inputs.len() < 20_000 {
let x = [next(), next(), next(), next(), next(), next()];
if below(x) {
inputs.push(x);
}
}
for (i, x) in inputs.iter().enumerate() {
let inv = invert_384(x);
assert!(below(inv), "{x:x?}");
if i < 3_000 {
assert_eq!(mulmod(*x, inv, m), [1, 0, 0, 0, 0, 0], "{x:x?}");
} else {
assert_eq!(invert_384(&inv), *x, "{x:x?}");
}
}
}
}