logicaffeine-proof 0.10.1

Backward-chaining proof engine (certified SAT/CDCL, tactics, Socratic hints) plus the number-theory / cryptanalysis substrate (factoring, isogeny, lattice, order-finding)
Documentation
//! # The symmetry-collapse reach map — one executable proof of reach
//!
//! This integration test is the "tool, not claim" artifact: a single green run that exercises the whole
//! validated chain of the symmetry-breaking cryptanalysis campaign, and marks — honestly — where the wins
//! stop and the walls begin. Everything here re-checks from scratch through the public API.
//!
//! - **WIN (lattice):** CDPR short-generator recovery — we recover the secret on Soliloquy/SV-shape.
//! - **WIN (isogeny):** images → generator recovery at SIDH scale, and the keyspace collapses into Aut-orbits.
//! - **MECHANISM:** the matched-pair (2,2)-gluing (`#Jac = #E₁·#E₂`), genus-2 Jacobian arithmetic
//!   (`#Jac·D = 0`), and the Richelot dual as a genuine isogeny (equal order, Tate).
//! - **WALL (Module-LWE / ML-KEM):** the log-unit decoding scale grows with `n`, and short secrets sit
//!   inside the cheap rung — so the collapse does *not* reach ML-KEM; its defense is upstream. Quantified,
//!   not claimed.

use logicaffeine_proof::cyclotomic::{
    approximation_scale, cyclotomic_units, recover_short_generator, recovery_margin, Cyclo,
};
use logicaffeine_proof::factor::{mod_inverse, modpow};
use logicaffeine_proof::fp2::{
    derive_isogeny_path2, fp2_const, full_order_basis2, kernel_generator2, keyspace_codomain_classes,
    push_through2, recover_secret2, Curve2,
};
use logicaffeine_proof::hyperelliptic::{
    cantor_add, count_curve_fp, genus2_jacobian_order, genus2_jacobian_order_general, glue_shared_2torsion,
    jac_identity, jac_scalar_mul, poly_eval, poly_mul, richelot, Mumford,
};
use logicaffeine_base::BigInt;

fn bi(x: i64) -> BigInt {
    BigInt::from_i64(x)
}

// ── WIN (lattice): CDPR strips the unit and recovers the short principal-ideal generator. ──
#[test]
fn reach_win_cdpr_recovers_the_short_generator() {
    let n = 8;
    let secret = Cyclo::from_ints(n, &[1, -1, 0, 0, 0, 0, 0, 0]); // g = 1 − X
    let units = cyclotomic_units(n);
    let mask = units[0].mul(&units[1]); // b₃·b₅ — a genuine unit
    let public = secret.mul(&mask); // h = g·u (the long, unit-masked generator)
    assert!(public.coeff_norm() > secret.coeff_norm());
    let recovered = recover_short_generator(&public).expect("CDPR recovery");
    assert_eq!(recovered.coeff_norm(), secret.coeff_norm(), "recovered a generator as short as the secret");
    assert!(!recovered.is_unit(), "it generates the secret ideal, not R");
}

// ── WIN (isogeny): images → generator at SIDH scale, and the keyspace collapses into Aut-orbits. ──
#[test]
fn reach_win_images_to_generator_and_keyspace_collapse() {
    let p = BigInt::parse_decimal("107").unwrap();
    let e0 = Curve2::new(fp2_const(1, &p), fp2_const(0, &p), p.clone());
    let (pa, qa) = full_order_basis2(&e0, 3, 3).expect("rank-2 E[3³]");
    let (pb, qb) = full_order_basis2(&e0, 2, 2).expect("rank-2 E[2²]");
    let gen = kernel_generator2(&e0, &pa, &qa, &bi(7));
    let path = derive_isogeny_path2(&e0, &gen, 3, 3).unwrap();
    let images = (push_through2(&path, 3, &pb).unwrap(), push_through2(&path, 3, &qb).unwrap());

    let (_s, _g, rpath) =
        recover_secret2(&e0, (&pa, &qa), 3, 3, (&pb, &qb), (&images.0, &images.1)).expect("recovery");
    assert_eq!(push_through2(&rpath, 3, &pb).unwrap(), images.0, "recovered isogeny reproduces the images");

    let classes = keyspace_codomain_classes(&e0, (&pa, &qa), 3, 3);
    let total: usize = classes.iter().map(|(_, s)| s.len()).sum();
    assert_eq!(total, 27, "the whole keyspace is partitioned");
    assert!(classes.len() < 27, "and it COLLAPSES into Aut-orbits (symmetry = compression)");
}

