use super::reduction::montgomery_reduction;
use crate::{CtLt, Limb, Odd, Uint, UintRef, WideWord, Word, word};
#[inline]
pub(crate) fn almost_montgomery_mul(
x: &UintRef,
y: &UintRef,
out: &mut UintRef,
modulus: &UintRef,
mod_neg_inv: Limb,
) {
let overflow = montgomery_multiply_inner(
x.as_limbs(),
y.as_limbs(),
out.as_mut_limbs(),
modulus.as_limbs(),
mod_neg_inv,
);
let overflow = word::choice_from_lsb(overflow.0);
out.conditional_borrowing_sub_assign(modulus, overflow);
}
pub(crate) fn almost_montgomery_reduce(z: &mut UintRef, modulus: &UintRef) {
z.conditional_borrowing_sub_assign(modulus, !z.ct_lt(modulus));
z.conditional_borrowing_sub_assign(modulus, !z.ct_lt(modulus));
}
#[inline(always)]
#[allow(clippy::cast_possible_truncation)]
pub const fn montgomery_multiply_inner(
x: &[Limb],
y: &[Limb],
out: &mut [Limb],
modulus: &[Limb],
mod_neg_inv: Limb,
) -> Limb {
let nlimbs = modulus.len();
assert!(nlimbs == x.len() && nlimbs == y.len() && nlimbs == out.len());
let mut meta_carry = 0;
let mut i = 0;
while i < nlimbs {
let xi = x[i];
let axy = (xi.0 as WideWord) * (y[0].0 as WideWord) + out[0].0 as WideWord;
let u = (axy as Word).wrapping_mul(mod_neg_inv.0);
let um = (u as WideWord) * (modulus[0].0 as WideWord);
let lo = (axy as Word as WideWord) + (um as Word as WideWord);
let mut carry = (axy >> Word::BITS) + (um >> Word::BITS) + (lo >> Word::BITS);
let mut j = 1;
while j < nlimbs {
let axy = (xi.0 as WideWord) * (y[j].0 as WideWord) + out[j].0 as WideWord;
let umc = (u as WideWord) * (modulus[j].0 as WideWord) + carry;
let lo = (axy as Word as WideWord) + (umc as Word as WideWord);
out[j - 1] = Limb(lo as Word);
carry = (axy >> Word::BITS) + (umc >> Word::BITS) + (lo >> Word::BITS);
j += 1;
}
carry += meta_carry;
(out[nlimbs - 1], meta_carry) = (Limb(carry as Word), carry >> Word::BITS);
i += 1;
}
Limb(meta_carry as Word)
}
pub(crate) const fn mul_montgomery_form<const LIMBS: usize>(
a: &Uint<LIMBS>,
b: &Uint<LIMBS>,
modulus: &Odd<Uint<LIMBS>>,
mod_neg_inv: Limb,
) -> Uint<LIMBS> {
let mut out = Uint::<LIMBS>::ZERO;
let carry = montgomery_multiply_inner(
&a.limbs,
&b.limbs,
&mut out.limbs,
&modulus.as_ref().limbs,
mod_neg_inv,
);
out.try_sub_with_carry(carry, modulus.as_ref()).0
}
pub(crate) const fn square_montgomery_form<const LIMBS: usize>(
a: &Uint<LIMBS>,
modulus: &Odd<Uint<LIMBS>>,
mod_neg_inv: Limb,
) -> Uint<LIMBS> {
if LIMBS == 4 {
let lower_upper = a.widening_square();
return montgomery_reduction(&lower_upper, modulus, mod_neg_inv);
}
let mut out = Uint::<LIMBS>::ZERO;
let carry = montgomery_multiply_inner(
&a.limbs,
&a.limbs,
&mut out.limbs,
&modulus.as_ref().limbs,
mod_neg_inv,
);
out.try_sub_with_carry(carry, modulus.as_ref()).0
}
#[inline(always)]
pub(crate) const fn square_repeat_montgomery_form<const LIMBS: usize>(
a: &Uint<LIMBS>,
n: u32,
modulus: &Odd<Uint<LIMBS>>,
mod_neg_inv: Limb,
) -> Uint<LIMBS> {
if n == 0 {
return *a;
}
if n == 1 {
return square_montgomery_form(a, modulus, mod_neg_inv);
}
let mut i = 0;
let mut out = *a;
let mut base;
while i != n {
(base, out) = (out, Uint::ZERO);
let carry = montgomery_multiply_inner(
&base.limbs,
&base.limbs,
&mut out.limbs,
&modulus.as_ref().limbs,
mod_neg_inv,
);
out = out.try_sub_with_carry(carry, modulus.as_ref()).0;
i += 1;
}
out
}
#[cfg(test)]
mod tests {
use super::montgomery_multiply_inner;
use crate::{Limb, Uint, Word, modular::FixedMontyParams};
use num_bigint::BigUint;
fn to_biguint(limbs: &[Limb]) -> BigUint {
limbs.iter().rev().fold(BigUint::from(0u8), |value, limb| {
(value << Word::BITS) + BigUint::from(limb.0)
})
}
fn check_product<const N: usize>(x: [Limb; N], y: [Limb; N], modulus: [Limb; N]) {
let modulus = Uint::new(modulus).to_odd().unwrap();
let params = FixedMontyParams::new_vartime(modulus);
let m = to_biguint(modulus.as_ref().as_limbs());
let mut expected = to_biguint(&x) * to_biguint(&y);
let mask = BigUint::from(Word::MAX);
let inverse = BigUint::from(params.mod_neg_inv().0);
for _ in 0..N {
let u = (&expected * &inverse) & &mask;
expected = (expected + u * &m) >> Word::BITS;
}
let mut out = [Limb::ZERO; N];
let carry = montgomery_multiply_inner(
&x,
&y,
&mut out,
modulus.as_ref().as_limbs(),
params.mod_neg_inv(),
);
let actual = to_biguint(&out) + (BigUint::from(carry.0) << (N * Limb::BITS as usize));
assert_eq!(actual, expected, "x={x:?}, y={y:?}, modulus={modulus:?}");
}
#[test]
fn multiply_carry_boundaries() {
fn check<const N: usize>() {
let values = [
[Limb::ZERO; N],
Uint::<N>::ONE.to_limbs(),
[Limb::MAX; N],
[Limb(Word::MAX - 1); N],
[Limb(1 << (Word::BITS - 1)); N],
core::array::from_fn(|i| if i & 1 == 0 { Limb::MAX } else { Limb::ZERO }),
];
for mut modulus in values {
modulus[0].0 |= 1;
for x in values {
for y in values {
check_product(x, y, modulus);
}
}
}
}
check::<1>();
check::<2>();
check::<3>();
check::<4>();
check::<16>();
}
#[test]
fn multiply_matches_biguint() {
use chacha20::ChaCha8Rng;
use rand_core::{Rng, SeedableRng};
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let mut limb = || {
let mut bytes = [0u8; Limb::BYTES];
rng.fill_bytes(&mut bytes);
Limb(Word::from_le_bytes(bytes))
};
for _ in 0..1024 {
let x = core::array::from_fn(|_| limb());
let y = core::array::from_fn(|_| limb());
let mut modulus: [Limb; 4] = core::array::from_fn(|_| limb());
modulus[0].0 |= 1;
check_product(x, y, modulus);
}
}
}