Skip to content

Commit 7905775

Browse files
committed
Add windowed exponentiation for (Dyn)Residue
1 parent d038c97 commit 7905775

7 files changed

Lines changed: 177 additions & 25 deletions

File tree

benches/bench.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
use criterion::{
22
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion,
33
};
4-
use crypto_bigint::{NonZero, Random, Reciprocal, Uint};
4+
use crypto_bigint::{
5+
modular::{
6+
runtime_mod::{DynResidue, DynResidueParams},
7+
PowResidue,
8+
},
9+
Encoding, NonZero, Random, Reciprocal, Uint, U256,
10+
};
511
use rand_core::OsRng;
612

713
fn bench_division<'a, M: Measurement>(group: &mut BenchmarkGroup<'a, M>) {
@@ -72,9 +78,42 @@ fn bench_division<'a, M: Measurement>(group: &mut BenchmarkGroup<'a, M>) {
7278
});
7379
}
7480

81+
fn bench_modpow<'a, M: Measurement>(group: &mut BenchmarkGroup<'a, M>) {
82+
const TEST_SET: usize = 10;
83+
let xs = (0..TEST_SET)
84+
.map(|_| U256::random(&mut OsRng))
85+
.collect::<Vec<_>>();
86+
let moduli = (0..TEST_SET)
87+
.map(|_| U256::random(&mut OsRng) | U256::ONE)
88+
.collect::<Vec<_>>();
89+
let powers = (0..TEST_SET)
90+
.map(|_| U256::random(&mut OsRng) | (U256::ONE << (U256::BIT_SIZE - 1)))
91+
.collect::<Vec<_>>();
92+
93+
let params = moduli
94+
.iter()
95+
.map(|modulus| DynResidueParams::new(*modulus))
96+
.collect::<Vec<_>>();
97+
let xs_m = xs
98+
.iter()
99+
.zip(params.iter())
100+
.map(|(x, p)| DynResidue::new(*x, *p))
101+
.collect::<Vec<_>>();
102+
103+
group.bench_function("modpow, 4^4", |b| {
104+
b.iter(|| {
105+
xs_m.iter()
106+
.zip(powers.iter())
107+
.map(|(x, p)| x.pow(&p))
108+
.for_each(drop)
109+
})
110+
});
111+
}
112+
75113
fn bench_wrapping_ops(c: &mut Criterion) {
76114
let mut group = c.benchmark_group("wrapping ops");
77115
bench_division(&mut group);
116+
bench_modpow(&mut group);
78117
group.finish();
79118
}
80119

src/limb/cmp.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ impl Limb {
4242
(gt as SignedWord) - (lt as SignedWord)
4343
}
4444

45+
/// Returns `Word::MAX` if `lhs == rhs` and `0` otherwise.
46+
#[inline]
47+
pub(crate) const fn ct_eq(lhs: Self, rhs: Self) -> Word {
48+
let x = lhs.0;
49+
let y = rhs.0;
50+
51+
// c == 0 if and only if x == y
52+
let c = x ^ y;
53+
54+
// If c == 0, then c and -c are both equal to zero;
55+
// otherwise, one or both will have its high bit set.
56+
let d = (c | c.wrapping_neg()) >> (Limb::BIT_SIZE - 1);
57+
58+
// Result is the opposite of the high bit (now shifted to low).
59+
// Convert 1 to Word::MAX.
60+
(d ^ 1).wrapping_neg()
61+
}
62+
4563
/// Returns `Word::MAX` if `lhs < rhs` and `0` otherwise.
4664
#[inline]
4765
pub(crate) const fn ct_lt(lhs: Self, rhs: Self) -> Word {

src/uint/modular.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ where
4444
self.pow_specific(exponent, LIMBS * Word::BITS as usize)
4545
}
4646

47-
/// Computes the (reduced) exponentiation of a residue, here `exponent_bits` represents the number of bits to take into account for the exponent. Note that this value is leaked in the time pattern.
47+
/// Computes the (reduced) exponentiation of a residue,
48+
/// here `exponent_bits` represents the number of bits to take into account for the exponent.
49+
///
50+
/// NOTE: `exponent_bits` is leaked in the time pattern.
4851
fn pow_specific(self, exponent: &Uint<LIMBS>, exponent_bits: usize) -> Self;
4952
}
5053

