use crypto_bigint::{RandomMod, UnsignedWithMontyForm};
use rand_core::CryptoRng;
use crate::{
hazmat::{
ConventionsTestResult, LucasCheck, MillerRabin, Primality, SelfridgeBase, conventions_test, equals_primitive,
lucas_test, minimum_mr_iterations, small_factors_test,
},
presets::Flavor,
};
#[derive(Copy, Clone, Debug)]
pub struct FipsOptions {
mr_iterations: usize,
add_lucas_test: bool,
add_trial_division_test: bool,
}
impl FipsOptions {
pub const fn with_mr_iterations(mr_iterations: usize) -> Self {
Self {
mr_iterations,
add_lucas_test: false,
add_trial_division_test: false,
}
}
pub const fn with_error_bound(bit_length: u32, log2_target: u32) -> Option<Self> {
let iterations = match minimum_mr_iterations(bit_length, log2_target) {
None => return None,
Some(iterations) => iterations,
};
Some(Self::with_mr_iterations(iterations))
}
pub const fn with_lucas_test(self) -> Self {
Self {
mr_iterations: self.mr_iterations,
add_lucas_test: true,
add_trial_division_test: self.add_trial_division_test,
}
}
pub const fn with_trial_division_test(self) -> Self {
Self {
mr_iterations: self.mr_iterations,
add_lucas_test: self.add_lucas_test,
add_trial_division_test: true,
}
}
}
pub fn is_prime<T>(rng: &mut (impl CryptoRng + ?Sized), flavor: Flavor, candidate: &T, options: FipsOptions) -> bool
where
T: UnsignedWithMontyForm + RandomMod,
{
match flavor {
Flavor::Any => {}
Flavor::Safe => return is_safe_prime(rng, candidate, options),
}
let odd_candidate = match conventions_test(candidate.clone()) {
ConventionsTestResult::Prime => return true,
ConventionsTestResult::Composite => return false,
ConventionsTestResult::Undecided { odd_candidate } => odd_candidate,
};
if options.add_trial_division_test && small_factors_test(&odd_candidate) == Primality::Composite {
return false;
}
if equals_primitive(candidate, 3) {
return true;
}
let mr = MillerRabin::new(odd_candidate.clone());
for _ in 0..options.mr_iterations {
if mr.test_random_base(rng).is_composite() {
return false;
}
}
if options.add_lucas_test {
return lucas_test(odd_candidate, SelfridgeBase, LucasCheck::Strong).is_probably_prime();
}
true
}
fn is_safe_prime<T>(rng: &mut (impl CryptoRng + ?Sized), candidate: &T, options: FipsOptions) -> bool
where
T: UnsignedWithMontyForm + RandomMod,
{
if equals_primitive(candidate, 5) {
return true;
}
if candidate.as_limbs()[0].0 & 3 != 3 {
return false;
}
is_prime(rng, Flavor::Any, candidate, options)
&& is_prime(rng, Flavor::Any, &candidate.wrapping_shr_vartime(1), options)
}
#[cfg(test)]
mod tests {
use crypto_bigint::U64;
use super::{FipsOptions, is_prime};
use crate::Flavor;
#[test]
fn cannot_create_options() {
assert!(FipsOptions::with_error_bound(128, 1024).is_none());
}
#[test]
fn trial_division_only() {
let mut rng = rand::rng();
assert!(is_prime(
&mut rng,
Flavor::Any,
&U64::from(4651u64),
FipsOptions::with_mr_iterations(0).with_trial_division_test(),
));
assert!(!is_prime(
&mut rng,
Flavor::Any,
&U64::from(113u64 * 137),
FipsOptions::with_mr_iterations(0).with_trial_division_test(),
));
}
#[test]
fn lucas_test_only() {
let mut rng = rand::rng();
assert!(is_prime(
&mut rng,
Flavor::Any,
&U64::from(4651u64),
FipsOptions::with_mr_iterations(0).with_lucas_test(),
));
assert!(!is_prime(
&mut rng,
Flavor::Any,
&U64::from(113u64 * 137),
FipsOptions::with_mr_iterations(0).with_lucas_test(),
));
assert!(is_prime(
&mut rng,
Flavor::Any,
&U64::from(53u64 * 103),
FipsOptions::with_mr_iterations(0).with_lucas_test(),
));
}
#[test]
fn no_tests() {
let mut rng = rand::rng();
assert!(is_prime(
&mut rng,
Flavor::Any,
&U64::from(4651u64),
FipsOptions::with_mr_iterations(0)
));
}
}