Skip to content

Commit 4ac32fb

Browse files
committed
Redo sieving to dramatically increase safe prime finding speed
1 parent 0c66176 commit 4ac32fb

File tree

4 files changed

+123
-114
lines changed

4 files changed

+123
-114
lines changed

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_uint, sieve_once, Sieve};
17+
pub use sieve::{random_odd_uint, Sieve};
1818

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

src/hazmat/miller_rabin.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ pub struct MillerRabin<const L: usize> {
2929

3030
impl<const L: usize> MillerRabin<L> {
3131
/// Initializes a Miller-Rabin test for `candidate`.
32-
/// `candidate` must be odd.
32+
///
33+
/// Panics if `candidate` is even.
3334
pub fn new(candidate: &Uint<L>) -> Self {
34-
debug_assert!(bool::from(candidate.is_odd()));
35+
if candidate.is_even().into() {
36+
panic!("`candidate` must be odd.");
37+
}
38+
3539
let params = DynResidueParams::<L>::new(candidate);
3640
let one = DynResidue::<L>::one(params);
3741
let minus_one = -one;
@@ -78,11 +82,18 @@ impl<const L: usize> MillerRabin<L> {
7882
self.check(&Uint::<L>::from(2u32))
7983
}
8084

81-
/// Perform a Miller-Rabin check with a random base drawn using the provided RNG.
85+
/// Perform a Miller-Rabin check with a random base (in the range `[3, candidate-2]`)
86+
/// drawn using the provided RNG.
87+
///
88+
/// Note: panics if `candidate == 3` (so the range above is empty).
8289
pub fn test_random_base(&self, rng: &mut impl CryptoRngCore) -> Primality {
8390
// We sample a random base from the range `[3, candidate-2]`:
8491
// - we have a separate method for base 2;
8592
// - the test holds trivially for bases 1 or `candidate-1`.
93+
if self.candidate.bits() < 3 {
94+
panic!("No suitable random base possible when `candidate == 3`; use the base 2 test.")
95+
}
96+
8697
let range = self.candidate.wrapping_sub(&Uint::<L>::from(4u32));
8798
let range_nonzero = NonZero::new(range).unwrap();
8899
let random =

src/hazmat/sieve.rs

Lines changed: 76 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@
33
44
use alloc::{vec, vec::Vec};
55

6-
use crypto_bigint::{Integer, Limb, NonZero, Random, Uint, Zero};
6+
use crypto_bigint::{Random, Uint};
77
use rand_core::CryptoRngCore;
88

9-
use crate::hazmat::{
10-
precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES},
11-
Primality,
12-
};
9+
use crate::hazmat::precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES};
1310

1411
/// Returns a random odd integer with given bit length
1512
/// (that is, with both `0` and `bit_length-1` bits set).
@@ -42,6 +39,14 @@ pub fn random_odd_uint<const L: usize>(rng: &mut impl CryptoRngCore, bit_length:
4239
random
4340
}
4441

42+
// The type we use to calculate incremental residues.
43+
// Should be >= `SmallPrime` in size.
44+
type Residue = u32;
45+
46+
// The maximum increment that won't overflow the type we use to calculate residues of increments:
47+
// we need `(max_prime - 1) + max_incr <= Type::MAX`.
48+
const INCR_LIMIT: Residue = Residue::MAX - SMALL_PRIMES[SMALL_PRIMES.len() - 1] as Residue + 1;
49+
4550
/// An iterator returning numbers with up to and including given bit length,
4651
/// starting from a given number, that are not multiples of the first 2048 small primes.
4752
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -60,52 +65,49 @@ pub struct Sieve<const L: usize> {
6065
last_round: bool,
6166
}
6267

63-
// The type we use to calculate incremental residues.
64-
// Should be >= `SmallPrime` in size.
65-
type Residue = u32;
66-
67-
// The maximum increment that won't overflow the type we use to calculate residues of increments:
68-
// we need `(max_prime - 1) + max_incr <= Type::MAX`.
69-
const INCR_LIMIT: Residue = Residue::MAX - SMALL_PRIMES[SMALL_PRIMES.len() - 1] as Residue + 1;
70-
7168
impl<const L: usize> Sieve<L> {
7269
/// Creates a new sieve, iterating from `start` and
7370
/// until the last number with `max_bit_length` bits,
7471
/// producing numbers that are not non-trivial multiples
75-
/// of a list of small primes in the range `[2, start)`.
72+
/// of a list of small primes in the range `[2, start)` (`safe_primes = false`)
73+
/// or `[2, start/2)` (`safe_primes = true`).
74+
///
75+
/// Note that `start` is adjusted to `2`, or the next `1 mod 2` number (`safe_primes = false`);
76+
/// and `5`, or `3 mod 4` number (`safe_primes = true`).
7677
///
77-
/// Note that `start` is adjusted to `2`, or the next `1 mod 2` number (`safe_prime = false`);
78-
/// and `5`, or `3 mod 4` number (`safe_prime = true`).
78+
/// Panics if `max_bit_length` is greater than the size of the target `Uint`.
7979
///
80-
/// If `safe_primes` is `true`, it only produces the candidates
81-
/// that can possibly be safe primes (that is, 5, and those equal to 3 modulo 4).
80+
/// If `safe_primes` is `true`, both the returned `n` and `n/2` are sieved.
8281
pub fn new(start: &Uint<L>, max_bit_length: usize, safe_primes: bool) -> Self {
83-
let mut base = *start;
82+
if max_bit_length > Uint::<L>::BITS {
83+
panic!(
84+
"The requested bit length ({}) is larger than the chosen Uint size",
85+
max_bit_length
86+
);
87+
}
88+
89+
// If we are targeting safe primes, iterate over the corresponding
90+
// possible Germain primes (`n/2`), reducing the task to that with `safe_primes = false`.
91+
let (max_bit_length, base) = if safe_primes {
92+
(max_bit_length - 1, start >> 1)
93+
} else {
94+
(max_bit_length, *start)
95+
};
96+
97+
let mut base = base;
8498

8599
// This is easier than making all the methods generic enough to handle these corner cases.
86-
let produces_nothing = max_bit_length < base.bits()
87-
|| (!safe_primes && max_bit_length < 2)
88-
|| (safe_primes && max_bit_length < 3);
100+
let produces_nothing = max_bit_length < base.bits() || max_bit_length < 2;
89101

90-
// Add exceptions to the produced candidates - the only ones that don't fit
91-
// the general pattern of incrementing the base by 2 or by 4.
102+
// Add the exception to the produced candidates - the only one that doesn't fit
103+
// the general pattern of incrementing the base by 2.
92104
let mut starts_from_exception = false;
93-
if !safe_primes {
94-
if base <= Uint::<L>::from(2u32) {
95-
starts_from_exception = true;
96-
base = Uint::<L>::from(3u32);
97-
} else {
98-
// Adjust the base so that we hit correct numbers when incrementing it by 2.
99-
base |= Uint::<L>::ONE;
100-
}
101-
} else if base <= Uint::<L>::from(5u32) {
105+
if base <= Uint::<L>::from(2u32) {
102106
starts_from_exception = true;
103-
base = Uint::<L>::from(7u32);
107+
base = Uint::<L>::from(3u32);
104108
} else {
105-
// Adjust the base so that we hit correct numbers when incrementing it by 4.
106-
// If we are looking for safe primes, we are only interested
107-
// in the numbers == 3 mod 4.
108-
base |= Uint::<L>::from(3u32);
109+
// Adjust the base so that we hit odd numbers when incrementing it by 2.
110+
base |= Uint::<L>::ONE;
109111
}
110112

111113
// Only calculate residues by primes up to and not including `base`,
@@ -157,8 +159,6 @@ impl<const L: usize> Sieve<L> {
157159
}
158160

159161
// Find the increment limit.
160-
// Note that the max value is the same regardless of the value of `self.safe_prime`,
161-
// since `(2^n - 1) = 3 mod 4` (for n > 1).
162162
let max_value = (Uint::<L>::ONE << self.max_bit_length).wrapping_sub(&Uint::<L>::ONE);
163163
let incr_limit = max_value.wrapping_sub(&self.base);
164164
self.incr_limit = if incr_limit > INCR_LIMIT.into() {
@@ -179,11 +179,24 @@ impl<const L: usize> Sieve<L> {
179179
// Returns `true` if the current `base + incr` is divisible by any of the small primes.
180180
fn current_is_composite(&self) -> bool {
181181
for (i, m) in self.residues.iter().enumerate() {
182-
let r = (*m as Residue + self.incr) % (SMALL_PRIMES[i] as Residue);
182+
let d = SMALL_PRIMES[i] as Residue;
183+
let r = (*m as Residue + self.incr) % d;
184+
183185
if r == 0 {
184186
return true;
185187
}
188+
189+
// A trick from "Safe Prime Generation with a Combined Sieve" by Michael J. Wiener
190+
// (https://eprint.iacr.org/2003/186).
191+
// Remember that the check above was for the `(n - 1)/2`;
192+
// If `(n - 1)/2 mod d == (d - 1)/2`, it means that `n mod d == 0`.
193+
// In other words, we are checking the remainder of `n mod d`
194+
// for virtually no additional cost.
195+
if self.safe_primes && r == (d - 1) >> 1 {
196+
return true;
197+
}
186198
}
199+
187200
false
188201
}
189202

@@ -193,11 +206,14 @@ impl<const L: usize> Sieve<L> {
193206
let result = if self.current_is_composite() {
194207
None
195208
} else {
196-
Some(self.base.wrapping_add(&self.incr.into()))
209+
let mut num = self.base.wrapping_add(&self.incr.into());
210+
if self.safe_primes {
211+
num = (num << 1) | Uint::<L>::ONE;
212+
}
213+
Some(num)
197214
};
198215

199-
// If we are looking for safe primes, we are only interested in the numbers == 3 mod 4.
200-
self.incr += if self.safe_primes { 4 } else { 2 };
216+
self.incr += 2;
201217
result
202218
}
203219

@@ -229,33 +245,8 @@ impl<const L: usize> Iterator for Sieve<L> {
229245
type Item = Uint<L>;
230246

231247
fn next(&mut self) -> Option<Self::Item> {
232-
Sieve::<L>::next(self)
233-
}
234-
}
235-
236-
/// Performs trial division of the given number by a list of small primes starting from 2.
237-
/// Returns `Some(primality)` if there was a definitive conclusion about `num`'s primality,
238-
/// and `None` otherwise.
239-
pub fn sieve_once<const L: usize>(num: &Uint<L>) -> Option<Primality> {
240-
// Our reciprocals start from 3, so we check for 2 separately
241-
if num == &Uint::<L>::from(2u32) {
242-
return Some(Primality::Prime);
243-
}
244-
if num.is_even().into() {
245-
return Some(Primality::Composite);
246-
}
247-
for small_prime in SMALL_PRIMES.iter() {
248-
let (quo, rem) = num.div_rem_limb(NonZero::new(Limb::from(*small_prime)).unwrap());
249-
if rem.is_zero().into() {
250-
let primality = if quo == Uint::<L>::ONE {
251-
Primality::Prime
252-
} else {
253-
Primality::Composite
254-
};
255-
return Some(primality);
256-
}
248+
Self::next(self)
257249
}
258-
None
259250
}
260251

261252
#[cfg(test)]
@@ -328,15 +319,26 @@ mod tests {
328319
check_sieve(5, 3, true, &[5, 7]);
329320
check_sieve(7, 3, true, &[7]);
330321

331-
// start is adjusted to 5 here, so multiples of 3 can be excluded
332-
check_sieve(1, 4, true, &[5, 7, 11]);
322+
// In the following three cases, the "half-start" would be set to 3,
323+
// and since every small divisor equal or greater than the start is not tested
324+
// (because we can't distinguish between the remainder being 0
325+
// and the number being actually equal to the divisor),
326+
// no divisors will actually be tested at all, so 15 (a composite)
327+
// is included in the output.
328+
check_sieve(1, 4, true, &[5, 7, 11, 15]);
329+
check_sieve(5, 4, true, &[5, 7, 11, 15]);
330+
check_sieve(7, 4, true, &[7, 11, 15]);
333331

334-
check_sieve(5, 4, true, &[5, 7, 11]);
335-
check_sieve(7, 4, true, &[7, 11]);
336332
check_sieve(9, 4, true, &[11]);
337333
check_sieve(13, 4, true, &[]);
338334
}
339335

336+
#[test]
337+
#[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")]
338+
fn sieve_too_many_bits() {
339+
let _sieve = Sieve::new(&U64::ONE, 65, false);
340+
}
341+
340342
#[test]
341343
fn random_below_max_length() {
342344
for _ in 0..10 {

0 commit comments

Comments
 (0)