#![allow(clippy::clone_on_copy, clippy::op_ref)]
use const_num_traits::{CtIsZero, CtParity, One, WrappingAdd, WrappingSub, Zero};
use modmath_cios::CiosRowOps;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeLess, CtOption};
pub(crate) const fn divsteps_total(modulus_bits: usize) -> usize {
if modulus_bits < 46 {
(49 * modulus_bits + 80) / 17
} else {
(49 * modulus_bits + 57) / 17
}
}
#[inline]
fn ct_low_bit<T>(value: &T) -> Choice
where
T: CiosRowOps,
T::Word: CtParity,
{
value.word(0).ct_is_odd()
}
#[inline]
pub(crate) fn ct_msb_set<T>(value: &T) -> Choice
where
T: CiosRowOps,
T::Word: core::ops::BitAnd<Output = T::Word>
+ core::ops::Shl<usize, Output = T::Word>
+ One
+ CtIsZero,
{
let n = value.word_count();
let word_bits = core::mem::size_of::<T::Word>() * 8;
let msb_mask = T::Word::one() << (word_bits - 1);
let masked = value.word(n - 1) & msb_mask;
!masked.ct_is_zero()
}
#[inline]
fn ct_i64_positive(delta: i64) -> Choice {
let delta_u = delta as u64;
let nonzero_top = (delta_u | delta_u.wrapping_neg()) >> 63;
let sign_bit_clear = (!delta_u) >> 63;
Choice::from(((nonzero_top & sign_bit_clear) & 1) as u8)
}
#[inline]
fn arithmetic_shr_one<T>(value: &T) -> T
where
T: CiosRowOps
+ Clone
+ ConditionallySelectable
+ One
+ Zero
+ core::ops::Shr<usize, Output = T>
+ core::ops::Shl<usize, Output = T>
+ core::ops::BitOr<Output = T>,
T::Word: core::ops::BitAnd<Output = T::Word>
+ core::ops::Shl<usize, Output = T::Word>
+ One
+ CtIsZero,
{
let logical = value.clone() >> 1;
let msb_set = ct_msb_set(value);
let n_bits = value.word_count() * core::mem::size_of::<T::Word>() * 8;
let top_bit_mask = T::one() << (n_bits - 1);
let with_sign_ext = logical.clone() | top_bit_mask;
T::conditional_select(&logical, &with_sign_ext, msb_set)
}
#[inline]
fn reduce_lt_2m_ct<T>(sum: T, m: &T) -> T
where
T: Clone + ConditionallySelectable + WrappingSub<Output = T> + ConstantTimeLess,
{
let sum_minus_m = sum.clone().wrapping_sub(m.clone());
let need_sub = !sum.ct_lt(m);
T::conditional_select(&sum, &sum_minus_m, need_sub)
}
#[inline]
fn add_mod_ct<T>(a: &T, b: &T, m: &T) -> T
where
T: Clone
+ ConditionallySelectable
+ WrappingAdd<Output = T>
+ WrappingSub<Output = T>
+ ConstantTimeLess,
{
let sum = a.clone().wrapping_add(b.clone());
reduce_lt_2m_ct(sum, m)
}
#[inline]
fn neg_mod_ct<T>(x: &T, m: &T) -> T
where
T: Clone + Zero + ConditionallySelectable + WrappingSub<Output = T> + CtIsZero,
{
let zero = T::zero();
let x_is_zero = x.ct_is_zero();
let neg = m.clone().wrapping_sub(x.clone());
T::conditional_select(&neg, &zero, x_is_zero)
}
#[inline]
fn half_mod_ct<T>(x: &T, m: &T) -> T
where
T: CiosRowOps
+ Clone
+ ConditionallySelectable
+ WrappingAdd<Output = T>
+ core::ops::Shr<usize, Output = T>,
T::Word: CtParity,
{
let x_odd = ct_low_bit(x);
let x_plus_m = x.clone().wrapping_add(m.clone());
let candidate_odd = x_plus_m >> 1;
let candidate_even = x.clone() >> 1;
T::conditional_select(&candidate_even, &candidate_odd, x_odd)
}
pub fn safegcd_inv_ct<T>(value: &T, modulus: &T) -> CtOption<T>
where
T: CiosRowOps
+ Clone
+ ConditionallySelectable
+ ConstantTimeEq
+ ConstantTimeLess
+ CtIsZero
+ Zero
+ One
+ WrappingAdd<Output = T>
+ WrappingSub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ core::ops::Shl<usize, Output = T>
+ core::ops::BitOr<Output = T>,
T::Word: Copy
+ ConditionallySelectable
+ ConstantTimeEq
+ CtIsZero
+ CtParity
+ One
+ Zero
+ core::ops::BitAnd<Output = T::Word>
+ core::ops::Shl<usize, Output = T::Word>,
{
let n_bits = value.word_count() * core::mem::size_of::<T::Word>() * 8;
let total_steps = divsteps_total(n_bits);
let mut f = modulus.clone();
let mut g = value.clone();
let mut d = T::zero();
let mut e = T::one();
let mut delta: i64 = 1;
for _ in 0..total_steps {
let delta_pos = ct_i64_positive(delta);
let g_odd = ct_low_bit(&g);
let swap = delta_pos & g_odd;
let new_f_if_swap = g.clone();
let new_g_if_swap = f.clone().wrapping_neg_two_complement();
let new_d_if_swap = e.clone();
let new_e_if_swap = neg_mod_ct(&d, modulus);
f = T::conditional_select(&f, &new_f_if_swap, swap);
g = T::conditional_select(&g, &new_g_if_swap, swap);
d = T::conditional_select(&d, &new_d_if_swap, swap);
e = T::conditional_select(&e, &new_e_if_swap, swap);
let neg_delta = (delta as u64).wrapping_neg() as i64;
delta = i64::conditional_select(&delta, &neg_delta, swap);
delta = delta.wrapping_add(1);
let g_odd_now = g_odd;
let to_add_to_g = T::conditional_select(&T::zero(), &f, g_odd_now);
g = g.wrapping_add(to_add_to_g);
let add_to_e = add_mod_ct(&e, &d, modulus);
e = T::conditional_select(&e, &add_to_e, g_odd_now);
g = arithmetic_shr_one(&g);
e = half_mod_ct(&e, modulus);
}
let one = T::one();
let neg_one_pattern = one.clone().wrapping_neg_two_complement();
let f_is_one = f.ct_eq(&one);
let f_is_neg_one = f.ct_eq(&neg_one_pattern);
let has_inverse = f_is_one | f_is_neg_one;
let neg_d = neg_mod_ct(&d, modulus);
let result = T::conditional_select(&d, &neg_d, f_is_neg_one);
let modulus_is_odd = ct_low_bit(modulus);
let modulus_is_one = modulus.ct_eq(&one);
let modulus_ok = modulus_is_odd & !modulus_is_one;
CtOption::new(result, has_inverse & modulus_ok)
}
trait WrappingNegT: Sized {
fn wrapping_neg_two_complement(self) -> Self;
}
impl<T> WrappingNegT for T
where
T: Zero + WrappingSub<Output = T>,
{
#[inline]
fn wrapping_neg_two_complement(self) -> Self {
T::zero().wrapping_sub(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inv::basic_mod_inv;
#[test]
fn divsteps_total_matches_paper_bound() {
assert!(divsteps_total(256) >= (45usize * 256 + 64).div_ceil(19));
assert!(divsteps_total(2048) >= (45usize * 2048 + 64).div_ceil(19));
assert!(divsteps_total(4096) >= (45usize * 4096 + 64).div_ceil(19));
}
#[test]
fn matches_basic_mod_inv_mod_small_primes_u32() {
for &m in &[3u32, 5, 7, 11, 13, 17, 19, 23, 29, 31, 97, 251] {
for v in 1..m {
let got = safegcd_inv_ct::<u32>(&v, &m);
let want = basic_mod_inv(v, m);
match (got.into_option(), want) {
(Some(g), Some(w)) => {
assert_eq!(
g, w,
"value={v} modulus={m}: safegcd gave {g}, basic_mod_inv gave {w}"
);
assert_eq!((g as u64 * v as u64) % m as u64, 1, "inv check failed");
}
(Some(_), None) | (None, Some(_)) => {
panic!("disagreement on value={v} modulus={m}");
}
(None, None) => {}
}
}
}
}
#[test]
fn handles_composite_modulus_u32() {
let m: u32 = 15;
let coprime_values = [1u32, 2, 4, 7, 8, 11, 13, 14];
for &v in &coprime_values {
let got = safegcd_inv_ct::<u32>(&v, &m).into_option();
let want = basic_mod_inv(v, m);
assert_eq!(got, want, "value={v} modulus=15: composite case mismatch");
if let Some(inv) = got {
assert_eq!((inv as u64 * v as u64) % 15, 1);
}
}
for &v in &[3u32, 5, 6, 9, 10, 12] {
assert!(
safegcd_inv_ct::<u32>(&v, &m).into_option().is_none(),
"value={v} modulus=15: expected None (not coprime)"
);
}
}
#[test]
fn fixed_bigint_smoke_test() {
use const_num_traits::Ct;
use fixed_bigint::FixedUInt;
type U64Ct = FixedUInt<u32, 2, Ct>;
type U64Nct = FixedUInt<u32, 2>;
let m_raw: u64 = 0x7FFF_FFFF_FFFF_FFE7; let m = U64Ct::from(m_raw);
let m_nct = U64Nct::from(m_raw);
let test_vals = [1u64, 2, 7, 42, 0xDEAD_BEEF];
for &v_raw in &test_vals {
let v = U64Ct::from(v_raw);
let v_nct = U64Nct::from(v_raw);
let got = safegcd_inv_ct(&v, &m).into_option();
assert!(got.is_some(), "expected inverse to exist for v={v_raw}");
let inv = got.unwrap();
let baseline = basic_mod_inv(v_nct, m_nct).expect("baseline inv");
let inv_nct: U64Nct = inv.forget_ct();
assert_eq!(
inv_nct, baseline,
"FixedUInt v={v_raw}: mismatch with basic_mod_inv"
);
}
}
#[test]
fn u64_composite_coprime() {
let n: u64 = 0x1_0000_0007 * 0x100_0007;
for v in [1u64, 2, 3, 0xCAFE_BABE, 0xFEED_FACE] {
let got = safegcd_inv_ct::<u64>(&v, &n).into_option();
assert!(got.is_some(), "u64 case: v={v} mod {n:#x} expected Some");
let inv = got.unwrap();
let prod = (inv as u128 * v as u128) % n as u128;
assert_eq!(prod, 1, "u64 v={v}: inv*v mod n != 1");
}
assert!(
safegcd_inv_ct::<u64>(&0xDEAD_BEEF, &n)
.into_option()
.is_none()
);
}
#[test]
fn u64_smoke_test() {
let m: u64 = 0x7FFF_FFFF_FFFF_FFE7; let test_vals = [1u64, 2, 7, 0xDEAD_BEEF, 0xCAFE_BABE];
for &v in &test_vals {
let got = safegcd_inv_ct::<u64>(&v, &m).into_option();
let want = basic_mod_inv(v, m);
assert_eq!(got, want, "value={v} modulus={m}: u64 case mismatch");
if let Some(inv) = got {
let prod = (inv as u128 * v as u128) % m as u128;
assert_eq!(prod, 1, "u64 inv * value mod m != 1");
}
}
}
#[test]
fn modulus_precondition_masks() {
for m in [2u32, 4, 6, 100, 0xFFFF_FFFE] {
for v in [1u32, 3, 7, 0xCAFE_BABE] {
assert!(
safegcd_inv_ct::<u32>(&v, &m).into_option().is_none(),
"even modulus {m:#x} with value {v:#x}: expected None"
);
}
}
for v in [0u32, 1, 7] {
assert!(
safegcd_inv_ct::<u32>(&v, &1u32).into_option().is_none(),
"modulus = 1 with value {v}: expected None"
);
}
}
}