use super::div::divrem;
use super::uint::BigUint;
use oxinum_core::{OxiNumError, OxiNumResult};
pub struct MontgomeryContext {
m: BigUint,
r_mod_m: BigUint,
r_squared: BigUint,
m_prime: u64,
n: usize,
}
impl MontgomeryContext {
pub fn new(m: BigUint) -> OxiNumResult<Self> {
if m.is_zero() || m.is_one() {
return Err(OxiNumError::DivByZero);
}
let limbs = m.as_limbs();
if limbs[0] & 1 == 0 {
return Err(OxiNumError::Domain(
"Montgomery multiplication requires an odd modulus".into(),
));
}
let n = limbs.len();
let m0 = limbs[0];
let mut t = 1u64;
for _ in 0..6 {
t = t.wrapping_mul(2u64.wrapping_sub(m0.wrapping_mul(t)));
}
let m_prime: u64 = t.wrapping_neg();
let mut r_limbs = vec![0u64; n + 1];
r_limbs[n] = 1u64;
let r_big = BigUint::from_le_limbs(&r_limbs);
let (_q, r_mod_m) = divrem(&r_big, &m);
let r_mod_m_sq = r_mod_m.clone() * r_mod_m.clone();
let (_q2, r_squared) = divrem(&r_mod_m_sq, &m);
Ok(MontgomeryContext {
m,
r_mod_m,
r_squared,
m_prime,
n,
})
}
#[inline]
pub fn to_mont(&self, a: &BigUint) -> BigUint {
self.mont_mul(a, &self.r_squared)
}
#[inline]
pub fn from_mont(&self, a_mont: &BigUint) -> BigUint {
self.mont_mul(a_mont, &BigUint::one())
}
#[inline]
pub fn mul(&self, a: &BigUint, b: &BigUint) -> BigUint {
self.mont_mul(a, b)
}
pub fn pow(&self, base: &BigUint, exp: &BigUint) -> BigUint {
if exp.is_zero() {
return BigUint::one();
}
let base_mont = self.to_mont(base);
let mut result_mont = self.r_mod_m.clone();
let mut current_mont = base_mont;
let bits = exp.bit_length();
for i in 0..bits {
if exp.test_bit(i) {
result_mont = self.mont_mul(&result_mont, ¤t_mont);
}
if i + 1 < bits {
current_mont = self.mont_mul(¤t_mont, ¤t_mont);
}
}
self.from_mont(&result_mont)
}
#[inline]
pub fn modulus(&self) -> &BigUint {
&self.m
}
fn mont_mul(&self, a: &BigUint, b: &BigUint) -> BigUint {
let mut t: BigUint = a.clone() * b.clone();
for i in 0..self.n {
let shift = (i as u64) * 64;
let t_shifted = t.shr_bits(shift);
let t_i: u64 = match t_shifted.as_limbs() {
[] => 0u64,
[lo, ..] => *lo,
};
let q_i: u64 = t_i.wrapping_mul(self.m_prime);
if q_i != 0 {
let q_big = BigUint::from_u64(q_i);
let addition = (q_big * self.m.clone()).shl_bits(shift);
t += addition;
}
}
t = t.shr_bits((self.n as u64) * 64);
if t >= self.m {
t = t.checked_sub(&self.m).unwrap_or_else(BigUint::zero);
}
t
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::native::{mod_mul, mod_pow};
fn bu(n: u64) -> BigUint {
BigUint::from_u64(n)
}
#[test]
fn new_rejects_zero() {
assert!(MontgomeryContext::new(bu(0)).is_err());
}
#[test]
fn new_rejects_one() {
assert!(MontgomeryContext::new(bu(1)).is_err());
}
#[test]
fn new_rejects_even() {
assert!(MontgomeryContext::new(bu(10)).is_err());
assert!(MontgomeryContext::new(bu(2)).is_err());
assert!(MontgomeryContext::new(bu(100)).is_err());
}
#[test]
fn new_accepts_odd() {
assert!(MontgomeryContext::new(bu(7)).is_ok());
assert!(MontgomeryContext::new(bu(13)).is_ok());
assert!(MontgomeryContext::new(bu(65537)).is_ok());
}
#[test]
fn roundtrip_to_from_mont() {
let ctx = MontgomeryContext::new(bu(7)).expect("odd");
for a in 0u64..7 {
let a_mont = ctx.to_mont(&bu(a));
let a_back = ctx.from_mont(&a_mont);
assert_eq!(a_back, bu(a), "roundtrip failed for a={a}");
}
}
#[test]
fn mul_basic_3x4_mod7() {
let ctx = MontgomeryContext::new(bu(7)).expect("odd");
let a_mont = ctx.to_mont(&bu(3));
let b_mont = ctx.to_mont(&bu(4));
let c_mont = ctx.mul(&a_mont, &b_mont);
let c = ctx.from_mont(&c_mont);
assert_eq!(c, bu(5)); }
#[test]
fn pow_2_10_mod13() {
let ctx = MontgomeryContext::new(bu(13)).expect("odd");
let result = ctx.pow(&bu(2), &bu(10));
assert_eq!(result, bu(10));
}
#[test]
fn pow_zero_exp() {
let ctx = MontgomeryContext::new(bu(7)).expect("odd");
assert_eq!(ctx.pow(&bu(5), &bu(0)), bu(1));
}
#[test]
fn fermat_little_theorem() {
for &p in &[7u64, 13, 101, 65537] {
let ctx = MontgomeryContext::new(bu(p)).expect("odd prime");
for a in 2u64..p.min(8) {
let result = ctx.pow(&bu(a), &bu(p - 1));
assert_eq!(result, bu(1), "Fermat failed for a={a}, p={p}");
}
}
}
#[test]
fn montgomery_vs_schoolbook() {
let odd_moduli = [7u64, 13, 101, 4093, 65537];
for &m in &odd_moduli {
let ctx = MontgomeryContext::new(bu(m)).expect("ctx");
for a in [0u64, 1, 2, m - 1, m / 2 + 1] {
for b in [0u64, 1, 3, m - 1, (m / 2).saturating_add(1)] {
let a = a.min(m - 1);
let b = b.min(m - 1);
let expected = mod_mul(&bu(a), &bu(b), &bu(m)).expect("schoolbook");
let a_mont = ctx.to_mont(&bu(a));
let b_mont = ctx.to_mont(&bu(b));
let got_mont = ctx.mul(&a_mont, &b_mont);
let got = ctx.from_mont(&got_mont);
assert_eq!(
got, expected,
"Montgomery vs schoolbook mismatch: a={a}, b={b}, m={m}"
);
}
}
}
}
#[test]
fn montgomery_pow_vs_mod_pow() {
let odd_moduli = [7u64, 13, 101, 65537];
let exps = [0u64, 1, 2, 10, 100, 1000];
for &m in &odd_moduli {
let ctx = MontgomeryContext::new(bu(m)).expect("ctx");
for a in [1u64, 2, 3, m - 1] {
for &e in &exps {
let expected = mod_pow(&bu(a), &bu(e), &bu(m)).expect("mod_pow");
let got = ctx.pow(&bu(a), &bu(e));
assert_eq!(
got, expected,
"Montgomery pow vs mod_pow: a={a}, e={e}, m={m}"
);
}
}
}
}
}