use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::int::Int;
const TRIAL_BOUND: u64 = 1 << 16;
const WITNESS_LIMIT: u64 = 4096;
const FACTOR_CAP_BITS: u32 = 128;
#[derive(Clone, Debug)]
pub enum Primality {
Prime(PrimalityCertificate),
Composite,
Unproven,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Bound {
Sqrt,
Cbrt,
}
#[derive(Clone, Debug)]
enum PrimeProof {
Small,
Recursive(Box<PrimalityCertificate>),
}
#[derive(Clone, Debug)]
struct FactorWitness {
prime: Int,
exp: u32,
witness: Int,
proof: PrimeProof,
}
#[derive(Clone, Debug)]
enum Kind {
SmallBpsw,
NMinusOne {
factors: Vec<FactorWitness>,
cofactor: Int,
bound: Bound,
},
}
#[derive(Clone, Debug)]
pub struct PrimalityCertificate {
n: Int,
kind: Kind,
}
impl PrimalityCertificate {
pub fn n(&self) -> &Int {
&self.n
}
pub fn bound(&self) -> Option<Bound> {
match &self.kind {
Kind::SmallBpsw => None,
Kind::NMinusOne { bound, .. } => Some(*bound),
}
}
pub fn verify(&self, n: &Int) -> bool {
if &self.n != n {
return false;
}
verify_cert(self)
}
}
pub fn prove_prime(n: &Int) -> Primality {
if n < &Int::from(2) {
return Primality::Composite;
}
if n.to_u64().is_some() {
return if n.is_prime_bpsw() {
Primality::Prime(PrimalityCertificate {
n: n.clone(),
kind: Kind::SmallBpsw,
})
} else {
Primality::Composite
};
}
if !n.is_prime_bpsw() {
return Primality::Composite;
}
match prove_n_minus_1(n) {
Attempt::Proved(cert) => Primality::Prime(cert),
Attempt::Composite => Primality::Composite,
Attempt::Insufficient => Primality::Unproven,
}
}
enum Attempt {
Proved(PrimalityCertificate),
Composite,
Insufficient,
}
fn prove_n_minus_1(n: &Int) -> Attempt {
let m = n.sub(&Int::ONE); let (mut primes, mut cofactor) = peel_smooth(&m);
if cofactor > Int::ONE && cofactor.is_prime_bpsw() {
primes.push((cofactor.clone(), 1));
cofactor = Int::ONE;
}
let mut factored = product_pow(&primes);
if cube_le(&factored, n) && cofactor > Int::ONE && cofactor.bit_len() <= FACTOR_CAP_BITS {
for p in cofactor.factorize() {
match primes.iter_mut().find(|(q, _)| *q == p) {
Some((_, e)) => *e += 1,
None => primes.push((p, 1)),
}
}
primes.sort_by(|a, b| a.0.cmp(&b.0));
factored = product_pow(&primes);
cofactor = m.div_trunc(&factored);
}
let bound = if factored.mul(&factored) > *n {
Bound::Sqrt
} else if !cube_le(&factored, n) {
Bound::Cbrt
} else {
return Attempt::Insufficient;
};
assemble(n, &m, primes, cofactor, bound)
}
fn cube_le(f: &Int, n: &Int) -> bool {
f.mul(f).mul(f) <= *n
}
fn assemble(n: &Int, m: &Int, primes: Vec<(Int, u32)>, cofactor: Int, bound: Bound) -> Attempt {
let mut factors = Vec::with_capacity(primes.len());
for (q, e) in primes {
let witness = match find_witness(n, m, &q) {
Witness::Found(a) => a,
Witness::Composite => return Attempt::Composite,
Witness::NotFound => return Attempt::Insufficient,
};
let proof = if q.to_u64().is_some() {
PrimeProof::Small
} else {
match prove_prime(&q) {
Primality::Prime(sub) => PrimeProof::Recursive(Box::new(sub)),
_ => return Attempt::Insufficient,
}
};
factors.push(FactorWitness {
prime: q,
exp: e,
witness,
proof,
});
}
if bound == Bound::Cbrt {
let f = product_pow_from(&factors);
if matches!(bls_discriminant(n, &f, &cofactor), Disc::Composite) {
return Attempt::Composite;
}
}
Attempt::Proved(PrimalityCertificate {
n: n.clone(),
kind: Kind::NMinusOne {
factors,
cofactor,
bound,
},
})
}
enum Witness {
Found(Int),
Composite,
NotFound,
}
fn find_witness(n: &Int, m: &Int, q: &Int) -> Witness {
let e = m.div_trunc(q); let mut a = 2u64;
while a <= WITNESS_LIMIT {
let base = Int::from(a);
if base.modpow(m, n) != Int::ONE {
return Witness::Composite;
}
let x = base.modpow(&e, n);
let g = x.sub(&Int::ONE).gcd(n);
if g == Int::ONE {
return Witness::Found(base);
}
if g != *n {
return Witness::Composite;
}
a += 1;
}
Witness::NotFound
}
fn peel_smooth(m: &Int) -> (Vec<(Int, u32)>, Int) {
let mut primes: Vec<(Int, u32)> = Vec::new();
let mut c = m.clone();
let two = Int::from(2);
let mut e = 0u32;
while c > Int::ZERO && c.is_even() {
c = c.div_trunc(&two);
e += 1;
}
if e > 0 {
primes.push((two, e));
}
let mut d = 3u64;
while d <= TRIAL_BOUND {
let dn = Int::from(d);
if dn.mul(&dn) > c {
break;
}
let mut e = 0u32;
loop {
let (q, r) = c.div_rem_trunc(&dn);
if r.is_zero() {
c = q;
e += 1;
} else {
break;
}
}
if e > 0 {
primes.push((dn, e));
}
d += 2;
}
(primes, c)
}
fn product_pow(primes: &[(Int, u32)]) -> Int {
let mut f = Int::ONE;
for (p, e) in primes {
f = f.mul(&p.pow(*e));
}
f
}
fn product_pow_from(factors: &[FactorWitness]) -> Int {
let mut f = Int::ONE;
for fw in factors {
f = f.mul(&fw.prime.pow(fw.exp));
}
f
}
enum Disc {
Prime,
Composite,
}
fn bls_discriminant(n: &Int, f: &Int, r: &Int) -> Disc {
let (s, t) = r.div_rem_trunc(f); let disc = t.mul(&t).sub(&s.mul(&Int::from(4)));
if disc.is_negative() {
return Disc::Prime; }
let root = match disc.sqrt_exact() {
Some(root) => root,
None => return Disc::Prime, };
let num = t.sub(&root); if num.is_odd() || num.is_negative() {
return Disc::Prime; }
let a1 = num.div_trunc(&Int::from(2));
if a1.is_zero() {
return Disc::Prime; }
let cand = a1.mul(f).add(&Int::ONE); if cand > Int::ONE && &cand < n && cand.divides(n) {
return Disc::Composite;
}
Disc::Prime
}
fn verify_cert(cert: &PrimalityCertificate) -> bool {
let n = &cert.n;
if n < &Int::from(2) {
return false;
}
match &cert.kind {
Kind::SmallBpsw => n.to_u64().is_some() && n.is_prime_bpsw(),
Kind::NMinusOne {
factors,
cofactor,
bound,
} => verify_n_minus_1(n, factors, cofactor, *bound),
}
}
fn verify_n_minus_1(n: &Int, factors: &[FactorWitness], cofactor: &Int, bound: Bound) -> bool {
if factors.is_empty() || cofactor < &Int::ONE {
return false;
}
let m = n.sub(&Int::ONE);
let f = product_pow_from(factors);
if f.mul(cofactor) != m {
return false;
}
if f.gcd(cofactor) != Int::ONE {
return false;
}
let f2 = f.mul(&f);
match bound {
Bound::Sqrt => {
if f2 <= *n {
return false;
}
}
Bound::Cbrt => {
if f2.mul(&f) <= *n {
return false;
}
}
}
for fw in factors {
match &fw.proof {
PrimeProof::Small => {
if fw.prime.to_u64().is_none() || !fw.prime.is_prime_bpsw() {
return false;
}
}
PrimeProof::Recursive(sub) => {
if sub.n() != &fw.prime || !sub.verify(&fw.prime) {
return false;
}
}
}
let qe = fw.prime.pow(fw.exp);
if !qe.divides(&m) {
return false;
}
if fw.witness < Int::from(2) {
return false;
}
if fw.witness.modpow(&m, n) != Int::ONE {
return false;
}
let e = m.div_trunc(&fw.prime);
let x = fw.witness.modpow(&e, n);
if x.sub(&Int::ONE).gcd(n) != Int::ONE {
return false;
}
}
if bound == Bound::Cbrt && matches!(bls_discriminant(n, &f, cofactor), Disc::Composite) {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tampered_certificate_is_rejected() {
let n = Int::from(2).pow(89).sub(&Int::ONE);
let cert = match prove_prime(&n) {
Primality::Prime(c) => c,
other => panic!("expected a proof, got {other:?}"),
};
assert!(cert.verify(&n));
let mut bad = cert.clone();
if let Kind::NMinusOne { factors, .. } = &mut bad.kind {
factors[0].witness = factors[0].witness.add(&Int::ONE);
}
assert!(!bad.verify(&n), "bumped witness must fail");
let mut bad = cert.clone();
if let Kind::NMinusOne { factors, .. } = &mut bad.kind {
factors[0].exp += 1;
}
assert!(!bad.verify(&n), "wrong exponent must fail");
let mut bad = cert.clone();
if let Kind::NMinusOne { cofactor, .. } = &mut bad.kind {
*cofactor = cofactor.add(&Int::ONE);
}
assert!(!bad.verify(&n), "wrong cofactor must fail");
let mut bad = cert.clone();
if let Kind::NMinusOne { factors, .. } = &mut bad.kind {
factors.pop();
}
assert!(!bad.verify(&n), "shrunken F must fail");
assert!(!cert.verify(&n.add(&Int::from(2))), "wrong n must fail");
}
#[test]
fn forged_bound_label_is_rejected() {
let n = Int::from(2).pow(127).sub(&Int::ONE); let cert = match prove_prime(&n) {
Primality::Prime(c) => c,
other => panic!("expected a proof, got {other:?}"),
};
assert_eq!(cert.bound(), Some(Bound::Cbrt));
let mut bad = cert.clone();
if let Kind::NMinusOne { bound, .. } = &mut bad.kind {
*bound = Bound::Sqrt;
}
assert!(!bad.verify(&n), "F² ≤ n, so the √ label is a lie");
}
}