use alloc::vec::Vec;
use crate::int::Int;
use crate::nat::{Nat, Reciprocal};
struct ModCtx {
n: Nat,
recip: Reciprocal,
}
impl ModCtx {
fn new(n: &Nat) -> ModCtx {
ModCtx {
n: n.clone(),
recip: Reciprocal::new(n),
}
}
fn small(&self, k: u64) -> Nat {
let v = Nat::from_u64(k);
if v >= self.n {
v.div_rem(&self.n).expect("n != 0").1
} else {
v
}
}
fn add(&self, a: &Nat, b: &Nat) -> Nat {
let s = a.add(b);
if s >= self.n {
s.checked_sub(&self.n).expect("s >= n")
} else {
s
}
}
fn sub(&self, a: &Nat, b: &Nat) -> Nat {
if a >= b {
a.checked_sub(b).expect("a >= b")
} else {
self.n
.checked_sub(&b.checked_sub(a).expect("b > a"))
.expect("difference < n")
}
}
fn mul(&self, a: &Nat, b: &Nat) -> Nat {
self.recip.reduce(&a.mul(b))
}
fn sqr(&self, a: &Nat) -> Nat {
self.recip.reduce(&a.square())
}
}
#[derive(Clone)]
struct Point {
x: Nat,
z: Nat,
}
fn x_dbl(ctx: &ModCtx, p: &Point, a24: &Nat) -> Point {
let u = ctx.add(&p.x, &p.z); let uu = ctx.sqr(&u); let v = ctx.sub(&p.x, &p.z); let vv = ctx.sqr(&v); let diff = ctx.sub(&uu, &vv); let x = ctx.mul(&uu, &vv);
let t = ctx.add(&vv, &ctx.mul(a24, &diff)); let z = ctx.mul(&diff, &t);
Point { x, z }
}
fn x_add(ctx: &ModCtx, p: &Point, q: &Point, d: &Point) -> Point {
let t1 = ctx.mul(&ctx.sub(&p.x, &p.z), &ctx.add(&q.x, &q.z)); let t2 = ctx.mul(&ctx.add(&p.x, &p.z), &ctx.sub(&q.x, &q.z)); let x = ctx.mul(&d.z, &ctx.sqr(&ctx.add(&t1, &t2)));
let z = ctx.mul(&d.x, &ctx.sqr(&ctx.sub(&t1, &t2)));
Point { x, z }
}
fn ladder(ctx: &ModCtx, k: &Nat, p: &Point, a24: &Nat) -> Point {
let mut r0 = p.clone(); let mut r1 = x_dbl(ctx, p, a24); let bits = k.bit_len();
for i in (0..bits - 1).rev() {
if k.bit(i) {
r0 = x_add(ctx, &r0, &r1, p);
r1 = x_dbl(ctx, &r1, a24);
} else {
r1 = x_add(ctx, &r0, &r1, p);
r0 = x_dbl(ctx, &r0, a24);
}
}
r0
}
enum Curve {
Ready {
a24: Nat,
base: Point,
},
Factor(Nat),
Retry,
}
fn suyama_curve(ctx: &ModCtx, sigma: &Nat) -> Curve {
let five = ctx.small(5);
let four = ctx.small(4);
let three = ctx.small(3);
let sixteen = ctx.small(16);
let s2 = ctx.sqr(sigma);
let u = ctx.sub(&s2, &five); let v = ctx.mul(&four, sigma); let u2 = ctx.sqr(&u);
let u3 = ctx.mul(&u2, &u); let v3 = ctx.mul(&ctx.sqr(&v), &v); let base = Point {
x: u3.clone(),
z: v3,
};
let vmu = ctx.sub(&v, &u);
let vmu3 = ctx.mul(&ctx.sqr(&vmu), &vmu);
let num = ctx.mul(&vmu3, &ctx.add(&ctx.mul(&three, &u), &v));
let den = ctx.mul(&sixteen, &ctx.mul(&u3, &v));
if den.is_zero() {
return Curve::Retry;
}
match Int::from(den.clone()).modinv(&Int::from(ctx.n.clone())) {
Some(inv) => {
let a24 = ctx.mul(&num, &inv.magnitude());
Curve::Ready { a24, base }
}
None => {
let g = den.gcd(&ctx.n);
if g.is_one() || g == ctx.n {
Curve::Retry
} else {
Curve::Factor(g)
}
}
}
}
fn primes_up_to(limit: u64) -> Vec<u64> {
if limit < 2 {
return Vec::new();
}
let n = limit as usize + 1;
let mut sieve = alloc::vec![true; n];
sieve[0] = false;
sieve[1] = false;
let mut i = 2usize;
while i * i < n {
if sieve[i] {
let mut j = i * i;
while j < n {
sieve[j] = false;
j += i;
}
}
i += 1;
}
(2..n).filter(|&i| sieve[i]).map(|i| i as u64).collect()
}
fn stage1_scalar(primes: &[u64], b1: u64) -> Nat {
let mut k = Nat::one();
let mut batch: u128 = 1;
for &p in primes {
if p > b1 {
break;
}
let mut pe = p;
while let Some(next) = pe.checked_mul(p) {
if next > b1 {
break;
}
pe = next;
}
if batch.saturating_mul(pe as u128) > u64::MAX as u128 {
k = k.mul(&Nat::from_u64(batch as u64));
batch = pe as u128;
} else {
batch *= pe as u128;
}
}
if batch > 1 {
k = k.mul(&Nat::from_u64(batch as u64));
}
k
}
fn nontrivial_gcd(z: &Nat, n: &Nat) -> Option<Nat> {
if z.is_zero() {
return None;
}
let g = z.gcd(n);
if g.is_one() || &g == n { None } else { Some(g) }
}
fn stage2(ctx: &ModCtx, q: &Point, a24: &Nat, primes: &[u64], b1: u64, b2: u64) -> Option<Nat> {
if b2 <= b1 {
return None;
}
let mut d = b2.isqrt();
d = d.max(2) & !1; let half = d / 2;
let mut baby: Vec<Point> = Vec::with_capacity(half as usize + 1);
baby.push(Point {
x: Nat::one(),
z: Nat::zero(),
}); baby.push(q.clone()); if half >= 2 {
baby.push(x_dbl(ctx, q, a24)); for j in 3..=half as usize {
let next = x_add(ctx, &baby[j - 1], &baby[1], &baby[j - 2]);
baby.push(next);
}
}
let i_min = b1 / d;
let i_max = b2 / d + 1;
if i_max < i_min {
return None;
}
let step = ladder(ctx, &Nat::from_u64(d), q, a24); let mut giant: Vec<Point> = Vec::with_capacity((i_max - i_min + 1) as usize);
giant.push(ladder(ctx, &Nat::from_u64(i_min * d), q, a24)); if i_max > i_min {
giant.push(ladder(ctx, &Nat::from_u64((i_min + 1) * d), q, a24)); for idx in 2..=(i_max - i_min) as usize {
let next = x_add(ctx, &giant[idx - 1], &step, &giant[idx - 2]);
giant.push(next);
}
}
let mut acc = Nat::one();
let mut progressed = false;
for &p in primes {
if p <= b1 {
continue;
}
if p > b2 {
break;
}
let i = (p + half) / d;
if i < i_min || i > i_max {
continue;
}
let id = i * d;
let j = p.abs_diff(id);
if j == 0 || j > half {
continue;
}
let g = &giant[(i - i_min) as usize];
let b = &baby[j as usize];
let cross = ctx.sub(&ctx.mul(&g.x, &b.z), &ctx.mul(&b.x, &g.z));
if !cross.is_zero() {
acc = ctx.mul(&acc, &cross);
progressed = true;
}
}
if progressed {
nontrivial_gcd(&acc, &ctx.n)
} else {
None
}
}
struct SplitMix64(u64);
impl SplitMix64 {
fn seeded(n: &Nat) -> SplitMix64 {
let mut s = 0x9E37_79B9_7F4A_7C15u64;
for limb in n.as_limbs() {
s = s.wrapping_add(*limb).wrapping_mul(0xD1B5_4A32_D192_ED03);
s ^= s >> 31;
}
SplitMix64(s ^ 0xA0761D6478BD642F)
}
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
}
fn ecm_attempt(n: &Nat, b1: u64, b2: u64, curves: u32, rng: &mut SplitMix64) -> Option<Nat> {
let ctx = ModCtx::new(n);
let primes = primes_up_to(b2.max(b1));
let k = stage1_scalar(&primes, b1);
for _ in 0..curves {
let sigma = loop {
let bits = n.bit_len();
let mut limbs = Vec::with_capacity(n.as_limbs().len());
for _ in 0..n.as_limbs().len() {
limbs.push(rng.next());
}
let mut cand = Nat::from_limbs(&limbs);
cand = cand.low_bits(bits);
if cand >= *n {
cand = cand.div_rem(n).expect("n != 0").1;
}
if cand > Nat::from_u64(5) {
break cand;
}
};
let (a24, base) = match suyama_curve(&ctx, &sigma) {
Curve::Ready { a24, base } => (a24, base),
Curve::Factor(f) => return Some(f),
Curve::Retry => continue,
};
let q = ladder(&ctx, &k, &base, &a24);
if let Some(f) = nontrivial_gcd(&q.z, n) {
return Some(f);
}
if let Some(f) = stage2(&ctx, &q, &a24, &primes, b1, b2) {
return Some(f);
}
}
None
}
const ECM_SCHEDULE: &[(u64, u64, u32)] = &[
(2_000, 200_000, 25),
(11_000, 1_100_000, 90),
(50_000, 5_000_000, 300),
(250_000, 25_000_000, 700),
];
pub(crate) fn ecm_factor(n: &Nat) -> Option<Nat> {
let mut rng = SplitMix64::seeded(n);
for &(b1, b2, curves) in ECM_SCHEDULE {
if let Some(f) = ecm_attempt(n, b1, b2, curves, &mut rng) {
return Some(f);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn prime_at_least(start: u64) -> Nat {
let mut c = start | 1;
loop {
let v = Nat::from_u64(c);
if v.is_prime_bpsw() {
return v;
}
c += 2;
}
}
fn assert_splits(p: &Nat, q: &Nat) {
let composite = p.mul(q);
let f = ecm_factor(&composite).expect("ECM finds a factor");
assert!(f == *p || f == *q, "factor {f:?} is one of the two primes");
let (cof, r) = composite.div_rem(&f).expect("f != 0");
assert!(r.is_zero(), "factor divides n");
assert!(cof == *p || cof == *q, "cofactor is the other prime");
}
#[test]
fn splits_semiprimes_beyond_rho() {
assert_splits(
&prime_at_least(9_999_999_001),
&prime_at_least(8_888_888_881),
);
assert_splits(
&prime_at_least(50_000_000_021),
&prime_at_least(70_000_000_027),
);
}
#[test]
fn suyama_denominator_hit_is_a_factor() {
let p = prime_at_least(1_000_003);
let q = prime_at_least(2_000_003);
assert_splits(&p, &q);
}
}