Skip to content

Commit d6c01b7

Browse files
authored
Montgomery multiplication support for BoxedUint (#323)
Adds an internal `impl_montgomery_reduction!` macro which can be used to abstract over `const fn` use cases as well as `BoxedUint` use cases which would require `const_mut_refs` to be usable from a `const fn`. This is used to define an inner `mul_montgomery_from` function which works on `BoxedUint`. It isn't yet tested/used and marked as `allow(dead_code)`, but I'd like to perform some major refactoring without losing these changes.
1 parent 3bf7066 commit d6c01b7

5 files changed

Lines changed: 109 additions & 31 deletions

File tree

src/boxed/uint.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod add_mod;
55
mod bit_and;
66
mod cmp;
77
pub(crate) mod encoding;
8+
mod modular;
89
mod mul;
910
mod sub;
1011
mod sub_mod;
@@ -262,6 +263,14 @@ impl From<u128> for BoxedUint {
262263
}
263264
}
264265

266+
impl From<&[Limb]> for BoxedUint {
267+
fn from(limbs: &[Limb]) -> BoxedUint {
268+
Self {
269+
limbs: limbs.into(),
270+
}
271+
}
272+
}
273+
265274
impl From<Box<[Limb]>> for BoxedUint {
266275
fn from(limbs: Box<[Limb]>) -> BoxedUint {
267276
Self { limbs }

src/boxed/uint/modular.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Modular arithmetic support for [`BoxedUint`].
2+
3+
use super::BoxedUint;
4+
use crate::{uint::modular::reduction::montgomery_reduction_core, Limb};
5+
6+
#[allow(dead_code)]
7+
pub(crate) fn mul_montgomery_form(
8+
a: &BoxedUint,
9+
b: &BoxedUint,
10+
modulus: &BoxedUint,
11+
mod_neg_inv: Limb,
12+
) -> BoxedUint {
13+
debug_assert_eq!(a.nlimbs(), modulus.nlimbs());
14+
debug_assert_eq!(b.nlimbs(), modulus.nlimbs());
15+
16+
let mut product = a.mul_wide(b);
17+
let (lower, upper) = product.limbs.split_at_mut(modulus.nlimbs());
18+
let meta_carry = montgomery_reduction_core(lower, upper, &modulus.limbs, mod_neg_inv);
19+
let ret = BoxedUint::from(&*upper);
20+
21+
#[cfg(feature = "zeroize")]
22+
zeroize::Zeroize::zeroize(&mut product);
23+
24+
ret.sub_mod_with_carry(meta_carry, modulus, modulus)
25+
}

src/boxed/uint/sub_mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@ impl BoxedUint {
1818
// borrow = 0x000...000. Thus, we use it as a mask to conditionally add the modulus.
1919
out.wrapping_add(&p.bitand_limb(mask))
2020
}
21+
22+
/// Returns `(self..., carry) - (rhs...) mod (p...)`, where `carry <= 1`.
23+
/// Assumes `-(p...) <= (self..., carry) - (rhs...) < (p...)`.
24+
#[inline(always)]
25+
pub(crate) fn sub_mod_with_carry(&self, carry: Limb, rhs: &Self, p: &Self) -> Self {
26+
debug_assert_eq!(self.nlimbs(), p.nlimbs());
27+
debug_assert_eq!(rhs.nlimbs(), p.nlimbs());
28+
debug_assert!(carry.0 <= 1);
29+
30+
let (out, borrow) = self.sbb(rhs, Limb::ZERO);
31+
32+
// The new `borrow = Word::MAX` iff `carry == 0` and `borrow == Word::MAX`.
33+
let borrow = (!carry.0.wrapping_neg()) & borrow.0;
34+
35+
// If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise
36+
// borrow = 0x000...000. Thus, we use it as a mask to conditionally add the modulus.
37+
let mask = Self::from_words(vec![borrow; p.nlimbs()]);
38+
39+
out.wrapping_add(&p.bitand(&mask))
40+
}
2141
}
2242

2343
impl SubMod for BoxedUint {

src/uint/modular.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//! the modulus can vary at runtime.
1818
1919
mod constant_mod;
20-
mod reduction;
20+
pub(crate) mod reduction;
2121
mod runtime_mod;
2222

2323
mod add;

src/uint/modular/reduction.rs

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,70 @@ const fn muladdcarry(x: Word, y: Word, z: Word, w: Word) -> (Word, Word) {
1010
((res >> Word::BITS) as Word, res as Word)
1111
}
1212

13-
/// Algorithm 14.32 in Handbook of Applied Cryptography <https://cacr.uwaterloo.ca/hac/about/chap14.pdf>
14-
pub const fn montgomery_reduction<const LIMBS: usize>(
15-
lower_upper: &(Uint<LIMBS>, Uint<LIMBS>),
16-
modulus: &Uint<LIMBS>,
17-
mod_neg_inv: Limb,
18-
) -> Uint<LIMBS> {
19-
let (mut lower, mut upper) = *lower_upper;
13+
/// Impl the core Montgomery reduction algorithm.
14+
///
15+
/// This is implemented as a macro to abstract over `const fn` and boxed use cases, since the latter
16+
/// needs mutable references and thus the unstable `const_mut_refs` feature (rust-lang/rust#57349).
17+
// TODO(tarcieri): change this into a `const fn` when `const_mut_refs` is stable
18+
macro_rules! impl_montgomery_reduction {
19+
($upper:expr, $lower:expr, $modulus:expr, $mod_neg_inv:expr, $limbs:expr) => {{
20+
let mut meta_carry = Limb(0);
21+
let mut new_sum;
2022

21-
let mut meta_carry = Limb(0);
22-
let mut new_sum;
23+
let mut i = 0;
24+
while i < $limbs {
25+
let u = $lower[i].0.wrapping_mul($mod_neg_inv.0);
2326

24-
let mut i = 0;
25-
while i < LIMBS {
26-
let u = lower.limbs[i].0.wrapping_mul(mod_neg_inv.0);
27+
let (mut carry, _) = muladdcarry(u, $modulus[0].0, $lower[i].0, 0);
28+
let mut new_limb;
2729

28-
let (mut carry, _) = muladdcarry(u, modulus.limbs[0].0, lower.limbs[i].0, 0);
29-
let mut new_limb;
30+
let mut j = 1;
31+
while j < ($limbs - i) {
32+
(carry, new_limb) = muladdcarry(u, $modulus[j].0, $lower[i + j].0, carry);
33+
$lower[i + j] = Limb(new_limb);
34+
j += 1;
35+
}
36+
while j < $limbs {
37+
(carry, new_limb) = muladdcarry(u, $modulus[j].0, $upper[i + j - $limbs].0, carry);
38+
$upper[i + j - $limbs] = Limb(new_limb);
39+
j += 1;
40+
}
3041

31-
let mut j = 1;
32-
while j < (LIMBS - i) {
33-
(carry, new_limb) = muladdcarry(u, modulus.limbs[j].0, lower.limbs[i + j].0, carry);
34-
lower.limbs[i + j] = Limb(new_limb);
35-
j += 1;
36-
}
37-
while j < LIMBS {
38-
(carry, new_limb) =
39-
muladdcarry(u, modulus.limbs[j].0, upper.limbs[i + j - LIMBS].0, carry);
40-
upper.limbs[i + j - LIMBS] = Limb(new_limb);
41-
j += 1;
42+
(new_sum, meta_carry) = $upper[i].adc(Limb(carry), meta_carry);
43+
$upper[i] = new_sum;
44+
45+
i += 1;
4246
}
4347

44-
(new_sum, meta_carry) = upper.limbs[i].adc(Limb(carry), meta_carry);
45-
upper.limbs[i] = new_sum;
48+
meta_carry
49+
}};
50+
}
4651

47-
i += 1;
48-
}
52+
/// Algorithm 14.32 in Handbook of Applied Cryptography <https://cacr.uwaterloo.ca/hac/about/chap14.pdf>
53+
pub const fn montgomery_reduction<const LIMBS: usize>(
54+
lower_upper: &(Uint<LIMBS>, Uint<LIMBS>),
55+
modulus: &Uint<LIMBS>,
56+
mod_neg_inv: Limb,
57+
) -> Uint<LIMBS> {
58+
let (mut lower, mut upper) = *lower_upper;
59+
let meta_carry =
60+
impl_montgomery_reduction!(upper.limbs, lower.limbs, &modulus.limbs, mod_neg_inv, LIMBS);
4961

5062
// Division is simply taking the upper half of the limbs
5163
// Final reduction (at this point, the value is at most 2 * modulus,
5264
// so `meta_carry` is either 0 or 1)
53-
5465
upper.sub_mod_with_carry(meta_carry, modulus, modulus)
5566
}
67+
68+
/// Shim used by [`BoxedUint`] to perform a Montgomery reduction.
69+
#[cfg(feature = "alloc")]
70+
pub(crate) fn montgomery_reduction_core(
71+
lower: &mut [Limb],
72+
upper: &mut [Limb],
73+
modulus: &[Limb],
74+
mod_neg_inv: Limb,
75+
) -> Limb {
76+
debug_assert_eq!(lower.len(), modulus.len());
77+
debug_assert_eq!(upper.len(), modulus.len());
78+
impl_montgomery_reduction!(upper, lower, modulus, mod_neg_inv, modulus.len())
79+
}

0 commit comments

Comments
 (0)