use crate::bigint::{BigInt, BigUint};
use crate::csprng::Csprng;
use crate::secure::Zeroizing;
#[must_use]
pub fn gcd(lhs: &BigUint, rhs: &BigUint) -> BigUint {
let mut current = lhs.clone();
let mut next = rhs.clone();
while !next.is_zero() {
let remainder = current.modulo(&next);
current = next;
next = remainder;
}
current
}
#[must_use]
pub fn mod_pow(base: &BigUint, exp: &BigUint, n: &BigUint) -> BigUint {
if n.is_one() {
return BigUint::zero();
}
let mut result = BigUint::one();
let mut acc = base.modulo(n);
let bits = exp.bits();
for i in 0..bits {
if exp.bit(i) {
result = BigUint::mod_mul(&result, &acc, n);
}
if i + 1 < bits {
acc = BigUint::mod_mul(&acc, &acc, n);
}
}
result
}
#[must_use]
pub fn is_probable_prime(n: &BigUint) -> bool {
if n < &BigUint::from_u64(2) {
return false;
}
for &p in &[2u64, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] {
let p_big = BigUint::from_u64(p);
if n == &p_big {
return true;
}
if n.modulo(&p_big).is_zero() {
return false;
}
}
let n_minus_1 = n.sub_ref(&BigUint::one());
let mut d = n_minus_1.clone();
let mut s = 0usize;
while !d.is_odd() {
d.shr1();
s += 1;
}
const BASES: &[u64] = &[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
'witness: for &base in BASES {
let a = BigUint::from_u64(base);
if a >= *n {
continue;
}
let mut x = mod_pow(&a, &d, n);
if x.is_one() || x == n_minus_1 {
continue;
}
for _ in 0..(s - 1) {
x = BigUint::mod_mul(&x, &x, n);
if x == n_minus_1 {
continue 'witness;
}
if x.is_one() {
return false;
}
}
return false;
}
true
}
#[must_use]
pub fn is_probable_prime_random<R: Csprng>(rng: &mut R, n: &BigUint, rounds: usize) -> bool {
if n < &BigUint::from_u64(2) {
return false;
}
for &p in &[2u64, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] {
let p_big = BigUint::from_u64(p);
if n == &p_big {
return true;
}
if n.modulo(&p_big).is_zero() {
return false;
}
}
let n_minus_1 = n.sub_ref(&BigUint::one());
let mut d = n_minus_1.clone();
let mut s = 0usize;
while !d.is_odd() {
d.shr1();
s += 1;
}
let n_minus_3 = n.sub_ref(&BigUint::from_u64(3));
'witness: for _ in 0..rounds {
let r = match random_below(rng, &n_minus_3) {
Some(v) => v.add_ref(&BigUint::from_u64(2)),
None => return false,
};
let mut x = mod_pow(&r, &d, n);
if x.is_one() || x == n_minus_1 {
continue;
}
for _ in 0..(s - 1) {
x = BigUint::mod_mul(&x, &x, n);
if x == n_minus_1 {
continue 'witness;
}
if x.is_one() {
return false;
}
}
return false;
}
true
}
#[must_use]
pub fn mod_inverse(a: &BigUint, n: &BigUint) -> Option<BigUint> {
if n.is_zero() {
return None;
}
let mut t = BigInt::zero();
let mut new_t = BigInt::from_biguint(BigUint::one());
let mut r = n.clone();
let mut new_r = a.modulo(n);
while !new_r.is_zero() {
let (quotient, remainder) = r.div_rem(&new_r);
let next_t = t.sub_ref(&new_t.mul_biguint_ref("ient));
t = new_t;
new_t = next_t;
r = new_r;
new_r = remainder;
}
if !r.is_one() {
return None;
}
Some(t.modulo_positive(n))
}
#[must_use]
pub fn random_below<R: Csprng>(rng: &mut R, upper_exclusive: &BigUint) -> Option<BigUint> {
if upper_exclusive.is_zero() {
return None;
}
let bits = upper_exclusive.bits();
let mut bytes_holder = Zeroizing::new(vec![0u8; bits.div_ceil(8)]);
let excess_bits = bytes_holder.len() * 8 - bits;
let top_mask = 0xff_u8 >> excess_bits;
loop {
rng.fill_bytes(&mut bytes_holder);
bytes_holder[0] &= top_mask;
let candidate = BigUint::from_be_bytes(&bytes_holder);
if candidate < *upper_exclusive {
return Some(candidate);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csprng::ChaCha20Rng;
#[test]
fn gcd_small_values() {
assert_eq!(
gcd(&BigUint::from_u64(48), &BigUint::from_u64(18)),
BigUint::from_u64(6)
);
}
#[test]
fn miller_rabin_accepts_known_primes() {
for &p in &[2u64, 3, 5, 7, 11, 13, 65521, 65537, (1u64 << 31) - 1, (1u64 << 61) - 1] {
assert!(is_probable_prime(&BigUint::from_u64(p)), "{p} is prime");
}
}
#[test]
fn miller_rabin_random_round_trip() {
let mut rng = ChaCha20Rng::from_seed(&[0x11u8; 32]);
for &p in &[
2u64, 3, 5, 7, 11, 13, 65537, (1u64 << 31) - 1, (1u64 << 61) - 1,
] {
assert!(
is_probable_prime_random(&mut rng, &BigUint::from_u64(p), 16),
"{p} prime under random-witness MR",
);
}
for &n in &[4u64, 6, 9, 25, 91, 561, 1105, 65535] {
assert!(!is_probable_prime_random(&mut rng, &BigUint::from_u64(n), 16));
}
}
#[test]
fn miller_rabin_rejects_composites() {
for &n in &[0u64, 1, 4, 6, 9, 15, 49, 91, 561, 1105, 1729, 65535] {
assert!(!is_probable_prime(&BigUint::from_u64(n)), "{n} is not prime");
}
}
#[test]
fn modular_inverse_small_values() {
assert_eq!(
mod_inverse(&BigUint::from_u64(11), &BigUint::from_u64(16)),
Some(BigUint::from_u64(3))
);
assert_eq!(
mod_inverse(&BigUint::from_u64(23), &BigUint::from_u64(46)),
None
);
}
#[test]
fn random_below_is_in_range() {
let mut rng = ChaCha20Rng::from_seed(&[7u8; 32]);
let upper = BigUint::from_u64(1000);
for _ in 0..100 {
let x = random_below(&mut rng, &upper).unwrap();
assert!(x < upper);
}
}
#[test]
fn random_below_zero_is_none() {
let mut rng = ChaCha20Rng::from_seed(&[0u8; 32]);
assert!(random_below(&mut rng, &BigUint::zero()).is_none());
}
}