Skip to content

Commit

Permalink
Generalize sieving
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Dec 9, 2024
1 parent d2c4a39 commit 0267b9d
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 126 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/crypto-primes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 13 additions & 8 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ 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};

#[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,
};
Expand All @@ -25,6 +29,7 @@ fn make_rng() -> ChaCha8Rng {
ChaCha8Rng::from_seed(*b"01234567890123456789012345678901")
}

#[cfg(feature = "multicore")]
fn make_random_rng() -> ChaCha8Rng {
let mut seed = <ChaCha8Rng as SeedableRng>::Seed::default();
OsRng.fill_bytes(&mut seed);
Expand All @@ -35,9 +40,9 @@ fn random_odd_uint<T: RandomBits + Integer>(rng: &mut impl CryptoRngCore, bit_le
random_odd_integer::<T>(rng, NonZero::new(bit_length).unwrap())
}

fn make_sieve<const L: usize>(rng: &mut impl CryptoRngCore) -> Sieve<Uint<L>> {
fn make_sieve<const L: usize>(rng: &mut impl CryptoRngCore) -> SmallPrimesSieve<Uint<L>> {
let start = random_odd_uint::<Uint<L>>(rng, Uint::<L>::BITS);
Sieve::new(start.get(), NonZero::new(Uint::<L>::BITS).unwrap(), false)
SmallPrimesSieve::new(start.get(), NonZero::new(Uint::<L>::BITS).unwrap(), false)
}

fn make_presieved_num<const L: usize>(rng: &mut impl CryptoRngCore) -> Odd<Uint<L>> {
Expand All @@ -55,7 +60,7 @@ fn bench_sieve(c: &mut Criterion) {
group.bench_function("(U128) creation", |b| {
b.iter_batched(
|| random_odd_uint::<U128>(&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,
)
});
Expand All @@ -76,7 +81,7 @@ fn bench_sieve(c: &mut Criterion) {
group.bench_function("(U1024) creation", |b| {
b.iter_batched(
|| random_odd_uint::<U1024>(&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,
)
});
Expand Down Expand Up @@ -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::<BoxedUint>(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();

Expand All @@ -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::<BoxedUint>(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();

Expand Down
160 changes: 160 additions & 0 deletions src/generic.rs
Original file line number Diff line number Diff line change
@@ -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<R, S>(
rng: &mut R,
sieve_factory: S,
predicate: impl Fn(&mut R, &S::Item) -> bool,
) -> Option<S::Item>
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<R, S, F>(rng: &mut R, sieve_factory: S, predicate: F, threadcount: usize) -> Option<S::Item>
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<Self> {
let mut sieve_factory = sieve_factory;
let sieve = sieve_factory.make_sieve(rng, None)?;
Some(Self {
sieve_factory,
rng,
sieve,
})
}
}

impl<R: CryptoRngCore, S: SieveFactory> Iterator for SieveIterator<'_, R, S> {
type Item = S::Item;

fn next(&mut self) -> Option<Self::Item> {
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<usize>;

fn make_sieve(
&mut self,
_rng: &mut impl CryptoRngCore,
previous_sieve: Option<&Self::Sieve>,
) -> Option<Self::Sieve> {
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());
}
}
}
2 changes: 1 addition & 1 deletion src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
4 changes: 2 additions & 2 deletions src/hazmat/miller_rabin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -197,7 +197,7 @@ mod tests {
fn trivial() {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let start = random_odd_integer::<U1024>(&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.
Expand Down
Loading

0 comments on commit 0267b9d

Please sign in to comment.