#![cfg(feature = "dlog")]
use puremp::dlog::{bsgs, discrete_log, pohlig_hellman, pollard_rho};
use puremp::{Int, ModInt};
fn brute(g: u64, h: u64, n: u64, order: u64) -> Option<u64> {
let mut acc = 1u64 % n;
for x in 0..order {
if acc == h % n {
return Some(x);
}
acc = (acc * g) % n;
}
None
}
fn i(v: u64) -> Int {
Int::from(v)
}
#[test]
fn bsgs_matches_brute_force() {
let (modulus, order) = (101u64, 100u64);
for target in 1..modulus {
let expected = brute(2, target, modulus, order);
let got = bsgs(&i(2), &i(target), &i(modulus), &i(order));
assert_eq!(
got.as_ref().and_then(Int::to_u64),
expected,
"target={target}"
);
}
}
#[test]
fn bsgs_known_values() {
let n = i(101);
let g = i(2);
let h = g.modpow(&i(37), &n);
assert_eq!(h, i(55));
assert_eq!(bsgs(&g, &h, &n, &i(100)), Some(i(37)));
assert_eq!(bsgs(&g, &Int::ONE, &n, &i(100)), Some(Int::ZERO));
}
#[test]
fn bsgs_least_solution() {
let n = i(13);
let g = i(5);
let h = g.modpow(&i(1), &n); assert_eq!(bsgs(&g, &h, &n, &i(12)), Some(i(1)));
}
#[test]
fn no_solution_returns_none() {
let n = i(11);
let g = i(3);
assert_eq!(brute(3, 2, 11, 5), None);
assert_eq!(bsgs(&g, &i(2), &n, &i(5)), None);
assert_eq!(discrete_log(&g, &i(2), &n, &i(5)), None);
assert_eq!(discrete_log(&i(2), &Int::ZERO, &i(101), &i(100)), None);
}
#[test]
fn pollard_rho_matches_brute_force() {
let (g, n, order) = (i(3), i(1019), i(1018));
for &e in &[1u64, 2, 17, 222, 500, 1017] {
let h = g.modpow(&i(e), &n);
let x = (0..16)
.find_map(|s| pollard_rho(&g, &h, &n, &order, s))
.expect("rho should converge within 16 seeds");
assert_eq!(g.modpow(&x, &n), h, "e={e}");
}
}
#[test]
fn discrete_log_random_roundtrip() {
let n = i(1_000_003); let order = i(1_000_002);
let mut state = 0x1234_5678u64;
let mut next = || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
state >> 33
};
for _ in 0..40 {
let g_val = 2 + next() % 1000;
let x = next() % 1_000_002;
let g = i(g_val);
let h = g.modpow(&i(x), &n);
let found = discrete_log(&g, &h, &n, &order).expect("solution exists (h = g^x)");
assert_eq!(g.modpow(&found, &n), h, "g={g_val} x={x}");
}
}
#[test]
fn discrete_log_dispatches_to_rho_for_large_order() {
let p = Int::from(1u64 << 40).next_prime();
let order = p.sub(&Int::ONE);
assert!(order.magnitude().bit_len() > 40);
let g = i(2);
let x = i(123_456_789);
let h = g.modpow(&x, &p);
let found = discrete_log(&g, &h, &p, &order).expect("rho should find a solution");
assert_eq!(g.modpow(&found, &p), h);
}
#[test]
fn pollard_rho_montgomery_path_large_modulus() {
let q = Int::from(1u64 << 20).next_prime(); let mut k = Int::from(1u64 << 45);
let mut p = q.mul(&k).add(&Int::ONE);
while !p.is_prime_bpsw() {
k = k.add(&Int::ONE);
p = q.mul(&k).add(&Int::ONE);
}
assert!(p.magnitude().bit_len() > 64);
let mut a = i(2);
let mut g = a.modpow(&k, &p);
while g.is_one() {
a = a.add(&Int::ONE);
g = a.modpow(&k, &p);
}
let x = i(700_001).rem_euclid(&q);
let h = g.modpow(&x, &p);
let found = (0..16)
.find_map(|s| pollard_rho(&g, &h, &p, &q, s))
.expect("Montgomery rho should converge over the order-q subgroup");
assert_eq!(g.modpow(&found, &p), h);
}
#[test]
fn modint_method() {
let g = ModInt::new(i(2), i(101));
let h = g.pow(&i(73));
assert_eq!(g.discrete_log(&h, &i(100)), Some(i(73)));
let g3 = ModInt::new(i(3), i(11));
let two = ModInt::new(i(2), i(11));
assert_eq!(g3.discrete_log(&two, &i(5)), None);
}
#[test]
fn degenerate_inputs() {
assert_eq!(
discrete_log(&i(7), &Int::ONE, &i(97), &i(96)),
Some(Int::ZERO)
);
assert_eq!(
discrete_log(&i(7), &i(5), &Int::ONE, &i(10)),
Some(Int::ZERO)
);
}
#[test]
fn pohlig_hellman_matches_brute_force() {
let (modulus, order) = (1009u64, 1008u64);
for e in 0..order {
let h = i(11).modpow(&i(e), &i(modulus));
let got = pohlig_hellman(&i(11), &h, &i(modulus), &i(order));
let x = got.expect("solution exists (h = g^e)");
assert_eq!(x.to_u64(), Some(e), "e={e}");
assert_eq!(i(11).modpow(&x, &i(modulus)), h);
}
}
#[test]
fn pohlig_hellman_known_and_identity() {
let (g, n, order) = (i(11), i(1009), i(1008));
let h = g.modpow(&i(555), &n);
assert_eq!(h, i(149));
let x = pohlig_hellman(&g, &h, &n, &order).unwrap();
assert_eq!(x, i(555));
assert_eq!(g.modpow(&x, &n), h);
assert_eq!(pohlig_hellman(&g, &Int::ONE, &n, &order), Some(Int::ZERO));
}
#[test]
fn pohlig_hellman_no_solution() {
assert_eq!(pohlig_hellman(&i(3), &i(2), &i(11), &i(5)), None);
assert_eq!(pohlig_hellman(&i(11), &Int::ZERO, &i(1009), &i(1008)), None);
}
#[test]
fn pohlig_hellman_smooth_advantage_large_order() {
let p = Int::from(1_288_490_188_801u64);
let order = p.sub(&Int::ONE); assert!(order.magnitude().bit_len() > 40);
let g = i(11); let x = i(987_654_321);
let h = g.modpow(&x, &p);
let found = pohlig_hellman(&g, &h, &p, &order).expect("PH must find the log");
assert_eq!(found, x);
assert_eq!(g.modpow(&found, &p), h);
let via_dispatch = discrete_log(&g, &h, &p, &order).expect("dispatch to PH");
assert_eq!(g.modpow(&via_dispatch, &p), h);
}
fn pow_mod(mut g: u64, mut e: u64, n: u64) -> u64 {
let (mut r, m) = (1u64 % n, n as u128);
g %= n;
while e > 0 {
if e & 1 == 1 {
r = ((r as u128 * g as u128) % m) as u64;
}
g = ((g as u128 * g as u128) % m) as u64;
e >>= 1;
}
r
}
fn order_mod_1009(g: u64) -> u64 {
let mut t = 1008u64;
for &p in &[2u64, 3, 7] {
while t.is_multiple_of(p) && pow_mod(g, t / p, 1009) == 1 {
t /= p;
}
}
t
}
#[test]
fn pohlig_hellman_random_roundtrips() {
let n = i(1009);
let mut state = 0x9e37_79b9u64;
let mut next = || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
state >> 33
};
for _ in 0..40 {
let g_val = 2 + next() % 1006;
let ord = order_mod_1009(g_val);
let x = next() % ord;
let g = i(g_val);
let h = g.modpow(&i(x), &n);
let found = pohlig_hellman(&g, &h, &n, &i(ord)).expect("solution exists");
assert_eq!(found, i(x), "g={g_val} x={x}");
assert_eq!(g.modpow(&found, &n), h, "g={g_val} x={x}");
}
}