1use super::reduction::montgomery_reduction;
2use crate::{CtLt, Limb, Odd, Uint, UintRef, WideWord, Word, word};
3
4#[inline]
31pub(crate) fn almost_montgomery_mul(
32 x: &UintRef,
33 y: &UintRef,
34 out: &mut UintRef,
35 modulus: &UintRef,
36 mod_neg_inv: Limb,
37) {
38 let overflow = montgomery_multiply_inner(
39 x.as_limbs(),
40 y.as_limbs(),
41 out.as_mut_limbs(),
42 modulus.as_limbs(),
43 mod_neg_inv,
44 );
45 let overflow = word::choice_from_lsb(overflow.0);
46 out.conditional_borrowing_sub_assign(modulus, overflow);
47}
48
49pub(crate) fn almost_montgomery_reduce(z: &mut UintRef, modulus: &UintRef) {
63 z.conditional_borrowing_sub_assign(modulus, !z.ct_lt(modulus));
64 z.conditional_borrowing_sub_assign(modulus, !z.ct_lt(modulus));
65}
66
67#[inline(always)]
81#[allow(clippy::cast_possible_truncation)]
82pub const fn montgomery_multiply_inner(
83 x: &[Limb],
84 y: &[Limb],
85 out: &mut [Limb],
86 modulus: &[Limb],
87 mod_neg_inv: Limb,
88) -> Limb {
89 let nlimbs = modulus.len();
90 assert!(nlimbs == x.len() && nlimbs == y.len() && nlimbs == out.len());
91
92 let mut meta_carry = 0;
93
94 let mut i = 0;
95 while i < nlimbs {
96 let xi = x[i];
97 let axy = (xi.0 as WideWord) * (y[0].0 as WideWord) + out[0].0 as WideWord;
99 let u = (axy as Word).wrapping_mul(mod_neg_inv.0);
100
101 let um = (u as WideWord) * (modulus[0].0 as WideWord);
104 let lo = (axy as Word as WideWord) + (um as Word as WideWord);
105 let mut carry = (axy >> Word::BITS) + (um >> Word::BITS) + (lo >> Word::BITS);
107
108 let mut j = 1;
109 while j < nlimbs {
110 let axy = (xi.0 as WideWord) * (y[j].0 as WideWord) + out[j].0 as WideWord;
112 let umc = (u as WideWord) * (modulus[j].0 as WideWord) + carry;
114 let lo = (axy as Word as WideWord) + (umc as Word as WideWord);
115 out[j - 1] = Limb(lo as Word);
116 carry = (axy >> Word::BITS) + (umc >> Word::BITS) + (lo >> Word::BITS);
118 j += 1;
119 }
120
121 carry += meta_carry;
123 (out[nlimbs - 1], meta_carry) = (Limb(carry as Word), carry >> Word::BITS);
124
125 i += 1;
126 }
127
128 Limb(meta_carry as Word)
129}
130
131pub(crate) const fn mul_montgomery_form<const LIMBS: usize>(
134 a: &Uint<LIMBS>,
135 b: &Uint<LIMBS>,
136 modulus: &Odd<Uint<LIMBS>>,
137 mod_neg_inv: Limb,
138) -> Uint<LIMBS> {
139 let mut out = Uint::<LIMBS>::ZERO;
140 let carry = montgomery_multiply_inner(
141 &a.limbs,
142 &b.limbs,
143 &mut out.limbs,
144 &modulus.as_ref().limbs,
145 mod_neg_inv,
146 );
147 out.try_sub_with_carry(carry, modulus.as_ref()).0
148}
149
150pub(crate) const fn square_montgomery_form<const LIMBS: usize>(
153 a: &Uint<LIMBS>,
154 modulus: &Odd<Uint<LIMBS>>,
155 mod_neg_inv: Limb,
156) -> Uint<LIMBS> {
157 if LIMBS == 4 {
159 let lower_upper = a.widening_square();
160 return montgomery_reduction(&lower_upper, modulus, mod_neg_inv);
161 }
162
163 let mut out = Uint::<LIMBS>::ZERO;
164 let carry = montgomery_multiply_inner(
165 &a.limbs,
166 &a.limbs,
167 &mut out.limbs,
168 &modulus.as_ref().limbs,
169 mod_neg_inv,
170 );
171 out.try_sub_with_carry(carry, modulus.as_ref()).0
172}
173
174#[inline(always)]
181pub(crate) const fn square_repeat_montgomery_form<const LIMBS: usize>(
182 a: &Uint<LIMBS>,
183 n: u32,
184 modulus: &Odd<Uint<LIMBS>>,
185 mod_neg_inv: Limb,
186) -> Uint<LIMBS> {
187 if n == 0 {
188 return *a;
189 }
190 if n == 1 {
191 return square_montgomery_form(a, modulus, mod_neg_inv);
192 }
193
194 let mut i = 0;
195 let mut out = *a;
196 let mut base;
197
198 while i != n {
199 (base, out) = (out, Uint::ZERO);
200 let carry = montgomery_multiply_inner(
201 &base.limbs,
202 &base.limbs,
203 &mut out.limbs,
204 &modulus.as_ref().limbs,
205 mod_neg_inv,
206 );
207 out = out.try_sub_with_carry(carry, modulus.as_ref()).0;
210 i += 1;
211 }
212
213 out
214}
215
216#[cfg(test)]
217mod tests {
218 use super::montgomery_multiply_inner;
219 use crate::{Limb, Uint, Word, modular::FixedMontyParams};
220 use num_bigint::BigUint;
221
222 fn to_biguint(limbs: &[Limb]) -> BigUint {
223 limbs.iter().rev().fold(BigUint::from(0u8), |value, limb| {
224 (value << Word::BITS) + BigUint::from(limb.0)
225 })
226 }
227
228 fn check_product<const N: usize>(x: [Limb; N], y: [Limb; N], modulus: [Limb; N]) {
229 let modulus = Uint::new(modulus).to_odd().unwrap();
230 let params = FixedMontyParams::new_vartime(modulus);
231 let m = to_biguint(modulus.as_ref().as_limbs());
232
233 let mut expected = to_biguint(&x) * to_biguint(&y);
236 let mask = BigUint::from(Word::MAX);
237 let inverse = BigUint::from(params.mod_neg_inv().0);
238 for _ in 0..N {
239 let u = (&expected * &inverse) & &mask;
240 expected = (expected + u * &m) >> Word::BITS;
241 }
242
243 let mut out = [Limb::ZERO; N];
244 let carry = montgomery_multiply_inner(
245 &x,
246 &y,
247 &mut out,
248 modulus.as_ref().as_limbs(),
249 params.mod_neg_inv(),
250 );
251 let actual = to_biguint(&out) + (BigUint::from(carry.0) << (N * Limb::BITS as usize));
252 assert_eq!(actual, expected, "x={x:?}, y={y:?}, modulus={modulus:?}");
253 }
254
255 #[test]
256 fn multiply_carry_boundaries() {
257 fn check<const N: usize>() {
258 let values = [
259 [Limb::ZERO; N],
260 Uint::<N>::ONE.to_limbs(),
261 [Limb::MAX; N],
262 [Limb(Word::MAX - 1); N],
263 [Limb(1 << (Word::BITS - 1)); N],
264 core::array::from_fn(|i| if i & 1 == 0 { Limb::MAX } else { Limb::ZERO }),
265 ];
266 for mut modulus in values {
267 modulus[0].0 |= 1;
268 for x in values {
269 for y in values {
270 check_product(x, y, modulus);
271 }
272 }
273 }
274 }
275 check::<1>();
276 check::<2>();
277 check::<3>();
278 check::<4>();
279 check::<16>();
280 }
281
282 #[test]
283 fn multiply_matches_biguint() {
284 use chacha20::ChaCha8Rng;
285 use rand_core::{Rng, SeedableRng};
286
287 let mut rng = ChaCha8Rng::from_seed([42; 32]);
288 let mut limb = || {
289 let mut bytes = [0u8; Limb::BYTES];
290 rng.fill_bytes(&mut bytes);
291 Limb(Word::from_le_bytes(bytes))
292 };
293 for _ in 0..1024 {
294 let x = core::array::from_fn(|_| limb());
295 let y = core::array::from_fn(|_| limb());
296 let mut modulus: [Limb; 4] = core::array::from_fn(|_| limb());
297 modulus[0].0 |= 1;
298 check_product(x, y, modulus);
299 }
300 }
301}