Skip to content

Commit 0f9dd03

Browse files
committed
Propagate random bits generation errors through the hazmat level
1 parent c10e4e9 commit 0f9dd03

File tree

6 files changed

+125
-49
lines changed

6 files changed

+125
-49
lines changed

src/error.rs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use core::fmt;
2+
3+
/// Errors returned by the crate's API.
4+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5+
pub enum Error {
6+
/// The requested bit length of the candidate is larger than the maximum size of the target integer type.
7+
BitLengthTooLarge {
8+
/// The requested bit length.
9+
bit_length: u32,
10+
/// The maximum size of the integer type.
11+
bits_precision: u32,
12+
},
13+
}
14+
15+
impl fmt::Display for Error {
16+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
17+
match self {
18+
Error::BitLengthTooLarge {
19+
bit_length,
20+
bits_precision,
21+
} => write!(
22+
f,
23+
concat![
24+
"The requested bit length of the candidate ({}) ",
25+
"is larger than the maximum size of the target integer type ({})."
26+
],
27+
bit_length, bits_precision
28+
),
29+
}
30+
}
31+
}

src/generic.rs

+15-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use rand_core::CryptoRng;
22

3-
use crate::hazmat::SieveFactory;
3+
use crate::{hazmat::SieveFactory, Error};
44

55
/// Sieves through the results of `sieve_factory` and returns the first item for which `predicate` is `true`.
66
///
@@ -9,7 +9,7 @@ pub fn sieve_and_find<R, S>(
99
rng: &mut R,
1010
sieve_factory: S,
1111
predicate: impl Fn(&mut R, &S::Item) -> bool,
12-
) -> Option<S::Item>
12+
) -> Result<Option<S::Item>, Error>
1313
where
1414
S: SieveFactory,
1515
R: CryptoRng + ?Sized,
@@ -18,16 +18,19 @@ where
1818
// Unlike the parallel version, it is avoidable here.
1919

2020
let mut sieve_factory = sieve_factory;
21-
let mut sieve = sieve_factory.make_sieve(rng, None)?;
21+
let mut sieve = match sieve_factory.make_sieve(rng, None)? {
22+
Some(sieve) => sieve,
23+
None => return Ok(None),
24+
};
2225

2326
loop {
2427
if let Some(value) = sieve.find(|num| predicate(rng, num)) {
25-
return Some(value);
28+
return Ok(Some(value));
2629
}
27-
if let Some(new_sieve) = sieve_factory.make_sieve(rng, Some(&sieve)) {
30+
if let Some(new_sieve) = sieve_factory.make_sieve(rng, Some(&sieve))? {
2831
sieve = new_sieve;
2932
} else {
30-
return None;
33+
return Ok(None);
3134
}
3235
}
3336
}
@@ -37,7 +40,7 @@ mod tests {
3740
use rand_core::{CryptoRng, OsRng, TryRngCore};
3841

3942
use super::sieve_and_find;
40-
use crate::hazmat::SieveFactory;
43+
use crate::{hazmat::SieveFactory, Error};
4144

4245
#[test]
4346
fn test_exhaustable_sieve_factory() {
@@ -54,22 +57,22 @@ mod tests {
5457
&mut self,
5558
_rng: &mut R,
5659
previous_sieve: Option<&Self::Sieve>,
57-
) -> Option<Self::Sieve> {
60+
) -> Result<Option<Self::Sieve>, Error> {
5861
self.count += 1;
5962
if previous_sieve.is_none() {
60-
Some(self.count * 10..(self.count * 10 + 2))
63+
Ok(Some(self.count * 10..(self.count * 10 + 2)))
6164
} else {
62-
None
65+
Ok(None)
6366
}
6467
}
6568
}
6669

6770
let factory = TestSieveFactory { count: 0 };
6871
let result = sieve_and_find(&mut OsRng.unwrap_mut(), factory, |_rng, num| *num == 11);
69-
assert!(result.is_some());
72+
assert!(result.unwrap().is_some());
7073

7174
let factory = TestSieveFactory { count: 0 };
7275
let result = sieve_and_find(&mut OsRng.unwrap_mut(), factory, |_rng, num| *num == 20);
73-
assert!(result.is_none());
76+
assert!(result.unwrap().is_none());
7477
}
7578
}

