Skip to content

Commit 65ed5d4

Browse files
authored
Merge pull request #19 from fjarri/bounded-primes
Bounded MR test
2 parents 4a29760 + 8739568 commit 65ed5d4

File tree

5 files changed

+69
-25
lines changed

5 files changed

+69
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- `sieve_once()` was removed ([#22]).
1212
- `MillerRabin::new()` and `test_random_base()` will panic if the input is invalid. ([#22])
1313
- `MillerRabin::check()` renamed to `test()`. ([#22])
14+
- Prime-generating function take `Option<usize>` instead of `usize`, where `None` means the full size of the `Uint`. ([#19])
1415

1516

1617
### Added
@@ -22,8 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2223

2324
- Some mistakes in the description of Lucas checks (the logic itself was fine). ([#20])
2425
- Major performance increase across the board due to better sieving (especially for random safe prime finding). ([#22])
26+
- Performance increase for the cases when the bit size of the generated prime is smaller than that of the containing `Uint`. ([#19])
2527

2628

29+
[#19]: https://github.com/nucypher/rust-umbral/pull/19
2730
[#20]: https://github.com/nucypher/rust-umbral/pull/20
2831
[#22]: https://github.com/nucypher/rust-umbral/pull/22
2932

benches/bench.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,23 +207,45 @@ fn bench_presets(c: &mut Criterion) {
207207

208208
let mut rng = make_rng();
209209
group.bench_function("(U128) Random prime", |b| {
210-
b.iter(|| prime_with_rng::<{ nlimbs!(128) }>(&mut rng, 128))
210+
b.iter(|| prime_with_rng::<{ nlimbs!(128) }>(&mut rng, None))
211211
});
212212

213213
let mut rng = make_rng();
214214
group.bench_function("(U1024) Random prime", |b| {
215-
b.iter(|| prime_with_rng::<{ nlimbs!(1024) }>(&mut rng, 1024))
215+
b.iter(|| prime_with_rng::<{ nlimbs!(1024) }>(&mut rng, None))
216216
});
217217

218218
let mut rng = make_rng();
219219
group.bench_function("(U128) Random safe prime", |b| {
220-
b.iter(|| safe_prime_with_rng::<{ nlimbs!(128) }>(&mut rng, 128))
220+
b.iter(|| safe_prime_with_rng::<{ nlimbs!(128) }>(&mut rng, None))
221221
});
222222

223223
group.sample_size(20);
224224
let mut rng = make_rng();
225225
group.bench_function("(U1024) Random safe prime", |b| {
226-
b.iter(|| safe_prime_with_rng::<{ nlimbs!(1024) }>(&mut rng, 1024))
226+
b.iter(|| safe_prime_with_rng::<{ nlimbs!(1024) }>(&mut rng, None))
227+
});
228+
229+
group.finish();
230+
231+
// A separate group for bounded tests, to make it easier to run them separately.
232+
let mut group = c.benchmark_group("Presets (bounded)");
233+
234+
let mut rng = make_rng();
235+
group.bench_function("(U128) Random safe prime", |b| {
236+
b.iter(|| safe_prime_with_rng::<{ nlimbs!(128) }>(&mut rng, None))
237+
});
238+
239+
// The performance should scale with the prime size, not with the Uint size.
240+
// So we should strive for this test's result to be as close as possible
241+
// to that of the previous one and as far away as possible from the next one.
242+
group.bench_function("(U256) Random 128 bit safe prime", |b| {
243+
b.iter(|| safe_prime_with_rng::<{ nlimbs!(256) }>(&mut rng, Some(128)))
244+
});
245+
246+
// The upper bound for the previous test.
247+
group.bench_function("(U256) Random 256 bit safe prime", |b| {
248+
b.iter(|| safe_prime_with_rng::<{ nlimbs!(256) }>(&mut rng, None))
227249
});
228250

229251
group.finish();

src/hazmat/miller_rabin.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use super::Primality;
2020
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2121
pub struct MillerRabin<const L: usize> {
2222
candidate: Uint<L>,
23+
bit_length: usize,
2324
montgomery_params: DynResidueParams<L>,
2425
one: DynResidue<L>,
2526
minus_one: DynResidue<L>,
@@ -47,6 +48,7 @@ impl<const L: usize> MillerRabin<L> {
4748

4849
Self {
4950
candidate: *candidate,
51+
bit_length: candidate.bits_vartime(),
5052
montgomery_params: params,
5153
one,
5254
minus_one,
@@ -61,7 +63,13 @@ impl<const L: usize> MillerRabin<L> {
6163
// otherwise we can return `Composite` right away.
6264

6365
let base = DynResidue::<L>::new(base, self.montgomery_params);
64-
let mut test = base.pow(&self.d);
66+
67+
// Implementation detail: bounded exp gets faster every time we decrease the bound
68+
// by the window length it uses, which is currently 4 bits.
69+
// So even when the bound isn't low enough that the number can fit
70+
// in a smaller number of limbs, there is still a performance gain
71+
// from specifying the bound.
72+
let mut test = base.pow_bounded_exp(&self.d, self.bit_length);
6573

6674
if test == self.one || test == self.minus_one {
6775
return Primality::ProbablyPrime;

src/presets.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ use crate::hazmat::{
99
};
1010

1111
/// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG.
12+
/// If `bit_length` is `None`, the full size of `Uint<L>` is used.
1213
///
1314
/// See [`is_prime_with_rng`] for details about the performed checks.
1415
#[cfg(feature = "default-rng")]
15-
pub fn prime<const L: usize>(bit_length: usize) -> Uint<L> {
16+
pub fn prime<const L: usize>(bit_length: Option<usize>) -> Uint<L> {
1617
prime_with_rng(&mut OsRng, bit_length)
1718
}
1819

1920
/// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime)
2021
/// of size `bit_length` using [`OsRng`] as the RNG.
22+
/// If `bit_length` is `None`, the full size of `Uint<L>` is used.
2123
///
2224
/// See [`is_prime_with_rng`] for details about the performed checks.
2325
#[cfg(feature = "default-rng")]
24-
pub fn safe_prime<const L: usize>(bit_length: usize) -> Uint<L> {
26+
pub fn safe_prime<const L: usize>(bit_length: Option<usize>) -> Uint<L> {
2527
safe_prime_with_rng(&mut OsRng, bit_length)
2628
}
2729

@@ -44,11 +46,16 @@ pub fn is_safe_prime<const L: usize>(num: &Uint<L>) -> bool {
4446
}
4547

4648
/// Returns a random prime of size `bit_length` using the provided RNG.
49+
/// If `bit_length` is `None`, the full size of `Uint<L>` is used.
4750
///
4851
/// Panics if `bit_length` is less than 2, or greater than the bit size of the target `Uint`.
4952
///
5053
/// See [`is_prime_with_rng`] for details about the performed checks.
51-
pub fn prime_with_rng<const L: usize>(rng: &mut impl CryptoRngCore, bit_length: usize) -> Uint<L> {
54+
pub fn prime_with_rng<const L: usize>(
55+
rng: &mut impl CryptoRngCore,
56+
bit_length: Option<usize>,
57+
) -> Uint<L> {
58+
let bit_length = bit_length.unwrap_or(Uint::<L>::BITS);
5259
if bit_length < 2 {
5360
panic!("`bit_length` must be 2 or greater.");
5461
}
@@ -65,14 +72,16 @@ pub fn prime_with_rng<const L: usize>(rng: &mut impl CryptoRngCore, bit_length:
6572

6673
/// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime)
6774
/// of size `bit_length` using the provided RNG.
75+
/// If `bit_length` is `None`, the full size of `Uint<L>` is used.
6876
///
6977
/// Panics if `bit_length` is less than 3, or is greater than the bit size of the target `Uint`.
7078
///
7179
/// See [`is_prime_with_rng`] for details about the performed checks.
7280
pub fn safe_prime_with_rng<const L: usize>(
7381
rng: &mut impl CryptoRngCore,
74-
bit_length: usize,
82+
bit_length: Option<usize>,
7583
) -> Uint<L> {
84+
let bit_length = bit_length.unwrap_or(Uint::<L>::BITS);
7685
if bit_length < 3 {
7786
panic!("`bit_length` must be 3 or greater.");
7887
}
@@ -240,7 +249,7 @@ mod tests {
240249
#[test]
241250
fn generate_prime() {
242251
for bit_length in (28..=128).step_by(10) {
243-
let p: U128 = prime(bit_length);
252+
let p: U128 = prime(Some(bit_length));
244253
assert!(p.bits_vartime() == bit_length);
245254
assert!(is_prime(&p));
246255
}
@@ -249,7 +258,7 @@ mod tests {
249258
#[test]
250259
fn generate_safe_prime() {
251260
for bit_length in (28..=128).step_by(10) {
252-
let p: U128 = safe_prime(bit_length);
261+
let p: U128 = safe_prime(Some(bit_length));
253262
assert!(p.bits_vartime() == bit_length);
254263
assert!(is_safe_prime(&p));
255264
}
@@ -282,25 +291,25 @@ mod tests {
282291
#[test]
283292
#[should_panic(expected = "`bit_length` must be 2 or greater")]
284293
fn generate_prime_too_few_bits() {
285-
let _p: U64 = prime_with_rng(&mut OsRng, 1);
294+
let _p: U64 = prime_with_rng(&mut OsRng, Some(1));
286295
}
287296

288297
#[test]
289298
#[should_panic(expected = "`bit_length` must be 3 or greater")]
290299
fn generate_safe_prime_too_few_bits() {
291-
let _p: U64 = safe_prime_with_rng(&mut OsRng, 2);
300+
let _p: U64 = safe_prime_with_rng(&mut OsRng, Some(2));
292301
}
293302

294303
#[test]
295304
#[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")]
296305
fn generate_prime_too_many_bits() {
297-
let _p: U64 = prime_with_rng(&mut OsRng, 65);
306+
let _p: U64 = prime_with_rng(&mut OsRng, Some(65));
298307
}
299308

300309
#[test]
301310
#[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")]
302311
fn generate_safe_prime_too_many_bits() {
303-
let _p: U64 = safe_prime_with_rng(&mut OsRng, 65);
312+
let _p: U64 = safe_prime_with_rng(&mut OsRng, Some(65));
304313
}
305314

306315
fn is_prime_ref(num: Word) -> bool {
@@ -311,7 +320,7 @@ mod tests {
311320
fn corner_cases_generate_prime() {
312321
for bits in 2usize..5 {
313322
for _ in 0..100 {
314-
let p: U64 = prime(bits);
323+
let p: U64 = prime(Some(bits));
315324
let p_word = p.as_words()[0];
316325
assert!(is_prime_ref(p_word));
317326
}
@@ -322,7 +331,7 @@ mod tests {
322331
fn corner_cases_generate_safe_prime() {
323332
for bits in 3usize..5 {
324333
for _ in 0..100 {
325-
let p: U64 = safe_prime(bits);
334+
let p: U64 = safe_prime(Some(bits));
326335
let p_word = p.as_words()[0];
327336
assert!(is_prime_ref(p_word) && is_prime_ref(p_word / 2));
328337
}
@@ -360,7 +369,7 @@ mod tests_openssl {
360369

361370
// Generate primes, let OpenSSL check them
362371
for _ in 0..100 {
363-
let p: U128 = prime(128);
372+
let p: U128 = prime(Some(128));
364373
let p_bn = to_openssl(&p);
365374
assert!(
366375
openssl_is_prime(&p_bn, &mut ctx),
@@ -419,7 +428,7 @@ mod tests_gmp {
419428
fn gmp_cross_check() {
420429
// Generate primes, let GMP check them
421430
for _ in 0..100 {
422-
let p: U128 = prime(128);
431+
let p: U128 = prime(Some(128));
423432
let p_bn = to_gmp(&p);
424433
assert!(gmp_is_prime(&p_bn), "GMP reports {p} as composite");
425434
}

src/traits.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ use crate::{is_prime_with_rng, is_safe_prime_with_rng, prime_with_rng, safe_prim
77
/// and primality checking, wrapping the standalone functions ([`is_prime_with_rng`] etc).
88
pub trait RandomPrimeWithRng {
99
/// Returns a random prime of size `bit_length` using the provided RNG.
10+
/// If `bit_length` is `None`, the full size of `Uint<L>` is used.
1011
///
1112
/// Panics if `bit_length` is less than 2, or greater than the bit size of the target `Uint`.
1213
///
1314
/// See [`is_prime_with_rng`] for details about the performed checks.
14-
fn prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self;
15+
fn prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option<usize>) -> Self;
1516

1617
/// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime)
1718
/// of size `bit_length` using the provided RNG.
19+
/// If `bit_length` is `None`, the full size of `Uint<L>` is used.
1820
///
1921
/// Panics if `bit_length` is less than 3, or greater than the bit size of the target `Uint`.
2022
///
2123
/// See [`is_prime_with_rng`] for details about the performed checks.
22-
fn safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self;
24+
fn safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option<usize>) -> Self;
2325

2426
/// Checks probabilistically if the given number is prime using the provided RNG.
2527
///
@@ -33,10 +35,10 @@ pub trait RandomPrimeWithRng {
3335
}
3436

3537
impl<const L: usize> RandomPrimeWithRng for Uint<L> {
36-
fn prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self {
38+
fn prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option<usize>) -> Self {
3739
prime_with_rng(rng, bit_length)
3840
}
39-
fn safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self {
41+
fn safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option<usize>) -> Self {
4042
safe_prime_with_rng(rng, bit_length)
4143
}
4244
fn is_prime_with_rng(&self, rng: &mut impl CryptoRngCore) -> bool {
@@ -62,7 +64,7 @@ mod tests {
6264
assert!(!U64::from(13u32).is_safe_prime_with_rng(&mut OsRng));
6365
assert!(U64::from(11u32).is_safe_prime_with_rng(&mut OsRng));
6466

65-
assert!(U64::prime_with_rng(&mut OsRng, 10).is_prime_with_rng(&mut OsRng));
66-
assert!(U64::safe_prime_with_rng(&mut OsRng, 10).is_safe_prime_with_rng(&mut OsRng));
67+
assert!(U64::prime_with_rng(&mut OsRng, Some(10)).is_prime_with_rng(&mut OsRng));
68+
assert!(U64::safe_prime_with_rng(&mut OsRng, Some(10)).is_safe_prime_with_rng(&mut OsRng));
6769
}
6870
}

0 commit comments

Comments
 (0)