use logicaffeine_base::describe;
use crate::cdcl::{Lit, Var};
use crate::proof::Perm;
use std::collections::BTreeSet;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Descriptor {
IntSeq { encoded: Vec<u8> },
BooleanFunction { num_vars: usize, tree: StructureTree },
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DescriptionBound {
pub object_hash: u64,
pub bytes: usize,
pub descriptor: Descriptor,
}
impl DescriptionBound {
pub fn of_int_seq(v: &[i64]) -> DescriptionBound {
let encoded = describe::describe_int_seq(v);
DescriptionBound { object_hash: hash_ints(v), bytes: encoded.len(), descriptor: Descriptor::IntSeq { encoded } }
}
pub fn of_boolean(truth: &[bool]) -> Option<DescriptionBound> {
let num_vars = truth.len().trailing_zeros() as usize;
let tree = structure_tree(truth)?;
let bytes = tree.total_description_bits().div_ceil(8).max(1);
Some(DescriptionBound {
object_hash: hash_bools(truth),
bytes,
descriptor: Descriptor::BooleanFunction { num_vars, tree },
})
}
pub fn verify(&self) -> bool {
match &self.descriptor {
Descriptor::IntSeq { encoded } => {
encoded.len() == self.bytes
&& describe::decode_int_seq(encoded).map_or(false, |v| hash_ints(&v) == self.object_hash)
}
Descriptor::BooleanFunction { num_vars, tree } => {
tree.total_description_bits().div_ceil(8).max(1) == self.bytes
&& tree.reconstruct().map_or(false, |t| {
t.len() == (1usize << num_vars) && hash_bools(&t) == self.object_hash
})
}
}
}
}
fn hash_bools(v: &[bool]) -> u64 {
const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0000_0100_0000_01b3;
let mut h = OFFSET;
let mut mix = |b: u8| {
h ^= b as u64;
h = h.wrapping_mul(PRIME);
};
for b in (v.len() as u64).to_le_bytes() {
mix(b);
}
for &x in v {
mix(x as u8);
}
h
}
fn hash_ints(v: &[i64]) -> u64 {
const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0000_0100_0000_01b3;
let mut h = OFFSET;
let mut mix = |bytes: [u8; 8]| {
for b in bytes {
h ^= b as u64;
h = h.wrapping_mul(PRIME);
}
};
mix((v.len() as u64).to_le_bytes());
for &x in v {
mix(x.to_le_bytes());
}
h
}
pub const GROUP_DECODER_OVERHEAD: usize = 8;
#[derive(Clone, Debug)]
pub struct StructuralBound {
pub num_vars: usize,
pub whole: DescriptionBound,
pub rep: DescriptionBound,
pub gens: DescriptionBound,
pub group_entropy_bits: f64,
}
impl StructuralBound {
pub fn group_bytes(&self) -> usize {
self.rep.bytes + self.gens.bytes + GROUP_DECODER_OVERHEAD
}
pub fn is_compression(&self) -> bool {
self.group_bytes() < self.whole.bytes
}
pub fn best_bytes(&self) -> usize {
self.whole.bytes.min(self.group_bytes())
}
pub fn verify(&self) -> bool {
if !(self.whole.verify() && self.rep.verify() && self.gens.verify()) {
return false;
}
let (nv_f, f_clauses) = match decode_cnf(&self.whole.descriptor) {
Some(x) => x,
None => return false,
};
let (_, rep_clauses) = match decode_cnf(&self.rep.descriptor) {
Some(x) => x,
None => return false,
};
let (nv_g, gens) = match decode_gens(&self.gens.descriptor) {
Some(x) => x,
None => return false,
};
if nv_f != self.num_vars || nv_g != self.num_vars {
return false;
}
if !gens.iter().all(|p| crate::symmetry_detect::perm_is_automorphism(&f_clauses, p)) {
return false;
}
reconstructs(&f_clauses, &rep_clauses, &gens)
}
}
fn decode_cnf(d: &Descriptor) -> Option<(usize, Vec<Vec<Lit>>)> {
let Descriptor::IntSeq { encoded } = d else { return None };
unflatten_cnf(&describe::decode_int_seq(encoded)?)
}
fn decode_gens(d: &Descriptor) -> Option<(usize, Vec<Perm>)> {
let Descriptor::IntSeq { encoded } = d else { return None };
unflatten_gens(&describe::decode_int_seq(encoded)?)
}
pub fn structural_bound(num_vars: usize, clauses: &[Vec<Lit>], generators: &[Perm]) -> Option<StructuralBound> {
if !generators.iter().all(|g| crate::symmetry_detect::perm_is_automorphism(clauses, g)) {
return None;
}
let orbits = crate::hypercube::clause_orbits(clauses, generators);
let rep_clauses: Vec<Vec<Lit>> = orbits.iter().filter_map(|o| o.first().map(|&i| clauses[i].clone())).collect();
if !reconstructs(clauses, &rep_clauses, generators) {
return None;
}
Some(StructuralBound {
num_vars,
whole: DescriptionBound::of_int_seq(&flatten_cnf(num_vars, clauses)),
rep: DescriptionBound::of_int_seq(&flatten_cnf(num_vars, &rep_clauses)),
gens: DescriptionBound::of_int_seq(&flatten_gens(num_vars, generators)),
group_entropy_bits: crate::hypercube::symmetry_entropy_bits(num_vars, clauses),
})
}
fn reconstructs(f_clauses: &[Vec<Lit>], rep_clauses: &[Vec<Lit>], gens: &[Perm]) -> bool {
let f_keys = clause_set(f_clauses);
let rep_keys = clause_set(rep_clauses);
if !rep_keys.is_subset(&f_keys) {
return false; }
let orbits = crate::hypercube::clause_orbits(f_clauses, gens);
orbits.iter().all(|orbit| {
orbit.iter().any(|&i| rep_keys.contains(&crate::symmetry_detect::clause_key(&f_clauses[i])))
})
}
fn clause_set(clauses: &[Vec<Lit>]) -> BTreeSet<Vec<u32>> {
clauses.iter().map(|c| crate::symmetry_detect::clause_key(c)).collect()
}
fn lit_code(l: Lit) -> i64 {
(l.var() as i64) * 2 + if l.is_positive() { 0 } else { 1 }
}
fn lit_from_code(code: i64) -> Option<Lit> {
if code < 0 {
return None;
}
Some(Lit::new((code / 2) as Var, code % 2 == 0))
}
fn flatten_cnf(num_vars: usize, clauses: &[Vec<Lit>]) -> Vec<i64> {
let mut out = vec![num_vars as i64, clauses.len() as i64];
for c in clauses {
out.push(c.len() as i64);
for &l in c {
out.push(lit_code(l));
}
}
out
}
fn unflatten_cnf(flat: &[i64]) -> Option<(usize, Vec<Vec<Lit>>)> {
let mut it = flat.iter().copied();
let num_vars = usize::try_from(it.next()?).ok()?;
let num_clauses = usize::try_from(it.next()?).ok()?;
let mut clauses = Vec::with_capacity(num_clauses.min(4096));
for _ in 0..num_clauses {
let len = usize::try_from(it.next()?).ok()?;
let mut c = Vec::with_capacity(len.min(4096));
for _ in 0..len {
c.push(lit_from_code(it.next()?)?);
}
clauses.push(c);
}
if it.next().is_some() {
return None; }
Some((num_vars, clauses))
}
fn flatten_gens(num_vars: usize, gens: &[Perm]) -> Vec<i64> {
let mut out = vec![gens.len() as i64, num_vars as i64];
for g in gens {
for v in 0..num_vars {
out.push(lit_code(g.apply(Lit::pos(v as Var))));
}
}
out
}
fn unflatten_gens(flat: &[i64]) -> Option<(usize, Vec<Perm>)> {
let mut it = flat.iter().copied();
let num_gens = usize::try_from(it.next()?).ok()?;
let num_vars = usize::try_from(it.next()?).ok()?;
let mut gens = Vec::with_capacity(num_gens.min(4096));
for _ in 0..num_gens {
let mut images = Vec::with_capacity(num_vars.min(4096));
for _ in 0..num_vars {
images.push(lit_from_code(it.next()?)?);
}
gens.push(Perm::from_images(images));
}
if it.next().is_some() {
return None;
}
Some((num_vars, gens))
}
const RIGIDITY_MAX_VARS: usize = 64;
#[derive(Clone, Copy, Debug)]
pub struct Budget {
pub max_gaussian_dim: usize,
}
impl Budget {
pub fn standard() -> Budget {
Budget { max_gaussian_dim: RIGIDITY_MAX_VARS }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Refusal {
OverBudgetGaussian { dim: usize, cap: usize },
NoLinearStructure,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LinearRigidityCert {
pub num_vars: usize,
pub rows: Vec<u64>,
pub rank: usize,
pub kernel_basis: Vec<Vec<bool>>,
pub solution_count_log2: u32,
}
pub fn certify_linear_rigidity(num_vars: usize, clauses: &[Vec<Lit>]) -> Option<LinearRigidityCert> {
certify_linear_rigidity_within(num_vars, clauses, &Budget::standard()).ok()
}
pub fn certify_linear_rigidity_within(
num_vars: usize,
clauses: &[Vec<Lit>],
budget: &Budget,
) -> Result<LinearRigidityCert, Refusal> {
if num_vars > budget.max_gaussian_dim {
return Err(Refusal::OverBudgetGaussian { dim: num_vars, cap: budget.max_gaussian_dim });
}
let eqs = crate::lyapunov::extract_xor(num_vars, clauses);
if eqs.is_empty() {
return Err(Refusal::NoLinearStructure);
}
let rows = eqs_to_rows(&eqs, num_vars);
let sol = crate::gf2::solve_gf2(num_vars, &rows, &vec![false; rows.len()]).ok_or(Refusal::NoLinearStructure)?;
let rank = num_vars - sol.kernel_basis.len();
Ok(LinearRigidityCert {
num_vars,
rows,
rank,
solution_count_log2: sol.kernel_basis.len() as u32,
kernel_basis: sol.kernel_basis,
})
}
pub fn check_linear_rigidity(cert: &LinearRigidityCert, clauses: &[Vec<Lit>]) -> bool {
if cert.num_vars > RIGIDITY_MAX_VARS {
return false;
}
let eqs = crate::lyapunov::extract_xor(cert.num_vars, clauses);
if eqs.is_empty() {
return false;
}
let rows = eqs_to_rows(&eqs, cert.num_vars);
let recovered: BTreeSet<u64> = rows.iter().copied().collect();
let claimed: BTreeSet<u64> = cert.rows.iter().copied().collect();
if recovered != claimed {
return false;
}
let sol = match crate::gf2::solve_gf2(cert.num_vars, &rows, &vec![false; rows.len()]) {
Some(s) => s,
None => return false,
};
let true_kernel_dim = sol.kernel_basis.len();
if cert.rank != cert.num_vars - true_kernel_dim
|| cert.solution_count_log2 as usize != true_kernel_dim
|| cert.kernel_basis.len() != true_kernel_dim
{
return false;
}
for v in &cert.kernel_basis {
if v.len() != cert.num_vars || rows.iter().any(|&row| gf2_dot(row, v)) {
return false;
}
}
independent_gf2(&cert.kernel_basis)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LinearStructureCert {
pub num_vars: usize,
pub num_xor_eqs: usize,
pub rank: usize,
pub kernel_dim: usize,
}
pub fn certify_linear_structure(num_vars: usize, clauses: &[Vec<Lit>]) -> Option<LinearStructureCert> {
let eqs = crate::lyapunov::extract_xor(num_vars, clauses);
if eqs.is_empty() {
return None;
}
let inc = crate::xor_engine::IncXor::new(num_vars, &eqs);
Some(LinearStructureCert {
num_vars,
num_xor_eqs: eqs.len(),
rank: inc.rank(),
kernel_dim: inc.kernel_dim(),
})
}
pub fn check_linear_structure(cert: &LinearStructureCert, clauses: &[Vec<Lit>]) -> bool {
let eqs = crate::lyapunov::extract_xor(cert.num_vars, clauses);
if eqs.len() != cert.num_xor_eqs {
return false;
}
let inc = crate::xor_engine::IncXor::new(cert.num_vars, &eqs);
inc.rank() == cert.rank && inc.kernel_dim() == cert.kernel_dim
}
#[derive(Clone, Debug)]
pub enum LinearShortcut {
None { rigidity: Option<LinearRigidityCert>, structure: LinearStructureCert },
NoLinearStructure,
}
pub fn linear_shortcut_verdict(num_vars: usize, clauses: &[Vec<Lit>]) -> LinearShortcut {
match certify_linear_structure(num_vars, clauses) {
None => LinearShortcut::NoLinearStructure,
Some(structure) => {
let rigidity = certify_linear_rigidity(num_vars, clauses);
LinearShortcut::None { rigidity, structure }
}
}
}
const GATE_SYMMETRY_MAX_VARS: usize = 48;
pub fn incompressibility_gate(num_vars: usize, clauses: &[Vec<Lit>]) -> Option<LinearStructureCert> {
let structure = certify_linear_structure(num_vars, clauses)?;
if num_vars > GATE_SYMMETRY_MAX_VARS {
return None; }
if crate::hypercube::symmetry_entropy_bits(num_vars, clauses) == 0.0 {
Some(structure) } else {
None
}
}
fn eqs_to_rows(eqs: &[crate::xorsat::XorEquation], num_vars: usize) -> Vec<u64> {
eqs.iter()
.map(|e| e.vars.iter().filter(|&&v| v < num_vars.min(64)).fold(0u64, |m, &v| m | (1u64 << v)))
.collect()
}
fn gf2_dot(row: u64, v: &[bool]) -> bool {
let mut acc = false;
let mut r = row;
while r != 0 {
let i = r.trailing_zeros() as usize;
if i < v.len() {
acc ^= v[i];
}
r &= r - 1;
}
acc
}
pub fn incompressible_string_exists(n: u32) -> Option<crate::pigeonhole::CountingCert> {
if n == 0 || n > 127 {
return None; }
let strings: u128 = 1u128 << n;
let shorter_programs: u128 = strings - 1; crate::pigeonhole::certify_pigeonhole_unsat(strings, shorter_programs)
}
pub fn certified_incompressible_function_exists(n: u32) -> Option<crate::pigeonhole::CountingCert> {
if n == 0 || n > 6 {
return None;
}
incompressible_string_exists(1u32 << n)
}
#[derive(Clone, Debug)]
pub enum CryptoStrength {
Weak { witness: DescriptionBound, ratio: f64 },
IncompressibleInClass { ratio: f64 },
}
pub fn incompressibility_ratio(data: &[u8]) -> f64 {
if data.is_empty() {
return 1.0;
}
let ints: Vec<i64> = data.iter().map(|&b| b as i64).collect();
describe::describe_int_seq(&ints).len() as f64 / data.len() as f64
}
pub fn gf256_word_complexity(data: &[u8]) -> usize {
let elems: Vec<describe::Gf256> = data.iter().map(|&b| describe::Gf256(b)).collect();
describe::berlekamp_massey_field(&elems).0
}
pub fn two_adic_complexity_of_bytes(data: &[u8]) -> usize {
let bits: Vec<bool> = data.iter().flat_map(|&b| (0..8).map(move |j| (b >> j) & 1 == 1)).collect();
describe::two_adic_complexity(&bits)
}
pub fn maximal_order_complexity_of_bytes(data: &[u8]) -> usize {
let bits: Vec<bool> = data.iter().flat_map(|&b| (0..8).map(move |j| (b >> j) & 1 == 1)).collect();
describe::maximal_order_complexity(&bits)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgebraicAttack {
pub order: usize,
pub degree: usize,
pub anf_terms: usize,
pub anf: Vec<bool>,
pub truth_table: usize,
}
pub fn algebraic_attack_on_bytes(data: &[u8], max_degree: usize, max_order: usize) -> Option<AlgebraicAttack> {
let bits: Vec<bool> = data.iter().flat_map(|&b| (0..8).map(move |j| (b >> j) & 1 == 1)).collect();
let cap = max_order.min(bits.len() / 2);
for l in 1..=cap {
if let Some(anf) = describe::detect_algebraic_recurrence(&bits, l, max_degree) {
return Some(AlgebraicAttack {
order: l,
degree: max_degree,
anf_terms: anf.iter().filter(|&&c| c).count().max(1),
anf,
truth_table: 1usize.checked_shl(l as u32).unwrap_or(usize::MAX),
});
}
}
None
}
#[derive(Clone, Debug, PartialEq)]
pub struct CombinerLeak {
pub candidate_index: usize,
pub attack: describe::CorrelationAttack,
pub floor: f64,
pub margin: f64,
}
pub fn scan_for_combiner_leaks(keystream: &[u8], candidates: &[Vec<bool>], significance: f64) -> Vec<CombinerLeak> {
let bits: Vec<bool> = keystream.iter().flat_map(|&b| (0..8).map(move |j| (b >> j) & 1 == 1)).collect();
let mut leaks = Vec::new();
for (i, taps) in candidates.iter().enumerate() {
let Some(attack) = describe::correlation_attack(&bits, taps) else {
continue;
};
let floor = describe::spurious_bias_floor(taps.len(), attack.samples);
if floor > 0.0 && attack.bias > significance * floor {
leaks.push(CombinerLeak { candidate_index: i, margin: attack.bias / floor, floor, attack });
}
}
leaks
}
pub fn fast_correlation_attack(keystream: &[bool], taps: &[bool], max_iters: usize) -> Option<Vec<bool>> {
describe::fast_correlation_attack(keystream, taps, max_iters)
}
pub fn attack_shrinking_generator(output: &[bool], a_taps: &[bool], s_taps: &[bool]) -> Option<(Vec<bool>, Vec<bool>)> {
describe::attack_shrinking_generator(output, a_taps, s_taps)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LensCoverage {
pub lens: &'static str,
pub description_bits: usize,
}
#[derive(Clone, Debug)]
pub struct LensReport {
pub length_bits: usize,
pub coverage: Vec<LensCoverage>,
pub best_lens: &'static str,
pub best_description_bits: usize,
pub covered: bool,
}
pub fn lens_report(bits: &[bool]) -> LensReport {
let n = bits.len();
let mut coverage = Vec::new();
let lc = describe::berlekamp_massey_gf2(bits).0;
coverage.push(LensCoverage { lens: "linear (LFSR / Berlekamp–Massey)", description_bits: lc.saturating_mul(2) });
let tc = describe::two_adic_complexity(bits);
coverage.push(LensCoverage { lens: "2-adic (FCSR)", description_bits: tc.saturating_mul(2) });
let moc = describe::maximal_order_complexity(bits);
let moc_bits = if moc < 20 { (1usize << moc).saturating_add(moc) } else { usize::MAX };
coverage.push(LensCoverage { lens: "maximal-order (nonlinear FSR)", description_bits: moc_bits });
let alg = (1..=12)
.find_map(|l| describe::detect_algebraic_recurrence(bits, l, 2).map(|c| l + c.iter().filter(|&&b| b).count()));
coverage.push(LensCoverage { lens: "algebraic-recurrence (deg-2 ANF)", description_bits: alg.unwrap_or(usize::MAX) });
let bytes = describe::bits_to_bytes(bits);
let enc = describe::describe_int_seq(&bytes).len().saturating_mul(8);
coverage.push(LensCoverage { lens: "MDL codec menu", description_bits: enc });
let best = coverage.iter().min_by_key(|c| c.description_bits).expect("nonempty");
let covered = best.description_bits.saturating_mul(2) < n;
LensReport {
length_bits: n,
best_lens: best.lens,
best_description_bits: best.description_bits,
covered,
coverage,
}
}
#[derive(Clone, Debug)]
pub struct CoverageCensus {
pub total: usize,
pub covered: usize,
pub uncovered: usize,
pub by_lens: Vec<(&'static str, usize)>,
}
pub fn census(corpus: &[Vec<bool>]) -> CoverageCensus {
let mut counts: std::collections::BTreeMap<&'static str, usize> = std::collections::BTreeMap::new();
let mut covered = 0;
for seq in corpus {
let r = lens_report(seq);
if r.covered {
covered += 1;
*counts.entry(r.best_lens).or_insert(0) += 1;
}
}
CoverageCensus {
total: corpus.len(),
covered,
uncovered: corpus.len() - covered,
by_lens: counts.into_iter().collect(),
}
}
pub fn exhaustive_coverage(len: usize) -> CoverageCensus {
let corpus: Vec<Vec<bool>> =
(0u64..(1u64 << len)).map(|code| (0..len).map(|i| (code >> i) & 1 == 1).collect()).collect();
census(&corpus)
}
#[derive(Clone, Debug)]
pub struct RecursiveReduction {
pub sizes: Vec<usize>,
pub depth: usize,
pub irreducible_bytes: usize,
pub compressed: bool,
}
pub fn recursive_reduce(bytes: &[u8]) -> RecursiveReduction {
let mut cur: Vec<i64> = bytes.iter().map(|&b| b as i64).collect();
let mut sizes = vec![cur.len()];
loop {
let enc = describe::describe_int_seq(&cur);
if enc.len() >= cur.len() {
break; }
cur = enc.iter().map(|&b| b as i64).collect();
sizes.push(cur.len());
}
RecursiveReduction {
depth: sizes.len() - 1,
irreducible_bytes: *sizes.last().expect("nonempty"),
compressed: sizes.len() > 1,
sizes,
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LinearDistinguisher {
pub mask: usize,
pub bias: f64,
pub mask_weight: u32,
pub nonlinearity: u64,
pub immunity_order: usize,
}
pub fn linear_cryptanalysis(truth: &[bool]) -> Option<LinearDistinguisher> {
let (mask, bias) = describe::best_linear_approximation(truth, true)?;
Some(LinearDistinguisher {
mask,
bias,
mask_weight: mask.count_ones(),
nonlinearity: describe::nonlinearity(truth)?,
immunity_order: describe::correlation_immunity_order(truth)?,
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgebraicImmunityReport {
pub immunity: usize,
pub max_possible: usize,
pub witness: describe::AnnihilatorWitness,
pub is_maximal: bool,
}
pub fn algebraic_immunity_of(truth: &[bool]) -> Option<AlgebraicImmunityReport> {
let (immunity, witness) = describe::algebraic_immunity(truth)?;
let n = truth.len().trailing_zeros() as usize;
let max_possible = n.div_ceil(2);
Some(AlgebraicImmunityReport { immunity, max_possible, witness, is_maximal: immunity == max_possible })
}
pub fn algebraic_filter_attack(keystream: &[bool], taps: &[bool], filter_truth: &[bool]) -> Option<Vec<bool>> {
describe::algebraic_filter_attack(keystream, taps, filter_truth)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CubeStructure {
Constant(bool),
Junta { relevant: Vec<usize>, subtable: Vec<bool> },
Affine { coeffs: Vec<bool>, constant: bool },
Symmetric { by_weight: Vec<bool> },
LowDegree { degree: usize, anf: Vec<bool> },
ResistedArsenal { nonlinearity: u64, degree: usize },
}
impl CubeStructure {
pub fn reconstruct(&self, n: usize) -> Option<Vec<bool>> {
let size = 1usize << n;
match self {
CubeStructure::Constant(b) => Some(vec![*b; size]),
CubeStructure::Junta { relevant, subtable } => {
if subtable.len() != 1usize << relevant.len() {
return None;
}
Some(
(0..size)
.map(|x| {
let c = relevant.iter().enumerate().fold(0usize, |acc, (bit, &v)| {
if x & (1 << v) != 0 { acc | (1 << bit) } else { acc }
});
subtable[c]
})
.collect(),
)
}
CubeStructure::Affine { coeffs, constant } => {
if coeffs.len() != n {
return None;
}
Some(
(0..size)
.map(|x| {
coeffs.iter().enumerate().fold(*constant, |acc, (i, &a)| acc ^ (a && (x & (1 << i) != 0)))
})
.collect(),
)
}
CubeStructure::Symmetric { by_weight } => {
if by_weight.len() != n + 1 {
return None;
}
Some((0..size).map(|x| by_weight[(x as u64).count_ones() as usize]).collect())
}
CubeStructure::LowDegree { anf, .. } => {
if anf.len() != size {
return None;
}
describe::anf(anf)
}
CubeStructure::ResistedArsenal { .. } => None,
}
}
}
#[derive(Clone, Debug)]
pub struct StructureReport {
pub num_vars: usize,
pub class: CubeStructure,
pub raw_bits: usize,
pub description_bits: usize,
pub compressed: bool,
}
fn var_index_bits(n: usize) -> usize {
if n <= 1 { 1 } else { (usize::BITS - (n - 1).leading_zeros()) as usize }
}
fn partial_binomial_sum(n: usize, d: usize) -> usize {
let mut sum = 0usize;
let mut c = 1usize;
for k in 0..=d.min(n) {
sum = sum.saturating_add(c);
c = c.saturating_mul(n - k) / (k + 1);
}
sum
}
fn symmetric_profile(truth: &[bool], n: usize) -> Option<Vec<bool>> {
let mut by_weight: Vec<Option<bool>> = vec![None; n + 1];
for (x, &val) in truth.iter().enumerate() {
let w = (x as u64).count_ones() as usize;
match by_weight[w] {
None => by_weight[w] = Some(val),
Some(v) if v != val => return None,
_ => {}
}
}
Some(by_weight.into_iter().map(|o| o.unwrap_or(false)).collect())
}
pub fn find_structure(truth: &[bool]) -> Option<StructureReport> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let raw = truth.len();
let mut cands: Vec<(CubeStructure, usize)> = Vec::new();
if truth.iter().all(|&b| b == truth[0]) {
cands.push((CubeStructure::Constant(truth[0]), 1));
}
let relevant: Vec<usize> =
(0..n).filter(|&i| (0..truth.len()).any(|x| truth[x] != truth[x ^ (1 << i)])).collect();
if !relevant.is_empty() && relevant.len() < n {
let k = relevant.len();
let subtable: Vec<bool> = (0..1usize << k)
.map(|c| {
let x = relevant.iter().enumerate().fold(0usize, |acc, (bit, &v)| {
if c & (1 << bit) != 0 { acc | (1 << v) } else { acc }
});
truth[x]
})
.collect();
let bits = k * var_index_bits(n) + (1usize << k);
cands.push((CubeStructure::Junta { relevant, subtable }, bits));
}
if describe::nonlinearity(truth) == Some(0) {
let constant = truth[0];
let coeffs: Vec<bool> = (0..n).map(|i| truth[1 << i] ^ constant).collect();
cands.push((CubeStructure::Affine { coeffs, constant }, n + 1));
}
if let Some(by_weight) = symmetric_profile(truth, n) {
cands.push((CubeStructure::Symmetric { by_weight }, n + 1));
}
if let Some(anf) = describe::anf(truth) {
let weight = anf.iter().filter(|&&c| c).count();
let degree =
anf.iter().enumerate().filter(|(_, &c)| c).map(|(m, _)| (m as u64).count_ones() as usize).max().unwrap_or(0);
let dense = if degree + 2 <= n { partial_binomial_sum(n, degree) } else { usize::MAX };
let bits = weight.saturating_mul(n).min(dense).max(1);
cands.push((CubeStructure::LowDegree { degree, anf }, bits));
}
let (class, description_bits) = cands
.into_iter()
.min_by_key(|(_, b)| *b)
.filter(|(_, b)| *b < raw)
.unwrap_or_else(|| {
let degree = describe::algebraic_degree(truth).unwrap_or(n);
let nonlinearity = describe::nonlinearity(truth).unwrap_or(0);
(CubeStructure::ResistedArsenal { nonlinearity, degree }, raw)
});
Some(StructureReport { num_vars: n, compressed: description_bits < raw, description_bits, raw_bits: raw, class })
}
#[derive(Clone, Debug)]
pub struct StructureCover {
pub num_vars: usize,
pub monomials_by_degree: Vec<usize>,
pub total_monomials: usize,
pub residue_degree: usize,
pub residue_monomials: usize,
pub description_bits: usize,
pub raw_bits: usize,
pub compressed: bool,
}
pub fn structure_cover(truth: &[bool]) -> Option<StructureCover> {
let anf = describe::anf(truth)?;
let n = truth.len().trailing_zeros() as usize;
let mut by_degree = vec![0usize; n + 1];
for (m, &c) in anf.iter().enumerate() {
if c {
by_degree[(m as u64).count_ones() as usize] += 1;
}
}
let total: usize = by_degree.iter().sum();
let residue_degree = by_degree.iter().rposition(|&c| c > 0).unwrap_or(0);
let residue_monomials = by_degree[residue_degree];
let raw = truth.len();
let description_bits = if total == 0 { 1 } else { total * n };
Some(StructureCover {
num_vars: n,
compressed: description_bits < raw,
monomials_by_degree: by_degree,
total_monomials: total,
residue_degree,
residue_monomials,
description_bits,
raw_bits: raw,
})
}
#[derive(Clone, Debug)]
pub struct LinearStructureReport {
pub num_vars: usize,
pub basis: Vec<usize>,
pub derivative: Vec<bool>,
}
impl LinearStructureReport {
pub fn dim(&self) -> usize {
self.basis.len()
}
pub fn is_reducible(&self) -> bool {
!self.basis.is_empty()
}
pub fn verify(&self, truth: &[bool]) -> bool {
if self.basis.len() != self.derivative.len() {
return false;
}
if gf2_echelon_basis(&self.basis).len() != self.basis.len() {
return false; }
for (&a, &c) in self.basis.iter().zip(&self.derivative) {
if a == 0 || a >= truth.len() {
return false;
}
if !(0..truth.len()).all(|x| (truth[x] ^ truth[x ^ a]) == c) {
return false;
}
}
true
}
}
fn gf2_echelon_basis(vectors: &[usize]) -> Vec<usize> {
let mut basis: Vec<usize> = Vec::new();
for &v in vectors {
let mut x = v;
for &b in &basis {
x = x.min(x ^ b);
}
if x != 0 {
basis.push(x);
basis.sort_unstable_by(|a, b| b.cmp(a));
}
}
basis
}
fn reduce_by_basis(mut x: usize, basis: &[usize]) -> usize {
for &b in basis {
let hb = 1usize << (usize::BITS - 1 - b.leading_zeros());
if x & hb != 0 {
x ^= b;
}
}
x
}
pub fn linear_structures(truth: &[bool]) -> Option<LinearStructureReport> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let r = describe::autocorrelation(truth)?;
let full = truth.len() as i64;
let structs: Vec<usize> = (1..truth.len()).filter(|&a| r[a].abs() == full).collect();
let basis = gf2_echelon_basis(&structs);
let derivative = basis.iter().map(|&a| truth[a] ^ truth[0]).collect();
Some(LinearStructureReport { num_vars: n, basis, derivative })
}
#[derive(Clone, Debug)]
pub struct InvarianceReduction {
pub original_vars: usize,
pub reduced_vars: usize,
pub free_positions: Vec<usize>,
pub reduced_truth: Vec<bool>,
}
impl InvarianceReduction {
pub fn verify(&self, truth: &[bool], invariance_basis: &[usize]) -> bool {
if self.reduced_truth.len() != 1 << self.reduced_vars {
return false;
}
(0..truth.len()).all(|x| {
let rep = reduce_by_basis(x, invariance_basis);
let y = self
.free_positions
.iter()
.enumerate()
.fold(0usize, |acc, (i, &p)| if rep & (1 << p) != 0 { acc | (1 << i) } else { acc });
self.reduced_truth[y] == truth[x]
})
}
}
fn gf2_rref(basis: &[usize]) -> Vec<usize> {
let mut b = basis.to_vec();
let pivots: Vec<usize> = b.iter().map(|&v| 1usize << (usize::BITS - 1 - v.leading_zeros())).collect();
for i in 0..b.len() {
for j in 0..b.len() {
if i != j && b[j] & pivots[i] != 0 {
b[j] ^= b[i];
}
}
}
b
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AffineReduction {
pub num_vars: usize,
pub linear_form: usize,
pub invariance_basis: Vec<usize>,
pub free_positions: Vec<usize>,
pub reduced_truth: Vec<bool>,
}
impl AffineReduction {
pub fn reconstruct(&self) -> Option<Vec<bool>> {
if self.reduced_truth.len() != 1 << self.free_positions.len() {
return None;
}
Some(
(0..1usize << self.num_vars)
.map(|x| {
let rep = reduce_by_basis(x, &self.invariance_basis);
let y = self
.free_positions
.iter()
.enumerate()
.fold(0usize, |acc, (j, &p)| if rep & (1 << p) != 0 { acc | (1 << j) } else { acc });
self.reduced_truth[y] ^ ((self.linear_form & x).count_ones() % 2 == 1)
})
.collect(),
)
}
}
pub fn affine_reduce(truth: &[bool]) -> Option<AffineReduction> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let ls = linear_structures(truth)?;
if !ls.derivative.iter().any(|&d| d) {
return None; }
let rref = gf2_rref(&ls.basis);
let mut c = 0usize;
for &b in &rref {
if truth[b] ^ truth[0] {
c |= 1usize << (usize::BITS - 1 - b.leading_zeros());
}
}
let h: Vec<bool> = (0..truth.len()).map(|x| truth[x] ^ ((c & x).count_ones() % 2 == 1)).collect();
let (red, inv_basis) = reduce_by_invariance(&h)?;
let out = AffineReduction {
num_vars: n,
linear_form: c,
invariance_basis: inv_basis,
free_positions: red.free_positions,
reduced_truth: red.reduced_truth,
};
(out.reconstruct().as_deref() == Some(truth)).then_some(out)
}
pub fn reduce_by_invariance(truth: &[bool]) -> Option<(InvarianceReduction, Vec<usize>)> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let r = describe::autocorrelation(truth)?;
let full = truth.len() as i64;
let invariances: Vec<usize> = (1..truth.len()).filter(|&a| r[a] == full).collect();
let basis = gf2_echelon_basis(&invariances);
if basis.is_empty() {
return None;
}
let pivots: Vec<usize> = basis.iter().map(|&b| (usize::BITS - 1 - b.leading_zeros()) as usize).collect();
let free_positions: Vec<usize> = (0..n).filter(|p| !pivots.contains(p)).collect();
let reduced_vars = free_positions.len();
let reduced_truth: Vec<bool> = (0..1usize << reduced_vars)
.map(|y| {
let x = free_positions
.iter()
.enumerate()
.fold(0usize, |acc, (i, &p)| if y & (1 << i) != 0 { acc | (1 << p) } else { acc });
truth[x]
})
.collect();
Some((InvarianceReduction { original_vars: n, reduced_vars, free_positions, reduced_truth }, basis))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AffineSignature {
pub num_vars: usize,
pub walsh_abs_distribution: Vec<(u64, usize)>,
pub is_plateaued: bool,
pub amplitude: Option<u64>,
pub is_bent: bool,
}
impl AffineSignature {
pub fn implied_linear_dim(&self) -> Option<usize> {
let amp = self.amplitude?;
if !amp.is_power_of_two() {
return None;
}
let log = amp.trailing_zeros() as usize;
(2 * log).checked_sub(self.num_vars)
}
}
pub fn affine_signature(truth: &[bool]) -> Option<AffineSignature> {
let n = truth.len().trailing_zeros() as usize;
let spec = describe::walsh_spectrum(truth)?;
let mut counts: std::collections::BTreeMap<u64, usize> = std::collections::BTreeMap::new();
for &c in &spec {
*counts.entry(c.unsigned_abs()).or_default() += 1;
}
let nonzero: Vec<u64> = counts.keys().copied().filter(|&v| v != 0).collect();
let is_plateaued = nonzero.len() == 1;
let amplitude = is_plateaued.then(|| nonzero[0]);
let bent_amp = (n % 2 == 0).then(|| 1u64 << (n / 2));
let is_bent = amplitude.is_some() && amplitude == bent_amp;
Some(AffineSignature {
num_vars: n,
walsh_abs_distribution: counts.into_iter().collect(),
is_plateaued,
amplitude,
is_bent,
})
}
#[derive(Clone, Debug)]
pub struct SeparableDecomposition {
pub num_vars: usize,
pub constant: bool,
pub blocks: Vec<Vec<usize>>,
pub block_truths: Vec<Vec<bool>>,
}
impl SeparableDecomposition {
pub fn is_separable(&self) -> bool {
self.blocks.len() >= 2
}
pub fn table_bits(&self) -> usize {
self.block_truths.iter().map(|t| t.len()).sum()
}
pub fn reconstruct(&self, n: usize) -> Option<Vec<bool>> {
let size = 1usize << n;
let mut out = vec![self.constant; size];
for (b, t) in self.blocks.iter().zip(&self.block_truths) {
if t.len() != 1 << b.len() {
return None;
}
for (x, slot) in out.iter_mut().enumerate() {
let y = b.iter().enumerate().fold(0usize, |acc, (j, &i)| {
if x & (1 << i) != 0 { acc | (1 << j) } else { acc }
});
*slot ^= t[y];
}
}
Some(out)
}
}
fn uf_find(parent: &mut [usize], mut x: usize) -> usize {
while parent[x] != x {
parent[x] = parent[parent[x]];
x = parent[x];
}
x
}
pub fn separable_decomposition(truth: &[bool]) -> Option<SeparableDecomposition> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let anf = describe::anf(truth)?;
let constant = anf[0];
let mut parent: Vec<usize> = (0..n).collect();
let mut relevant = vec![false; n];
for (m, &c) in anf.iter().enumerate() {
if !c || m == 0 {
continue;
}
let bits: Vec<usize> = (0..n).filter(|&i| m & (1 << i) != 0).collect();
for &i in &bits {
relevant[i] = true;
}
for w in bits.windows(2) {
let (ra, rb) = (uf_find(&mut parent, w[0]), uf_find(&mut parent, w[1]));
parent[ra] = rb;
}
}
let mut comp: std::collections::BTreeMap<usize, Vec<usize>> = std::collections::BTreeMap::new();
for i in 0..n {
if relevant[i] {
let r = uf_find(&mut parent, i);
comp.entry(r).or_default().push(i);
}
}
let blocks: Vec<Vec<usize>> = comp.into_values().collect();
let mut block_truths = Vec::with_capacity(blocks.len());
for b in &blocks {
let bmask: usize = b.iter().fold(0, |acc, &i| acc | (1 << i));
let mut sub = vec![false; 1usize << b.len()];
for (m, &c) in anf.iter().enumerate() {
if !c || m == 0 || m & !bmask != 0 {
continue;
}
let sub_m = b.iter().enumerate().fold(0usize, |acc, (j, &i)| {
if m & (1 << i) != 0 { acc | (1 << j) } else { acc }
});
sub[sub_m] = true;
}
block_truths.push(describe::anf(&sub)?); }
Some(SeparableDecomposition { num_vars: n, constant, blocks, block_truths })
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VariableSymmetry {
pub num_vars: usize,
pub generators: Vec<Vec<usize>>,
pub order: u128,
pub orbit_count: usize,
pub orbit_values: Vec<bool>,
}
impl VariableSymmetry {
pub fn is_nontrivial(&self) -> bool {
self.order > 1
}
pub fn reconstruct(&self, n: usize) -> Option<Vec<bool>> {
let size = 1usize << n;
if self.orbit_values.len() != self.orbit_count {
return None;
}
let mut orbs = self.input_orbits(size)?;
orbs.sort_by_key(|o| *o.iter().min().expect("nonempty orbit"));
if orbs.len() != self.orbit_count {
return None;
}
let mut out = vec![false; size];
for (k, o) in orbs.iter().enumerate() {
for &x in o {
out[x] = self.orbit_values[k];
}
}
Some(out)
}
pub fn verify(&self, truth: &[bool]) -> bool {
for s in &self.generators {
if s.len() != self.num_vars || !is_variable_automorphism(truth, s) {
return false;
}
}
self.reconstruct(self.num_vars).as_deref() == Some(truth)
}
fn input_orbits(&self, size: usize) -> Option<Vec<Vec<usize>>> {
let lifted: Vec<Vec<usize>> =
self.generators.iter().map(|s| (0..size).map(|x| permute_input_bits(x, s)).collect()).collect();
Some(if lifted.is_empty() { (0..size).map(|x| vec![x]).collect() } else { crate::permgroup::orbits(size, &lifted) })
}
}
fn permute_input_bits(x: usize, sigma: &[usize]) -> usize {
sigma.iter().enumerate().fold(0usize, |acc, (i, &si)| if x & (1 << i) != 0 { acc | (1 << si) } else { acc })
}
fn is_variable_automorphism(truth: &[bool], sigma: &[usize]) -> bool {
(0..truth.len()).all(|x| truth[x] == truth[permute_input_bits(x, sigma)])
}
pub fn variable_symmetry(truth: &[bool]) -> Option<VariableSymmetry> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let mut generators: Vec<Vec<usize>> = Vec::new();
for i in 0..n {
for j in i + 1..n {
let mut s: Vec<usize> = (0..n).collect();
s.swap(i, j);
if is_variable_automorphism(truth, &s) {
generators.push(s);
}
}
}
for a in 0..n {
for b in a + 1..n {
for c in 0..n {
if c == a || c == b {
continue;
}
for d in c + 1..n {
if d == a || d == b {
continue;
}
let mut s: Vec<usize> = (0..n).collect();
s.swap(a, b);
s.swap(c, d);
if is_variable_automorphism(truth, &s) {
generators.push(s);
}
}
}
}
}
if n > 1 {
let rho: Vec<usize> = (0..n).map(|i| (i + 1) % n).collect();
if is_variable_automorphism(truth, &rho) {
generators.push(rho);
}
}
let order = crate::permgroup::schreier_sims(n, &generators).order();
let size = 1usize << n;
let sym = VariableSymmetry { num_vars: n, generators, order, orbit_count: 0, orbit_values: Vec::new() };
let mut orbs = sym.input_orbits(size)?;
orbs.sort_by_key(|o| *o.iter().min().expect("nonempty orbit"));
let orbit_values: Vec<bool> = orbs.iter().map(|o| truth[*o.iter().min().expect("nonempty")]).collect();
Some(VariableSymmetry { orbit_count: orbs.len(), orbit_values, ..sym })
}
#[derive(Clone, Debug)]
pub struct DeepStructureReport {
pub num_vars: usize,
pub raw_bits: usize,
pub description_bits: usize,
pub winner: &'static str,
pub compressed: bool,
}
pub fn find_structure_deep(truth: &[bool]) -> Option<DeepStructureReport> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let raw = truth.len();
let mut cands: Vec<(&'static str, usize)> = Vec::new();
if let Some(r) = find_structure(truth) {
if r.compressed {
let name = match r.class {
CubeStructure::Constant(_) => "coordinate:constant",
CubeStructure::Junta { .. } => "coordinate:junta",
CubeStructure::Affine { .. } => "coordinate:affine",
CubeStructure::Symmetric { .. } => "coordinate:symmetric",
CubeStructure::LowDegree { .. } => "coordinate:low-degree",
CubeStructure::ResistedArsenal { .. } => "coordinate:residue",
};
cands.push((name, r.description_bits));
}
}
if let Some(d) = separable_decomposition(truth) {
if d.is_separable() {
cands.push(("separable", d.table_bits()));
}
}
if let Some(s) = variable_symmetry(truth) {
if s.is_nontrivial() {
cands.push(("permutation", s.orbit_count));
}
}
if let Some((red, _)) = reduce_by_invariance(truth) {
cands.push(("linear-invariance", 1usize << red.reduced_vars));
}
if let Some(ar) = affine_reduce(truth) {
cands.push(("affine", ar.reduced_truth.len() + n));
}
let (winner, description_bits) =
cands.into_iter().filter(|(_, b)| *b < raw).min_by_key(|(_, b)| *b).unwrap_or(("residue", raw));
Some(DeepStructureReport { num_vars: n, raw_bits: raw, description_bits, winner, compressed: description_bits < raw })
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StructureTree {
Coordinate { class: CubeStructure, num_vars: usize, bits: usize },
Permutation { sym: VariableSymmetry, num_vars: usize },
Residue { truth: Vec<bool> },
Separable { num_vars: usize, constant: bool, blocks: Vec<(Vec<usize>, StructureTree)> },
LinearReduced { num_vars: usize, invariance_basis: Vec<usize>, free_positions: Vec<usize>, inner: Box<StructureTree> },
AffineReduced {
num_vars: usize,
linear_form: usize,
invariance_basis: Vec<usize>,
free_positions: Vec<usize>,
inner: Box<StructureTree>,
},
}
impl StructureTree {
pub fn depth(&self) -> usize {
match self {
StructureTree::Coordinate { .. } | StructureTree::Permutation { .. } | StructureTree::Residue { .. } => 0,
StructureTree::Separable { blocks, .. } => 1 + blocks.iter().map(|(_, t)| t.depth()).max().unwrap_or(0),
StructureTree::LinearReduced { inner, .. } | StructureTree::AffineReduced { inner, .. } => {
1 + inner.depth()
}
}
}
pub fn total_description_bits(&self) -> usize {
match self {
StructureTree::Coordinate { bits, .. } => *bits,
StructureTree::Permutation { sym, .. } => sym.orbit_count,
StructureTree::Residue { truth } => truth.len(),
StructureTree::Separable { blocks, .. } => {
1 + blocks.iter().map(|(v, t)| v.len() + t.total_description_bits()).sum::<usize>()
}
StructureTree::LinearReduced { invariance_basis, inner, .. } => {
invariance_basis.len() + inner.total_description_bits()
}
StructureTree::AffineReduced { num_vars, inner, .. } => *num_vars + inner.total_description_bits(),
}
}
pub fn reconstruct(&self) -> Option<Vec<bool>> {
match self {
StructureTree::Coordinate { class, num_vars, .. } => class.reconstruct(*num_vars),
StructureTree::Permutation { sym, num_vars } => sym.reconstruct(*num_vars),
StructureTree::Residue { truth } => Some(truth.clone()),
StructureTree::Separable { num_vars, constant, blocks } => {
let size = 1usize << num_vars;
let mut out = vec![*constant; size];
for (vars, sub) in blocks {
let bt = sub.reconstruct()?;
for (x, slot) in out.iter_mut().enumerate() {
let y = vars.iter().enumerate().fold(0usize, |acc, (j, &i)| {
if x & (1 << i) != 0 { acc | (1 << j) } else { acc }
});
*slot ^= bt[y];
}
}
Some(out)
}
StructureTree::LinearReduced { num_vars, invariance_basis, free_positions, inner } => {
let inner_truth = inner.reconstruct()?;
Some(
(0..1usize << num_vars)
.map(|x| {
let rep = reduce_by_basis(x, invariance_basis);
let y = free_positions.iter().enumerate().fold(0usize, |acc, (j, &p)| {
if rep & (1 << p) != 0 { acc | (1 << j) } else { acc }
});
inner_truth[y]
})
.collect(),
)
}
StructureTree::AffineReduced { num_vars, linear_form, invariance_basis, free_positions, inner } => {
let inner_truth = inner.reconstruct()?;
Some(
(0..1usize << num_vars)
.map(|x| {
let rep = reduce_by_basis(x, invariance_basis);
let y = free_positions.iter().enumerate().fold(0usize, |acc, (j, &p)| {
if rep & (1 << p) != 0 { acc | (1 << j) } else { acc }
});
inner_truth[y] ^ ((linear_form & x).count_ones() % 2 == 1)
})
.collect(),
)
}
}
}
}
pub fn structure_tree(truth: &[bool]) -> Option<StructureTree> {
if truth.is_empty() || !truth.len().is_power_of_two() {
return None;
}
let n = truth.len().trailing_zeros() as usize;
let deep = find_structure_deep(truth)?;
let tree = match deep.winner {
"separable" => {
let d = separable_decomposition(truth)?;
let mut blocks = Vec::with_capacity(d.blocks.len());
for (vars, bt) in d.blocks.iter().zip(&d.block_truths) {
blocks.push((vars.clone(), structure_tree(bt)?));
}
StructureTree::Separable { num_vars: n, constant: d.constant, blocks }
}
"linear-invariance" => {
let (red, basis) = reduce_by_invariance(truth)?;
StructureTree::LinearReduced {
num_vars: n,
invariance_basis: basis,
free_positions: red.free_positions.clone(),
inner: Box::new(structure_tree(&red.reduced_truth)?),
}
}
"affine" => {
let ar = affine_reduce(truth)?;
StructureTree::AffineReduced {
num_vars: n,
linear_form: ar.linear_form,
invariance_basis: ar.invariance_basis,
free_positions: ar.free_positions,
inner: Box::new(structure_tree(&ar.reduced_truth)?),
}
}
"permutation" => StructureTree::Permutation { sym: variable_symmetry(truth)?, num_vars: n },
"residue" => StructureTree::Residue { truth: truth.to_vec() },
_ => StructureTree::Coordinate { class: find_structure(truth)?.class, num_vars: n, bits: deep.description_bits },
};
Some(tree)
}
#[derive(Clone, Debug)]
pub struct BooleanCensus {
pub num_vars: usize,
pub total: usize,
pub by_winner: Vec<(&'static str, usize)>,
pub compressed: usize,
pub residue: usize,
}
pub fn boolean_function_census(n: usize) -> Option<BooleanCensus> {
if n == 0 || n > 4 {
return None;
}
let dim = 1usize << n;
let total = 1u64 << dim;
let mut counts: std::collections::BTreeMap<&'static str, usize> = std::collections::BTreeMap::new();
for code in 0..total {
let truth: Vec<bool> = (0..dim).map(|i| (code >> i) & 1 == 1).collect();
*counts.entry(find_structure_deep(&truth)?.winner).or_default() += 1;
}
let residue = counts.get("residue").copied().unwrap_or(0);
Some(BooleanCensus {
num_vars: n,
total: total as usize,
by_winner: counts.into_iter().collect(),
compressed: total as usize - residue,
residue,
})
}
#[derive(Clone, Debug)]
pub struct SampledCensus {
pub num_vars: usize,
pub samples: usize,
pub residue: usize,
pub by_winner: Vec<(&'static str, usize)>,
}
impl SampledCensus {
pub fn residue_fraction(&self) -> f64 {
self.residue as f64 / self.samples.max(1) as f64
}
}
pub fn sampled_boolean_census(n: usize, samples: usize, seed: u64) -> Option<SampledCensus> {
if n == 0 || n > 12 {
return None;
}
let dim = 1usize << n;
let mut counts: std::collections::BTreeMap<&'static str, usize> = std::collections::BTreeMap::new();
for s in 0..samples {
let truth: Vec<bool> = (0..dim)
.map(|i| {
let mut z =
seed.wrapping_add((s as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15)).wrapping_add(i as u64 + 1);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
(z ^ (z >> 31)) & 1 == 1
})
.collect();
*counts.entry(find_structure_deep(&truth)?.winner).or_default() += 1;
}
let residue = counts.get("residue").copied().unwrap_or(0);
Some(SampledCensus { num_vars: n, samples, residue, by_winner: counts.into_iter().collect() })
}
#[derive(Clone, Debug)]
pub struct KolmogorovBound {
pub num_vars: usize,
pub bits: usize,
pub raw_bits: usize,
pub tree: StructureTree,
}
impl KolmogorovBound {
pub fn ratio(&self) -> f64 {
self.bits as f64 / self.raw_bits.max(1) as f64
}
pub fn is_compressed(&self) -> bool {
self.bits < self.raw_bits
}
pub fn verify(&self, truth: &[bool]) -> bool {
self.tree.reconstruct().as_deref() == Some(truth)
}
}
pub fn kolmogorov_bound(truth: &[bool]) -> Option<KolmogorovBound> {
let n = truth.len().trailing_zeros() as usize;
let tree = structure_tree(truth)?;
Some(KolmogorovBound { num_vars: n, bits: tree.total_description_bits(), raw_bits: truth.len(), tree })
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SboxProfile {
pub in_bits: usize,
pub out_bits: usize,
pub differential_uniformity: u32,
pub linearity: u64,
pub min_degree: usize,
pub is_affine: bool,
pub is_bijective: bool,
pub is_apn: bool,
}
pub fn sbox_profile(sbox: &[u32], out_bits: usize) -> Option<SboxProfile> {
if sbox.is_empty() || !sbox.len().is_power_of_two() {
return None;
}
let n = sbox.len().trailing_zeros() as usize;
let m = out_bits;
let size = sbox.len();
let mut differential_uniformity = 0u32;
for a in 1..size {
let mut count = vec![0u32; 1usize << m];
for x in 0..size {
count[(sbox[x] ^ sbox[x ^ a]) as usize] += 1;
}
differential_uniformity = differential_uniformity.max(*count.iter().max().unwrap_or(&0));
}
let mut linearity = 0u64;
let mut min_degree = usize::MAX;
let mut is_affine = true;
for b in 1..(1usize << m) {
let comp: Vec<bool> = sbox.iter().map(|&y| (b as u32 & y).count_ones() % 2 == 1).collect();
let spec = describe::walsh_spectrum(&comp)?;
linearity = linearity.max(spec.iter().map(|&c| c.unsigned_abs()).max().unwrap_or(0));
let deg = describe::algebraic_degree(&comp)?;
min_degree = min_degree.min(deg);
if deg > 1 {
is_affine = false;
}
}
let mut seen = vec![false; 1usize << m];
let is_bijective = n == m
&& sbox.iter().all(|&y| {
let u = y as usize;
u < seen.len() && !std::mem::replace(&mut seen[u], true)
});
Some(SboxProfile {
in_bits: n,
out_bits: m,
differential_uniformity,
linearity,
min_degree: if min_degree == usize::MAX { 0 } else { min_degree },
is_affine,
is_bijective,
is_apn: n == m && differential_uniformity == 2,
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SboxSpectra {
pub in_bits: usize,
pub out_bits: usize,
pub differential_spectrum: Vec<(u32, usize)>,
pub walsh_spectrum: Vec<(u64, usize)>,
}
pub fn sbox_spectra(sbox: &[u32], out_bits: usize) -> Option<SboxSpectra> {
if sbox.is_empty() || !sbox.len().is_power_of_two() {
return None;
}
let n = sbox.len().trailing_zeros() as usize;
let m = out_bits;
let size = sbox.len();
let mut ddt_hist: std::collections::BTreeMap<u32, usize> = std::collections::BTreeMap::new();
for a in 1..size {
let mut count = vec![0u32; 1usize << m];
for x in 0..size {
count[(sbox[x] ^ sbox[x ^ a]) as usize] += 1;
}
for &c in &count {
*ddt_hist.entry(c).or_default() += 1;
}
}
let mut walsh_hist: std::collections::BTreeMap<u64, usize> = std::collections::BTreeMap::new();
for b in 1..(1usize << m) {
let comp: Vec<bool> = sbox.iter().map(|&y| (b as u32 & y).count_ones() % 2 == 1).collect();
for &w in &describe::walsh_spectrum(&comp)? {
*walsh_hist.entry(w.unsigned_abs()).or_default() += 1;
}
}
Some(SboxSpectra {
in_bits: n,
out_bits: m,
differential_spectrum: ddt_hist.into_iter().collect(),
walsh_spectrum: walsh_hist.into_iter().collect(),
})
}
pub fn boomerang_uniformity(sbox: &[u32]) -> Option<u32> {
if sbox.is_empty() || !sbox.len().is_power_of_two() {
return None;
}
let size = sbox.len();
let mut inv = vec![u32::MAX; size];
for (x, &y) in sbox.iter().enumerate() {
let u = y as usize;
if u >= size || inv[u] != u32::MAX {
return None; }
inv[u] = x as u32;
}
let mut bu = 0u32;
for a in 1..size {
for b in 1..size {
let mut count = 0u32;
for x in 0..size {
let lhs = inv[(sbox[x] as usize) ^ b] ^ inv[(sbox[x ^ a] as usize) ^ b];
if lhs as usize == a {
count += 1;
}
}
bu = bu.max(count);
}
}
Some(bu)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SboxVerdict {
Affine,
LinearComponent,
DeterministicDifference,
Quadratic,
NoStructuralWeaknessFound {
differential_uniformity: u32,
linearity: u64,
min_degree: usize,
boomerang_uniformity: Option<u32>,
},
}
pub fn sbox_full_audit(sbox: &[u32], out_bits: usize) -> Option<SboxVerdict> {
let p = sbox_profile(sbox, out_bits)?;
let full = 1u64 << p.in_bits;
if p.is_affine {
return Some(SboxVerdict::Affine);
}
if p.linearity == full {
return Some(SboxVerdict::LinearComponent);
}
if p.differential_uniformity as u64 == full {
return Some(SboxVerdict::DeterministicDifference);
}
if p.min_degree == 2 {
return Some(SboxVerdict::Quadratic);
}
Some(SboxVerdict::NoStructuralWeaknessFound {
differential_uniformity: p.differential_uniformity,
linearity: p.linearity,
min_degree: p.min_degree,
boomerang_uniformity: if p.is_bijective { boomerang_uniformity(sbox) } else { None },
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RsaStrength {
Factored { p: logicaffeine_base::BigInt, q: logicaffeine_base::BigInt, method: &'static str },
SoundAgainstStructuralAttacks,
}
pub fn rsa_structural_audit(n: &logicaffeine_base::BigInt) -> RsaStrength {
match crate::factor::structural_factor(n, Default::default()) {
Some(w) => RsaStrength::Factored { p: w.p, q: w.q, method: w.method },
None => RsaStrength::SoundAgainstStructuralAttacks,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RsaAuditVerdict {
Factored { method: &'static str, p: logicaffeine_base::BigInt, q: logicaffeine_base::BigInt },
WeakExponent,
CompressibleModulus(CompressibilityClass),
ResistsFullArsenal,
}
pub fn rsa_full_audit(n: &logicaffeine_base::BigInt, e: &logicaffeine_base::BigInt) -> RsaAuditVerdict {
use crate::factor;
if let Some(w) = factor::structural_factor(n, Default::default()) {
return RsaAuditVerdict::Factored { method: w.method, p: w.p, q: w.q };
}
if factor::wiener(e, n).is_some() {
return RsaAuditVerdict::WeakExponent;
}
let (_, bytes) = n.to_le_bytes();
let report = classify_bytes(&bytes);
if report.class != CompressibilityClass::Incompressible {
return RsaAuditVerdict::CompressibleModulus(report.class);
}
RsaAuditVerdict::ResistsFullArsenal
}
pub fn assess_key_material(data: &[u8]) -> CryptoStrength {
let ints: Vec<i64> = data.iter().map(|&b| b as i64).collect();
let witness = DescriptionBound::of_int_seq(&ints);
let ratio = if data.is_empty() { 1.0 } else { witness.bytes as f64 / data.len() as f64 };
if witness.bytes < data.len() {
CryptoStrength::Weak { witness, ratio }
} else {
CryptoStrength::IncompressibleInClass { ratio }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompressibilityClass {
Generated,
Periodic,
LowEntropy,
Smooth,
Incompressible,
}
#[derive(Clone, Debug)]
pub struct CompressibilityReport {
pub class: CompressibilityClass,
pub ratio: f64,
pub described_bytes: usize,
pub raw_bytes: usize,
}
pub fn classify_bytes(data: &[u8]) -> CompressibilityReport {
let ints: Vec<i64> = data.iter().map(|&b| b as i64).collect();
let described = describe::describe_int_seq(&ints);
let raw = data.len();
let ratio = if raw == 0 { 1.0 } else { described.len() as f64 / raw as f64 };
let class = if described.len() >= raw.max(1) {
CompressibilityClass::Incompressible
} else {
class_of_tag(described.first().copied().unwrap_or(describe::T_INTS))
};
CompressibilityReport { class, ratio, described_bytes: described.len(), raw_bytes: raw }
}
pub fn classify_text(text: &str) -> CompressibilityReport {
classify_bytes(text.as_bytes())
}
pub fn classify_int_seq(data: &[i64]) -> CompressibilityReport {
let described = describe::describe_int_seq(data);
let mut baseline = vec![describe::T_INTS];
describe::leb128_encode(&mut baseline, data.iter().copied(), data.len());
let raw = baseline.len();
let ratio = if raw == 0 { 1.0 } else { described.len() as f64 / raw as f64 };
let class = if described.len() >= raw {
CompressibilityClass::Incompressible
} else {
class_of_tag(described.first().copied().unwrap_or(describe::T_INTS))
};
CompressibilityReport { class, ratio, described_bytes: described.len(), raw_bytes: raw }
}
fn class_of_tag(tag: u8) -> CompressibilityClass {
use describe::*;
match tag {
t if t == T_INTS_AFFINE
|| t == T_INTS_GEOMETRIC
|| t == T_INTS_POLY
|| t == T_GEN
|| t == T_INTS_LRECUR =>
{
CompressibilityClass::Generated
}
t if t == T_INTS_PERIODIC => CompressibilityClass::Periodic,
t if t == T_INTS_SPARSE || t == T_INTS_RLE || t == T_INTS_DICT => CompressibilityClass::LowEntropy,
t if t == T_INTS_DELTA || t == T_INTS_DOD || t == T_INTS_FOR => CompressibilityClass::Smooth,
_ => CompressibilityClass::Incompressible, }
}
fn independent_gf2(vectors: &[Vec<bool>]) -> bool {
let mut rows: Vec<u64> = vectors
.iter()
.map(|v| v.iter().enumerate().fold(0u64, |m, (i, &b)| if b && i < 64 { m | (1u64 << i) } else { m }))
.collect();
let mut rank = 0usize;
for col in 0..64u32 {
if let Some(piv) = (rank..rows.len()).find(|&r| rows[r] & (1u64 << col) != 0) {
rows.swap(rank, piv);
let pr = rows[rank];
for r in 0..rows.len() {
if r != rank && rows[r] & (1u64 << col) != 0 {
rows[r] ^= pr;
}
}
rank += 1;
}
}
rank == vectors.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn description_bound_decode_witness_round_trips() {
let v: Vec<i64> = (0..200).map(|i| 10 + 7 * i).collect();
let db = DescriptionBound::of_int_seq(&v);
assert!(db.verify(), "the decode witness must reproduce the described sequence");
let baseline = describe::describe_int_seq(&v); let mut plain = vec![19u8]; describe::leb128_encode(&mut plain, v.iter().copied(), v.len());
assert!(db.bytes <= plain.len(), "K̄ is never larger than the varint baseline");
assert!(db.bytes < 12, "an affine column ships as a generator (a handful of bytes)");
assert_eq!(db.bytes, baseline.len());
}
#[test]
fn description_bound_rejects_tampered_witness() {
let v: Vec<i64> = (0..50).map(|i| i * i - 3 * i + 5).collect();
let mut db = DescriptionBound::of_int_seq(&v);
assert!(db.verify(), "the honest witness verifies");
if let Descriptor::IntSeq { encoded } = &mut db.descriptor {
let last = encoded.len() - 1;
encoded[last] ^= 0xff;
}
assert!(!db.verify(), "a tampered decode witness must be rejected");
}
#[test]
fn compression_is_never_over_claimed() {
let mut s = 0x9e37_79b9_7f4a_7c15u64;
let v: Vec<i64> = (0..300)
.map(|_| {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
(s >> 1) as i64
})
.collect();
let db = DescriptionBound::of_int_seq(&v);
assert!(db.verify(), "any reported bound must decode back to the exact sequence");
let mut plain = vec![19u8];
describe::leb128_encode(&mut plain, v.iter().copied(), v.len());
assert!(db.bytes <= plain.len(), "K̄ is never larger than the varint baseline");
}
fn splitmix_bytes(n: usize, mut s: u64) -> Vec<u8> {
(0..n)
.map(|_| {
s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
(z >> 24) as u8
})
.collect()
}
#[test]
fn structured_key_material_is_certified_weak() {
let weak_key: Vec<u8> = (0..300).map(|i| (i % 7) as u8).collect();
match assess_key_material(&weak_key) {
CryptoStrength::Weak { witness, ratio } => {
assert!(witness.verify(), "the compression witness reproduces the key (the attack)");
assert!(ratio < 0.5, "a predictable key is far shorter than its raw bytes, got {ratio}");
}
CryptoStrength::IncompressibleInClass { .. } => panic!("a periodic key must be flagged weak"),
}
}
#[test]
fn lfsr_keystream_key_is_certified_weak() {
let taps = [false, false, true, false, false, false, true]; let seed = [true, false, true, true, false, false, true];
let bits = describe::lfsr_generate(&taps, &seed, 200 * 8);
let key: Vec<u8> = describe::bits_to_bytes(&bits).iter().map(|&x| x as u8).collect();
match assess_key_material(&key) {
CryptoStrength::Weak { witness, ratio } => {
assert!(witness.verify(), "the recovered register reproduces the keystream (the attack)");
assert!(ratio < 0.2, "the key collapses to its register, ratio {ratio}");
}
CryptoStrength::IncompressibleInClass { .. } => panic!("an LFSR keystream key must be weak"),
}
}
#[test]
fn word_and_two_adic_complexity_detect_their_keystreams() {
let taps = [describe::Gf256(0x02), describe::Gf256(0x8d), describe::Gf256(0x1f)];
let seed = [describe::Gf256(0x41), describe::Gf256(0x9c), describe::Gf256(0x07)];
let word_key: Vec<u8> = describe::lfsr_generate_field(&taps, &seed, 120).iter().map(|g| g.0).collect();
assert_eq!(gf256_word_complexity(&word_key), 3, "word-LFSR keystream has word complexity 3");
let bits = describe::fcsr_generate(
&logicaffeine_base::BigInt::from_i64(7),
&logicaffeine_base::BigInt::from_i64(19),
8 * 60,
);
let fcsr_key: Vec<u8> = describe::bits_to_bytes(&bits).iter().map(|&x| x as u8).collect();
assert!(two_adic_complexity_of_bytes(&fcsr_key) < 12, "FCSR key has low 2-adic complexity");
let random = splitmix_bytes(120, 0x1357_9bdf_2468_ace0);
assert!(gf256_word_complexity(&random) > 30, "random has high word complexity");
assert!(two_adic_complexity_of_bytes(&random) > 200, "random ≈ n/2 2-adic complexity");
let bits: Vec<bool> = random.iter().flat_map(|&b| (0..8).map(move |j| (b >> j) & 1 == 1)).collect();
assert!(
maximal_order_complexity_of_bytes(&random) <= describe::berlekamp_massey_gf2(&bits).0,
"MOC ≤ linear complexity for everything (it is the shorter, more general register)"
);
}
#[test]
fn algebraic_attack_breaks_a_nonlinear_keystream_that_maximal_order_only_measures() {
let seed: Vec<bool> = (0..8).map(|k| (0x9E37u64 >> k) & 1 == 1).collect();
let mut bits = seed.clone();
while bits.len() < 8 * 80 {
let i = bits.len();
let s = |k: usize| bits[i - k];
bits.push(s(1) ^ s(5) ^ s(6) ^ s(8) ^ (s(1) & s(6)));
}
let key: Vec<u8> = describe::bits_to_bytes(&bits).iter().map(|&x| x as u8).collect();
assert!(gf256_word_complexity(&key) > 30, "word-LFSR rung fooled by the nonlinear feedback");
assert!(two_adic_complexity_of_bytes(&key) > 100, "2-adic rung fooled too");
assert!(maximal_order_complexity_of_bytes(&key) <= 8, "maximal order complexity sees the order-8 register");
let attack = algebraic_attack_on_bytes(&key, 2, 16).expect("degree-2 attack recovers the register");
assert!(attack.order <= 8, "the shortest register found is order ≤ 8, got {}", attack.order);
assert_eq!(attack.truth_table, 1 << attack.order, "truth-table size is 2^order");
assert!(
attack.anf_terms < attack.truth_table,
"the ANF ({} terms) is smaller than the truth table ({})",
attack.anf_terms,
attack.truth_table
);
let regen = describe::algebraic_generate(attack.order, attack.degree, &attack.anf, &bits[..attack.order], bits.len());
assert_eq!(regen, bits, "the recovered ANF regenerates the whole nonlinear keystream — the attack");
}
#[test]
fn combiner_scan_breaks_geffe_but_not_a_cryptographic_keystream() {
let n = 8 * 250; let taps1 = [false, false, true, false, false, false, true]; let taps2 = [false, false, true, false, true]; let taps3 = [false, false, false, false, true, false, false, false, true]; let seed1 = [true, false, true, true, false, false, true];
let seed2 = [true, true, false, false, true];
let seed3 = [true, false, false, true, false, true, true, false, true];
let x1 = describe::lfsr_generate(&taps1, &seed1, n);
let x2 = describe::lfsr_generate(&taps2, &seed2, n);
let x3 = describe::lfsr_generate(&taps3, &seed3, n);
let z: Vec<bool> = (0..n).map(|i| if x2[i] { x1[i] } else { x3[i] }).collect();
let key: Vec<u8> = describe::bits_to_bytes(&z).iter().map(|&x| x as u8).collect();
let candidates = vec![taps1.to_vec(), taps2.to_vec(), taps3.to_vec()];
let leaks = scan_for_combiner_leaks(&key, &candidates, 3.0);
let leaking: Vec<usize> = leaks.iter().map(|l| l.candidate_index).collect();
assert_eq!(leaking, vec![0, 2], "Geffe leaks its outer registers, not the protected middle");
for leak in &leaks {
let taps = &candidates[leak.candidate_index];
let regen = describe::lfsr_generate(taps, &leak.attack.init_state, n);
let planted = if leak.candidate_index == 0 { &x1 } else { &x3 };
assert_eq!(®en, planted, "the recovered initial state regenerates the hidden register");
assert!(leak.margin > 3.0, "the leak clears the significance threshold, margin {}", leak.margin);
}
let random = splitmix_bytes(250, 0xfeed_face_0bad_c0de);
assert!(
scan_for_combiner_leaks(&random, &candidates, 3.0).is_empty(),
"a strong keystream leaks no register to first-order correlation — the ceiling"
);
}
#[test]
fn linear_cryptanalysis_reads_the_whole_spectrum_where_correlation_reads_one_bit() {
let f: Vec<bool> = (0..16)
.map(|x| {
let b = |i: usize| (x >> i) & 1 == 1;
b(0) ^ b(1) ^ (b(2) & b(3))
})
.collect();
let d = linear_cryptanalysis(&f).expect("well-formed");
assert_eq!(d.immunity_order, 1, "first-order correlation-immune — E finds nothing");
assert_eq!(d.bias, 0.25, "but a linear approximation leaks with bias ¼");
assert!(d.mask_weight >= 2, "at weight ≥ 2 — the multi-register leak beyond E's reach");
let g: Vec<bool> = (0..16)
.map(|x| {
let b = |i: usize| (x >> i) & 1 == 1;
(b(0) & b(1)) ^ (b(2) & b(3))
})
.collect();
let dg = linear_cryptanalysis(&g).expect("well-formed");
assert_eq!(dg.nonlinearity, 6, "bent ⇒ maximal nonlinearity 6 for n=4");
assert_eq!(dg.bias, 0.125, "no linear approximation beats the flat-spectrum floor 1/8 — the ceiling");
}
#[test]
fn algebraic_immunity_report_grades_filters_and_the_attack_recovers_state() {
let weak: Vec<bool> = (0..16).map(|x| (x >> 0) & 1 == 1).collect(); let wr = algebraic_immunity_of(&weak).expect("well-formed");
assert_eq!(wr.immunity, 1, "an affine filter has AI 1");
assert!(!wr.is_maximal, "AI 1 < ⌈4/2⌉ = 2 — exploitable");
assert!(describe::verify_annihilator(&weak, &wr.witness), "the annihilator re-checks");
let bent: Vec<bool> = (0..16)
.map(|x| {
let b = |i: usize| (x >> i) & 1 == 1;
(b(0) & b(1)) ^ (b(2) & b(3))
})
.collect();
let br = algebraic_immunity_of(&bent).expect("well-formed");
assert_eq!(br.immunity, 2, "the bent filter has AI 2");
assert!(br.is_maximal, "AI 2 = ⌈4/2⌉ — maximal algebraic immunity, the ceiling");
let taps = [false, false, false, false, false, false, true, false, false, true]; let s0 = [true, true, false, true, false, true, true, false, false, true];
let filter: Vec<bool> = (0..8).map(|x| (x as u32).count_ones() >= 2).collect(); let (m, n) = (3usize, 400usize);
let seq = describe::lfsr_generate(&taps, &s0, n + m);
let keystream: Vec<bool> = (0..n)
.map(|t| {
let idx = (0..m).fold(0usize, |a, i| a | (usize::from(seq[t + i]) << i));
filter[idx]
})
.collect();
let recovered = algebraic_filter_attack(&keystream, &taps, &filter).expect("attack succeeds");
assert_eq!(recovered, s0.to_vec(), "the algebraic attack recovers the exact secret state");
}
#[test]
fn fast_correlation_scales_the_correlation_break_past_exhaustive_search() {
let mut taps = vec![false; 17];
taps[13] = true;
taps[16] = true;
let seed: Vec<bool> = (0..17).map(|k| (0x5A5Au64 >> k) & 1 == 1).collect();
let n = 4000;
let a = describe::lfsr_generate(&taps, &seed, n);
let mut st = 0x0f1e_2d3c_4b5a_6978u64;
let z: Vec<bool> = a
.iter()
.map(|&bit| {
st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
bit ^ ((st >> 40) % 100 < 12)
})
.collect();
let state = fast_correlation_attack(&z, &taps, 400).expect("decodes the leaking register");
assert_eq!(state, seed, "the recovered state IS the register key — no 2¹⁷ search");
}
#[test]
fn shrinking_generator_falls_to_clock_control_inversion() {
let a_taps = [false, false, true, false, false, false, true];
let s_taps = [false, false, false, false, true, false, false, false, true];
let a_seed = [true, true, false, false, true, false, true];
let s_seed = [false, true, true, false, true, true, false, false, true];
let m = 300;
let output = describe::shrinking_generator(&a_taps, &a_seed, &s_taps, &s_seed, m);
let (a_rec, s_rec) = attack_shrinking_generator(&output, &a_taps, &s_taps).expect("the generator falls");
assert_eq!(
describe::shrinking_generator(&a_taps, &a_rec, &s_taps, &s_rec, 800),
describe::shrinking_generator(&a_taps, &a_seed, &s_taps, &s_seed, 800),
"the recovered registers reproduce the keystream well past the attacked length — a full break"
);
}
#[test]
fn full_arsenal_audit_flags_weak_keys_and_clears_a_sound_one() {
use crate::factor;
use logicaffeine_base::BigInt;
let big = |s: &str| BigInt::parse_decimal(s).unwrap();
let one = BigInt::from_i64(1);
let e = BigInt::from_i64(65537);
let p = factor::next_prime(&big("1000000000000000000000000000057"));
let q = factor::next_prime(&p.add(&BigInt::from_i64(100)));
let n = p.mul(&q);
assert!(matches!(rsa_full_audit(&n, &e), RsaAuditVerdict::Factored { .. }), "close primes caught");
let p = factor::next_prime(&big("1000000000000000"));
let q = factor::next_prime(&big("3000000000000000"));
let n = p.mul(&q);
let phi = p.sub(&one).mul(&q.sub(&one));
let small_d = BigInt::from_i64(7919);
let big_e = factor::mod_inverse(&small_d, &phi).expect("d coprime to φ");
assert_eq!(rsa_full_audit(&n, &big_e), RsaAuditVerdict::WeakExponent, "small d caught by Wiener");
let p = factor::next_prime(&big("1000000000000000000000000000057"));
let q = factor::next_prime(&big("9000000000000000000000000000000"));
let n = p.mul(&q);
assert_eq!(
rsa_full_audit(&n, &e),
RsaAuditVerdict::ResistsFullArsenal,
"a sound RSA key resists the entire arsenal — we are not breaking RSA"
);
}
#[test]
fn recursive_reduction_reaches_the_incompressible_fixed_point() {
let structured: Vec<u8> = (0..300).map(|i| [3u8, 1, 4, 1, 5, 9, 2, 6][i % 8]).collect();
let r = recursive_reduce(&structured);
assert!(r.compressed, "the periodic sequence is symmetry-broken down");
assert!(r.irreducible_bytes < structured.len() / 2, "and reaches a small fixed point, {:?}", r.sizes);
let random: Vec<u8> = (0..300u32)
.map(|i| {
let mut z = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(i as u64 + 1);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
((z ^ (z >> 31)) & 0xFF) as u8
})
.collect();
let r = recursive_reduce(&random);
assert_eq!(r.depth, 0, "random has no structure any lens breaks — the fixed point is the object");
assert!(!r.compressed, "no lens fires — the residue, which we cannot prove is truly structureless");
}
fn pseudorandom_truth(n: usize) -> Vec<bool> {
(0..1u64 << n)
.map(|i| {
let mut z = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(i + 1);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
(z ^ (z >> 31)) & 1 == 1
})
.collect()
}
#[test]
fn find_structure_reads_a_constant_off_the_cube() {
let truth = vec![true; 8]; let r = find_structure(&truth).expect("a well-formed cube");
assert_eq!(r.num_vars, 3);
assert!(matches!(r.class, CubeStructure::Constant(true)));
assert!(r.compressed && r.description_bits == 1, "the whole cube in one bit");
assert_eq!(r.class.reconstruct(3).as_deref(), Some(&truth[..]));
}
#[test]
fn find_structure_reads_a_junta_off_the_coordinate_walk() {
let n = 8;
let truth: Vec<bool> = (0..1usize << n).map(|x| (x & 0b111).count_ones() >= 2).collect();
let r = find_structure(&truth).unwrap();
match &r.class {
CubeStructure::Junta { relevant, .. } => assert_eq!(relevant, &vec![0, 1, 2]),
other => panic!("expected a junta, got {other:?}"),
}
assert!(r.compressed);
assert_eq!(r.class.reconstruct(n).as_deref(), Some(&truth[..]));
}
#[test]
fn find_structure_reads_affine_parity_off_the_walsh_walk() {
let n = 4;
let truth: Vec<bool> = (0..1usize << n).map(|x| (x as u32).count_ones() % 2 == 1).collect();
let r = find_structure(&truth).unwrap();
match &r.class {
CubeStructure::Affine { coeffs, constant } => {
assert_eq!(coeffs, &vec![true; 4]);
assert!(!constant);
}
other => panic!("expected affine, got {other:?}"),
}
assert!(r.compressed && r.description_bits == n + 1);
assert_eq!(r.class.reconstruct(n).as_deref(), Some(&truth[..]));
}
#[test]
fn find_structure_reads_a_symmetric_function_off_the_weight_shells() {
let n = 3;
let truth: Vec<bool> = (0..1usize << n).map(|x| (x as u32).count_ones() >= 2).collect();
let r = find_structure(&truth).unwrap();
assert!(matches!(r.class, CubeStructure::Symmetric { .. }), "got {:?}", r.class);
assert!(r.compressed);
assert_eq!(r.class.reconstruct(n).as_deref(), Some(&truth[..]));
}
#[test]
fn find_structure_reads_low_degree_off_the_mobius_walk() {
let n = 6;
let truth: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1usize << i) != 0;
(b(0) && b(1)) ^ (b(2) && b(3)) ^ (b(4) && b(5))
})
.collect();
let r = find_structure(&truth).unwrap();
match &r.class {
CubeStructure::LowDegree { degree, .. } => assert_eq!(*degree, 2),
other => panic!("expected low-degree, got {other:?}"),
}
assert!(r.compressed);
assert_eq!(r.class.reconstruct(n).as_deref(), Some(&truth[..]));
}
#[test]
fn find_structure_finds_no_lens_for_a_pseudorandom_function() {
let n = 6;
let truth = pseudorandom_truth(n);
let r = find_structure(&truth).unwrap();
assert!(matches!(r.class, CubeStructure::ResistedArsenal { .. }), "got {:?}", r.class);
assert!(!r.compressed, "the residue does not beat storing the truth table");
assert!(r.class.reconstruct(n).is_none(), "the residue carries no compressed witness");
}
#[test]
fn structure_witness_is_re_checkable_and_rejects_tampering() {
let n = 4;
let truth: Vec<bool> = (0..1usize << n).map(|x| (x as u32).count_ones() % 2 == 1).collect();
let r = find_structure(&truth).unwrap();
assert_eq!(r.class.reconstruct(n).as_deref(), Some(&truth[..]), "the witness reproduces the function");
let tampered = match r.class {
CubeStructure::Affine { mut coeffs, constant } => {
coeffs[0] = !coeffs[0];
CubeStructure::Affine { coeffs, constant }
}
other => panic!("expected affine, got {other:?}"),
};
assert_ne!(tampered.reconstruct(n).as_deref(), Some(&truth[..]), "a tampered witness does not");
}
#[test]
fn structure_cover_peels_a_sparse_function_to_a_small_residue() {
let n = 6;
let truth: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1usize << i) != 0;
(b(0) && b(1)) ^ (b(2) && b(3)) ^ (b(4) && b(5))
})
.collect();
let c = structure_cover(&truth).unwrap();
assert_eq!(c.monomials_by_degree, vec![0, 0, 3, 0, 0, 0, 0], "only the degree-2 slice is populated");
assert_eq!(c.total_monomials, 3);
assert_eq!((c.residue_degree, c.residue_monomials), (2, 3), "the residue is three quadratic terms");
assert_eq!(c.monomials_by_degree.iter().sum::<usize>(), c.total_monomials, "the slices sum to the whole");
assert!(c.compressed, "a sparse ANF covers all 2^n corners cheaply");
}
#[test]
fn structure_cover_of_a_constant_is_the_degree_zero_slice() {
let c = structure_cover(&vec![true; 8]).unwrap(); assert_eq!(c.monomials_by_degree, vec![1, 0, 0, 0], "just the constant term");
assert_eq!((c.total_monomials, c.residue_degree), (1, 0));
assert!(c.compressed);
}
#[test]
fn structure_cover_leaves_a_dense_high_degree_residue_for_a_pseudorandom_function() {
let n = 6;
let truth = pseudorandom_truth(n);
let c = structure_cover(&truth).unwrap();
assert_eq!(c.monomials_by_degree.iter().sum::<usize>(), c.total_monomials);
assert!(c.residue_degree >= 4, "structure reaches high degree, got {}", c.residue_degree);
assert!(c.total_monomials > n, "a dense ANF, not a sparse low-degree cover");
assert!(!c.compressed, "the peel does not beat storing the truth table — the incompressible core");
}
fn rotated_junta(n_free: usize) -> Vec<bool> {
let h = pseudorandom_truth(n_free); (0..1usize << (n_free + 1))
.map(|x| {
let u0 = ((x & 1) ^ ((x >> 1) & 1)) & 1; let rest = x >> 2; h[u0 | (rest << 1)]
})
.collect()
}
#[test]
fn linear_structures_finds_none_in_a_pseudorandom_function() {
let truth = pseudorandom_truth(6);
let r = linear_structures(&truth).unwrap();
assert_eq!(r.dim(), 0, "a random function has trivial linear space, got basis {:?}", r.basis);
assert!(!r.is_reducible());
assert!(reduce_by_invariance(&truth).is_none(), "nothing to peel");
assert!(r.verify(&truth), "the empty witness trivially re-checks");
}
#[test]
fn linear_structures_peel_a_rotated_junta_the_coordinate_lenses_miss() {
let n = 6;
let truth = rotated_junta(5);
let base = find_structure(&truth).unwrap();
assert!(matches!(base.class, CubeStructure::ResistedArsenal { .. }), "base arsenal: {:?}", base.class);
let ls = linear_structures(&truth).unwrap();
assert!(ls.is_reducible(), "the rotated junta has a nontrivial linear space");
assert_eq!(reduce_by_basis(0b11, &ls.basis), 0, "a = e0 ⊕ e1 lies in the linear space");
assert!(ls.verify(&truth), "the linear-structure witness re-checks");
let (red, basis) = reduce_by_invariance(&truth).expect("a rotated junta reduces");
assert!(red.reduced_vars < n, "peeled at least one dimension: {} → {}", n, red.reduced_vars);
assert!(red.verify(&truth, &basis), "the reduced function reproduces the original on every corner");
}
#[test]
fn affine_reduce_peels_a_residue_plus_a_linear_form() {
let n = 6;
let g = pseudorandom_truth(5);
let truth: Vec<bool> = (0..1usize << n).map(|x| g[x >> 1] ^ (x & 1 != 0)).collect();
assert!(matches!(find_structure(&truth).unwrap().class, CubeStructure::ResistedArsenal { .. }));
assert!(reduce_by_invariance(&truth).is_none(), "no pure invariance to peel");
let ar = affine_reduce(&truth).expect("the linear form peels off");
assert!(ar.reduced_truth.len() < truth.len(), "reduced onto fewer coordinates");
assert_eq!(ar.reconstruct().as_deref(), Some(&truth[..]), "h ⊕ ℓ reconstructs f exactly");
}
#[test]
fn linear_structure_witness_rejects_tampering() {
let truth = rotated_junta(5);
let r = linear_structures(&truth).unwrap();
assert!(r.verify(&truth));
let mut bad = r.clone();
bad.derivative[0] = !bad.derivative[0];
assert!(!bad.verify(&truth), "a tampered derivative is caught");
let mut bad2 = r.clone();
bad2.basis[0] ^= 0b100; assert!(!bad2.verify(&truth), "a tampered basis vector is caught");
}
fn apply_linear_input_map(truth: &[bool], rows: &[usize]) -> Vec<bool> {
(0..truth.len())
.map(|x| {
let ax = rows
.iter()
.enumerate()
.fold(0usize, |acc, (i, &r)| if (r & x).count_ones() % 2 == 1 { acc | (1 << i) } else { acc });
truth[ax]
})
.collect()
}
#[test]
fn affine_signature_is_invariant_under_a_linear_change_of_basis() {
let rows = vec![0b0001usize, 0b0011, 0b0111, 0b1111];
assert_eq!(gf2_echelon_basis(&rows).len(), 4, "the map must be invertible");
let bent: Vec<bool> = (0..16)
.map(|x| {
let b = |i: usize| x & (1 << i) != 0;
(b(0) && b(1)) ^ (b(2) && b(3))
})
.collect();
for f in [bent, pseudorandom_truth(4), (0..16).map(|x: usize| (x as u32).count_ones() % 2 == 1).collect()] {
let g = apply_linear_input_map(&f, &rows);
assert_eq!(
affine_signature(&f).unwrap().walsh_abs_distribution,
affine_signature(&g).unwrap().walsh_abs_distribution,
"the Walsh multiset is an affine invariant — a rotation cannot change the class"
);
}
}
#[test]
fn affine_signature_recognizes_bent_and_plateaued_and_reads_the_linear_dimension() {
let bent: Vec<bool> = (0..16)
.map(|x| {
let b = |i: usize| x & (1 << i) != 0;
(b(0) && b(1)) ^ (b(2) && b(3))
})
.collect();
let sb = affine_signature(&bent).unwrap();
assert!(sb.is_plateaued && sb.is_bent && sb.amplitude == Some(4));
assert_eq!(sb.implied_linear_dim(), Some(0));
assert_eq!(sb.implied_linear_dim(), Some(linear_structures(&bent).unwrap().dim()));
let pb: Vec<bool> = (0..8)
.map(|x| {
let b = |i: usize| x & (1 << i) != 0;
(b(0) && b(1)) ^ b(2)
})
.collect();
let sp = affine_signature(&pb).unwrap();
assert!(sp.is_plateaued && !sp.is_bent && sp.amplitude == Some(4));
assert_eq!(sp.implied_linear_dim(), Some(1), "amplitude 2^{{(n+k)/2}} reads off k=1");
assert_eq!(sp.implied_linear_dim(), Some(linear_structures(&pb).unwrap().dim()), "the two rungs agree");
let rnd = pseudorandom_truth(6);
let sr = affine_signature(&rnd).unwrap();
assert!(!sr.is_plateaued && sr.amplitude.is_none(), "the residue has many Walsh amplitudes");
assert!(sr.walsh_abs_distribution.len() >= 3, "a genuine spread of amplitudes");
}
fn split_dense(seed_lo: u64, seed_hi: u64) -> Vec<bool> {
let bit = |i: u64, s: u64| {
let mut z = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(i + s);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
(z ^ (z >> 27)) & 1 == 1
};
(0..64usize).map(|x| bit((x & 0b111) as u64, seed_lo) ^ bit(((x >> 3) & 0b111) as u64, seed_hi)).collect()
}
#[test]
fn separable_decomposition_splits_independent_blocks() {
let n = 6;
let truth: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1usize << i) != 0;
(b(0) && b(1)) ^ (b(2) && b(3)) ^ (b(4) && b(5))
})
.collect();
let d = separable_decomposition(&truth).unwrap();
assert_eq!(d.blocks, vec![vec![0, 1], vec![2, 3], vec![4, 5]], "the three pairs split apart");
assert!(d.is_separable());
assert!(d.table_bits() < truth.len(), "Σ 2^|B| = 12 beats 2ⁿ = 64");
assert_eq!(d.reconstruct(n).as_deref(), Some(&truth[..]), "the blocks XOR back to the whole");
}
#[test]
fn separable_decomposition_peels_dense_blocks_apart() {
let n = 6;
let truth = split_dense(1, 999);
let d = separable_decomposition(&truth).unwrap();
assert!(d.is_separable(), "the two dense halves are independent blocks");
for b in &d.blocks {
let lo = b.iter().any(|&i| i < 3);
let hi = b.iter().any(|&i| i >= 3);
assert!(!(lo && hi), "no block bridges the two independent halves: {b:?}");
}
assert_eq!(d.reconstruct(n).as_deref(), Some(&truth[..]));
}
#[test]
fn a_connected_function_is_one_irreducible_block() {
let n = 5;
let truth: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1usize << i) != 0;
(b(0) && b(1)) ^ (b(1) && b(2)) ^ (b(2) && b(3)) ^ (b(3) && b(4))
})
.collect();
let d = separable_decomposition(&truth).unwrap();
assert_eq!(d.blocks, vec![vec![0, 1, 2, 3, 4]], "one connected block");
assert!(!d.is_separable());
assert_eq!(d.reconstruct(n).as_deref(), Some(&truth[..]));
}
#[test]
fn separable_reconstruct_rejects_tampering() {
let truth = split_dense(7, 42);
let mut d = separable_decomposition(&truth).unwrap();
assert_eq!(d.reconstruct(6).as_deref(), Some(&truth[..]));
d.block_truths[0][0] = !d.block_truths[0][0]; assert_ne!(d.reconstruct(6).as_deref(), Some(&truth[..]), "a tampered block is caught");
}
fn rotation_symmetric(n: usize, seed: u64) -> Vec<bool> {
let mask = (1usize << n) - 1;
let rot = |x: usize| ((x << 1) | (x >> (n - 1))) & mask;
(0..1usize << n)
.map(|x| {
let (mut m, mut y) = (x, x);
for _ in 0..n {
y = rot(y);
m = m.min(y);
}
let mut z = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(m as u64 + seed);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
(z ^ (z >> 27)) & 1 == 1
})
.collect()
}
#[test]
fn variable_symmetry_generalizes_the_symmetric_class() {
let n = 4;
let truth: Vec<bool> = (0..1usize << n).map(|x| (x as u32).count_ones() >= 2).collect();
let s = variable_symmetry(&truth).unwrap();
assert_eq!(s.order, 24, "Aut(MAJ4) = S_4");
assert_eq!(s.orbit_count, n + 1, "the orbits are the weight classes");
assert!(s.verify(&truth));
assert_eq!(s.reconstruct(n).as_deref(), Some(&truth[..]));
}
#[test]
fn variable_symmetry_finds_rotation_symmetry_the_linear_ladder_misses() {
let n = 5;
let truth = rotation_symmetric(n, 12345);
let s = variable_symmetry(&truth).unwrap();
assert!(s.is_nontrivial() && s.order % 5 == 0, "C_5 ≤ Aut(f), order {}", s.order);
assert!(s.order < 120 && s.orbit_count > n + 1, "partial symmetry: order {} orbits {}", s.order, s.orbit_count);
assert!(s.orbit_count < 1 << n, "yet still compressed: {} < {}", s.orbit_count, 1 << n);
assert!(s.verify(&truth));
assert_eq!(s.reconstruct(n).as_deref(), Some(&truth[..]), "the orbit painting reproduces f");
}
#[test]
fn variable_symmetry_is_trivial_for_a_pseudorandom_function() {
let truth = pseudorandom_truth(6);
let s = variable_symmetry(&truth).unwrap();
assert_eq!(s.order, 1, "a random function has no variable symmetry");
assert!(!s.is_nontrivial());
assert_eq!(s.orbit_count, 1 << 6, "every input is its own orbit — the residue");
assert!(s.verify(&truth), "the trivial group trivially reproduces f");
}
#[test]
fn variable_symmetry_witness_rejects_tampering() {
let truth = rotation_symmetric(5, 999);
let mut s = variable_symmetry(&truth).unwrap();
assert!(s.verify(&truth));
s.orbit_values[0] = !s.orbit_values[0]; assert!(!s.verify(&truth), "a tampered orbit value is caught");
let mut s2 = variable_symmetry(&truth).unwrap();
if let Some(g) = s2.generators.first_mut() {
g.swap(0, 1); }
s2.orbit_values[1] = !s2.orbit_values[1];
assert!(!s2.verify(&truth));
}
#[test]
fn deep_finder_routes_each_function_to_its_tightest_axis() {
let sep = split_dense(1, 999);
assert_eq!(find_structure_deep(&sep).unwrap().winner, "separable");
let rot = rotation_symmetric(5, 12345);
assert_eq!(find_structure_deep(&rot).unwrap().winner, "permutation");
let rj = rotated_junta(5);
assert_eq!(find_structure_deep(&rj).unwrap().winner, "linear-invariance");
let mono: Vec<bool> = (0..16usize).map(|x| (x & 1 != 0) && (x & 2 != 0) && (x & 4 != 0)).collect();
assert_eq!(find_structure_deep(&mono).unwrap().winner, "coordinate:low-degree");
let parity: Vec<bool> = (0..16usize).map(|x| (x as u32).count_ones() % 2 == 1).collect();
assert_eq!(find_structure_deep(&parity).unwrap().winner, "linear-invariance");
let rnd = pseudorandom_truth(6);
let d = find_structure_deep(&rnd).unwrap();
assert_eq!(d.winner, "residue");
assert!(!d.compressed && d.description_bits == d.raw_bits);
}
#[test]
fn structure_tree_recurses_to_a_nested_fixed_point() {
let n = 6;
let truth: Vec<bool> = (0..1usize << n)
.map(|x| {
let b = |i: usize| x & (1usize << i) != 0;
((b(0) ^ b(1)) && b(2)) ^ (b(3) && b(4) && b(5))
})
.collect();
let tree = structure_tree(&truth).unwrap();
match &tree {
StructureTree::Separable { blocks, .. } => {
assert_eq!(blocks.len(), 2, "the two halves split apart");
assert!(
blocks.iter().any(|(_, t)| matches!(t, StructureTree::LinearReduced { .. })),
"a block peels deeper: {:?}",
blocks.iter().map(|(_, t)| t.depth()).collect::<Vec<_>>()
);
}
other => panic!("expected a separable top level, got {other:?}"),
}
assert!(tree.depth() >= 2, "a genuinely nested decomposition, depth {}", tree.depth());
assert_eq!(tree.reconstruct().as_deref(), Some(&truth[..]), "the whole tree reconstructs f exactly");
assert!(tree.total_description_bits() < truth.len(), "and it is a real compression of the cube");
}
#[test]
fn structure_tree_bottoms_out_at_the_residue() {
let truth = pseudorandom_truth(6);
let tree = structure_tree(&truth).unwrap();
assert!(matches!(tree, StructureTree::Residue { .. }), "no axis peels a random function");
assert_eq!(tree.depth(), 0);
assert_eq!(tree.reconstruct().as_deref(), Some(&truth[..]), "the residue is stored and reproduced raw");
}
#[test]
fn census_residue_is_certified_nonempty_by_counting() {
for nv in 2..=4u32 {
let census = boolean_function_census(nv as usize).unwrap();
assert!(census.residue > 0, "the arsenal empirically leaves a residue at n={nv}");
let cert = certified_incompressible_function_exists(nv).expect("a counting certificate exists");
assert!(crate::pigeonhole::check_counting_cert(&cert), "the counting certificate re-checks");
assert_eq!(cert.pigeons, 1u128 << (1u128 << nv), "one pigeon per Boolean function");
assert_eq!(cert.holes, (1u128 << (1u128 << nv)) - 1, "one hole per shorter program");
}
assert!(certified_incompressible_function_exists(6).is_some());
assert!(certified_incompressible_function_exists(7).is_none());
}
#[test]
fn sampled_census_shows_the_residue_approaching_one() {
let fracs: Vec<f64> =
(4..=8).map(|n| sampled_boolean_census(n, 1200, 12345).unwrap().residue_fraction()).collect();
assert!((fracs[0] - 0.62).abs() < 0.08, "n=4 sample ≈ exhaustive census: got {}", fracs[0]);
for w in fracs.windows(2) {
assert!(w[1] >= w[0] - 0.01, "the residue fraction climbs toward 1 with n: {fracs:?}");
}
assert!(*fracs.last().unwrap() > 0.99, "by n=8 almost every function is incompressible: {fracs:?}");
}
#[test]
fn boolean_census_maps_the_space_and_the_residue_grows() {
let (c2, c3, c4) = (
boolean_function_census(2).unwrap(),
boolean_function_census(3).unwrap(),
boolean_function_census(4).unwrap(),
);
for c in [&c2, &c3, &c4] {
assert_eq!(c.total, 1usize << (1usize << c.num_vars), "2^{{2ⁿ}} functions");
assert_eq!(c.by_winner.iter().map(|(_, k)| *k).sum::<usize>(), c.total, "every function is classified");
assert_eq!(c.compressed + c.residue, c.total);
}
let (f3, f4) = (c3.residue as f64 / c3.total as f64, c4.residue as f64 / c4.total as f64);
assert!(f4 > f3 + 0.3, "the residue fraction jumps with n (n=3 → {f3:.3}, n=4 → {f4:.3})");
assert!(c4.residue * 2 > c4.total, "by n=4 the incompressible residue is already the majority");
for axis in
["affine", "coordinate:constant", "coordinate:low-degree", "linear-invariance", "permutation", "separable"]
{
assert!(c4.by_winner.iter().any(|(w, k)| *w == axis && *k > 0), "axis {axis} wins somewhere");
}
}
#[test]
fn description_bound_covers_boolean_functions_in_the_unified_framework() {
let structured: Vec<bool> = (0..64usize)
.map(|x| {
let b = |i: usize| x & (1usize << i) != 0;
((b(0) ^ b(1)) && b(2)) ^ (b(3) && b(4) && b(5))
})
.collect();
let db = DescriptionBound::of_boolean(&structured).unwrap();
assert!(matches!(db.descriptor, Descriptor::BooleanFunction { .. }));
assert!(db.verify(), "the tree decode witness re-checks inside DescriptionBound");
assert!(db.bytes < structured.len() / 8 + 1, "K̄ beats the packed truth table, {} bytes", db.bytes);
let mut bad = DescriptionBound::of_boolean(&structured).unwrap();
bad.bytes += 1;
assert!(!bad.verify(), "an inconsistent bound is rejected");
let seq = DescriptionBound::of_int_seq(&(0..100).map(|i| 3 * i + 1).collect::<Vec<_>>());
assert!(seq.verify());
}
#[test]
fn kolmogorov_bound_certifies_compression_and_the_honest_residue() {
let structured: Vec<bool> = (0..64usize)
.map(|x| {
let b = |i: usize| x & (1usize << i) != 0;
((b(0) ^ b(1)) && b(2)) ^ (b(3) && b(4) && b(5))
})
.collect();
let kb = kolmogorov_bound(&structured).unwrap();
assert!(kb.is_compressed() && kb.ratio() < 1.0, "structure gives K̄ < 2ⁿ, ratio {}", kb.ratio());
assert!(kb.verify(&structured), "the decode witness re-checks the bound");
let rnd = pseudorandom_truth(6);
let kr = kolmogorov_bound(&rnd).unwrap();
assert_eq!(kr.bits, kr.raw_bits, "no axis beats the truth table — the residue, K̄ = 2ⁿ");
assert!(!kr.is_compressed() && kr.verify(&rnd));
let mut bad = kolmogorov_bound(&structured).unwrap();
bad.tree = StructureTree::Residue { truth: rnd.clone() };
assert!(!bad.verify(&structured), "a witness for a different function does not verify");
}
#[test]
fn sbox_profile_flags_a_linear_sbox_as_weak() {
let id: Vec<u32> = (0..16u32).collect();
let p = sbox_profile(&id, 4).unwrap();
assert!(p.is_affine && p.min_degree == 1, "a linear S-box has affine components");
assert_eq!(p.differential_uniformity, 16, "S(x⊕a)⊕S(x)=a is constant — maximally weak");
assert_eq!(p.linearity, 16, "a perfect linear approximation exists");
assert!(p.is_bijective && !p.is_apn);
}
#[test]
fn sbox_profile_certifies_the_aes_sbox_is_strong() {
#[rustfmt::skip]
const AES: [u8; 256] = [
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
];
let sbox: Vec<u32> = AES.iter().map(|&b| b as u32).collect();
let p = sbox_profile(&sbox, 8).unwrap();
assert_eq!(p.differential_uniformity, 4, "AES S-box differential uniformity");
assert_eq!(p.linearity, 32, "AES S-box linearity (nonlinearity 128 − 16 = 112)");
assert_eq!(p.min_degree, 7, "AES S-box algebraic degree");
assert!(p.is_bijective && !p.is_affine && !p.is_apn, "strong, invertible, not linear, not APN");
}
fn apply_input_linear(sbox: &[u32], rows: &[usize]) -> Vec<u32> {
(0..sbox.len())
.map(|x| {
let ax = rows
.iter()
.enumerate()
.fold(0usize, |acc, (i, &r)| if (r & x).count_ones() % 2 == 1 { acc | (1 << i) } else { acc });
sbox[ax]
})
.collect()
}
fn apply_output_linear(sbox: &[u32], rows: &[usize]) -> Vec<u32> {
sbox.iter()
.map(|&y| {
rows.iter().enumerate().fold(0u32, |acc, (i, &r)| {
if (r & y as usize).count_ones() % 2 == 1 { acc | (1 << i) } else { acc }
})
})
.collect()
}
#[test]
fn sbox_spectra_are_affine_invariants() {
let present: Vec<u32> = vec![0xC, 5, 6, 0xB, 9, 0, 0xA, 0xD, 3, 0xE, 0xF, 8, 4, 7, 1, 2];
let rows = vec![0b0001usize, 0b0011, 0b0111, 0b1111]; assert_eq!(gf2_echelon_basis(&rows).len(), 4);
let base = sbox_spectra(&present, 4).unwrap();
let g = apply_input_linear(&present, &rows);
assert_eq!(sbox_spectra(&g, 4).unwrap(), base, "spectra invariant under an input linear change");
let h = apply_output_linear(&present, &rows);
assert_eq!(sbox_spectra(&h, 4).unwrap(), base, "spectra invariant under an output linear change");
}
#[test]
fn sbox_spectra_distinguish_inequivalent_boxes() {
let id: Vec<u32> = (0..16u32).collect();
let present: Vec<u32> = vec![0xC, 5, 6, 0xB, 9, 0, 0xA, 0xD, 3, 0xE, 0xF, 8, 4, 7, 1, 2];
assert_ne!(
sbox_spectra(&id, 4).unwrap().differential_spectrum,
sbox_spectra(&present, 4).unwrap().differential_spectrum,
"a linear box and a nonlinear box cannot be affine-equivalent"
);
}
#[test]
fn sbox_profile_recognizes_an_apn_gold_function() {
fn gf8_mul(mut a: u32, mut b: u32) -> u32 {
let mut p = 0;
for _ in 0..3 {
if b & 1 == 1 {
p ^= a;
}
b >>= 1;
let hi = a & 0b100;
a = (a << 1) & 0b111;
if hi != 0 {
a ^= 0b011; }
}
p
}
let sbox: Vec<u32> = (0..8u32).map(|x| gf8_mul(gf8_mul(x, x), x)).collect();
let p = sbox_profile(&sbox, 3).unwrap();
assert_eq!(p.differential_uniformity, 2, "the Gold function is APN");
assert!(p.is_apn && p.is_bijective, "an APN permutation on an odd number of bits");
assert_eq!(boomerang_uniformity(&sbox), Some(2), "APN ⟹ boomerang uniformity 2");
assert_eq!(sbox_full_audit(&sbox, 3), Some(SboxVerdict::Quadratic), "optimal differential, weak algebra");
}
#[test]
fn sbox_audit_flags_weakness_and_clears_aes() {
let id: Vec<u32> = (0..16u32).collect();
assert_eq!(sbox_full_audit(&id, 4), Some(SboxVerdict::Affine));
#[rustfmt::skip]
const AES: [u8; 256] = [
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
];
let aes: Vec<u32> = AES.iter().map(|&b| b as u32).collect();
assert_eq!(
sbox_full_audit(&aes, 8),
Some(SboxVerdict::NoStructuralWeaknessFound {
differential_uniformity: 4,
linearity: 32,
min_degree: 7,
boomerang_uniformity: Some(6),
}),
"AES resists the structural arsenal — not broken"
);
}
#[test]
fn boomerang_uniformity_matches_the_aes_published_value() {
#[rustfmt::skip]
const AES: [u8; 256] = [
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
];
let sbox: Vec<u32> = AES.iter().map(|&b| b as u32).collect();
assert_eq!(boomerang_uniformity(&sbox), Some(6), "AES S-box boomerang uniformity (Cid et al. 2018)");
}
#[test]
fn coverage_census_shows_covered_families_and_the_uncovered_residue() {
use logicaffeine_base::BigInt;
let n = 300;
let mut structured: Vec<Vec<bool>> = Vec::new();
structured.push(describe::lfsr_generate(
&[false, false, true, false, false, false, true],
&[true, false, true, true, false, false, true],
n,
)); structured.push(describe::fcsr_generate(&BigInt::from_i64(7), &BigInt::from_i64(19), n)); structured.push(describe::fcsr_generate(&BigInt::from_i64(11), &BigInt::from_i64(23), n));
structured.push((0..n).map(|i| [true, false, false, true, true, false][i % 6]).collect()); let sc = census(&structured);
assert_eq!(sc.uncovered, 0, "every structured sequence is covered; map = {:?}", sc.by_lens);
let random: Vec<Vec<bool>> = (0..40u64)
.map(|i| {
let mut st = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(i.wrapping_add(1));
(0..n)
.map(|_| {
st = st.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = st;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
z & 1 == 1
})
.collect()
})
.collect();
assert_eq!(census(&random).covered, 0, "cryptographic-random sequences are the uncovered residue");
let ex = exhaustive_coverage(14);
eprintln!(
"exhaustive len=14: covered {}/{} = {:.2}%, residue {} = {:.2}%",
ex.covered,
ex.total,
100.0 * ex.covered as f64 / ex.total as f64,
ex.uncovered,
100.0 * ex.uncovered as f64 / ex.total as f64,
);
assert!(ex.covered * 2 < ex.total, "the covered families are a minority sliver — most of the space is residue");
}
#[test]
fn rsa_structural_audit_breaks_weak_moduli_and_certifies_the_ceiling() {
use crate::factor;
use logicaffeine_base::BigInt;
let big = |s: &str| BigInt::parse_decimal(s).unwrap();
let p = factor::next_prime(&big("1000000000000000000"));
let q = factor::next_prime(&p.add(&BigInt::from_i64(2)));
let n = p.mul(&q);
match rsa_structural_audit(&n) {
RsaStrength::Factored { p: a, q: b, method } => {
assert!(factor::verify_factorization(&n, &a, &b), "the witness re-multiplies to N");
assert!(method.contains("Fermat"), "close primes are caught by Fermat, got {method}");
}
RsaStrength::SoundAgainstStructuralAttacks => panic!("close primes must be broken"),
}
let p = factor::next_prime(&big("1000000000000000000"));
let q = factor::next_prime(&big("9000000000000000000"));
let n = p.mul(&q);
assert_eq!(
rsa_structural_audit(&n),
RsaStrength::SoundAgainstStructuralAttacks,
"a sound modulus is the number-theoretic incompressible residue"
);
}
#[test]
fn algebraic_attack_stops_at_the_ceiling_on_random_bytes() {
let random = splitmix_bytes(120, 0xdead_beef_cafe_babe);
assert!(
algebraic_attack_on_bytes(&random, 2, 20).is_none(),
"no degree-2 register regenerates a cryptographic keystream — the Chaitin ceiling"
);
}
#[test]
fn random_key_material_is_incompressible_in_class() {
let key = splitmix_bytes(400, 0x1234_5678_9abc_def0);
match assess_key_material(&key) {
CryptoStrength::IncompressibleInClass { ratio } => {
assert!(ratio >= 0.95, "random bytes are ~incompressible, got ratio {ratio}");
}
CryptoStrength::Weak { ratio, .. } => panic!("random key wrongly flagged weak, ratio {ratio}"),
}
}
#[test]
fn incompressibility_ratio_separates_structure_from_randomness() {
let structured: Vec<u8> = (0..300).map(|i| (i % 8) as u8).collect(); let random = splitmix_bytes(300, 0x9e37_79b9_7f4a_7c15);
assert!(
incompressibility_ratio(&structured) < 0.3 * incompressibility_ratio(&random),
"structured key ({}) must be far more compressible than random ({})",
incompressibility_ratio(&structured),
incompressibility_ratio(&random),
);
}
#[test]
fn classify_recognizes_the_ordered_to_random_spectrum() {
let counter: Vec<u8> = (0..200u16).map(|i| i as u8).collect();
assert_eq!(classify_bytes(&counter).class, CompressibilityClass::Generated);
let periodic: Vec<u8> = (0..300).map(|i| (i % 8) as u8).collect();
assert_eq!(classify_bytes(&periodic).class, CompressibilityClass::Periodic);
let random = splitmix_bytes(400, 0xDEAD_BEEF_CAFE_1234);
assert_eq!(classify_bytes(&random).class, CompressibilityClass::Incompressible);
assert!(classify_bytes(&counter).ratio < 0.2, "a counter is nearly free to describe");
assert!(classify_bytes(&periodic).ratio < 0.2, "a short period is nearly free to describe");
assert!(classify_bytes(&random).ratio >= 0.95, "random bytes cost ~their full length");
}
#[test]
fn classify_recognizes_fibonacci_as_a_generator() {
let mut fib = vec![0i64, 1];
while fib.len() < 60 {
let n = fib.len();
fib.push(fib[n - 1].wrapping_add(fib[n - 2]));
}
let report = classify_int_seq(&fib);
assert_eq!(report.class, CompressibilityClass::Generated, "Fibonacci is a generator, not random");
assert!(report.ratio < 0.1, "60 Fibonacci terms collapse to a handful, ratio {}", report.ratio);
}
#[test]
fn classify_text_places_inputs_on_the_compressibility_spectrum() {
let repeated = "the quick brown fox jumps over the lazy dog. ".repeat(30);
let rep = classify_text(&repeated);
assert_ne!(rep.class, CompressibilityClass::Incompressible, "repeated text has structure");
assert!(rep.ratio < 0.5, "repeated text compresses well, ratio {}", rep.ratio);
let random_bytes = splitmix_bytes(400, 0x0102_0304_0506_0708);
let rand_ratio = classify_bytes(&random_bytes).ratio;
assert_eq!(classify_bytes(&random_bytes).class, CompressibilityClass::Incompressible);
let ascii: String =
splitmix_bytes(400, 0x0a0b_0c0d_0e0f_1011).iter().map(|&b| char::from(33 + b % 94)).collect();
let ascii_ratio = classify_text(&ascii).ratio;
assert!(
rep.ratio < ascii_ratio && ascii_ratio < rand_ratio,
"spectrum: repetition ({}) < narrow-alphabet text ({ascii_ratio}) < full randomness ({rand_ratio})",
rep.ratio,
);
}
#[test]
fn structural_bound_certifies_symmetry_and_realizes_compression() {
let (cnf, _) = crate::families::php(6);
let gens = crate::hypercube::php_perm_symmetries(6);
let sb = structural_bound(cnf.num_vars, &cnf.clauses, &gens).expect("php is symmetric");
assert!(sb.verify(), "the group description must re-check: automorphisms + full reconstruction");
assert!(sb.group_entropy_bits > 0.0, "a symmetric formula has positive symmetry-entropy");
assert!(sb.best_bytes() <= sb.whole.bytes, "the certificate never worsens the upper bound");
assert!(sb.is_compression(), "at scale, PHP compresses via its symmetry group (group < flat)");
assert_eq!(sb.best_bytes(), sb.group_bytes(), "the group description is the better bound here");
}
#[test]
fn structural_bound_declines_a_non_automorphism() {
let (cnf, _) = crate::families::php(3);
let mut imgs: Vec<Lit> = (0..cnf.num_vars).map(|v| Lit::pos(v as Var)).collect();
imgs[0] = Lit::pos(1); let bogus = Perm::from_images(imgs);
assert!(structural_bound(cnf.num_vars, &cnf.clauses, &[bogus]).is_none());
}
fn xor_gadget(vars: &[usize], rhs: bool) -> Vec<Vec<Lit>> {
let k = vars.len();
let target = if rhs { 0 } else { 1 }; let mut clauses = Vec::new();
for mask in 0u32..(1u32 << k) {
if (mask.count_ones() % 2) as u32 == target {
let clause = vars
.iter()
.enumerate()
.map(|(i, &v)| Lit::new(v as Var, mask & (1 << i) == 0))
.collect();
clauses.push(clause);
}
}
clauses
}
#[test]
fn linear_rigidity_kernel_is_rechecked() {
let mut clauses = xor_gadget(&[0, 1, 2], true);
clauses.extend(xor_gadget(&[1, 2, 3], true));
let cert = certify_linear_rigidity(4, &clauses).expect("has parity structure");
assert_eq!(cert.rank, 2, "two independent parity rows");
assert_eq!(cert.solution_count_log2, 2, "2-dimensional kernel ⇒ 2² solutions");
assert_eq!(cert.kernel_basis.len(), 2);
assert_eq!(cert.kernel_basis.len(), cert.num_vars - cert.rank, "rank–nullity");
assert!(check_linear_rigidity(&cert, &clauses), "the exposed linear structure re-checks");
}
#[test]
fn linear_rigidity_rejects_tampered_kernel() {
let mut clauses = xor_gadget(&[0, 1, 2], true);
clauses.extend(xor_gadget(&[1, 2, 3], true));
let mut cert = certify_linear_rigidity(4, &clauses).expect("has parity structure");
assert!(check_linear_rigidity(&cert, &clauses));
cert.kernel_basis[0][0] ^= true;
assert!(!check_linear_rigidity(&cert, &clauses), "a non-solution kernel vector is rejected");
}
#[test]
fn certify_linear_rigidity_declines_without_parity() {
let clauses = vec![vec![Lit::pos(0), Lit::pos(1)], vec![Lit::neg(0), Lit::pos(2)]];
assert!(certify_linear_rigidity(3, &clauses).is_none());
}
#[test]
fn linear_structure_certified_at_par32_scale() {
let (_eqs, cnf, _) = crate::families::tseitin_expander(60, 0x51A7);
assert!(cnf.num_vars > 64, "the par32-scale regime, beyond gf2's u64 rows");
assert!(certify_linear_rigidity(cnf.num_vars, &cnf.clauses).is_none());
let cert = certify_linear_structure(cnf.num_vars, &cnf.clauses).expect("has parity structure");
assert!(cert.rank > 0 && cert.num_xor_eqs > 0, "a non-trivial recovered parity system");
assert!(check_linear_structure(&cert, &cnf.clauses), "rank + kernel dimension re-check");
let mut bad = cert.clone();
bad.rank += 1;
assert!(!check_linear_structure(&bad, &cnf.clauses), "an inflated rank is rejected");
}
#[test]
fn certify_linear_structure_declines_without_parity() {
let clauses = vec![vec![Lit::pos(0), Lit::pos(1)], vec![Lit::neg(0), Lit::pos(2)]];
assert!(certify_linear_structure(3, &clauses).is_none());
}
#[test]
fn linear_shortcut_verdict_reports_rigidity_with_a_rechecking_certificate() {
let mut small = xor_gadget(&[0, 1, 2], true);
small.extend(xor_gadget(&[1, 2, 3], true));
match linear_shortcut_verdict(4, &small) {
LinearShortcut::None { rigidity, structure } => {
assert!(check_linear_structure(&structure, &small));
let rig = rigidity.expect("≤64 vars ⇒ an explicit kernel basis is available");
assert!(check_linear_rigidity(&rig, &small));
}
LinearShortcut::NoLinearStructure => panic!("the parity system has linear structure"),
}
let (_e, cnf, _) = crate::families::tseitin_expander(60, 0x51A7);
assert!(cnf.num_vars > 64);
match linear_shortcut_verdict(cnf.num_vars, &cnf.clauses) {
LinearShortcut::None { rigidity, structure } => {
assert!(rigidity.is_none(), "past the u64 budget the explicit basis is withheld");
assert!(check_linear_structure(&structure, &cnf.clauses));
}
LinearShortcut::NoLinearStructure => panic!("the Tseitin expander has linear structure"),
}
let plain = vec![vec![Lit::pos(0), Lit::pos(1)]];
assert!(matches!(linear_shortcut_verdict(2, &plain), LinearShortcut::NoLinearStructure));
}
#[test]
fn incompressibility_gate_fires_on_rigid_linear_and_declines_on_symmetric() {
let (php, _) = crate::families::php(4);
assert!(incompressibility_gate(php.num_vars, &php.clauses).is_none(), "PHP is symmetric → decline");
assert!(incompressibility_gate(3, &[vec![Lit::pos(0), Lit::pos(1)]]).is_none());
let (_e, tseitin, _) = crate::families::tseitin_expander(20, 0x51A7);
assert!(certify_linear_structure(tseitin.num_vars, &tseitin.clauses).is_some(), "has parity structure");
assert!(incompressibility_gate(tseitin.num_vars, &tseitin.clauses).is_none(), "but not rigid → decline");
let mut fired = false;
for seed in [0x51A7u64, 0xBEEF, 0x1234, 0xF00D, 0xCAFE, 0xABCD, 0x9E37, 0x2718] {
let base = crate::families::random_3sat(12, 40, seed);
let mut clauses = base.clauses.clone();
clauses.extend(xor_gadget(&[0, 1, 2], true));
if let Some(cert) = incompressibility_gate(base.num_vars, &clauses) {
assert!(check_linear_structure(&cert, &clauses), "the attached structure cert re-checks");
fired = true;
break;
}
}
assert!(fired, "a rigid random-3SAT + an XOR gadget is linear AND rigid — the gate must fire");
}
#[test]
fn incompressibility_lemma_is_certified_by_counting() {
for n in 1..=100u32 {
let cert = incompressible_string_exists(n).expect("2ⁿ strings > 2ⁿ − 1 shorter programs");
assert_eq!(cert.pigeons, 1u128 << n, "2ⁿ strings of length n");
assert_eq!(cert.holes, (1u128 << n) - 1, "2ⁿ − 1 programs shorter than n");
assert!(crate::pigeonhole::check_counting_cert(&cert), "the counting refutation re-checks");
}
let c = incompressible_string_exists(10).unwrap();
assert_eq!((c.pigeons, c.holes), (1024, 1023));
assert!(incompressible_string_exists(0).is_none());
assert!(incompressible_string_exists(200).is_none());
}
#[test]
fn budget_refuses_oversized_gaussian_fail_closed() {
let mut clauses = xor_gadget(&[0, 1, 2], true);
clauses.extend(xor_gadget(&[1, 2, 3], true));
let tiny = Budget { max_gaussian_dim: 2 };
assert_eq!(
certify_linear_rigidity_within(4, &clauses, &tiny),
Err(Refusal::OverBudgetGaussian { dim: 4, cap: 2 })
);
let cert = certify_linear_rigidity_within(4, &clauses, &Budget::standard()).expect("within budget");
assert!(check_linear_rigidity(&cert, &clauses));
let plain = vec![vec![Lit::pos(0), Lit::pos(1)]];
assert_eq!(
certify_linear_rigidity_within(2, &plain, &Budget::standard()),
Err(Refusal::NoLinearStructure)
);
}
#[test]
fn structural_bound_rejects_tampered_generators() {
let (cnf, _) = crate::families::php(3);
let gens = crate::hypercube::php_perm_symmetries(3);
let mut sb = structural_bound(cnf.num_vars, &cnf.clauses, &gens).expect("php is symmetric");
assert!(sb.verify());
if let Descriptor::IntSeq { encoded } = &mut sb.gens.descriptor {
let mid = encoded.len() / 2;
encoded[mid] ^= 0xff;
}
assert!(!sb.verify(), "a tampered generator description must be rejected");
}
}