use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::int::Int;
use crate::mod_int::ModInt;
use crate::nat::{MontCtx, Nat};
const BSGS_MAX_ORDER_BITS: u64 = 40;
const RHO_ATTEMPTS: u64 = 24;
const RHO_MONT_MIN_MODULUS_BITS: u64 = 64;
#[inline]
fn reduce(value: &Int, modulus: &Int) -> Int {
value.rem_euclid(modulus)
}
#[inline]
fn mul_mod(a: &Int, b: &Int, modulus: &Int) -> Int {
a.mul(b).rem_euclid(modulus)
}
fn prepare(
base: &Int,
target: &Int,
modulus: &Int,
order: &Int,
) -> core::result::Result<(Int, Int, Int), Option<Int>> {
let m = modulus.abs();
if m <= Int::ONE {
return Err(Some(Int::ZERO));
}
if !order.is_positive() {
return Err(None);
}
let g = reduce(base, &m);
let h = reduce(target, &m);
if h.is_one() {
return Err(Some(Int::ZERO));
}
if g.is_zero() {
return Err(if h.is_zero() { Some(Int::ONE) } else { None });
}
Ok((g, h, m))
}
fn isqrt_ceil(order: &Nat) -> Nat {
let s = order.isqrt();
if &s.mul(&s) < order {
s.add(&Nat::one())
} else {
s
}
}
pub fn bsgs(base: &Int, target: &Int, modulus: &Int, order: &Int) -> Option<Int> {
let (g, h, m) = match prepare(base, target, modulus, order) {
Ok(v) => v,
Err(done) => return done,
};
let m_steps = isqrt_ceil(&order.magnitude());
let m_steps = m_steps.to_u64().expect("bsgs: order is too large");
let m_step_int = Int::from(m_steps);
if m.is_even() {
bsgs_scalar(&g, &h, &m, order, m_steps, &m_step_int)
} else {
bsgs_mont(&g, &h, &m, order, m_steps, &m_step_int)
}
}
fn bsgs_scalar(
g: &Int,
h: &Int,
m: &Int,
order: &Int,
m_steps: u64,
m_step_int: &Int,
) -> Option<Int> {
let mut table: BTreeMap<Nat, u64> = BTreeMap::new();
let mut power = Int::ONE; for j in 0..m_steps {
table.entry(power.magnitude()).or_insert(j);
power = mul_mod(&power, g, m);
}
let g_inv = g.modinv(m)?;
let factor = g_inv.modpow(m_step_int, m);
let mut gamma = h.clone();
for i in 0..m_steps {
if let Some(&j) = table.get(&gamma.magnitude()) {
let x = Int::from(i).mul(m_step_int).add(&Int::from(j));
if &x < order {
return Some(x);
}
}
gamma = mul_mod(&gamma, &factor, m);
}
None
}
fn bsgs_mont(
g: &Int,
h: &Int,
m: &Int,
order: &Int,
m_steps: u64,
m_step_int: &Int,
) -> Option<Int> {
let ctx = MontCtx::new(&m.magnitude());
let g_m = ctx.to_mont(&g.magnitude());
let mut table: BTreeMap<Nat, u64> = BTreeMap::new();
let mut power = ctx.one().clone();
for j in 0..m_steps {
table.entry(power.clone()).or_insert(j);
power = ctx.mul(&power, &g_m);
}
let g_inv = g.modinv(m)?;
let factor = g_inv.modpow(m_step_int, m);
let factor_m = ctx.to_mont(&factor.magnitude());
let mut gamma = ctx.to_mont(&h.magnitude());
for i in 0..m_steps {
if let Some(&j) = table.get(&gamma) {
let x = Int::from(i).mul(m_step_int).add(&Int::from(j));
if &x < order {
return Some(x);
}
}
gamma = ctx.mul(&gamma, &factor_m);
}
None
}
#[derive(Clone)]
struct Walk {
x: Int,
a: Int,
b: Int,
}
fn rho_step(w: &Walk, g: &Int, h: &Int, m: &Int, order: &Int) -> Walk {
let part = w.x.rem_euclid(&Int::from(3u64));
if part.is_zero() {
Walk {
x: mul_mod(&w.x, &w.x, m),
a: w.a.add(&w.a).rem_euclid(order),
b: w.b.add(&w.b).rem_euclid(order),
}
} else if part.is_one() {
Walk {
x: mul_mod(&w.x, g, m),
a: w.a.add(&Int::ONE).rem_euclid(order),
b: w.b.clone(),
}
} else {
Walk {
x: mul_mod(&w.x, h, m),
a: w.a.clone(),
b: w.b.add(&Int::ONE).rem_euclid(order),
}
}
}
fn solve_and_verify(coeff: &Int, rhs: &Int, order: &Int, g: &Int, h: &Int, m: &Int) -> Option<Int> {
let coeff = coeff.rem_euclid(order);
let rhs = rhs.rem_euclid(order);
if coeff.is_zero() {
return None;
}
let d = coeff.gcd(order);
if !d.divides(&rhs) {
return None;
}
let coeff_d = coeff.div_exact(&d);
let rhs_d = rhs.div_exact(&d);
let order_d = order.div_exact(&d);
let inv = coeff_d.modinv(&order_d)?;
let x0 = mul_mod(&rhs_d, &inv, &order_d);
let dd = d.to_u64().unwrap_or(u64::MAX);
let mut cand = x0;
for _ in 0..dd {
if &g.modpow(&cand, m) == h {
return Some(cand);
}
cand = cand.add(&order_d);
if &cand >= order {
break;
}
}
None
}
pub fn pollard_rho(base: &Int, target: &Int, modulus: &Int, order: &Int, seed: u64) -> Option<Int> {
let (g, h, m) = match prepare(base, target, modulus, order) {
Ok(v) => v,
Err(done) => return done,
};
if m.is_even() || m.magnitude().bit_len() <= RHO_MONT_MIN_MODULUS_BITS {
pollard_rho_scalar(&g, &h, &m, order, seed)
} else {
pollard_rho_mont(&g, &h, &m, order, seed)
}
}
fn pollard_rho_scalar(g: &Int, h: &Int, m: &Int, order: &Int, seed: u64) -> Option<Int> {
let a0 = Int::from(seed).rem_euclid(order);
let b0 = Int::from(seed / 2 + 1).rem_euclid(order);
let start = Walk {
x: mul_mod(&g.modpow(&a0, m), &h.modpow(&b0, m), m),
a: a0,
b: b0,
};
let steps = isqrt_ceil(&order.magnitude());
let cap = steps.to_u64().unwrap_or(u64::MAX).saturating_mul(8).max(64);
let mut tortoise = start.clone();
let mut hare = start;
for _ in 0..cap {
tortoise = rho_step(&tortoise, g, h, m, order);
hare = rho_step(&rho_step(&hare, g, h, m, order), g, h, m, order);
if tortoise.x == hare.x {
let coeff = hare.b.sub(&tortoise.b);
let rhs = tortoise.a.sub(&hare.a);
return solve_and_verify(&coeff, &rhs, order, g, h, m);
}
}
None
}
#[derive(Clone)]
struct WalkMont {
x: Nat,
a: Int,
b: Int,
}
fn rho_step_mont(w: &WalkMont, g_m: &Nat, h_m: &Nat, ctx: &MontCtx, order: &Int) -> WalkMont {
let part = Int::from(ctx.to_residue(&w.x)).rem_euclid(&Int::from(3u64));
if part.is_zero() {
WalkMont {
x: ctx.sqr(&w.x),
a: w.a.add(&w.a).rem_euclid(order),
b: w.b.add(&w.b).rem_euclid(order),
}
} else if part.is_one() {
WalkMont {
x: ctx.mul(&w.x, g_m),
a: w.a.add(&Int::ONE).rem_euclid(order),
b: w.b.clone(),
}
} else {
WalkMont {
x: ctx.mul(&w.x, h_m),
a: w.a.clone(),
b: w.b.add(&Int::ONE).rem_euclid(order),
}
}
}
fn pollard_rho_mont(g: &Int, h: &Int, m: &Int, order: &Int, seed: u64) -> Option<Int> {
let ctx = MontCtx::new(&m.magnitude());
let g_m = ctx.to_mont(&g.magnitude());
let h_m = ctx.to_mont(&h.magnitude());
let a0 = Int::from(seed).rem_euclid(order);
let b0 = Int::from(seed / 2 + 1).rem_euclid(order);
let start_true = mul_mod(&g.modpow(&a0, m), &h.modpow(&b0, m), m);
let start = WalkMont {
x: ctx.to_mont(&start_true.magnitude()),
a: a0,
b: b0,
};
let steps = isqrt_ceil(&order.magnitude());
let cap = steps.to_u64().unwrap_or(u64::MAX).saturating_mul(8).max(64);
let mut tortoise = start.clone();
let mut hare = start;
for _ in 0..cap {
tortoise = rho_step_mont(&tortoise, &g_m, &h_m, &ctx, order);
hare = rho_step_mont(
&rho_step_mont(&hare, &g_m, &h_m, &ctx, order),
&g_m,
&h_m,
&ctx,
order,
);
if tortoise.x == hare.x {
let coeff = hare.b.sub(&tortoise.b);
let rhs = tortoise.a.sub(&hare.a);
return solve_and_verify(&coeff, &rhs, order, g, h, m);
}
}
None
}
pub fn pohlig_hellman(base: &Int, target: &Int, modulus: &Int, order: &Int) -> Option<Int> {
let (g, h, m) = match prepare(base, target, modulus, order) {
Ok(v) => v,
Err(done) => return done,
};
let g_inv = g.modinv(&m)?;
let factors = order.factor_exponents();
let mut residues: Vec<Int> = Vec::with_capacity(factors.len());
let mut moduli: Vec<Int> = Vec::with_capacity(factors.len());
for (p, e) in &factors {
let gamma = g.modpow(&order.div_exact(p), &m);
let mut xi = Int::ZERO; let mut pj = Int::ONE; for j in 0..*e {
let stripped = mul_mod(&h, &g_inv.modpow(&xi, &m), &m);
let beta = stripped.modpow(&order.div_exact(&p.pow(j + 1)), &m);
let digit = discrete_log(&gamma, &beta, &m, p)?;
xi = xi.add(&digit.mul(&pj));
pj = pj.mul(p); }
residues.push(xi);
moduli.push(pj);
}
let x = Int::crt(&residues, &moduli)?;
if g.modpow(&x, &m) == h { Some(x) } else { None }
}
pub fn discrete_log(base: &Int, target: &Int, modulus: &Int, order: &Int) -> Option<Int> {
if order.is_positive() {
let factors = order.factor_exponents();
let composite = factors.len() > 1 || factors.first().is_some_and(|(_, e)| *e > 1);
if composite && let Some(x) = pohlig_hellman(base, target, modulus, order) {
return Some(x);
}
}
if order.is_positive() && order.magnitude().bit_len() <= BSGS_MAX_ORDER_BITS {
return bsgs(base, target, modulus, order);
}
for seed in 0..RHO_ATTEMPTS {
if let Some(x) = pollard_rho(base, target, modulus, order, seed) {
return Some(x);
}
}
bsgs(base, target, modulus, order)
}
impl ModInt {
pub fn discrete_log(&self, target: &ModInt, order: &Int) -> Option<Int> {
discrete_log(&self.to_int(), &target.to_int(), &self.modulus(), order)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn brute(g: u64, h: u64, n: u64, order: u64) -> Option<u64> {
let mut acc = 1u128 % n as u128;
let (g, h, m) = (g as u128, h as u128 % n as u128, n as u128);
for x in 0..order {
if acc == h {
return Some(x);
}
acc = (acc * g) % m;
}
None
}
fn lcg(state: &mut u64) -> u64 {
*state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*state >> 33
}
#[test]
fn bsgs_scalar_and_mont_agree_and_match_brute() {
let mut state = 0x1234_5678u64;
for &n in &[101u64, 251, 1009, 4093] {
let order = n - 1;
let m_steps = isqrt_ceil(&Int::from(order).magnitude()).to_u64().unwrap();
let m_step_int = Int::from(m_steps);
for _ in 0..40 {
let g = 2 + lcg(&mut state) % (n - 2);
let h = lcg(&mut state) % n;
let (gi, hi, ni, oi) = (Int::from(g), Int::from(h), Int::from(n), Int::from(order));
let scalar = bsgs_scalar(&gi, &hi, &ni, &oi, m_steps, &m_step_int);
let mont = bsgs_mont(&gi, &hi, &ni, &oi, m_steps, &m_step_int);
assert_eq!(scalar, mont, "n={n} g={g} h={h}");
assert_eq!(
scalar.as_ref().and_then(Int::to_u64),
brute(g, h, n, order),
"n={n} g={g} h={h}"
);
}
}
}
#[test]
fn pollard_rho_scalar_and_mont_agree() {
let mut state = 0x9e37_79b9u64;
for &n in &[1019u64, 2003, 4093, 7919] {
let order = n - 1;
let (ni, oi) = (Int::from(n), Int::from(order));
for _ in 0..30 {
let g = 2 + lcg(&mut state) % (n - 2);
let x = lcg(&mut state) % order;
let gi = Int::from(g);
let h = gi.modpow(&Int::from(x), &ni);
for seed in 0..4 {
let scalar = pollard_rho_scalar(&gi, &h, &ni, &oi, seed);
let mont = pollard_rho_mont(&gi, &h, &ni, &oi, seed);
assert_eq!(scalar, mont, "n={n} g={g} x={x} seed={seed}");
if let Some(sol) = &scalar {
assert_eq!(gi.modpow(sol, &ni), h, "n={n} g={g} x={x}");
}
}
}
}
}
#[test]
fn bsgs_even_modulus_scalar_path_matches_brute() {
fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
(a, b) = (b, a % b);
}
a
}
let mut state = 0xf00d_babeu64;
for &n in &[100u64, 128, 210, 256, 512] {
let order = n;
let mut done = 0;
while done < 24 {
let g = 2 + lcg(&mut state) % (n - 2);
if gcd(g, n) != 1 {
continue;
}
let h = lcg(&mut state) % n;
let got = bsgs(
&Int::from(g),
&Int::from(h),
&Int::from(n),
&Int::from(order),
);
assert_eq!(
got.and_then(|v| v.to_u64()),
brute(g, h, n, order),
"n={n} g={g} h={h}"
);
done += 1;
}
}
}
}
#[cfg(all(test, feature = "std"))]
mod bench {
use super::*;
use core::str::FromStr;
use std::println;
fn now() -> std::time::Instant {
std::time::Instant::now()
}
fn primes() -> [(&'static str, Int); 3] {
[
("64-bit", Int::from(18446744073709551557u64)),
(
"128-bit",
Int::from_str("340282366920938463463374607431768211297").unwrap(),
),
(
"256-bit",
Int::from_str(
"115792089237316195423570985008687907853269984665640564039457584007913129639747",
)
.unwrap(),
),
]
}
#[test]
#[ignore = "micro-benchmark; run in --release with --ignored"]
fn bench_bsgs_scalar_vs_mont() {
let g = Int::from(2u64);
for order_bits in [20u32, 30, 40] {
let order = Int::from(2u64).pow(order_bits);
let m_steps = isqrt_ceil(&order.magnitude()).to_u64().unwrap();
let m_step_int = Int::from(m_steps);
for (name, p) in primes() {
let x = order.sub(&Int::ONE); let g_r = reduce(&g, &p);
let h = g_r.modpow(&x, &p);
let h_r = reduce(&h, &p);
let t = now();
let a = bsgs_scalar(&g_r, &h_r, &p, &order, m_steps, &m_step_int);
let scalar = t.elapsed();
let t = now();
let b = bsgs_mont(&g_r, &h_r, &p, &order, m_steps, &m_step_int);
let mont = t.elapsed();
assert_eq!(a, b);
println!(
"bsgs order=2^{order_bits} mod={name:>8} scalar={:>10.3?} mont={:>10.3?} speedup={:.2}x",
scalar,
mont,
scalar.as_secs_f64() / mont.as_secs_f64(),
);
}
}
}
#[test]
#[ignore = "micro-benchmark; run in --release with --ignored"]
fn bench_rho_scalar_vs_mont() {
let g = Int::from(2u64);
for order_bits in [24u32, 30, 36] {
let order = Int::from(2u64).pow(order_bits);
for (name, p) in primes() {
let x = Int::from(1234567u64).rem_euclid(&order);
let g_r = reduce(&g, &p);
let h = g_r.modpow(&x, &p);
let h_r = reduce(&h, &p);
let t = now();
let a = pollard_rho_scalar(&g_r, &h_r, &p, &order, 3);
let scalar = t.elapsed();
let t = now();
let b = pollard_rho_mont(&g_r, &h_r, &p, &order, 3);
let mont = t.elapsed();
assert_eq!(a, b);
println!(
"rho order=2^{order_bits} mod={name:>8} scalar={:>10.3?} mont={:>10.3?} speedup={:.2}x",
scalar,
mont,
scalar.as_secs_f64() / mont.as_secs_f64(),
);
}
}
}
}