src/hazmat/sieve.rs

+43-25
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use core::marker::PhantomData;
66
use core::num::{NonZero, NonZeroU32};
77

88
use crypto_bigint::{Integer, Odd, RandomBits, RandomBitsError};
9-
use rand_core::{CryptoRng, TryCryptoRng};
9+
use rand_core::CryptoRng;
1010

11-
use crate::hazmat::precomputed::{SmallPrime, LAST_SMALL_PRIME, RECIPROCALS, SMALL_PRIMES};
12-
use crate::presets::Flavor;
11+
use super::precomputed::{SmallPrime, LAST_SMALL_PRIME, RECIPROCALS, SMALL_PRIMES};
12+
use crate::{error::Error, presets::Flavor};
1313

1414
/// Decide how prime candidates are manipulated by setting certain bits before primality testing,
1515
/// influencing the range of the prime.
@@ -33,18 +33,26 @@ pub enum SetBits {
3333
///
3434
/// Returns an error variant if `bit_length` is greater than the maximum allowed for `T`
3535
/// (applies to fixed-length types).
36-
pub fn random_odd_integer<T, R>(
37-
rng: &mut R,
38-
bit_length: NonZeroU32,
39-
set_bits: SetBits,
40-
) -> Result<Odd<T>, RandomBitsError<R::Error>>
36+
pub fn random_odd_integer<T, R>(rng: &mut R, bit_length: NonZeroU32, set_bits: SetBits) -> Result<Odd<T>, Error>
4137
where
4238
T: Integer + RandomBits,
43-
R: TryCryptoRng + ?Sized,
39+
R: CryptoRng + ?Sized,
4440
{
4541
let bit_length = bit_length.get();
4642

47-
let mut random = T::try_random_bits(rng, bit_length)?;
43+
let mut random = T::try_random_bits(rng, bit_length).map_err(|err| match err {
44+
RandomBitsError::RandCore(_) => unreachable!("`rng` impls `CryptoRng` and therefore is infallible"),
45+
RandomBitsError::BitsPrecisionMismatch { .. } => {
46+
unreachable!("we are not requesting a specific `bits_precision`")
47+
}
48+
RandomBitsError::BitLengthTooLarge {
49+
bit_length,
50+
bits_precision,
51+
} => Error::BitLengthTooLarge {
52+
bit_length,
53+
bits_precision,
54+
},
55+
})?;
4856

4957
// Make it odd
5058
// `bit_length` is non-zero, so the 0-th bit exists.
@@ -302,11 +310,13 @@ pub trait SieveFactory {
302310
/// Makes a sieve given an RNG and the previous exhausted sieve (if any).
303311
///
304312
/// Returning `None` signals that the prime generation should stop.
305-
fn make_sieve<R: CryptoRng + ?Sized>(
313+
fn make_sieve<R>(
306314
&mut self,
307315
rng: &mut R,
308316
previous_sieve: Option<&Self::Sieve>,
309-
) -> Option<Self::Sieve>;
317+
) -> Result<Option<Self::Sieve>, Error>
318+
where
319+
R: CryptoRng + ?Sized;
310320
}
311321

312322
/// A sieve returning numbers that are not multiples of a set of small factors.
@@ -366,18 +376,20 @@ where
366376
{
367377
type Item = T;
368378
type Sieve = SmallPrimesSieve<T>;
369-
fn make_sieve<R: CryptoRng + ?Sized>(
379+
fn make_sieve<R>(
370380
&mut self,
371381
rng: &mut R,
372382
_previous_sieve: Option<&Self::Sieve>,
373-
) -> Option<Self::Sieve> {
374-
let start =
375-
random_odd_integer::<T, _>(rng, self.max_bit_length, self.set_bits).expect("random_odd_integer() failed");
376-
Some(SmallPrimesSieve::new(
383+
) -> Result<Option<Self::Sieve>, Error>
384+
where
385+
R: CryptoRng + ?Sized,
386+
{
387+
let start = random_odd_integer::<T, _>(rng, self.max_bit_length, self.set_bits)?;
388+
Ok(Some(SmallPrimesSieve::new(
377389
start.get(),
378390
self.max_bit_length,
379391
self.safe_primes,
380-
))
392+
)))
381393
}
382394
}
383395

@@ -391,7 +403,7 @@ mod tests {
391403
use crypto_bigint::U64;
392404
use num_prime::nt_funcs::factorize64;
393405
use rand_chacha::ChaCha8Rng;
394-
use rand_core::{OsRng, SeedableRng};
406+
use rand_core::{OsRng, SeedableRng, TryRngCore};
395407

396408
use super::{random_odd_integer, SetBits, SmallPrimesSieve, SmallPrimesSieveFactory};
397409
use crate::{hazmat::precomputed::SMALL_PRIMES, Flavor};
@@ -500,7 +512,7 @@ mod tests {
500512
#[test]
501513
fn random_below_max_length() {
502514
for _ in 0..10 {
503-
let r = random_odd_integer::<U64, _>(&mut OsRng, NonZero::new(50).unwrap(), SetBits::Msb)
515+
let r = random_odd_integer::<U64, _>(&mut OsRng.unwrap_mut(), NonZero::new(50).unwrap(), SetBits::Msb)
504516
.unwrap()
505517
.get();
506518
assert_eq!(r.bits(), 50);
@@ -509,7 +521,9 @@ mod tests {
509521

510522
#[test]
511523
fn random_odd_uint_too_many_bits() {
512-
assert!(random_odd_integer::<U64, _>(&mut OsRng, NonZero::new(65).unwrap(), SetBits::Msb).is_err());
524+
assert!(
525+
random_odd_integer::<U64, _>(&mut OsRng.unwrap_mut(), NonZero::new(65).unwrap(), SetBits::Msb).is_err()
526+
);
513527
}
514528

515529
#[test]
@@ -549,27 +563,31 @@ mod tests {
549563
#[test]
550564
fn set_bits() {
551565
for _ in 0..10 {
552-
let x = random_odd_integer::<U64, _>(&mut OsRng, NonZero::new(64).unwrap(), SetBits::Msb).unwrap();
566+
let x =
567+
random_odd_integer::<U64, _>(&mut OsRng.unwrap_mut(), NonZero::new(64).unwrap(), SetBits::Msb).unwrap();
553568
assert!(bool::from(x.bit(63)));
554569
}
555570

556571
for _ in 0..10 {
557-
let x = random_odd_integer::<U64, _>(&mut OsRng, NonZero::new(64).unwrap(), SetBits::TwoMsb).unwrap();
572+
let x = random_odd_integer::<U64, _>(&mut OsRng.unwrap_mut(), NonZero::new(64).unwrap(), SetBits::TwoMsb)
573+
.unwrap();
558574
assert!(bool::from(x.bit(63)));
559575
assert!(bool::from(x.bit(62)));
560576
}
561577

562578
// 1 in 2^30 chance of spurious failure... good enough?
563579
assert!((0..30)
564-
.map(|_| { random_odd_integer::<U64, _>(&mut OsRng, NonZero::new(64).unwrap(), SetBits::None).unwrap() })
580+
.map(|_| {
581+
random_odd_integer::<U64, _>(&mut OsRng.unwrap_mut(), NonZero::new(64).unwrap(), SetBits::None).unwrap()
582+
})
565583
.any(|x| !bool::from(x.bit(63))));
566584
}
567585

568586
#[test]
569587
fn set_two_msb_small_bit_length() {
570588
// Check that when technically there isn't a second most significant bit,
571589
// `random_odd_integer()` still returns a number.
572-
let x = random_odd_integer::<U64, _>(&mut OsRng, NonZero::new(1).unwrap(), SetBits::TwoMsb)
590+
let x = random_odd_integer::<U64, _>(&mut OsRng.unwrap_mut(), NonZero::new(1).unwrap(), SetBits::TwoMsb)
573591
.unwrap()
574592
.get();
575593
assert_eq!(x, U64::ONE);

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
extern crate alloc;
1818

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

2324
#[cfg(feature = "multicore")]
2425
pub mod multicore;
2526

27+
pub use error::Error;
2628
pub use generic::sieve_and_find;
2729
pub use presets::{fips_is_prime, is_prime, random_prime, Flavor};

src/multicore.rs

+25-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rand_core::CryptoRng;
55
use rayon::iter::{ParallelBridge, ParallelIterator};
66

77
use crate::{
8+
error::Error,
89
hazmat::{SetBits, SieveFactory, SmallPrimesSieveFactory},
910
presets::{is_prime, Flavor},
1011
};
@@ -13,7 +14,12 @@ use crate::{
1314
/// and returns the first item for which `predicate` is `true`.
1415
///
1516
/// If `sieve_factory` signals that no more results can be created, returns `None`.
16-
pub fn sieve_and_find<R, S, F>(rng: &mut R, sieve_factory: S, predicate: F, threadcount: usize) -> Option<S::Item>
17+
pub fn sieve_and_find<R, S, F>(
18+
rng: &mut R,
19+
sieve_factory: S,
20+
predicate: F,
21+
threadcount: usize,
22+
) -> Result<Option<S::Item>, Error>
1723
where
1824
R: CryptoRng + Clone + Send + Sync,
1925
S: Send + Sync + SieveFactory,
@@ -27,13 +33,16 @@ where
2733
.expect("If the platform can spawn threads, then this call will work.");
2834

2935
let mut iter_rng = rng.clone();
30-
let iter = SieveIterator::new(&mut iter_rng, sieve_factory)?;
36+
let iter = match SieveIterator::new(&mut iter_rng, sieve_factory)? {
37+
Some(iter) => iter,
38+
None => return Ok(None),
39+
};
3140

3241
threadpool.install(|| {
33-
iter.par_bridge().find_any(|c| {
42+
Ok(iter.par_bridge().find_any(|c| {
3443
let mut rng = rng.clone();
3544
predicate(&mut rng, c)
36-
})
45+
}))
3746
})
3847
}
3948

@@ -52,14 +61,17 @@ where
5261
S: SieveFactory,
5362
{
5463
/// Creates a new chained iterator producing results from sieves returned from `sieve_factory`.
55-
pub fn new(rng: &'a mut R, sieve_factory: S) -> Option<Self> {
64+
pub fn new(rng: &'a mut R, sieve_factory: S) -> Result<Option<Self>, Error> {
5665
let mut sieve_factory = sieve_factory;
57-
let sieve = sieve_factory.make_sieve(rng, None)?;
58-
Some(Self {
66+
let sieve = match sieve_factory.make_sieve(rng, None)? {
67+
Some(sieve) => sieve,
68+
None => return Ok(None),
69+
};
70+
Ok(Some(Self {
5971
sieve_factory,
6072
rng,
6173
sieve,
62-
})
74+
}))
6375
}
6476
}
6577

@@ -76,7 +88,10 @@ where
7688
return Some(result);
7789
}
7890

79-
self.sieve = self.sieve_factory.make_sieve(self.rng, Some(&self.sieve))?;
91+
self.sieve = self
92+
.sieve_factory
93+
.make_sieve(self.rng, Some(&self.sieve))
94+
.expect("the first attempt to make a sieve succeeded, so the next ones should too")?;
8095
}
8196
}
8297
}
@@ -99,6 +114,7 @@ where
99114
|_rng, candidate| is_prime(flavor, candidate),
100115
threadcount,
101116
)
117+
.unwrap_or_else(|err| panic!("Error generating random candidates: {}", err))
102118
.expect("will produce a result eventually")
103119
}
104120

0 commit comments

Comments
 (0)