Skip to content

Commit b5a6566

Browse files
committed
Generalize sieving
1 parent 4ea84ca commit b5a6566

File tree

6 files changed

+91
-29
lines changed

6 files changed

+91
-29
lines changed

src/generic.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use rand_core::CryptoRngCore;
2+
3+
use crate::SieveFactory;
4+
5+
/// Sieves through the results of `sieve_factory` and returns the first item for which `predicate` is `true`.
6+
///
7+
/// If `sieve_factory` signals that no more results can be created, returns `None`.
8+
pub fn sieve_and_find<R, S, T>(rng: &mut R, sieve_factory: &S, predicate: impl Fn(&mut R, &T) -> bool) -> Option<T>
9+
where
10+
S: SieveFactory<T>,
11+
R: CryptoRngCore,
12+
{
13+
let mut sieve = sieve_factory.make_sieve(rng, None)?;
14+
15+
loop {
16+
let prime = sieve.find(|num| predicate(rng, num));
17+
if prime.is_some() {
18+
return prime;
19+
}
20+
if let Some(new_sieve) = sieve_factory.make_sieve(rng, Some(sieve)) {
21+
sieve = new_sieve;
22+
} else {
23+
return None;
24+
}
25+
}
26+
}

src/hazmat.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mod sieve;
1414

1515
pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase};
1616
pub use miller_rabin::MillerRabin;
17-
pub use sieve::{random_odd_integer, Sieve};
17+
pub use sieve::{random_odd_integer, DefaultSieveFactory, Sieve};
1818

1919
/// Possible results of various primality tests.
2020
#[derive(Copy, Clone, Debug, PartialEq, Eq)]

src/hazmat/sieve.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
//! before proceeding with slower tests.
33
44
use alloc::{vec, vec::Vec};
5-
use core::num::NonZeroU32;
5+
use core::num::{NonZero, NonZeroU32};
66

77
use crypto_bigint::{Integer, Odd, RandomBits};
88
use rand_core::CryptoRngCore;
99

1010
use crate::hazmat::precomputed::{SmallPrime, LAST_SMALL_PRIME, RECIPROCALS, SMALL_PRIMES};
11+
use crate::traits::SieveFactory;
1112

1213
/// Returns a random odd integer with given bit length
1314
/// (that is, with both `0` and `bit_length-1` bits set).
@@ -244,6 +245,42 @@ impl<T: Integer> Iterator for Sieve<T> {
244245
}
245246
}
246247

248+
/// A sieve returning numbers that are not multiples of a set of small factors.
249+
#[derive(Debug, Clone, Copy)]
250+
pub struct DefaultSieveFactory {
251+
max_bit_length: NonZeroU32,
252+
safe_primes: bool,
253+
}
254+
255+
impl DefaultSieveFactory {
256+
/// Creates a factory that produces sieves returning numbers of `max_bit_length` bits (with the top bit set).
257+
///
258+
/// If `safe_primes` is `true`, additionally filters out such `n` that `(n - 1) / 2` are divisible
259+
/// by any of the small factors tested.
260+
pub fn new(max_bit_length: u32, safe_primes: bool) -> Self {
261+
let max_bit_length = NonZero::new(max_bit_length).expect("`bit_length` should be non-zero");
262+
Self {
263+
max_bit_length,
264+
safe_primes,
265+
}
266+
}
267+
}
268+
269+
impl<T: Integer + RandomBits> SieveFactory<T> for DefaultSieveFactory {
270+
type Sieve = Sieve<T>;
271+
fn make_sieve(&self, rng: &mut impl CryptoRngCore, _previous_sieve: Option<Self::Sieve>) -> Option<Self::Sieve> {
272+
if !self.safe_primes && self.max_bit_length.get() < 2 {
273+
panic!("`bit_length` must be 2 or greater.");
274+
}
275+
if self.safe_primes && self.max_bit_length.get() < 3 {
276+
panic!("`bit_length` must be 3 or greater.");
277+
}
278+
279+
let start = random_odd_integer::<T>(rng, self.max_bit_length);
280+
Some(Sieve::new(start.get(), self.max_bit_length, self.safe_primes))
281+
}
282+
}
283+
247284
#[cfg(test)]
248285
mod tests {
249286

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
extern crate alloc;
1818

19+
mod generic;
1920
pub mod hazmat;
2021
mod presets;
2122
mod traits;
2223

24+
pub use generic::sieve_and_find;
2325
pub use presets::{generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, is_safe_prime_with_rng};
24-
pub use traits::RandomPrimeWithRng;
26+
pub use traits::{RandomPrimeWithRng, SieveFactory};
2527

2628
#[cfg(feature = "default-rng")]
2729
pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime};

