Skip to content

Commit 0061609

Browse files
committed
Generalize sieving
1 parent d2c4a39 commit 0061609

File tree

6 files changed

+175
-83
lines changed

6 files changed

+175
-83
lines changed

src/generic.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use rand_core::CryptoRngCore;
2+
3+
#[cfg(feature = "multicore")]
4+
use rayon::iter::{ParallelBridge, ParallelIterator};
5+
6+
use crate::SieveFactory;
7+
8+
/// Sieves through the results of `sieve_factory` and returns the first item for which `predicate` is `true`.
9+
///
10+
/// If `sieve_factory` signals that no more results can be created, returns `None`.
11+
pub fn sieve_and_find<R, S, T>(rng: &mut R, sieve_factory: &S, predicate: impl Fn(&mut R, &T) -> bool) -> Option<T>
12+
where
13+
S: SieveFactory<T>,
14+
R: CryptoRngCore,
15+
{
16+
// We could use `SieveIterator` here, but it requires cloning the `rng`.
17+
// Unlike the parallel version, it is avoidable here.
18+
19+
let mut sieve = sieve_factory.make_sieve(rng, None)?;
20+
21+
loop {
22+
if let Some(value) = sieve.find(|num| predicate(rng, num)) {
23+
return Some(value);
24+
}
25+
if let Some(new_sieve) = sieve_factory.make_sieve(rng, Some(&sieve)) {
26+
sieve = new_sieve;
27+
} else {
28+
return None;
29+
}
30+
}
31+
}
32+
33+
/// Sieves through the results of `sieve_factory` using a thread pool with `threadcount` threads,
34+
/// and returns the first item for which `predicate` is `true`.
35+
///
36+
/// If `sieve_factory` signals that no more results can be created, returns `None`.
37+
#[cfg(feature = "multicore")]
38+
pub fn par_sieve_and_find<R, S, T, F>(rng: &mut R, sieve_factory: S, predicate: F, threadcount: usize) -> Option<T>
39+
where
40+
R: CryptoRngCore + Clone + Send + Sync,
41+
T: Send,
42+
S: Send + Sync + SieveFactory<T>,
43+
S::Sieve: Send,
44+
F: Sync + Fn(&mut R, &T) -> bool,
45+
{
46+
let threadpool = rayon::ThreadPoolBuilder::new()
47+
.num_threads(threadcount)
48+
.build()
49+
.expect("If the platform can spawn threads, then this call will work.");
50+
51+
let mut iter_rng = rng.clone();
52+
let iter = SieveIterator::new(&mut iter_rng, sieve_factory)?;
53+
54+
threadpool.install(|| {
55+
iter.par_bridge().find_any(|c| {
56+
let mut rng = rng.clone();
57+
predicate(&mut rng, c)
58+
})
59+
})
60+
}
61+
62+
/// A structure that chains the creation of sieves, returning the results from one until it is exhausted,
63+
/// and then creating a new one.
64+
#[derive(Debug)]
65+
pub struct SieveIterator<'a, R: CryptoRngCore, T, S: SieveFactory<T>> {
66+
sieve_factory: S,
67+
sieve: S::Sieve,
68+
rng: &'a mut R,
69+
}
70+
71+
impl<'a, R: CryptoRngCore, T, S: SieveFactory<T>> SieveIterator<'a, R, T, S> {
72+
/// Creates a new chained iterator producing results from sieves returned from `sieve_factory`.
73+
pub fn new(rng: &'a mut R, sieve_factory: S) -> Option<Self> {
74+
let sieve = sieve_factory.make_sieve(rng, None)?;
75+
Some(Self {
76+
sieve_factory,
77+
rng,
78+
sieve,
79+
})
80+
}
81+
}
82+
83+
impl<'a, R: CryptoRngCore, T, S: SieveFactory<T>> Iterator for SieveIterator<'a, R, T, S> {
84+
type Item = T;
85+
86+
fn next(&mut self) -> Option<Self::Item> {
87+
loop {
88+
if let Some(result) = self.sieve.next() {
89+
return Some(result);
90+
}
91+
92+
self.sieve = self.sieve_factory.make_sieve(self.rng, Some(&self.sieve))?;
93+
}
94+
}
95+
}

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: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@
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, SieveIterator};
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};
2830
#[cfg(all(feature = "default-rng", feature = "multicore"))]
2931
pub use presets::{par_generate_prime, par_generate_safe_prime};
3032
#[cfg(feature = "multicore")]
3133
pub use presets::{par_generate_prime_with_rng, par_generate_safe_prime_with_rng};
34+
35+
#[cfg(feature = "multicore")]
36+
pub use generic::par_sieve_and_find;

