use crypto_bigint::{
Limb, MontyForm, NonZero as CTNonZero, Odd, PowBoundedExp, RandomMod, Square, UnsignedWithMontyForm,
};
use rand_core::CryptoRng;
use super::{
Primality, equals_primitive,
float::{floor_sqrt, two_powf_upper_bound, two_powi},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MillerRabin<T: UnsignedWithMontyForm> {
candidate: T,
bits: u32,
montgomery_params: <<T as UnsignedWithMontyForm>::MontyForm as MontyForm>::Params,
one: <T as UnsignedWithMontyForm>::MontyForm,
minus_one: <T as UnsignedWithMontyForm>::MontyForm,
s: u32,
d: T,
}
impl<T: UnsignedWithMontyForm + RandomMod> MillerRabin<T> {
pub fn new(candidate: Odd<T>) -> Self {
let params = <T as UnsignedWithMontyForm>::MontyForm::new_params_vartime(candidate.clone());
let m_one = <T as UnsignedWithMontyForm>::MontyForm::one(¶ms);
let m_minus_one = -m_one.clone();
let one = T::one_like(candidate.as_ref());
let (s, d) = if candidate.as_ref() == &one {
(0, one)
} else {
let candidate_minus_one = candidate.wrapping_sub(&one);
let s = candidate_minus_one.trailing_zeros_vartime();
let d = candidate_minus_one
.overflowing_shr_vartime(s)
.expect("shifting by `s` is within range by construction: `candidate` is odd and greater than 1");
(s, d)
};
Self {
bits: candidate.bits_vartime(),
candidate: candidate.get(),
montgomery_params: params,
one: m_one,
minus_one: m_minus_one,
s,
d,
}
}
pub fn test(&self, base: &T) -> Primality {
let base = <T as UnsignedWithMontyForm>::MontyForm::new(base.clone(), &self.montgomery_params);
let mut test = base.pow_bounded_exp(&self.d, self.bits);
if test == self.one || test == self.minus_one {
return Primality::ProbablyPrime;
}
for _ in 1..self.s {
test = test.square();
if test == self.one {
return Primality::Composite;
} else if test == self.minus_one {
return Primality::ProbablyPrime;
}
}
Primality::Composite
}
pub fn test_base_two(&self) -> Primality {
self.test(&T::from_limb_like(Limb::from(2u32), &self.candidate))
}
pub fn test_random_base<R: CryptoRng + ?Sized>(&self, rng: &mut R) -> Primality {
if equals_primitive(&self.candidate, 1) {
return Primality::Composite;
}
if equals_primitive(&self.candidate, 3) {
return Primality::Prime;
}
let range = self.candidate.wrapping_sub(&T::from(3u32));
let range_nonzero = CTNonZero::new(range).expect("the range should be non-zero by construction");
let random = T::random_mod_vartime(rng, &range_nonzero)
.checked_add(&T::from(2u32))
.expect("addition should not overflow by construction");
self.test(&random)
}
}
const fn pseudoprime_probability(k: u32, t: u32, cap_m: u32) -> f64 {
let mut s = 0.;
let mut m = 3i32;
while m <= cap_m as i32 {
let mut j = 2i32;
while j <= m {
s += two_powf_upper_bound((m - (m - 1) * (t as i32) - j) as f64 - (k - 1) as f64 / j as f64);
j += 1;
}
m += 1;
}
const PI: f64 = core::f64::consts::PI;
const COEFF: f64 = 0.3478611111678627;
COEFF * k as f64 * (1. / two_powi(cap_m * t) + 8. * (PI * PI - 6.) / 3. * s)
}
pub const fn minimum_mr_iterations(bit_length: u32, log2_target: u32) -> Option<usize> {
let mut t = 1;
while t <= log2_target.div_ceil(2) {
let cap_m_limit = floor_sqrt(4 * (bit_length - 1)) - 1;
let mut cap_m = 3;
while cap_m <= cap_m_limit {
let p = pseudoprime_probability(bit_length, t, cap_m);
if p < 1. / two_powi(log2_target) {
return Some(t as usize);
}
cap_m += 1;
}
t += 1;
}
None
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::num::NonZero;
use crypto_bigint::{Odd, RandomMod, U64, U128, U1024, U1536, Uint, UnsignedWithMontyForm};
use rand::rngs::ChaCha8Rng;
use rand_core::{CryptoRng, SeedableRng};
#[cfg(feature = "tests-exhaustive")]
use num_prime::nt_funcs::is_prime64;
use super::{MillerRabin, minimum_mr_iterations};
use crate::hazmat::{Primality, SetBits, SmallFactorsSieve, primes, pseudoprimes, random_odd_integer};
#[test]
fn miller_rabin_derived_traits() {
let mr = MillerRabin::new(Odd::new(U64::ONE).unwrap());
assert!(format!("{mr:?}").starts_with("MillerRabin"));
assert_eq!(mr.clone(), mr);
}
#[test]
fn random_base_corner_cases() {
let mut rng = rand::rng();
let mr = MillerRabin::new(Odd::new(U64::from(1u32)).unwrap());
assert!(mr.test_random_base(&mut rng) == Primality::Composite);
let mr = MillerRabin::new(Odd::new(U64::from(3u32)).unwrap());
assert!(mr.test_random_base(&mut rng) == Primality::Prime);
}
fn is_spsp(num: u32) -> bool {
pseudoprimes::STRONG_BASE_2.contains(&num)
}
fn random_checks<T: UnsignedWithMontyForm + RandomMod, R: CryptoRng + ?Sized>(
rng: &mut R,
mr: &MillerRabin<T>,
count: usize,
) -> usize {
(0..count)
.map(|_| -> usize { mr.test_random_base(rng).is_probably_prime().into() })
.sum()
}
fn test_composites(numbers: &[u32], expected_result: bool) {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
for num in numbers.iter() {
let base_2_false_positive = is_spsp(*num);
let actual_expected_result = if base_2_false_positive { true } else { expected_result };
let mr = MillerRabin::new(Odd::new(U64::from(*num)).unwrap());
assert_eq!(mr.test_base_two().is_probably_prime(), actual_expected_result);
let reported_prime = random_checks(&mut rng, &mr, 100);
assert!(
reported_prime < 35,
"reported as prime in {reported_prime} out of 100 tests",
);
}
}
#[test]
fn trivial() {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let start = random_odd_integer::<U1024, _>(&mut rng, NonZero::new(1024).unwrap(), SetBits::Msb).unwrap();
for num in SmallFactorsSieve::new(start.get(), NonZero::new(1024).unwrap(), false)
.unwrap()
.take(10)
{
let mr = MillerRabin::new(Odd::new(num).unwrap());
assert!(mr.test(&1u32.into()).is_probably_prime());
assert!(mr.test(&num.wrapping_sub(&1u32.into())).is_probably_prime());
}
}
#[test]
fn mersenne_prime() {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let num = Odd::new(U128::from_be_hex("7fffffffffffffffffffffffffffffff")).unwrap();
let mr = MillerRabin::new(num);
assert!(mr.test_base_two().is_probably_prime());
for _ in 0..10 {
assert!(mr.test_random_base(&mut rng).is_probably_prime());
}
}
#[test]
fn strong_fibonacci_pseudoprimes() {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
for num in pseudoprimes::STRONG_FIBONACCI.iter() {
let mr = MillerRabin::new(Odd::new(*num).unwrap());
assert!(!mr.test_base_two().is_probably_prime());
for _ in 0..1000 {
assert!(!mr.test_random_base(&mut rng).is_probably_prime());
}
}
}
#[test]
fn strong_pseudoprimes_base_2() {
test_composites(pseudoprimes::STRONG_BASE_2, true);
}
#[test]
fn lucas_pseudoprimes() {
test_composites(pseudoprimes::EXTRA_STRONG_LUCAS, false);
test_composites(pseudoprimes::STRONG_LUCAS, false);
test_composites(pseudoprimes::ALMOST_EXTRA_STRONG_LUCAS, false);
test_composites(pseudoprimes::FIBONACCI, false);
test_composites(pseudoprimes::BRUCKMAN_LUCAS, false);
test_composites(pseudoprimes::LUCAS, false);
}
#[test]
fn large_carmichael_number() {
let mr = MillerRabin::new(Odd::new(pseudoprimes::LARGE_CARMICHAEL_NUMBER).unwrap());
assert!(mr.test_base_two().is_probably_prime());
assert!(mr.test(&U1536::from(293u64)).is_probably_prime());
assert!(!mr.test(&U1536::from(307u64)).is_probably_prime());
}
fn test_large_primes<const L: usize>(nums: &[Uint<L>]) {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
for num in nums {
let mr = MillerRabin::new(Odd::new(*num).unwrap());
assert!(mr.test_base_two().is_probably_prime());
for _ in 0..10 {
assert!(mr.test_random_base(&mut rng).is_probably_prime());
}
}
}
#[test]
fn large_primes() {
test_large_primes(primes::PRIMES_128);
test_large_primes(primes::PRIMES_256);
test_large_primes(primes::PRIMES_384);
test_large_primes(primes::PRIMES_512);
test_large_primes(primes::PRIMES_1024);
}
#[test]
fn mr_iterations() {
assert_eq!(minimum_mr_iterations(140, 100).unwrap(), 32);
assert_eq!(minimum_mr_iterations(140, 112).unwrap(), 38);
assert_eq!(minimum_mr_iterations(1024, 100).unwrap(), 4);
assert_eq!(minimum_mr_iterations(1024, 112).unwrap(), 5);
assert_eq!(minimum_mr_iterations(170, 100).unwrap(), 27);
assert_eq!(minimum_mr_iterations(170, 128).unwrap(), 41);
assert_eq!(minimum_mr_iterations(1536, 100).unwrap(), 3);
assert_eq!(minimum_mr_iterations(1536, 128).unwrap(), 4);
assert_eq!(minimum_mr_iterations(200, 100).unwrap(), 22);
assert_eq!(minimum_mr_iterations(200, 144).unwrap(), 44);
assert_eq!(minimum_mr_iterations(2048, 100).unwrap(), 2);
assert_eq!(minimum_mr_iterations(2048, 144).unwrap(), 4);
assert!(minimum_mr_iterations(128, 1024).is_none());
}
#[cfg(feature = "tests-exhaustive")]
#[test]
fn exhaustive() {
for num in (3..pseudoprimes::EXHAUSTIVE_TEST_LIMIT).step_by(2) {
let res_ref = is_prime64(num.into());
let spsp = is_spsp(num);
let mr = MillerRabin::new(Odd::new(U64::from(num)).unwrap());
let res = mr.test_base_two().is_probably_prime();
let expected = spsp || res_ref;
assert_eq!(
res, expected,
"Miller-Rabin: n={num}, expected={expected}, actual={res}",
);
}
}
}