diff --git a/.github/workflows/crypto-primes.yml b/.github/workflows/crypto-primes.yml index 0115ac8..5537992 100644 --- a/.github/workflows/crypto-primes.yml +++ b/.github/workflows/crypto-primes.yml @@ -44,7 +44,7 @@ jobs: - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage - run: cargo llvm-cov --workspace --lcov --output-path lcov.info + run: cargo llvm-cov --features default-rng,multicore --workspace --lcov --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: @@ -99,7 +99,7 @@ jobs: profile: minimal override: true - run: ${{ matrix.deps }} - - run: cargo test --target ${{ matrix.target }} --release --features tests-all + - run: cargo test --target ${{ matrix.target }} --release --all-features clippy: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b2fff2..adf6e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Renamed `Sieve` to `SmallPrimesSieve`. ([#64]) + + ### Added + - Parallel prime finding methods and a "multicore" feature ([#60]) +- Generalized sieving: `SieveFactory` trait, `SieveIterator`, and the convenience functions `sieve_and_find()` and `par_sieve_and_find()`. ([#64]) + [#60]: https://github.com/entropyxyz/crypto-primes/pull/60 +[#64]: https://github.com/entropyxyz/crypto-primes/pull/64 + ## [0.6.0-pre.2] - 2024-10-18 diff --git a/benches/bench.rs b/benches/bench.rs index c99c971..acbde65 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -3,7 +3,7 @@ use core::num::NonZero; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use crypto_bigint::{nlimbs, BoxedUint, Integer, Odd, RandomBits, Uint, U1024, U128, U256}; use rand_chacha::ChaCha8Rng; -use rand_core::{CryptoRngCore, OsRng, RngCore, SeedableRng}; +use rand_core::{CryptoRngCore, OsRng, SeedableRng}; #[cfg(feature = "tests-gmp")] use rug::{integer::Order, Integer as GmpInteger}; @@ -11,10 +11,14 @@ use rug::{integer::Order, Integer as GmpInteger}; #[cfg(feature = "tests-openssl")] use openssl::bn::BigNum; +#[cfg(feature = "multicore")] +use rand_core::RngCore; + use crypto_primes::{ generate_prime_with_rng, generate_safe_prime_with_rng, hazmat::{ - lucas_test, random_odd_integer, AStarBase, BruteForceBase, LucasCheck, MillerRabin, SelfridgeBase, Sieve, + lucas_test, random_odd_integer, AStarBase, BruteForceBase, LucasCheck, MillerRabin, SelfridgeBase, + SmallPrimesSieve, }, is_prime_with_rng, is_safe_prime_with_rng, }; @@ -25,6 +29,7 @@ fn make_rng() -> ChaCha8Rng { ChaCha8Rng::from_seed(*b"01234567890123456789012345678901") } +#[cfg(feature = "multicore")] fn make_random_rng() -> ChaCha8Rng { let mut seed = ::Seed::default(); OsRng.fill_bytes(&mut seed); @@ -35,9 +40,9 @@ fn random_odd_uint(rng: &mut impl CryptoRngCore, bit_le random_odd_integer::(rng, NonZero::new(bit_length).unwrap()) } -fn make_sieve(rng: &mut impl CryptoRngCore) -> Sieve> { +fn make_sieve(rng: &mut impl CryptoRngCore) -> SmallPrimesSieve> { let start = random_odd_uint::>(rng, Uint::::BITS); - Sieve::new(start.get(), NonZero::new(Uint::::BITS).unwrap(), false) + SmallPrimesSieve::new(start.get(), NonZero::new(Uint::::BITS).unwrap(), false) } fn make_presieved_num(rng: &mut impl CryptoRngCore) -> Odd> { @@ -55,7 +60,7 @@ fn bench_sieve(c: &mut Criterion) { group.bench_function("(U128) creation", |b| { b.iter_batched( || random_odd_uint::(&mut OsRng, 128), - |start| Sieve::new(start.get(), NonZero::new(128).unwrap(), false), + |start| SmallPrimesSieve::new(start.get(), NonZero::new(128).unwrap(), false), BatchSize::SmallInput, ) }); @@ -76,7 +81,7 @@ fn bench_sieve(c: &mut Criterion) { group.bench_function("(U1024) creation", |b| { b.iter_batched( || random_odd_uint::(&mut OsRng, 1024), - |start| Sieve::new(start.get(), NonZero::new(1024).unwrap(), false), + |start| SmallPrimesSieve::new(start.get(), NonZero::new(1024).unwrap(), false), BatchSize::SmallInput, ) }); @@ -445,7 +450,7 @@ fn bench_glass_pumpkin(c: &mut Criterion) { fn prime_like_gp(bit_length: u32, rng: &mut impl CryptoRngCore) -> BoxedUint { loop { let start = random_odd_integer::(rng, NonZero::new(bit_length).unwrap()).get(); - let sieve = Sieve::new(start, NonZero::new(bit_length).unwrap(), false); + let sieve = SmallPrimesSieve::new(start, NonZero::new(bit_length).unwrap(), false); for num in sieve { let odd_num = Odd::new(num.clone()).unwrap(); @@ -469,7 +474,7 @@ fn bench_glass_pumpkin(c: &mut Criterion) { fn safe_prime_like_gp(bit_length: u32, rng: &mut impl CryptoRngCore) -> BoxedUint { loop { let start = random_odd_integer::(rng, NonZero::new(bit_length).unwrap()).get(); - let sieve = Sieve::new(start, NonZero::new(bit_length).unwrap(), true); + let sieve = SmallPrimesSieve::new(start, NonZero::new(bit_length).unwrap(), true); for num in sieve { let odd_num = Odd::new(num.clone()).unwrap(); diff --git a/src/generic.rs b/src/generic.rs new file mode 100644 index 0000000..d155845 --- /dev/null +++ b/src/generic.rs @@ -0,0 +1,160 @@ +use rand_core::CryptoRngCore; + +#[cfg(feature = "multicore")] +use rayon::iter::{ParallelBridge, ParallelIterator}; + +use crate::SieveFactory; + +/// Sieves through the results of `sieve_factory` and returns the first item for which `predicate` is `true`. +/// +/// If `sieve_factory` signals that no more results can be created, returns `None`. +pub fn sieve_and_find( + rng: &mut R, + sieve_factory: S, + predicate: impl Fn(&mut R, &S::Item) -> bool, +) -> Option +where + S: SieveFactory, + R: CryptoRngCore, +{ + // We could use `SieveIterator` here, but it requires cloning the `rng`. + // Unlike the parallel version, it is avoidable here. + + let mut sieve_factory = sieve_factory; + let mut sieve = sieve_factory.make_sieve(rng, None)?; + + loop { + if let Some(value) = sieve.find(|num| predicate(rng, num)) { + return Some(value); + } + if let Some(new_sieve) = sieve_factory.make_sieve(rng, Some(&sieve)) { + sieve = new_sieve; + } else { + return None; + } + } +} + +/// Sieves through the results of `sieve_factory` using a thread pool with `threadcount` threads, +/// and returns the first item for which `predicate` is `true`. +/// +/// If `sieve_factory` signals that no more results can be created, returns `None`. +#[cfg(feature = "multicore")] +pub fn par_sieve_and_find(rng: &mut R, sieve_factory: S, predicate: F, threadcount: usize) -> Option +where + R: CryptoRngCore + Clone + Send + Sync, + S: Send + Sync + SieveFactory, + S::Sieve: Send, + S::Item: Send, + F: Sync + Fn(&mut R, &S::Item) -> bool, +{ + let threadpool = rayon::ThreadPoolBuilder::new() + .num_threads(threadcount) + .build() + .expect("If the platform can spawn threads, then this call will work."); + + let mut iter_rng = rng.clone(); + let iter = SieveIterator::new(&mut iter_rng, sieve_factory)?; + + threadpool.install(|| { + iter.par_bridge().find_any(|c| { + let mut rng = rng.clone(); + predicate(&mut rng, c) + }) + }) +} + +/// A structure that chains the creation of sieves, returning the results from one until it is exhausted, +/// and then creating a new one. +#[derive(Debug)] +pub struct SieveIterator<'a, R: CryptoRngCore, S: SieveFactory> { + sieve_factory: S, + sieve: S::Sieve, + rng: &'a mut R, +} + +impl<'a, R: CryptoRngCore, S: SieveFactory> SieveIterator<'a, R, S> { + /// Creates a new chained iterator producing results from sieves returned from `sieve_factory`. + pub fn new(rng: &'a mut R, sieve_factory: S) -> Option { + let mut sieve_factory = sieve_factory; + let sieve = sieve_factory.make_sieve(rng, None)?; + Some(Self { + sieve_factory, + rng, + sieve, + }) + } +} + +impl Iterator for SieveIterator<'_, R, S> { + type Item = S::Item; + + fn next(&mut self) -> Option { + loop { + if let Some(result) = self.sieve.next() { + return Some(result); + } + + self.sieve = self.sieve_factory.make_sieve(self.rng, Some(&self.sieve))?; + } + } +} + +#[cfg(test)] +mod tests { + use rand_core::{CryptoRngCore, OsRng}; + + use super::sieve_and_find; + use crate::SieveFactory; + + #[cfg(feature = "multicore")] + use super::par_sieve_and_find; + + #[test] + fn test_exhaustable_sieve_factory() { + // Test the logic handling the case of the sieve factory not being able to produce new sieves. + struct TestSieveFactory { + count: usize, + } + + impl SieveFactory for TestSieveFactory { + type Item = usize; + type Sieve = core::ops::Range; + + fn make_sieve( + &mut self, + _rng: &mut impl CryptoRngCore, + previous_sieve: Option<&Self::Sieve>, + ) -> Option { + self.count += 1; + if previous_sieve.is_none() { + Some(self.count * 10..(self.count * 10 + 2)) + } else { + None + } + } + } + + let factory = TestSieveFactory { count: 0 }; + let result = sieve_and_find(&mut OsRng, factory, |_rng, num| *num == 11); + assert!(result.is_some()); + + #[cfg(feature = "multicore")] + { + let factory = TestSieveFactory { count: 0 }; + let result = par_sieve_and_find(&mut OsRng, factory, |_rng, num| *num == 11, 1); + assert!(result.is_some()); + } + + let factory = TestSieveFactory { count: 0 }; + let result = sieve_and_find(&mut OsRng, factory, |_rng, num| *num == 20); + assert!(result.is_none()); + + #[cfg(feature = "multicore")] + { + let factory = TestSieveFactory { count: 0 }; + let result = par_sieve_and_find(&mut OsRng, factory, |_rng, num| *num == 20, 1); + assert!(result.is_none()); + } + } +} diff --git a/src/hazmat.rs b/src/hazmat.rs index 456b0d1..b668788 100644 --- a/src/hazmat.rs +++ b/src/hazmat.rs @@ -14,7 +14,7 @@ mod sieve; pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase}; pub use miller_rabin::MillerRabin; -pub use sieve::{random_odd_integer, Sieve}; +pub use sieve::{random_odd_integer, SmallPrimesSieve, SmallPrimesSieveFactory}; /// Possible results of various primality tests. #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 42ca5e8..8e5ae55 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -147,7 +147,7 @@ mod tests { use num_prime::nt_funcs::is_prime64; use super::MillerRabin; - use crate::hazmat::{primes, pseudoprimes, random_odd_integer, Sieve}; + use crate::hazmat::{primes, pseudoprimes, random_odd_integer, SmallPrimesSieve}; #[test] fn miller_rabin_derived_traits() { @@ -197,7 +197,7 @@ mod tests { fn trivial() { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); let start = random_odd_integer::(&mut rng, NonZero::new(1024).unwrap()); - for num in Sieve::new(start.get(), NonZero::new(1024).unwrap(), false).take(10) { + for num in SmallPrimesSieve::new(start.get(), NonZero::new(1024).unwrap(), false).take(10) { let mr = MillerRabin::new(Odd::new(num).unwrap()); // Trivial tests, must always be true. diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index e3eb253..bceb0c9 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -2,12 +2,14 @@ //! before proceeding with slower tests. use alloc::{vec, vec::Vec}; -use core::num::NonZeroU32; +use core::marker::PhantomData; +use core::num::{NonZero, NonZeroU32}; use crypto_bigint::{Integer, Odd, RandomBits}; use rand_core::CryptoRngCore; use crate::hazmat::precomputed::{SmallPrime, LAST_SMALL_PRIME, RECIPROCALS, SMALL_PRIMES}; +use crate::traits::SieveFactory; /// Returns a random odd integer with given bit length /// (that is, with both `0` and `bit_length-1` bits set). @@ -39,7 +41,7 @@ const INCR_LIMIT: Residue = Residue::MAX - LAST_SMALL_PRIME as Residue + 1; /// An iterator returning numbers with up to and including given bit length, /// starting from a given number, that are not multiples of the first 2048 small primes. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Sieve { +pub struct SmallPrimesSieve { // Instead of dividing a big integer by small primes every time (which is slow), // we keep a "base" and a small increment separately, // so that we can only calculate the residues of the increment. @@ -54,7 +56,7 @@ pub struct Sieve { last_round: bool, } -impl Sieve { +impl SmallPrimesSieve { /// Creates a new sieve, iterating from `start` and until the last number with `max_bit_length` /// bits, producing numbers that are not non-trivial multiples of a list of small primes in the /// range `[2, start)` (`safe_primes = false`) or `[2, start/2)` (`safe_primes = true`). @@ -236,7 +238,7 @@ impl Sieve { } } -impl Iterator for Sieve { +impl Iterator for SmallPrimesSieve { type Item = T; fn next(&mut self) -> Option { @@ -244,6 +246,60 @@ impl Iterator for Sieve { } } +/// A sieve returning numbers that are not multiples of a set of small factors. +#[derive(Debug, Clone, Copy)] +pub struct SmallPrimesSieveFactory { + max_bit_length: NonZeroU32, + safe_primes: bool, + phantom: PhantomData, +} + +impl SmallPrimesSieveFactory { + fn new_impl(max_bit_length: u32, safe_primes: bool) -> Self { + if !safe_primes && max_bit_length < 2 { + panic!("`bit_length` must be 2 or greater."); + } + if safe_primes && max_bit_length < 3 { + panic!("`bit_length` must be 3 or greater."); + } + let max_bit_length = NonZero::new(max_bit_length).expect("`bit_length` should be non-zero"); + Self { + max_bit_length, + safe_primes, + phantom: PhantomData, + } + } + + /// Creates a factory that produces sieves returning numbers of `max_bit_length` bits (with the top bit set) + /// that are not divisible by a number of small factors. + pub fn new(max_bit_length: u32) -> Self { + Self::new_impl(max_bit_length, false) + } + + /// Creates a factory that produces sieves returning numbers `n` of `max_bit_length` bits (with the top bit set) + /// such that neither `n` nor `(n - 1) / 2` are divisible by a number of small factors. + pub fn new_safe_primes(max_bit_length: u32) -> Self { + Self::new_impl(max_bit_length, true) + } +} + +impl SieveFactory for SmallPrimesSieveFactory { + type Item = T; + type Sieve = SmallPrimesSieve; + fn make_sieve( + &mut self, + rng: &mut impl CryptoRngCore, + _previous_sieve: Option<&Self::Sieve>, + ) -> Option { + let start = random_odd_integer::(rng, self.max_bit_length); + Some(SmallPrimesSieve::new( + start.get(), + self.max_bit_length, + self.safe_primes, + )) + } +} + #[cfg(test)] mod tests { @@ -256,7 +312,7 @@ mod tests { use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, SeedableRng}; - use super::{random_odd_integer, Sieve}; + use super::{random_odd_integer, SmallPrimesSieve, SmallPrimesSieveFactory}; use crate::hazmat::precomputed::SMALL_PRIMES; #[test] @@ -265,7 +321,7 @@ mod tests { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); let start = random_odd_integer::(&mut rng, NonZero::new(32).unwrap()).get(); - for num in Sieve::new(start, NonZero::new(32).unwrap(), false).take(100) { + for num in SmallPrimesSieve::new(start, NonZero::new(32).unwrap(), false).take(100) { let num_u64 = u64::from(num); assert!(num_u64.leading_zeros() == 32); @@ -282,7 +338,7 @@ mod tests { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); let start = random_odd_integer::(&mut rng, NonZero::new(32).unwrap()).get(); - for num in Sieve::new(start, NonZero::new(32).unwrap(), false).take(100) { + for num in SmallPrimesSieve::new(start, NonZero::new(32).unwrap(), false).take(100) { // For 32-bit targets #[allow(clippy::useless_conversion)] let num_u64: u64 = num.as_words()[0].into(); @@ -296,7 +352,8 @@ mod tests { } fn check_sieve(start: u32, bit_length: u32, safe_prime: bool, reference: &[u32]) { - let test = Sieve::new(U64::from(start), NonZero::new(bit_length).unwrap(), safe_prime).collect::>(); + let test = + SmallPrimesSieve::new(U64::from(start), NonZero::new(bit_length).unwrap(), safe_prime).collect::>(); assert_eq!(test.len(), reference.len()); for (x, y) in test.iter().zip(reference.iter()) { assert_eq!(x, &U64::from(*y)); @@ -351,7 +408,7 @@ mod tests { #[test] #[should_panic(expected = "The requested bit length (65) is larger than the precision of `start`")] fn sieve_too_many_bits() { - let _sieve = Sieve::new(U64::ONE, NonZero::new(65).unwrap(), false); + let _sieve = SmallPrimesSieve::new(U64::ONE, NonZero::new(65).unwrap(), false); } #[test] @@ -370,23 +427,35 @@ mod tests { #[test] fn sieve_derived_traits() { - let s = Sieve::new(U64::ONE, NonZero::new(10).unwrap(), false); + let s = SmallPrimesSieve::new(U64::ONE, NonZero::new(10).unwrap(), false); // Debug - assert!(format!("{s:?}").starts_with("Sieve")); + assert!(format!("{s:?}").starts_with("SmallPrimesSieve")); // Clone assert_eq!(s.clone(), s); // PartialEq - let s2 = Sieve::new(U64::ONE, NonZero::new(10).unwrap(), false); + let s2 = SmallPrimesSieve::new(U64::ONE, NonZero::new(10).unwrap(), false); assert_eq!(s, s2); - let s3 = Sieve::new(U64::ONE, NonZero::new(12).unwrap(), false); + let s3 = SmallPrimesSieve::new(U64::ONE, NonZero::new(12).unwrap(), false); assert_ne!(s, s3); } #[test] fn sieve_with_max_start() { let start = U64::MAX; - let mut sieve = Sieve::new(start, NonZero::new(U64::BITS).unwrap(), false); + let mut sieve = SmallPrimesSieve::new(start, NonZero::new(U64::BITS).unwrap(), false); assert!(sieve.next().is_none()); } + + #[test] + #[should_panic(expected = "`bit_length` must be 2 or greater")] + fn too_few_bits_regular_primes() { + let _fac = SmallPrimesSieveFactory::::new(1); + } + + #[test] + #[should_panic(expected = "`bit_length` must be 3 or greater")] + fn too_few_bits_safe_primes() { + let _fac = SmallPrimesSieveFactory::::new_safe_primes(2); + } } diff --git a/src/lib.rs b/src/lib.rs index 3d811fa..19ff3ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,12 +16,14 @@ extern crate alloc; +mod generic; pub mod hazmat; mod presets; mod traits; +pub use generic::{sieve_and_find, SieveIterator}; pub use presets::{generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, is_safe_prime_with_rng}; -pub use traits::RandomPrimeWithRng; +pub use traits::{RandomPrimeWithRng, SieveFactory}; #[cfg(feature = "default-rng")] pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime}; @@ -29,3 +31,6 @@ pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime}; pub use presets::{par_generate_prime, par_generate_safe_prime}; #[cfg(feature = "multicore")] pub use presets::{par_generate_prime_with_rng, par_generate_safe_prime_with_rng}; + +#[cfg(feature = "multicore")] +pub use generic::par_sieve_and_find; diff --git a/src/presets.rs b/src/presets.rs index 5c4044b..0fe44c1 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -1,15 +1,16 @@ -use core::num::NonZero; - use crypto_bigint::{Integer, Limb, Odd, RandomBits, RandomMod}; use rand_core::CryptoRngCore; #[cfg(feature = "default-rng")] use rand_core::OsRng; -#[cfg(feature = "multicore")] -use rayon::iter::{ParallelBridge, ParallelIterator}; +use crate::{ + generic::sieve_and_find, + hazmat::{lucas_test, AStarBase, LucasCheck, MillerRabin, Primality, SmallPrimesSieveFactory}, +}; -use crate::hazmat::{lucas_test, random_odd_integer, AStarBase, LucasCheck, MillerRabin, Primality, Sieve}; +#[cfg(feature = "multicore")] +use crate::generic::par_sieve_and_find; /// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG. /// @@ -83,18 +84,8 @@ pub fn generate_prime_with_rng( rng: &mut impl CryptoRngCore, bit_length: u32, ) -> T { - if bit_length < 2 { - panic!("`bit_length` must be 2 or greater."); - } - let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero"); - // Empirically, this loop is traversed 1 time. - loop { - let start = random_odd_integer::(rng, bit_length); - let mut sieve = Sieve::new(start.get(), bit_length, false); - if let Some(prime) = sieve.find(|num| is_prime_with_rng(rng, num)) { - return prime; - } - } + sieve_and_find(rng, SmallPrimesSieveFactory::new(bit_length), is_prime_with_rng) + .expect("will produce a result eventually") } /// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime) @@ -107,19 +98,12 @@ pub fn generate_safe_prime_with_rng( rng: &mut impl CryptoRngCore, bit_length: u32, ) -> T { - if bit_length < 3 { - panic!("`bit_length` must be 3 or greater."); - } - let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero"); - loop { - let start = random_odd_integer::(rng, bit_length); - let sieve = Sieve::new(start.get(), bit_length, true); - for num in sieve { - if is_safe_prime_with_rng(rng, &num) { - return num; - } - } - } + sieve_and_find( + rng, + SmallPrimesSieveFactory::new_safe_primes(bit_length), + is_safe_prime_with_rng, + ) + .expect("will produce a result eventually") } /// Returns a random prime of size `bit_length` using the provided RNG. @@ -138,31 +122,13 @@ pub fn par_generate_prime_with_rng( where T: Integer + RandomBits + RandomMod, { - if bit_length < 2 { - panic!("`bit_length` must be 2 or greater."); - } - let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero"); - - let threadpool = rayon::ThreadPoolBuilder::new() - .num_threads(threadcount) - .build() - .expect("If the platform can spawn threads, then this call will work."); - let start = random_odd_integer::(rng, bit_length).get(); - let sieve = Sieve::new(start, bit_length, false); - - let prime = threadpool.install(|| { - sieve.par_bridge().find_any(|c| { - let mut rng = rng.clone(); - is_prime_with_rng(&mut rng, c) - }) - }); - match prime { - Some(prime) => prime, - None => { - drop(threadpool); - par_generate_prime_with_rng(rng, bit_length.get(), threadcount) - } - } + par_sieve_and_find( + rng, + SmallPrimesSieveFactory::new(bit_length), + is_prime_with_rng, + threadcount, + ) + .expect("will produce a result eventually") } /// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime) @@ -183,31 +149,13 @@ pub fn par_generate_safe_prime_with_rng( where T: Integer + RandomBits + RandomMod, { - if bit_length < 2 { - panic!("`bit_length` must be 2 or greater."); - } - let bit_length = NonZero::new(bit_length).expect("`bit_length` should be non-zero"); - - let threadpool = rayon::ThreadPoolBuilder::new() - .num_threads(threadcount) - .build() - .expect("If the platform can spawn threads, then this call will work."); - let start = random_odd_integer::(rng, bit_length).get(); - let sieve = Sieve::new(start, bit_length, true); - - let prime = threadpool.install(|| { - sieve.par_bridge().find_any(|c| { - let mut rng = rng.clone(); - is_safe_prime_with_rng(&mut rng, c) - }) - }); - match prime { - Some(prime) => prime, - None => { - drop(threadpool); - par_generate_safe_prime_with_rng(rng, bit_length.get(), threadcount) - } - } + par_sieve_and_find( + rng, + SmallPrimesSieveFactory::new_safe_primes(bit_length), + is_safe_prime_with_rng, + threadcount, + ) + .expect("will produce a result eventually") } /// Probabilistically checks if the given number is prime using the provided RNG. @@ -436,18 +384,6 @@ mod tests { assert!(!is_safe_prime(&U64::from(17881u32 * 17891u32))); } - #[test] - #[should_panic(expected = "`bit_length` must be 2 or greater")] - fn generate_prime_too_few_bits() { - let _p: U64 = generate_prime_with_rng(&mut OsRng, 1); - } - - #[test] - #[should_panic(expected = "`bit_length` must be 3 or greater")] - fn generate_safe_prime_too_few_bits() { - let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, 2); - } - #[test] #[should_panic(expected = "try_random_bits() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }")] fn generate_prime_too_many_bits() { @@ -489,14 +425,13 @@ mod tests { #[cfg(all(test, feature = "multicore"))] mod multicore_tests { - use super::{is_prime, par_generate_prime_with_rng}; + use super::{is_prime, par_generate_prime, par_generate_safe_prime}; use crypto_bigint::{nlimbs, BoxedUint, U128}; - use super::*; #[test] fn parallel_prime_generation() { for bit_length in (28..=128).step_by(10) { - let p: U128 = par_generate_prime_with_rng(&mut OsRng, bit_length, 4); + let p: U128 = par_generate_prime(bit_length, 4); assert!(p.bits_vartime() == bit_length); assert!(is_prime(&p)); } @@ -505,7 +440,7 @@ mod multicore_tests { #[test] fn parallel_prime_generation_boxed() { for bit_length in (28..=128).step_by(10) { - let p: BoxedUint = par_generate_prime_with_rng(&mut OsRng, bit_length, 2); + let p: BoxedUint = par_generate_prime(bit_length, 2); assert!(p.bits_vartime() == bit_length); assert!(p.to_words().len() == nlimbs!(bit_length)); assert!(is_prime(&p)); @@ -515,7 +450,7 @@ mod multicore_tests { #[test] fn parallel_safe_prime_generation() { for bit_length in (28..=128).step_by(10) { - let p: U128 = par_generate_safe_prime_with_rng(&mut OsRng, bit_length, 8); + let p: U128 = par_generate_safe_prime(bit_length, 8); assert!(p.bits_vartime() == bit_length); assert!(is_prime(&p)); } @@ -524,7 +459,7 @@ mod multicore_tests { #[test] fn parallel_safe_prime_generation_boxed() { for bit_length in (28..=128).step_by(10) { - let p: BoxedUint = par_generate_safe_prime_with_rng(&mut OsRng, bit_length, 4); + let p: BoxedUint = par_generate_safe_prime(bit_length, 4); assert!(p.bits_vartime() == bit_length); assert!(p.to_words().len() == nlimbs!(bit_length)); assert!(is_prime(&p)); diff --git a/src/traits.rs b/src/traits.rs index 104c4c2..53ef7fd 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,6 +3,21 @@ use rand_core::CryptoRngCore; use crate::{generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, is_safe_prime_with_rng}; +/// A type producing sieves for random prime generation. +pub trait SieveFactory { + /// The type of items returning by the sieves. + type Item; + + /// The resulting sieve. + type Sieve: Iterator; + + /// Makes a sieve given an RNG and the previous exhausted sieve (if any). + /// + /// Returning `None` signals that the prime generation should stop. + fn make_sieve(&mut self, rng: &mut impl CryptoRngCore, previous_sieve: Option<&Self::Sieve>) + -> Option; +} + /// Provides a generic way to access methods for random prime number generation /// and primality checking, wrapping the standalone functions ([`is_prime_with_rng`] etc). pub trait RandomPrimeWithRng {