Skip to content

Commit 57c61d4

Browse files
committed
Add BoxedUint::pow
Initial support for modular exponentiation, adapted from the original implementation of `pow_montgomery_form` this crate used prior to #248: https://github.com/RustCrypto/crypto-bigint/blob/4838fd96e1bde8b0c5e0ce691c366c7ec930e466/src/uint/modular/pow.rs Proptested against `num_bigint::BitUint::modpow`.
1 parent c966aea commit 57c61d4

File tree

4 files changed

+171
-27
lines changed

4 files changed

+171
-27
lines changed

src/modular/boxed_residue.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! is chosen at runtime.
33
44
mod mul;
5+
mod pow;
56

67
use super::reduction::montgomery_reduction_boxed;
78
use crate::{BoxedUint, Limb, NonZero, Word};

src/modular/boxed_residue/mul.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ impl BoxedResidue {
2323

2424
/// Computes the (reduced) square of a residue.
2525
pub fn square(&self) -> Self {
26-
// TODO(tarcieri): optimized implementation
27-
self.mul(self)
26+
Self {
27+
montgomery_form: square_montgomery_form(
28+
&self.montgomery_form,
29+
&self.residue_params.modulus,
30+
self.residue_params.mod_neg_inv,
31+
),
32+
residue_params: self.residue_params.clone(),
33+
}
2834
}
2935
}
3036

@@ -83,7 +89,7 @@ impl Square for BoxedResidue {
8389
}
8490
}
8591

