adv-mixer: integral degree-saturation test for Q1; note the re-scope in the plan

Internal adversarial pass, not an independent review. Adds the cube-sum
(higher-order differential) command to bound the algebraic degree of the
applications: a low degree would admit a cheap polynomial batching of the 8
applications, so degree saturation at small d is the no-shortcut bound. Plan
section 8 records main's three-lane re-scope: this lane is the algebraic
structure of M_r.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 18:27:00 +00:00
parent 5bddb289c3
commit 29a03a0d74
2 changed files with 101 additions and 1 deletions

View file

@ -213,3 +213,14 @@ Only these were read. Nothing else in the repository.
- An algebraic-degree or integral-distinguisher measurement across the 8 applications, to tighten Q1 beyond the
affinity probe.
- The census at more than 2^24 days if any class sits near the gate.
## 8. Re-scope (19:2x BST, 7 October 2026, from main)
The mixer work split into three lanes. This lane, adv-mixer, is narrowed to the algebraic structure of M_r: the
fold or commutation of the multiply layer across applications (only rk changes), the algebraic form of the 8
applications between two reads, and any composition cheaper than 8x one application, priced in ops per item
against the chip model. Sibling adv-mixer-2 owns Q3 (the day-key weakness census and the calendar). Sibling
adv-mixer-3 owns Q2 (differential, linear, rotational-XOR, SAT and MILP on reduced applications, the round
margin). The Q3 census and the Q2 diffusion sweep already run in this lane are kept and recorded as courtesy
runs, attributed to the owning lane. This lane's own deepening is the fold probe plus an algebraic-degree
(integral cube-sum) saturation test across the applications. First results by 00:00 BST tonight.

View file

@ -316,6 +316,89 @@ fn fold(day: u64, trials: u64) {
println!(" VERDICT: {}", verdict);
}
// --------------------------------------------------------------------------------------------------------------
// integral (Q1): algebraic-degree saturation across K keyed applications
// --------------------------------------------------------------------------------------------------------------
//
// The cube-sum (higher-order differential) test. Pick d input-bit positions. Over a fixed random base state, XOR
// every one of the 2^d subsets of those positions into the base, apply K applications, and XOR-accumulate the 16
// output words. For an output bit that is a GF(2) polynomial of the input of degree < d, the sum over the full
// d-cube is 0 (the d-th derivative of a degree < d polynomial is 0). A nonzero sum proves the output bit has
// algebraic degree >= d in those d variables. We report, per d, the fraction of (base, cube) placements whose
// accumulated 512-bit sum is nonzero on at least one output bit, and the mean number of output bits set. When
// nonzero sums appear at small d the algebraic degree is already high, so no low-degree algebraic batching of the
// applications exists: an attacker cannot evaluate the 8 applications through a cheap polynomial. A composition
// cheaper than 8x would need a low-degree form; its absence is the bound, priced against 9,360 ops per item.
fn integral(day: u64, apps: usize, dmax: usize, placements: u64, threads: usize) {
let mp = Arc::new(params_of_day(day));
let keys = Arc::new(app_keys());
println!("integral day={} apps={} placements_per_d={} threads={}", day, apps, placements, threads);
println!(" d = cube dimension; 'nonzero' = placements whose cube-sum is nonzero on >=1 output bit (degree >= d reached)");
for d in 1..=dmax {
let per = placements / threads as u64;
let mut handles = Vec::new();
for t in 0..threads {
let mp = Arc::clone(&mp);
let keys = Arc::clone(&keys);
let count = if t as u64 == threads as u64 - 1 { placements - per * (threads as u64 - 1) } else { per };
let seed = 0xa1b2_c3d4_e5f6_0718 ^ day.wrapping_mul(0x9E3779B97F4A7C15) ^ ((d as u64) << 40) ^ (t as u64 + 1);
handles.push(thread::spawn(move || {
let mut rng = Rng::new(seed);
let mut nonzero = 0u64;
let mut bits_set_total = 0u64;
for _ in 0..count {
let base = rng.state();
// choose d distinct input bit positions in 0..512
let mut pos = [0usize; 32];
let mut chosen = 0usize;
while chosen < d {
let b = (rng.0.next() % 512) as usize;
if !pos[..chosen].contains(&b) {
pos[chosen] = b;
chosen += 1;
}
}
let mut acc = [0u32; 16];
for mask in 0u32..(1u32 << d) {
let mut s = base;
for (bit, &p) in pos[..d].iter().enumerate() {
if (mask >> bit) & 1 == 1 {
flip_bit(&mut s, p);
}
}
let o = apply_n(s, &mp, &keys, 0, apps);
for i in 0..16 {
acc[i] ^= o[i];
}
}
let set: u32 = acc.iter().map(|w| w.count_ones()).sum();
bits_set_total += set as u64;
if set > 0 {
nonzero += 1;
}
}
(nonzero, bits_set_total, count)
}));
}
let (mut nonzero, mut bits, mut n) = (0u64, 0u64, 0u64);
for h in handles {
let (a, b, c) = h.join().unwrap();
nonzero += a;
bits += b;
n += c;
}
let frac = nonzero as f64 / n as f64;
let mean_bits = bits as f64 / n as f64;
let note = if nonzero == 0 { "sum always 0: degree < d on every output bit (a low-degree relation)" } else { "degree >= d reached" };
println!(
" d={:2} nonzero {}/{} = {:.4} mean out-bits set {:.1}/512 [{}]",
d, nonzero, n, frac, mean_bits, note
);
}
println!(" READING: the smallest d at which nonzero is ~1.0 is the degree floor; high degree at small d means no low-degree algebraic shortcut across the applications");
}
// --------------------------------------------------------------------------------------------------------------
// startup self-check: the genesis test vector of the spec
// --------------------------------------------------------------------------------------------------------------
@ -362,8 +445,14 @@ fn main() {
let trials = arg_u64(&args, "--trials", 100_000);
fold(day, trials);
}
"integral" => {
let apps = arg_u64(&args, "--apps", 2) as usize;
let dmax = arg_u64(&args, "--dmax", 14) as usize;
let placements = arg_u64(&args, "--placements", 20000);
integral(day, apps, dmax, placements, threads);
}
_ => {
eprintln!("usage: attack-adv-mixer diffusion|fold [--day D] [--apps K] [--states N] [--start-app A] [--plant weak|none] [--trials N] [--threads T]");
eprintln!("usage: attack-adv-mixer diffusion|fold|integral [--day D] [--apps K] [--states N] [--start-app A] [--plant weak|none] [--trials N] [--dmax D] [--placements N] [--threads T]");
}
}
let _ = AtomicU64::new(0).fetch_add(0, Ordering::Relaxed);