crypto_primes/hazmat/
miller_rabin.rs1use crypto_bigint::{Integer, Limb, Monty, NonZero as CTNonZero, Odd, PowBoundedExp, RandomMod, Square};
4use rand_core::CryptoRngCore;
5
6use super::Primality;
7
8#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct MillerRabin<T: Integer> {
19 candidate: T,
21 bits: u32,
24 montgomery_params: <<T as Integer>::Monty as Monty>::Params,
26 one: <T as Integer>::Monty,
28 minus_one: <T as Integer>::Monty,
30 s: u32,
33 d: T,
36}
37
38impl<T: Integer + RandomMod> MillerRabin<T> {
39 pub fn new(candidate: Odd<T>) -> Self {
41 let params = <T as Integer>::Monty::new_params_vartime(candidate.clone());
42 let m_one = <T as Integer>::Monty::one(params.clone());
43 let m_minus_one = -m_one.clone();
44
45 let one = T::one_like(candidate.as_ref());
46
47 let (s, d) = if candidate.as_ref() == &one {
49 (0, one)
50 } else {
51 let candidate_minus_one = candidate.wrapping_sub(&one);
52 let s = candidate_minus_one.trailing_zeros_vartime();
53 let d = candidate_minus_one
55 .overflowing_shr_vartime(s)
56 .expect("shifting by `s` is within range by construction: `candidate` is odd and greater than 1");
57 (s, d)
58 };
59
60 Self {
61 bits: candidate.bits_vartime(),
62 candidate: candidate.get(),
63 montgomery_params: params,
64 one: m_one,
65 minus_one: m_minus_one,
66 s,
67 d,
68 }
69 }
70
71 pub fn test(&self, base: &T) -> Primality {
73 let base = <T as Integer>::Monty::new(base.clone(), self.montgomery_params.clone());
77
78 let mut test = base.pow_bounded_exp(&self.d, self.bits);
84
85 if test == self.one || test == self.minus_one {
86 return Primality::ProbablyPrime;
87 }
88 for _ in 1..self.s {
89 test = test.square();
90 if test == self.one {
91 return Primality::Composite;
92 } else if test == self.minus_one {
93 return Primality::ProbablyPrime;
94 }
95 }
96 Primality::Composite
97 }
98
99 pub fn test_base_two(&self) -> Primality {
101 self.test(&T::from_limb_like(Limb::from(2u32), &self.candidate))
102 }
103
104 pub fn test_random_base(&self, rng: &mut (impl CryptoRngCore + ?Sized)) -> Primality {
109 if self.candidate.bits() < 3 {
113 panic!("No suitable random base possible when `candidate == 3`; use the base 2 test.")
114 }
115
116 let range = self.candidate.wrapping_sub(&T::from(4u32));
117 let range_nonzero = CTNonZero::new(range).expect("the range should be non-zero by construction");
119 let random = T::random_mod(rng, &range_nonzero)
122 .checked_add(&T::from(3u32))
123 .expect("addition should not overflow by construction");
124 self.test(&random)
125 }
126
127 pub(crate) fn bits(&self) -> u32 {
133 self.bits
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use alloc::format;
140 use core::num::NonZero;
141
142 use crypto_bigint::{Integer, Odd, RandomMod, Uint, U1024, U128, U1536, U64};
143 use rand_chacha::ChaCha8Rng;
144 use rand_core::{CryptoRngCore, OsRng, SeedableRng};
145
146 #[cfg(feature = "tests-exhaustive")]
147 use num_prime::nt_funcs::is_prime64;
148
149 use super::MillerRabin;
150 use crate::hazmat::{primes, pseudoprimes, random_odd_integer, SetBits, SmallPrimesSieve};
151
152 #[test]
153 fn miller_rabin_derived_traits() {
154 let mr = MillerRabin::new(Odd::new(U64::ONE).unwrap());
155 assert!(format!("{mr:?}").starts_with("MillerRabin"));
156 assert_eq!(mr.clone(), mr);
157 }
158
159 #[test]
160 #[should_panic(expected = "No suitable random base possible when `candidate == 3`; use the base 2 test.")]
161 fn random_base_range_check() {
162 let mr = MillerRabin::new(Odd::new(U64::from(3u32)).unwrap());
163 mr.test_random_base(&mut OsRng);
164 }
165
166 fn is_spsp(num: u32) -> bool {
167 pseudoprimes::STRONG_BASE_2.iter().any(|x| *x == num)
168 }
169
170 fn random_checks<T: Integer + RandomMod>(
171 rng: &mut (impl CryptoRngCore + ?Sized),
172 mr: &MillerRabin<T>,
173 count: usize,
174 ) -> usize {
175 (0..count)
176 .map(|_| -> usize { mr.test_random_base(rng).is_probably_prime().into() })
177 .sum()
178 }
179
180 fn test_composites(numbers: &[u32], expected_result: bool) {
181 let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
182 for num in numbers.iter() {
183 let base_2_false_positive = is_spsp(*num);
184 let actual_expected_result = if base_2_false_positive { true } else { expected_result };
185
186 let mr = MillerRabin::new(Odd::new(U64::from(*num)).unwrap());
191 assert_eq!(mr.test_base_two().is_probably_prime(), actual_expected_result);
192 let reported_prime = random_checks(&mut rng, &mr, 100);
193 assert!(
194 reported_prime < 35,
195 "reported as prime in {reported_prime} out of 100 tests",
196 );
197 }
198 }
199
200 #[test]
201 fn trivial() {
202 let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
203 let start = random_odd_integer::<U1024>(&mut rng, NonZero::new(1024).unwrap(), SetBits::Msb).unwrap();
204 for num in SmallPrimesSieve::new(start.get(), NonZero::new(1024).unwrap(), false).take(10) {
205 let mr = MillerRabin::new(Odd::new(num).unwrap());
206
207 assert!(mr.test(&1u32.into()).is_probably_prime());
209 assert!(mr.test(&num.wrapping_sub(&1u32.into())).is_probably_prime());
210 }
211 }
212
213 #[test]
214 fn mersenne_prime() {
215 let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
216
217 let num = Odd::new(U128::from_be_hex("7fffffffffffffffffffffffffffffff")).unwrap();
219
220 let mr = MillerRabin::new(num);
221 assert!(mr.test_base_two().is_probably_prime());
222 for _ in 0..10 {
223 assert!(mr.test_random_base(&mut rng).is_probably_prime());
224 }
225 }
226
227 #[test]
228 fn strong_fibonacci_pseudoprimes() {
229 let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
230
231 for num in pseudoprimes::STRONG_FIBONACCI.iter() {
232 let mr = MillerRabin::new(Odd::new(*num).unwrap());
233 assert!(!mr.test_base_two().is_probably_prime());
234 for _ in 0..1000 {
235 assert!(!mr.test_random_base(&mut rng).is_probably_prime());
236 }
237 }
238 }
239
240 #[test]
241 fn strong_pseudoprimes_base_2() {
242 test_composites(pseudoprimes::STRONG_BASE_2, true);
244 }
245
246 #[test]
247 fn lucas_pseudoprimes() {
248 test_composites(pseudoprimes::EXTRA_STRONG_LUCAS, false);
251 test_composites(pseudoprimes::STRONG_LUCAS, false);
252 test_composites(pseudoprimes::ALMOST_EXTRA_STRONG_LUCAS, false);
253 test_composites(pseudoprimes::FIBONACCI, false);
254 test_composites(pseudoprimes::BRUCKMAN_LUCAS, false);
255 test_composites(pseudoprimes::LUCAS, false);
256 }
257
258 #[test]
259 fn large_carmichael_number() {
260 let mr = MillerRabin::new(Odd::new(pseudoprimes::LARGE_CARMICHAEL_NUMBER).unwrap());
261
262 assert!(mr.test_base_two().is_probably_prime());
264 assert!(mr.test(&U1536::from(293u64)).is_probably_prime());
265
266 assert!(!mr.test(&U1536::from(307u64)).is_probably_prime());
268 }
269
270 fn test_large_primes<const L: usize>(nums: &[Uint<L>]) {
271 let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
272 for num in nums {
273 let mr = MillerRabin::new(Odd::new(*num).unwrap());
274 assert!(mr.test_base_two().is_probably_prime());
275 for _ in 0..10 {
276 assert!(mr.test_random_base(&mut rng).is_probably_prime());
277 }
278 }
279 }
280
281 #[test]
282 fn large_primes() {
283 test_large_primes(primes::PRIMES_128);
284 test_large_primes(primes::PRIMES_256);
285 test_large_primes(primes::PRIMES_384);
286 test_large_primes(primes::PRIMES_512);
287 test_large_primes(primes::PRIMES_1024);
288 }
289
290 #[cfg(feature = "tests-exhaustive")]
291 #[test]
292 fn exhaustive() {
293 for num in (3..pseudoprimes::EXHAUSTIVE_TEST_LIMIT).step_by(2) {
296 let res_ref = is_prime64(num.into());
297
298 let spsp = is_spsp(num);
299
300 let mr = MillerRabin::new(Odd::new(U64::from(num)).unwrap());
301 let res = mr.test_base_two().is_probably_prime();
302 let expected = spsp || res_ref;
303 assert_eq!(
304 res, expected,
305 "Miller-Rabin: n={num}, expected={expected}, actual={res}",
306 );
307 }
308 }
309}