// ── MECHANISM: the matched-pair gluing, genus-2 Jacobian arithmetic, and the Richelot dual isogeny. ──
#[test]
fn reach_mechanism_gluing_jacobian_and_richelot_dual() {
    let p = BigInt::parse_decimal("103").unwrap();

    // General matched-pair (2,2)-gluing: #Jac(C) = #E₁·#E₂ (Tate).
    let g = glue_shared_2torsion(&[bi(3), bi(7), bi(20)], &p);
    let e1 = count_curve_fp(&g.e1, &p, 2) as i128;
    let e2 = count_curve_fp(&g.e2, &p, 2) as i128;
    assert_eq!(genus2_jacobian_order(&g.sextic, &p), e1 * e2, "matched-pair gluing verified");

    // The Richelot dual is a genuine isogeny: equal Jacobian order (with the δ⁻¹ quadratic twist).
    let r = richelot(&[[bi(1), bi(0), bi(1)], [bi(2), bi(1), bi(1)], [bi(5), bi(3), bi(1)]], &p);
    let dinv = mod_inverse(&r.delta, &p).unwrap();
    let reduce = |c: &BigInt| {
        let m = c.mul(&dinv);
        let rr = m.div_rem(&p).map(|(_, r)| r).unwrap_or(m);
        if rr.is_negative() {
            rr.add(&p)
        } else {
            rr
        }
    };
    let cprime: Vec<BigInt> = r.codomain.iter().map(reduce).collect();
    assert_eq!(
        genus2_jacobian_order_general(&r.domain, &p),
        genus2_jacobian_order_general(&cprime, &p),
        "Richelot dual is genuinely isogenous"
    );

    // Genus-2 Jacobian arithmetic (Cantor): the group order kills every class, #Jac · D = 0.
    let mut f = vec![bi(1)];
    for root in [0i64, 1, 2, 3, 4] {
        f = poly_mul(&f, &[p.sub(&bi(root)), bi(1)], &p); // (x − root)
    }
    let jac = genus2_jacobian_order_general(&f, &p) as u128;
    let sqrt_exp = p.add(&bi(1)).div_rem(&bi(4)).map(|(q, _)| q).unwrap(); // p ≡ 3 mod 4
    let d = (0..103i64)
        .find_map(|x0| {
            let fx = poly_eval(&f, &bi(x0), &p);
            let y = modpow(&fx, &sqrt_exp, &p);
            (modpow(&y, &bi(2), &p) == fx && !y.is_zero())
                .then(|| Mumford { u: vec![p.sub(&bi(x0)), bi(1)], v: vec![y] })
        })
        .expect("a rational point");
    assert_eq!(jac_scalar_mul(jac, &d, &f, &p), jac_identity(), "#Jac · D = 0 — Cantor is a genuine group");
}

// ── WALL (Module-LWE / ML-KEM): the log-unit collapse does NOT reach it — quantified, not claimed. ──
#[test]
fn reach_wall_mlkem_is_upstream_of_the_collapse() {
    // The log-unit decoding factor grows with the field dimension — it would have to stay polynomial to
    // threaten Module-LWE; instead it climbs (∼2^{Õ(√n)}, CDW).
    let (s8, s64) = (approximation_scale(8), approximation_scale(64));
    assert!(s64 > s8 && s8 > 0.0, "the approximation scale grows with n: {s8} → {s64}");

    // A short secret sits INSIDE the cheap rung (margin < 1). So the collapse is not ML-KEM's defense; that
    // is upstream (no principal-ideal generator handed over, module rank ≥ 2, the approximation gap).
    let short = Cyclo::from_ints(8, &[1, -1, 0, 0, 0, 0, 0, 0]);
    assert!(recovery_margin(&short) < 1.0, "short generators are inside the wall — the rung is cheap");
}