86-
fn mul_montgomery_form(
92+
pub(super) fn mul_montgomery_form(
8793
a: &BoxedUint,
8894
b: &BoxedUint,
8995
modulus: &BoxedUint,
@@ -100,3 +106,13 @@ fn mul_montgomery_form(
100106

101107
ret
102108
}
109+
110+
#[inline]
111+
pub(super) fn square_montgomery_form(
112+
a: &BoxedUint,
113+
modulus: &BoxedUint,
114+
mod_neg_inv: Limb,
115+
) -> BoxedUint {
116+
// TODO(tarcieri): optimized implementation
117+
mul_montgomery_form(a, a, modulus, mod_neg_inv)
118+
}

src/modular/boxed_residue/pow.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//! Modular exponentiation support.
2+
3+
use super::{
4+
mul::{mul_montgomery_form, square_montgomery_form},
5+
BoxedResidue,
6+
};
7+
use crate::{BoxedUint, Limb, PowBoundedExp, Word};
8+
use subtle::ConstantTimeEq;
9+
10+
impl BoxedResidue {
11+
/// Raises to the `exponent` power.
12+
pub fn pow(&self, exponent: &BoxedUint) -> Self {
13+
self.pow_bounded_exp(exponent, exponent.bits_precision())
14+
}
15+
16+
/// Raises to the `exponent` power,
17+
/// with `exponent_bits` representing the number of (least significant) bits
18+
/// to take into account for the exponent.
19+
///
20+
/// NOTE: `exponent_bits` may be leaked in the time pattern.
21+
pub fn pow_bounded_exp(&self, exponent: &BoxedUint, exponent_bits: usize) -> Self {
22+
Self {
23+
montgomery_form: pow_montgomery_form(
24+
&self.montgomery_form,
25+
exponent,
26+
exponent_bits,
27+
&self.residue_params.modulus,
28+
&self.residue_params.r,
29+
self.residue_params.mod_neg_inv,
30+
),
31+
residue_params: self.residue_params.clone(),
32+
}
33+
}
34+
}
35+
36+
impl PowBoundedExp<BoxedUint> for BoxedResidue {
37+
fn pow_bounded_exp(&self, exponent: &BoxedUint, exponent_bits: usize) -> Self {
38+
self.pow_bounded_exp(exponent, exponent_bits)
39+
}
40+
}
41+
42+
/// Performs modular exponentiation using Montgomery's ladder.
43+
/// `exponent_bits` represents the number of bits to take into account for the exponent.
44+
///
45+
/// NOTE: this value is leaked in the time pattern.
46+
fn pow_montgomery_form(
47+
x: &BoxedUint,
48+
exponent: &BoxedUint,
49+
exponent_bits: usize,
50+
modulus: &BoxedUint,
51+
r: &BoxedUint,
52+
mod_neg_inv: Limb,
53+
) -> BoxedUint {
54+
if exponent_bits == 0 {
55+
return r.clone(); // 1 in Montgomery form
56+
}
57+
58+
const WINDOW: usize = 4;
59+
const WINDOW_MASK: Word = (1 << WINDOW) - 1;
60+
61+
// powers[i] contains x^i
62+
let mut powers = vec![r.clone(); 1 << WINDOW];
63+
powers[1] = x.clone();
64+
let mut i = 2;
65+
while i < powers.len() {
66+
powers[i] = mul_montgomery_form(&powers[i - 1], x, modulus, mod_neg_inv);
67+
i += 1;
68+
}
69+
70+
let starting_limb = (exponent_bits - 1) / Limb::BITS;
71+
let starting_bit_in_limb = (exponent_bits - 1) % Limb::BITS;
72+
let starting_window = starting_bit_in_limb / WINDOW;
73+
let starting_window_mask = (1 << (starting_bit_in_limb % WINDOW + 1)) - 1;
74+
75+
let mut z = r.clone(); // 1 in Montgomery form
76+
77+
let mut limb_num = starting_limb + 1;
78+
while limb_num > 0 {
79+
limb_num -= 1;
80+
let w = exponent.as_limbs()[limb_num].0;
81+
82+
let mut window_num = if limb_num == starting_limb {
83+
starting_window + 1
84+
} else {
85+
Limb::BITS / WINDOW
86+
};
87+
while window_num > 0 {
88+
window_num -= 1;
89+
90+
let mut idx = (w >> (window_num * WINDOW)) & WINDOW_MASK;
91+
92+
if limb_num == starting_limb && window_num == starting_window {
93+
idx &= starting_window_mask;
94+
} else {
95+
let mut i = 0;
96+
while i < WINDOW {
97+
i += 1;
98+
z = square_montgomery_form(&z, modulus, mod_neg_inv);
99+
}
100+
}
101+
102+
// Constant-time lookup in the array of powers
103+
let mut power = powers[0].clone();
104+
let mut i = 1;
105+
while i < 1 << WINDOW {
106+
power = BoxedUint::conditional_select(&power, &powers[i], (i as Word).ct_eq(&idx));
107+
i += 1;
108+
}
109+
110+
z = mul_montgomery_form(&z, &power, modulus, mod_neg_inv);
111+
}
112+
}
113+
114+
z
115+
}

tests/boxed_residue_proptests.rs

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ fn retrieve_biguint(residue: &BoxedResidue) -> BigUint {
1818
to_biguint(&residue.retrieve())
1919
}
2020

21+
fn reduce(n: &BoxedUint, p: BoxedResidueParams) -> BoxedResidue {
22+
let bits_precision = p.modulus().bits_precision();
23+
let modulus = NonZero::new(p.modulus().clone()).unwrap();
24+
25+
let n = match n.bits_precision().cmp(&bits_precision) {
26+
Ordering::Less => n.widen(bits_precision),
27+
Ordering::Equal => n.clone(),
28+
Ordering::Greater => n.shorten(bits_precision),
29+
};
30+
31+
let n_reduced = n.rem_vartime(&modulus).widen(p.bits_precision());
32+
BoxedResidue::new(&n_reduced, p)
33+
}
34+
2135
prop_compose! {
2236
/// Generate a random `BoxedUint`.
2337
fn uint()(mut bytes in any::<Vec<u8>>()) -> BoxedUint {
@@ -29,47 +43,45 @@ prop_compose! {
2943
}
3044
prop_compose! {
3145
/// Generate a random modulus.
32-
fn modulus()(mut a in uint()) -> BoxedResidueParams {
33-
if a.is_even().into() {
34-
a = a.wrapping_add(&BoxedUint::one());
46+
fn modulus()(mut n in uint()) -> BoxedResidueParams {
47+
if n.is_even().into() {
48+
n = n.wrapping_add(&BoxedUint::one());
3549
}
3650

37-
BoxedResidueParams::new(a).expect("modulus should be valid")
51+
BoxedResidueParams::new(n).expect("modulus should be valid")
3852
}
3953
}
4054
prop_compose! {
4155
/// Generate two residues with a common modulus.
42-
fn residue_pair()(a in uint(), b in uint(), p in modulus()) -> (BoxedResidue, BoxedResidue) {
43-
fn reduce(n: &BoxedUint, p: BoxedResidueParams) -> BoxedResidue {
44-
let bits_precision = p.modulus().bits_precision();
45-
let modulus = NonZero::new(p.modulus().clone()).unwrap();
46-
47-
let n = match n.bits_precision().cmp(&bits_precision) {
48-
Ordering::Less => n.widen(bits_precision),
49-
Ordering::Equal => n.clone(),
50-
Ordering::Greater => n.shorten(bits_precision)
51-
};
52-
53-
let n_reduced = n.rem_vartime(&modulus).widen(p.bits_precision());
54-
BoxedResidue::new(&n_reduced, p)
55-
}
56-
57-
58-
(reduce(&a, p.clone()), reduce(&b, p.clone()))
56+
fn residue_pair()(a in uint(), b in uint(), n in modulus()) -> (BoxedResidue, BoxedResidue) {
57+
(reduce(&a, n.clone()), reduce(&b, n.clone()))
5958
}
6059
}
6160

6261
proptest! {
6362
#[test]
6463
fn mul((a, b) in residue_pair()) {
6564
let p = a.params().modulus();
66-
let c = &a * &b;
65+
let actual = &a * &b;
6766

6867
let a_bi = retrieve_biguint(&a);
6968
let b_bi = retrieve_biguint(&b);
7069
let p_bi = to_biguint(&p);
71-
let c_bi = (a_bi * b_bi) % p_bi;
70+
let expected = (a_bi * b_bi) % p_bi;
71+
72+
prop_assert_eq!(retrieve_biguint(&actual), expected);
73+
}
74+
75+
#[test]
76+
fn pow(a in uint(), b in uint(), n in modulus()) {
77+
let a = reduce(&a, n.clone());
78+
let actual = a.pow(&b);
79+
80+
let a_bi = retrieve_biguint(&a);
81+
let b_bi = to_biguint(&b);
82+
let n_bi = to_biguint(n.modulus());
83+
let expected = a_bi.modpow(&b_bi, &n_bi);
7284

73-
prop_assert_eq!(retrieve_biguint(&c), c_bi);
85+
prop_assert_eq!(retrieve_biguint(&actual), expected);
7486
}
7587
}

0 commit comments

Comments
 (0)