src/presets.rs

Lines changed: 24 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
use core::num::NonZero;
2-
31
use crypto_bigint::{Integer, Limb, Odd, RandomBits, RandomMod};
42
use rand_core::CryptoRngCore;
53

64
#[cfg(feature = "default-rng")]
75
use rand_core::OsRng;
86

9-
#[cfg(feature = "multicore")]
10-
use rayon::iter::{ParallelBridge, ParallelIterator};
7+
use crate::{
8+
generic::sieve_and_find,
9+
hazmat::{lucas_test, AStarBase, DefaultSieveFactory, LucasCheck, MillerRabin, Primality},
10+
};
1111

12-
use crate::hazmat::{lucas_test, random_odd_integer, AStarBase, LucasCheck, MillerRabin, Primality, Sieve};
12+
#[cfg(feature = "multicore")]
13+
use crate::generic::par_sieve_and_find;
1314

1415
/// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG.
1516
///
@@ -83,18 +84,8 @@ pub fn generate_prime_with_rng<T: Integer + RandomBits + RandomMod>(
8384
rng: &mut impl CryptoRngCore,
8485
bit_length: u32,
8586
) -> 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-
}
87+
sieve_and_find(rng, &DefaultSieveFactory::new(bit_length, false), is_prime_with_rng)
88+
.expect("will produce a result eventually")
9889
}
9990

10091
/// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime)
@@ -107,19 +98,8 @@ pub fn generate_safe_prime_with_rng<T: Integer + RandomBits + RandomMod>(
10798
rng: &mut impl CryptoRngCore,
10899
bit_length: u32,
109100
) -> 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-
}
101+
sieve_and_find(rng, &DefaultSieveFactory::new(bit_length, true), is_safe_prime_with_rng)
102+
.expect("will produce a result eventually")
123103
}
124104

125105
/// Returns a random prime of size `bit_length` using the provided RNG.
@@ -138,31 +118,13 @@ pub fn par_generate_prime_with_rng<T>(
138118
where
139119
T: Integer + RandomBits + RandomMod,
140120
{
141-
if bit_length < 2 {
142-
panic!("`bit_length` must be 2 or greater.");
143-
}
144-
let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero");
145-
146-
let threadpool = rayon::ThreadPoolBuilder::new()
147-
.num_threads(threadcount)
148-
.build()
149-
.expect("If the platform can spawn threads, then this call will work.");
150-
let start = random_odd_integer::<T>(rng, bit_length).get();
151-
let sieve = Sieve::new(start, bit_length, false);
152-
153-
let prime = threadpool.install(|| {
154-
sieve.par_bridge().find_any(|c| {
155-
let mut rng = rng.clone();
156-
is_prime_with_rng(&mut rng, c)
157-
})
158-
});
159-
match prime {
160-
Some(prime) => prime,
161-
None => {
162-
drop(threadpool);
163-
par_generate_prime_with_rng(rng, bit_length.get(), threadcount)
164-
}
165-
}
121+
par_sieve_and_find(
122+
rng,
123+
DefaultSieveFactory::new(bit_length, false),
124+
is_prime_with_rng,
125+
threadcount,
126+
)
127+
.expect("will produce a result eventually")
166128
}
167129

168130
/// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime)
@@ -183,31 +145,13 @@ pub fn par_generate_safe_prime_with_rng<T>(
183145
where
184146
T: Integer + RandomBits + RandomMod,
185147
{
186-
if bit_length < 2 {
187-
panic!("`bit_length` must be 2 or greater.");
188-
}
189-
let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero");
190-
191-
let threadpool = rayon::ThreadPoolBuilder::new()
192-
.num_threads(threadcount)
193-
.build()
194-
.expect("If the platform can spawn threads, then this call will work.");
195-
let start = random_odd_integer::<T>(rng, bit_length).get();
196-
let sieve = Sieve::new(start, bit_length, true);
197-
198-
let prime = threadpool.install(|| {
199-
sieve.par_bridge().find_any(|c| {
200-
let mut rng = rng.clone();
201-
is_safe_prime_with_rng(&mut rng, c)
202-
})
203-
});
204-
match prime {
205-
Some(prime) => prime,
206-
None => {
207-
drop(threadpool);
208-
par_generate_safe_prime_with_rng(rng, bit_length.get(), threadcount)
209-
}
210-
}
148+
par_sieve_and_find(
149+
rng,
150+
DefaultSieveFactory::new(bit_length, true),
151+
is_safe_prime_with_rng,
152+
threadcount,
153+
)
154+
.expect("will produce a result eventually")
211155
}
212156

213157
/// Probabilistically checks if the given number is prime 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 should 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)