@@ -53,11 +56,13 @@ pub trait InvResidue
5356
where
5457
Self: Sized,
5558
{
56-
/// Computes the (reduced) multiplicative inverse of the residue. Returns CtOption, which is `None` if the residue was not invertible.
59+
/// Computes the (reduced) multiplicative inverse of the residue. Returns CtOption,
60+
/// which is `None` if the residue was not invertible.
5761
fn inv(self) -> CtOption<Self>;
5862
}
5963

60-
/// The `GenericResidue` trait provides a consistent API for dealing with residues with a constant modulus.
64+
/// The `GenericResidue` trait provides a consistent API
65+
/// for dealing with residues with a constant modulus.
6166
pub trait GenericResidue<const LIMBS: usize>:
6267
AddResidue + MulResidue + PowResidue<LIMBS> + InvResidue
6368
{

src/uint/modular/pow.rs

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ use crate::{Limb, Uint, Word};
22

33
use super::mul::{mul_montgomery_form, square_montgomery_form};
44

5-
/// Performs modular exponentiation using Montgomery's ladder. `exponent_bits` represents the number of bits to take into account for the exponent. Note that this value is leaked in the time pattern.
5+
/// Performs modular exponentiation using Montgomery's ladder.
6+
/// `exponent_bits` represents the number of bits to take into account for the exponent.
7+
///
8+
/// NOTE: this value is leaked in the time pattern.
69
pub const fn pow_montgomery_form<const LIMBS: usize>(
710
x: Uint<LIMBS>,
811
exponent: &Uint<LIMBS>,
@@ -11,29 +14,66 @@ pub const fn pow_montgomery_form<const LIMBS: usize>(
1114
r: Uint<LIMBS>,
1215
mod_neg_inv: Limb,
1316
) -> Uint<LIMBS> {
14-
let mut x1: Uint<LIMBS> = r;
15-
let mut x2: Uint<LIMBS> = x;
17+
if exponent_bits == 0 {
18+
return r; // 1 in Montgomery form
19+
}
1620

17-
// Shift the exponent all the way to the left so the leftmost bit is the MSB of the `Uint`
18-
let mut n: Uint<LIMBS> = exponent.shl_vartime((LIMBS * Word::BITS as usize) - exponent_bits);
21+
const WINDOW: usize = 4;
22+
const WINDOW_MASK: Word = (1 << WINDOW) - 1;
1923

20-
let mut i = 0;
21-
while i < exponent_bits {
22-
// Peel off one bit at a time from the left side
23-
let (next_n, overflow) = n.shl_1();
24-
n = next_n;
24+
// powers[i] contains x^i
25+
let mut powers = [r; 1 << WINDOW];
26+
powers[1] = x;
27+
let mut i = 2;
28+
while i < powers.len() {
29+
powers[i] = mul_montgomery_form(&powers[i - 1], &x, modulus, mod_neg_inv);
30+
i += 1;
31+
}
2532

26-
let mut product: Uint<LIMBS> = x1;
27-
product = mul_montgomery_form(&product, &x2, modulus, mod_neg_inv);
33+
let starting_limb = (exponent_bits - 1) / Limb::BIT_SIZE;
34+
let starting_bit_in_limb = (exponent_bits - 1) % Limb::BIT_SIZE;
35+
let starting_window = starting_bit_in_limb / WINDOW;
36+
let starting_window_mask = (1 << (starting_bit_in_limb % WINDOW + 1)) - 1;
2837

29-
let mut square = Uint::ct_select(x1, x2, overflow);
30-
square = square_montgomery_form(&square, modulus, mod_neg_inv);
38+
let mut z = r; // 1 in Montgomery form
3139

32-
x1 = Uint::<LIMBS>::ct_select(square, product, overflow);
33-
x2 = Uint::<LIMBS>::ct_select(product, square, overflow);
40+
let mut limb_num = starting_limb + 1;
41+
while limb_num > 0 {
42+
limb_num -= 1;
43+
let w = exponent.as_limbs()[limb_num].0;
3444

35-
i += 1;
45+
let mut window_num = if limb_num == starting_limb {
46+
starting_window + 1
47+
} else {
48+
Limb::BIT_SIZE / WINDOW
49+
};
50+
while window_num > 0 {
51+
window_num -= 1;
52+
53+
let mut idx = (w >> (window_num * WINDOW)) & WINDOW_MASK;
54+
55+
if limb_num == starting_limb && window_num == starting_window {
56+
idx &= starting_window_mask;
57+
} else {
58+
let mut i = 0;
59+
while i < WINDOW {
60+
i += 1;
61+
z = square_montgomery_form(&z, modulus, mod_neg_inv);
62+
}
63+
}
64+
65+
// Constant-time lookup in the array of powers
66+
let mut power = powers[0];
67+
let mut i = 1;
68+
while i < 1 << WINDOW {
69+
let choice = Limb::ct_eq(Limb(i as Word), Limb(idx));
70+
power = Uint::<LIMBS>::ct_select(power, powers[i], choice);
71+
i += 1;
72+
}
73+
74+
z = mul_montgomery_form(&z, &power, modulus, mod_neg_inv);
75+
}
3676
}
3777

38-
x1
78+
z
3979
}

src/uint/modular/runtime_mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ impl<const LIMBS: usize> DynResidue<LIMBS> {
8484
residue_params,
8585
}
8686
}
87+
88+
/// Instantiates a new `Residue` that represents 1.
89+
pub const fn one(residue_params: DynResidueParams<LIMBS>) -> Self {
90+
Self {
91+
montgomery_form: residue_params.r,
92+
residue_params,
93+
}
94+
}
8795
}
8896

8997
impl<const LIMBS: usize> GenericResidue<LIMBS> for DynResidue<LIMBS> {

src/uint/modular/runtime_mod/runtime_pow.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ impl<const LIMBS: usize> PowResidue<LIMBS> for DynResidue<LIMBS> {
1212
}
1313

1414
impl<const LIMBS: usize> DynResidue<LIMBS> {
15-
/// Computes the (reduced) exponentiation of a residue, here `exponent_bits` represents the number of bits to take into account for the exponent. Note that this value is leaked in the time pattern.
15+
/// Computes the (reduced) exponentiation of a residue,
16+
/// here `exponent_bits` represents the number of bits to take into account for the exponent.
17+
///
18+
/// NOTE: `exponent_bits` is leaked in the time pattern.
1619
pub const fn pow_specific(self, exponent: &Uint<LIMBS>, exponent_bits: usize) -> Self {
1720
Self {
1821
montgomery_form: pow_montgomery_form(

tests/proptests.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
//! Equivalence tests between `num-bigint` and `crypto-bigint`
22
3-
use crypto_bigint::{Encoding, Limb, NonZero, Word, U256};
3+
use crypto_bigint::{
4+
modular::{
5+
runtime_mod::{DynResidue, DynResidueParams},
6+
PowResidue,
7+
},
8+
Encoding, Limb, NonZero, Word, U256,
9+
};
410
use num_bigint::BigUint;
511
use num_integer::Integer;
612
use num_traits::identities::Zero;
@@ -245,5 +251,38 @@ proptest! {
245251
let mut bytes = a.to_le_bytes();
246252
bytes.reverse();
247253
assert_eq!(a, U256::from_be_bytes(bytes));
248-
}
254+
}
255+
256+
#[test]
257+
fn residue_pow(a in uint_mod_p(P), b in uint()) {
258+
let a_bi = to_biguint(&a);
259+
let b_bi = to_biguint(&b);
260+
let p_bi = to_biguint(&P);
261+
262+
let expected = to_uint(a_bi.modpow(&b_bi, &p_bi));
263+
264+
let params = DynResidueParams::new(P);
265+
let a_m = DynResidue::new(a, params);
266+
let actual = a_m.pow(&b).retrieve();
267+
268+
assert_eq!(expected, actual);
269+
}
270+
271+
#[test]
272+
fn residue_pow_specific(a in uint_mod_p(P), b in uint(), exponent_bits in any::<u8>()) {
273+
274+
let b_masked = b & (U256::ONE << exponent_bits.into()).wrapping_sub(&U256::ONE);
275+
276+
let a_bi = to_biguint(&a);
277+
let b_bi = to_biguint(&b_masked);
278+
let p_bi = to_biguint(&P);
279+
280+
let expected = to_uint(a_bi.modpow(&b_bi, &p_bi));
281+
282+
let params = DynResidueParams::new(P);
283+
let a_m = DynResidue::new(a, params);
284+
let actual = a_m.pow_specific(&b, exponent_bits.into()).retrieve();
285+
286+
assert_eq!(expected, actual);
287+
}
249288
}

0 commit comments

Comments
 (0)