src/presets.rs

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#[cfg(feature = "multicore")]
12
use core::num::NonZero;
23

34
use crypto_bigint::{Integer, Limb, Odd, RandomBits, RandomMod};
@@ -9,7 +10,13 @@ use rand_core::OsRng;
910
#[cfg(feature = "multicore")]
1011
use rayon::iter::{ParallelBridge, ParallelIterator};
1112

12-
use crate::hazmat::{lucas_test, random_odd_integer, AStarBase, LucasCheck, MillerRabin, Primality, Sieve};
13+
use crate::{
14+
generic::sieve_and_find,
15+
hazmat::{lucas_test, AStarBase, DefaultSieveFactory, LucasCheck, MillerRabin, Primality},
16+
};
17+
18+
#[cfg(feature = "multicore")]
19+
use crate::hazmat::{random_odd_integer, Sieve};
1320

1421
/// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG.
1522
///
@@ -83,18 +90,8 @@ pub fn generate_prime_with_rng<T: Integer + RandomBits + RandomMod>(
8390
rng: &mut impl CryptoRngCore,
8491
bit_length: u32,
8592
) -> T {
86-
if bit_length < 2 {
87-
panic!("`bit_length` must be 2 or greater.");
88-
}
89-
let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero");
90-
// Empirically, this loop is traversed 1 time.
91-
loop {
92-
let start = random_odd_integer::<T>(rng, bit_length);
93-
let mut sieve = Sieve::new(start.get(), bit_length, false);
94-
if let Some(prime) = sieve.find(|num| is_prime_with_rng(rng, num)) {
95-
return prime;
96-
}
97-
}
93+
sieve_and_find(rng, &DefaultSieveFactory::new(bit_length, false), is_prime_with_rng)
94+
.expect("will produce a result eventually")
9895
}
9996

10097
/// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime)
@@ -107,19 +104,8 @@ pub fn generate_safe_prime_with_rng<T: Integer + RandomBits + RandomMod>(
107104
rng: &mut impl CryptoRngCore,
108105
bit_length: u32,
109106
) -> T {
110-
if bit_length < 3 {
111-
panic!("`bit_length` must be 3 or greater.");
112-
}
113-
let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero");
114-
loop {
115-
let start = random_odd_integer::<T>(rng, bit_length);
116-
let sieve = Sieve::new(start.get(), bit_length, true);
117-
for num in sieve {
118-
if is_safe_prime_with_rng(rng, &num) {
119-
return num;
120-
}
121-
}
122-
}
107+
sieve_and_find(rng, &DefaultSieveFactory::new(bit_length, true), is_safe_prime_with_rng)
108+
.expect("will produce a result eventually")
123109
}
124110

125111
/// Returns a random prime of size `bit_length` using the provided RNG.

src/traits.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ use rand_core::CryptoRngCore;
33

44
use crate::{generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, is_safe_prime_with_rng};
55

6+
/// A type producing sieves for random prime generation.
7+
pub trait SieveFactory<T> {
8+
/// The resulting sieve.
9+
type Sieve: Iterator<Item = T>;
10+
11+
/// Makes a sieve given an RNG and the previous exhausted sieve (if any).
12+
///
13+
/// Returning `None` signals that the prime generation shoulds stop.
14+
fn make_sieve(&self, rng: &mut impl CryptoRngCore, previous_sieve: Option<Self::Sieve>) -> Option<Self::Sieve>;
15+
}
16+
617
/// Provides a generic way to access methods for random prime number generation
718
/// and primality checking, wrapping the standalone functions ([`is_prime_with_rng`] etc).
819
pub trait RandomPrimeWithRng {

0 commit comments

Comments
 (0)