use crate::elliptic::{torsion_basis, weil_pairing, Curve, Isogeny, Point};
use crate::factor::modpow;
use logicaffeine_base::BigInt;
#[derive(Clone, Debug)]
pub struct TorsionImageWitness {
pub domain: Curve,
pub codomain: Curve,
pub degree: u64,
pub torsion_order: u64,
pub basis: (Point, Point),
pub images: (Point, Point),
}
impl TorsionImageWitness {
pub fn verify(&self) -> bool {
let n = self.torsion_order;
let one = BigInt::from_i64(1);
let ep = match weil_pairing(&self.domain, &self.basis.0, &self.basis.1, n) {
Some(e) if e != one && modpow(&e, &BigInt::from_i64(n as i64), &self.domain.p) == one => e,
_ => return false,
};
let ord_n = BigInt::from_i64(n as i64);
for pt in [&self.images.0, &self.images.1] {
if !self.codomain.is_on_curve(pt) || self.codomain.mul(&ord_n, pt) != Point::Infinity {
return false;
}
}
match weil_pairing(&self.codomain, &self.images.0, &self.images.1, n) {
Some(eq) => eq == modpow(&ep, &BigInt::from_i64(self.degree as i64), &self.domain.p),
None => false,
}
}
}
pub fn certify_isogeny(domain: &Curve, kernel_gen: &Point, ell: u64, n: u64) -> Option<TorsionImageWitness> {
let iso = Isogeny::from_kernel(domain, kernel_gen, ell)?;
let (p, q) = torsion_basis(domain, n)?;
let images = (iso.eval(&p), iso.eval(&q));
Some(TorsionImageWitness {
domain: domain.clone(),
codomain: iso.codomain.clone(),
degree: ell,
torsion_order: n,
basis: (p, q),
images,
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SidhAudit {
ConsistentButTorsionExposed,
Inconsistent,
}
pub fn audit(w: &TorsionImageWitness) -> SidhAudit {
if w.verify() {
SidhAudit::ConsistentButTorsionExposed
} else {
SidhAudit::Inconsistent
}
}
use crate::fp2::{
aut_1728, derive_isogeny_path2, fp2_const, fp2_pow, full_order_basis2, keyspace_codomain_classes,
kernel_generator2, point_of_order2, point_order2, product_weil_pairing, push_through2, recover_secret2,
recover_secret_recursive2, torsion_basis2, weil_pairing2, Curve2, Fp2, IsogenyStep2, Isogeny2, Point2,
};
#[derive(Clone, Debug)]
pub struct KaniGlue {
pub e1: Curve2,
pub e2: Curve2,
pub degree: u64,
pub torsion_order: u64,
pub basis: (Point2, Point2),
pub images: (Point2, Point2),
}
impl KaniGlue {
pub fn glue_pairing(&self) -> Option<Fp2> {
product_weil_pairing(
&self.e1,
&self.e2,
(&self.basis.0, &self.images.0),
(&self.basis.1, &self.images.1),
self.torsion_order,
)
}
pub fn verify(&self) -> bool {
let ep = match weil_pairing2(&self.e1, &self.basis.0, &self.basis.1, self.torsion_order) {
Some(e) => e,
None => return false,
};
match self.glue_pairing() {
Some(glue) => glue == fp2_pow(&ep, 1 + self.degree, &self.e1.p),
None => false,
}
}
pub fn is_lagrangian(&self) -> bool {
self.glue_pairing().map_or(false, |g| g == fp2_const(1, &self.e1.p))
}
}
pub fn build_kani_glue(domain: &Curve2, kernel_gen: &Point2, ell: u64, n: u64) -> Option<KaniGlue> {
let iso = Isogeny2::from_kernel(domain, kernel_gen, ell)?;
let (p, q) = torsion_basis2(domain, n)?;
let images = (iso.eval(&p), iso.eval(&q));
Some(KaniGlue { e1: domain.clone(), e2: iso.codomain.clone(), degree: ell, torsion_order: n, basis: (p, q), images })
}
pub fn is_smooth(mut n: u64, bound: u64) -> bool {
if n == 0 {
return false;
}
let mut f = 2u64;
while f * f <= n {
while n % f == 0 {
n /= f;
}
f += 1;
}
n == 1 || n <= bound
}
pub fn kani_diamond_degrees(d: u64, max_e: u32, smooth_bound: u64) -> Option<(u32, u64)> {
(1..=max_e).find_map(|e| {
let n = 1u64 << e;
(n > d + 1).then(|| n - d).filter(|&c| is_smooth(c, smooth_bound)).map(|c| (e, c))
})
}
#[derive(Clone, Debug)]
pub struct KaniDiamond {
pub e0: Curve2,
pub e_curve: Curve2,
pub c_curve: Curve2,
pub d: u64,
pub c: u64,
pub e: u32,
}
pub fn build_kani_diamond(
e0: &Curve2,
phi_gen: &Point2,
ell_phi: u64,
a: u32,
gamma_gen: &Point2,
c: u64,
) -> Option<KaniDiamond> {
let d = ell_phi.pow(a);
let n = d + c;
if !n.is_power_of_two() {
return None;
}
let phi = derive_isogeny_path2(e0, phi_gen, ell_phi, a)?;
let gamma = Isogeny2::from_kernel(e0, gamma_gen, c)?;
Some(KaniDiamond {
e0: e0.clone(),
e_curve: phi.last()?.codomain.clone(),
c_curve: gamma.codomain,
d,
c,
e: n.trailing_zeros(),
})
}
#[derive(Clone, Debug, PartialEq)]
pub enum RecoveryLaw {
GeneratorOrder { ell: u64, a: u32 },
DescendingKernels { ell: u64 },
ImagesReproduced,
KeyspaceTreePartition { ell: u64, a: u32 },
AutOrbitClosure,
}
impl RecoveryLaw {
pub fn statement(&self) -> String {
match self {
RecoveryLaw::GeneratorOrder { ell, a } => format!("ord(gen) = {ell}^{a} on E₀"),
RecoveryLaw::DescendingKernels { ell } => {
format!("each of the a chain steps quotients an order-{ell} subgroup; the chain is connected")
}
RecoveryLaw::ImagesReproduced => "φ(P_B), φ(Q_B) = the published torsion images".into(),
RecoveryLaw::KeyspaceTreePartition { ell, a } => {
format!("the {ell}-adic keyspace tree (depth {a}) recovers the same secret as flat search")
}
RecoveryLaw::AutOrbitClosure => "E₀/⟨K⟩ ≅ E₀/⟨ι(K)⟩ — recovering one kernel recovers its orbit".into(),
}
}
}
#[derive(Clone, Debug)]
pub struct SecretRecovery {
pub e0: Curve2,
pub basis_a: (Point2, Point2),
pub basis_b: (Point2, Point2),
pub images: (Point2, Point2),
pub ell: u64,
pub a: u32,
pub secret: BigInt,
pub generator: Point2,
pub path: Vec<IsogenyStep2>,
pub laws: Vec<RecoveryLaw>,
}
impl SecretRecovery {
pub fn learnings(&self) -> Vec<String> {
self.laws.iter().map(RecoveryLaw::statement).collect()
}
pub fn verify(&self) -> bool {
let n = self.ell.pow(self.a);
let (pa, qa) = (&self.basis_a.0, &self.basis_a.1);
let (pb, qb) = (&self.basis_b.0, &self.basis_b.1);
if kernel_generator2(&self.e0, pa, qa, &self.secret) != self.generator {
return false;
}
for law in &self.laws {
let ok = match law {
RecoveryLaw::GeneratorOrder { ell, a } => {
let m = ell.pow(*a);
point_order2(&self.e0, &self.generator, m + 1) == Some(m)
}
RecoveryLaw::DescendingKernels { ell } => {
self.path.len() as u32 == self.a
&& self.path.iter().enumerate().all(|(k, st)| {
let order_ok = point_order2(&st.domain, &st.kernel, *ell + 1) == Some(*ell);
let connected = k == 0 || st.domain == self.path[k - 1].codomain;
order_ok && connected
})
}
RecoveryLaw::ImagesReproduced => {
push_through2(&self.path, self.ell, pb).as_ref() == Some(&self.images.0)
&& push_through2(&self.path, self.ell, qb).as_ref() == Some(&self.images.1)
}
RecoveryLaw::KeyspaceTreePartition { ell, a } => {
recover_secret_recursive2(&self.e0, (pa, qa), *ell, *a, (pb, qb), (&self.images.0, &self.images.1))
.and_then(|(_, _s, g)| {
let path = derive_isogeny_path2(&self.e0, &g, *ell, *a)?;
Some(
push_through2(&path, *ell, pb).as_ref() == Some(&self.images.0)
&& push_through2(&path, *ell, qb).as_ref() == Some(&self.images.1),
)
})
.unwrap_or(false)
}
RecoveryLaw::AutOrbitClosure => {
let j = |g: &Point2| {
derive_isogeny_path2(&self.e0, g, self.ell, self.a)
.and_then(|p| p.last().and_then(|st| st.codomain.j_invariant()))
};
let ig = aut_1728(&self.e0.p, &self.generator);
point_order2(&self.e0, &ig, n + 1) == Some(n) && j(&self.generator) == j(&ig) && j(&ig).is_some()
}
};
if !ok {
return false;
}
}
true
}
}
pub fn certify_recovery(
e0: &Curve2,
basis_a: (&Point2, &Point2),
ell: u64,
a: u32,
basis_b: (&Point2, &Point2),
images: (&Point2, &Point2),
) -> Option<SecretRecovery> {
let (secret, generator, path) = recover_secret2(e0, basis_a, ell, a, basis_b, images)?;
let laws = vec![
RecoveryLaw::GeneratorOrder { ell, a },
RecoveryLaw::DescendingKernels { ell },
RecoveryLaw::ImagesReproduced,
RecoveryLaw::KeyspaceTreePartition { ell, a },
RecoveryLaw::AutOrbitClosure,
];
Some(SecretRecovery {
e0: e0.clone(),
basis_a: (basis_a.0.clone(), basis_a.1.clone()),
basis_b: (basis_b.0.clone(), basis_b.1.clone()),
images: (images.0.clone(), images.1.clone()),
ell,
a,
secret,
generator,
path,
laws,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn i(x: i64) -> BigInt {
BigInt::from_i64(x)
}
#[test]
fn torsion_image_witness_verifies_and_rejects_tampering() {
use crate::elliptic::point_of_order;
let curve = Curve::new(i(2), i(25), i(43));
let kernel = point_of_order(&curve, 5).expect("𝔽₄₃ 5-torsion");
let w = certify_isogeny(&curve, &kernel, 5, 3).expect("a genuine isogeny witness");
assert!(w.verify(), "e_N(φP,φQ) = e_N(P,Q)^{{deg φ}} for a genuine isogeny");
assert_eq!(audit(&w), SidhAudit::ConsistentButTorsionExposed);
let ep = weil_pairing(&w.domain, &w.basis.0, &w.basis.1, 3).unwrap();
let eq = weil_pairing(&w.codomain, &w.images.0, &w.images.1, 3).unwrap();
assert_eq!(eq, modpow(&ep, &i(5), &w.domain.p), "the compatibility law holds numerically");
let mut bad = w.clone();
bad.images.0 = w.codomain.double(&w.images.0); assert!(!bad.verify(), "a tampered torsion image fails the pairing law");
assert_eq!(audit(&bad), SidhAudit::Inconsistent);
}
#[test]
fn kani_glue_is_lagrangian_exactly_when_deg_is_minus_one_mod_n() {
let p = BigInt::parse_decimal("59").unwrap();
let c = Curve2::new(fp2_const(1, &p), fp2_const(0, &p), p.clone());
let k5 = point_of_order2(&c, 5).expect("5-torsion");
let glue = build_kani_glue(&c, &k5, 5, 3).expect("a genuine glue kernel");
assert!(glue.verify(), "the gluing relation e_product = e_N^{{1+deg}} holds");
assert!(glue.is_lagrangian(), "deg 5 ≡ −1 (mod 3) ⟹ Lagrangian glue kernel");
assert_eq!(glue.glue_pairing().unwrap(), fp2_const(1, &p), "isotropic: the product pairing is trivial");
let k3 = point_of_order2(&c, 3).expect("3-torsion");
let glue2 = build_kani_glue(&c, &k3, 3, 5).expect("a genuine glue kernel");
assert!(glue2.verify(), "the gluing relation holds regardless of the degree");
assert!(!glue2.is_lagrangian(), "deg 3 ≢ −1 (mod 5) ⟹ not Lagrangian");
}
#[test]
fn images_to_generator_recovery_certifies_itself_and_rejects_tampering() {
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³] basis");
let (pb, qb) = full_order_basis2(&e0, 2, 2).expect("rank-2 E[2²] basis");
let secret = i(13);
let gen = kernel_generator2(&e0, &pa, &qa, &secret);
let path = derive_isogeny_path2(&e0, &gen, 3, 3).expect("the secret 3³-isogeny");
let images = (push_through2(&path, 3, &pb).unwrap(), push_through2(&path, 3, &qb).unwrap());
let rec = certify_recovery(&e0, (&pa, &qa), 3, 3, (&pb, &qb), (&images.0, &images.1))
.expect("images → generator recovery");
assert_eq!(push_through2(&rec.path, 3, &pb).unwrap(), images.0, "the recovered isogeny reproduces φ(P_B)");
assert_eq!(push_through2(&rec.path, 3, &qb).unwrap(), images.1, "and φ(Q_B) — inversion succeeded");
assert!(rec.verify(), "every law the recovery invoked re-checks from scratch");
assert_eq!(rec.laws.len(), 5, "the full law set is emitted");
assert!(rec.learnings().iter().any(|l| l.contains("ι(K)")), "the Aut-orbit rule is among the learnings");
assert_eq!(kernel_generator2(&e0, &pa, &qa, &rec.secret), rec.generator);
let _ = secret; let mut forged = rec.clone();
forged.secret = rec.secret.add(&i(1)); assert!(!forged.verify(), "a tampered secret no longer matches its generator ⟹ rejected");
}
#[test]
fn kani_degree_diamond_is_well_formed() {
assert_eq!(kani_diamond_degrees(3, 8, 100), Some((3, 5)), "3 + 5 = 2³ — smallest smooth diamond");
assert!(is_smooth(5, 100) && is_smooth(8, 3) && !is_smooth(101, 50), "smoothness gate");
let p = BigInt::parse_decimal("59").unwrap();
let e0 = Curve2::new(fp2_const(1, &p), fp2_const(0, &p), p.clone());
let k3 = point_of_order2(&e0, 3).expect("3-torsion");
let k5 = point_of_order2(&e0, 5).expect("5-torsion");
let diamond = build_kani_diamond(&e0, &k3, 3, 1, &k5, 5).expect("a well-formed Kani diamond");
assert_eq!((diamond.d, diamond.c, diamond.e), (3, 5, 3), "degrees close to a power of two");
assert_eq!(diamond.d + diamond.c, 1 << diamond.e, "c + d = 2^e — the Kani degree condition");
assert!(diamond.e_curve.j_invariant().is_some(), "φ lands on a real curve E");
assert!(diamond.c_curve.j_invariant().is_some(), "γ lands on a real curve C");
assert!(build_kani_diamond(&e0, &k3, 3, 1, &k5, 5).is_some());
assert!(build_kani_diamond(&e0, &k3, 3, 1, &k3, 3).is_none(), "3 + 3 = 6 is not a power of two");
}
}