adv-mixer: fold-sweep, linrel, lineindex, cnf commands for the Q1 deepening
Internal adversarial pass, not an independent review. fold-sweep runs the affinity and commutation probes over many days and all 71 adjacent key pairs; linrel is the exact GF(2) affine-relation kernel test at full 32-bit width (the decidable algebraic probe in place of a SAT solve, which no solver on the box can run); lineindex tallies the 22 line-index bits of s[0] a chip would prefetch on; cnf writes the DIMACS commutation instance of two keyed applications with the real day constants. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
06f1382f3e
commit
a26e52c3dd
1 changed files with 420 additions and 58 deletions
|
|
@ -238,76 +238,49 @@ fn xor16(a: &[u32; 16], b: &[u32; 16]) -> [u32; 16] {
|
|||
o
|
||||
}
|
||||
|
||||
fn fold(day: u64, trials: u64) {
|
||||
let mp = params_of_day(day);
|
||||
let keys = app_keys();
|
||||
let rk1 = keys[0];
|
||||
let rk2 = keys[1];
|
||||
let mut rng = Rng::new(0xfeed_face_cafe_babe ^ day.wrapping_mul(0x9E3779B97F4A7C15));
|
||||
|
||||
// (a) GF(2) affinity of the two-application map g(s) = M(M(s,rk1),rk2). An affine map over GF(2)^512 obeys
|
||||
// g(a) ^ g(b) ^ g(c) ^ g(a^b^c) = g(0^...) summed; exactly, g(a)^g(b)^g(c)^g(a^b^c) is constant for an
|
||||
// affine g. We test the 4-point relation g(a)^g(b)^g(c)^g(a^b^c) == g(d0)^g(d0)... using the zero anchor:
|
||||
// for affine g, g(a)^g(b)^g(c)^g(a^b^c) == g(0) (four points a,b,c,a^b^c vs the origin). Count nonzero.
|
||||
let g = |s: [u32; 16]| -> [u32; 16] {
|
||||
let s1 = apply_n(s, &mp, &keys, 0, 1);
|
||||
apply_n(s1, &mp, &keys, 1, 1)
|
||||
};
|
||||
/// Probe (a) affinity and (c) key-order commutation on the adjacent application pair (i, i+1).
|
||||
fn fold_pair(mp: &MixParams, keys: &[u32; 72], i: usize, trials: u64, rng: &mut Rng) -> (u64, u64) {
|
||||
let (rk1, rk2) = (keys[i], keys[i + 1]);
|
||||
let g = |s: [u32; 16]| -> [u32; 16] { let mut t = s; mixer(&mut t, rk1, mp); mixer(&mut t, rk2, mp); t };
|
||||
let g0 = g([0u32; 16]);
|
||||
let mut affine_violations = 0u64;
|
||||
let mut violations = 0u64;
|
||||
let mut agree = 0u64;
|
||||
for _ in 0..trials {
|
||||
let a = rng.state();
|
||||
let b = rng.state();
|
||||
let c = rng.state();
|
||||
let a = rng.state(); let b = rng.state(); let c = rng.state();
|
||||
let abc = xor16(&xor16(&a, &b), &c);
|
||||
let lhs = xor16(&xor16(&g(a), &g(b)), &xor16(&g(c), &g(abc)));
|
||||
if lhs != g0 {
|
||||
affine_violations += 1;
|
||||
}
|
||||
if lhs != g0 { violations += 1; }
|
||||
let s = rng.state();
|
||||
let mut s1 = s; mixer(&mut s1, rk2, mp); mixer(&mut s1, rk1, mp);
|
||||
if g(s) == s1 { agree += 1; }
|
||||
}
|
||||
(violations, agree)
|
||||
}
|
||||
|
||||
// (b) word separability: flip each input word fully (xor 0xffffffff) and see whether every output word of the
|
||||
// full 8-application block moves on at least one probe. A dead (in_word -> out_word) pair over all probes
|
||||
// is a broken dependency a shortcut could exploit.
|
||||
let full = |s: [u32; 16]| apply_n(s, &mp, &keys, 0, 8);
|
||||
let mut dep = [[false; 16]; 16]; // dep[iw][ow] = out word ow ever changed when in word iw flipped
|
||||
/// Probe (b) word separability over the full 8-application block of round 0.
|
||||
fn fold_block(mp: &MixParams, keys: &[u32; 72], trials: u64, rng: &mut Rng) -> u64 {
|
||||
let full = |s: [u32; 16]| apply_n(s, mp, keys, 0, 8);
|
||||
let mut dep = [[false; 16]; 16];
|
||||
for _ in 0..trials {
|
||||
let base = rng.state();
|
||||
let o0 = full(base);
|
||||
for iw in 0..16 {
|
||||
let mut s = base;
|
||||
s[iw] ^= 0xffff_ffff;
|
||||
let mut s = base; s[iw] ^= 0xffff_ffff;
|
||||
let o1 = full(s);
|
||||
for ow in 0..16 {
|
||||
if o0[ow] != o1[ow] {
|
||||
dep[iw][ow] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut dead_pairs = 0u64;
|
||||
for iw in 0..16 {
|
||||
for ow in 0..16 {
|
||||
if !dep[iw][ow] {
|
||||
dead_pairs += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (c) key-order commutation: M(M(s,rk1),rk2) vs M(M(s,rk2),rk1). Agreement would let keys fold.
|
||||
let mut commute_agree = 0u64;
|
||||
for _ in 0..trials {
|
||||
let s = rng.state();
|
||||
let ab = apply_n(apply_n(s, &mp, &keys, 0, 1), &mp, &keys, 1, 1);
|
||||
// apply rk2 then rk1 by temporarily swapping via explicit keys
|
||||
let mut s1 = s;
|
||||
mixer(&mut s1, rk2, &mp);
|
||||
mixer(&mut s1, rk1, &mp);
|
||||
if ab == s1 {
|
||||
commute_agree += 1;
|
||||
for ow in 0..16 { if o0[ow] != o1[ow] { dep[iw][ow] = true; } }
|
||||
}
|
||||
}
|
||||
let mut dead = 0u64;
|
||||
for iw in 0..16 { for ow in 0..16 { if !dep[iw][ow] { dead += 1; } } }
|
||||
dead
|
||||
}
|
||||
|
||||
fn fold(day: u64, trials: u64) {
|
||||
let mp = params_of_day(day);
|
||||
let keys = app_keys();
|
||||
let mut rng = Rng::new(0xfeed_face_cafe_babe ^ day.wrapping_mul(0x9E3779B97F4A7C15));
|
||||
let (affine_violations, commute_agree) = fold_pair(&mp, &keys, 0, trials, &mut rng);
|
||||
let dead_pairs = fold_block(&mp, &keys, trials, &mut rng);
|
||||
println!("fold day={} trials={}", day, trials);
|
||||
println!(" (a) GF(2) affinity violations of the 2-application map: {} of {} (0 would be a BREAK: the map is affine)", affine_violations, trials);
|
||||
println!(" (b) dead (in_word -> out_word) pairs over the full 8-application block: {} of 256 (any would be a broken dependency)", dead_pairs);
|
||||
|
|
@ -316,6 +289,46 @@ fn fold(day: u64, trials: u64) {
|
|||
println!(" VERDICT: {}", verdict);
|
||||
}
|
||||
|
||||
/// The fold probes over `days` consecutive chain days and all 71 adjacent application-key pairs per day.
|
||||
fn fold_sweep(day0: u64, days: u64, trials: u64, threads: usize) {
|
||||
let keys = Arc::new(app_keys());
|
||||
let per = (days + threads as u64 - 1) / threads as u64;
|
||||
let mut handles = Vec::new();
|
||||
for t in 0..threads {
|
||||
let keys = Arc::clone(&keys);
|
||||
let lo = day0 + t as u64 * per;
|
||||
let hi = (lo + per).min(day0 + days);
|
||||
handles.push(thread::spawn(move || {
|
||||
let (mut viol, mut tot, mut agree, mut dead, mut n_days) = (0u64, 0u64, 0u64, 0u64, 0u64);
|
||||
let mut worst_viol_frac = 1.0f64;
|
||||
for d in lo..hi {
|
||||
let mp = params_of_day(d);
|
||||
let mut rng = Rng::new(0xfeed_face_cafe_babe ^ d.wrapping_mul(0x9E3779B97F4A7C15));
|
||||
for i in 0..71 {
|
||||
let (v, a) = fold_pair(&mp, &keys, i, trials, &mut rng);
|
||||
viol += v; tot += trials; agree += a;
|
||||
let f = v as f64 / trials as f64;
|
||||
if f < worst_viol_frac { worst_viol_frac = f; }
|
||||
}
|
||||
dead += fold_block(&mp, &keys, trials, &mut rng);
|
||||
n_days += 1;
|
||||
}
|
||||
(viol, tot, agree, dead, n_days, worst_viol_frac)
|
||||
}));
|
||||
}
|
||||
let (mut viol, mut tot, mut agree, mut dead, mut n_days) = (0u64, 0u64, 0u64, 0u64, 0u64);
|
||||
let mut worst = 1.0f64;
|
||||
for h in handles {
|
||||
let (v, t, a, dd, nd, w) = h.join().unwrap();
|
||||
viol += v; tot += t; agree += a; dead += dd; n_days += nd; if w < worst { worst = w; }
|
||||
}
|
||||
println!("fold-sweep from_day={} days={} pairs_per_day=71 trials_per_pair={}", day0, n_days, trials);
|
||||
println!(" (a) affinity violations: {} of {} pair-trials; lowest per-pair violation fraction {:.6} (1.0 = never affine)", viol, tot, worst);
|
||||
println!(" (b) dead word pairs summed over {} days: {} (0 = complete dependency every day)", n_days, dead);
|
||||
println!(" (c) key-order agreements: {} of {} pair-trials", agree, tot);
|
||||
println!(" VERDICT: {}", if viol < tot || dead > 0 || agree > 0 { "FINDING" } else { "BOUND: no fold on any day or pair probed" });
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
// integral (Q1): algebraic-degree saturation across K keyed applications
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -399,6 +412,333 @@ fn integral(day: u64, apps: usize, dmax: usize, placements: u64, threads: usize)
|
|||
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");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
// linrel (Q1): exact affine-relation search over the full 32-bit map by GF(2) elimination
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Collect N random (x, y = K applications of x) pairs. Each pair is a GF(2) row over the 1025 columns
|
||||
// [512 input bits | 512 output bits | 1]. Any nonzero v with A v = 0 is an exact affine relation
|
||||
// a.x XOR b.y = c that holds on every sample; with N far above 1025 a surviving relation is genuine (a chance
|
||||
// survivor has probability 2^-(N - 1025)). Kernel dimension 1025 - rank(A). Rank 1025 means NO affine relation
|
||||
// exists between the input bits and the output bits of the composed applications, at full width with the real
|
||||
// day constants. This is the decidable algebraic probe in place of a SAT solve (no solver reaches the box).
|
||||
// `--addr` restricts the output columns to the 22 line-index bits of s[0] (the bits a chip prefetches on).
|
||||
|
||||
const LR_IN: usize = 512;
|
||||
|
||||
fn gf2_rank(rows: &mut Vec<Vec<u64>>, ncols: usize) -> usize {
|
||||
let words = (ncols + 63) / 64;
|
||||
let mut rank = 0usize;
|
||||
let mut r = 0usize;
|
||||
for col in 0..ncols {
|
||||
let (w, b) = (col / 64, col % 64);
|
||||
let mut piv = None;
|
||||
for i in r..rows.len() {
|
||||
if (rows[i][w] >> b) & 1 == 1 {
|
||||
piv = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let Some(p) = piv else { continue };
|
||||
rows.swap(r, p);
|
||||
let pr = rows[r].clone();
|
||||
for i in 0..rows.len() {
|
||||
if i != r && (rows[i][w] >> b) & 1 == 1 {
|
||||
for k in 0..words {
|
||||
rows[i][k] ^= pr[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
rank += 1;
|
||||
r += 1;
|
||||
if r == rows.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rank
|
||||
}
|
||||
|
||||
fn linrel(day: u64, apps: usize, samples: usize, addr_only: bool) {
|
||||
let mp = params_of_day(day);
|
||||
let keys = app_keys();
|
||||
let mut rng = Rng::new(0x5a5a_1234_9e37_79b9 ^ day.wrapping_mul(0x9E3779B97F4A7C15) ^ ((apps as u64) << 48));
|
||||
let nout = if addr_only { 22 } else { 512 };
|
||||
let ncols = LR_IN + nout + 1;
|
||||
let words = (ncols + 63) / 64;
|
||||
let mut rows: Vec<Vec<u64>> = Vec::with_capacity(samples);
|
||||
for _ in 0..samples {
|
||||
let x = rng.state();
|
||||
let y = apply_n(x, &mp, &keys, 0, apps);
|
||||
let mut row = vec![0u64; words];
|
||||
for b in 0..LR_IN {
|
||||
if bit_of(&x, b) == 1 {
|
||||
row[b / 64] |= 1u64 << (b % 64);
|
||||
}
|
||||
}
|
||||
for b in 0..nout {
|
||||
if bit_of(&y, b) == 1 {
|
||||
let c = LR_IN + b;
|
||||
row[c / 64] |= 1u64 << (c % 64);
|
||||
}
|
||||
}
|
||||
let c = LR_IN + nout;
|
||||
row[c / 64] |= 1u64 << (c % 64);
|
||||
rows.push(row);
|
||||
}
|
||||
let rank = gf2_rank(&mut rows, ncols);
|
||||
let kernel = ncols - rank;
|
||||
println!(
|
||||
"linrel day={} apps={} samples={} columns={} ({} in + {} out + 1) rank={} kernel_dim={}",
|
||||
day, apps, samples, ncols, LR_IN, nout, rank, kernel
|
||||
);
|
||||
let margin = samples as i64 - ncols as i64;
|
||||
if kernel == 0 {
|
||||
println!(" BOUND: no exact affine relation a.x XOR b.y = c over the {} samples (false-survivor odds 2^-{})", samples, margin);
|
||||
} else {
|
||||
println!(" FINDING: {} independent affine relations survive all {} samples (chance survivor odds 2^-{} each)", kernel, samples, margin);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
// lineindex (Q1): the 22 line-index bits of s[0] across applications, avalanche with a tight band
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// The cache read uses s[0] AND (2^22 - 1). A chip that could predict those 22 bits from fewer than K
|
||||
// applications could issue the read early and hide the mixer behind the memory latency. We tally the flip
|
||||
// probability of each of the 22 address bits for each of the 512 input bits over N states after K applications,
|
||||
// report holes and cells beyond 8 sigma, and the worst cell.
|
||||
|
||||
fn lineindex(day: u64, apps: usize, states: u64, threads: usize) {
|
||||
let mp = Arc::new(params_of_day(day));
|
||||
let keys = Arc::new(app_keys());
|
||||
let per = states / 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 { states - per * (threads as u64 - 1) } else { per };
|
||||
let seed = 0x7777_0000_abcd_ef01 ^ day.wrapping_mul(0x9E3779B97F4A7C15) ^ ((apps as u64) << 40) ^ (t as u64 + 1);
|
||||
handles.push(thread::spawn(move || {
|
||||
let mut rng = Rng::new(seed);
|
||||
let mut counts = vec![0u64; 512 * 22];
|
||||
for _ in 0..count {
|
||||
let base = rng.state();
|
||||
let o0 = apply_n(base, &mp, &keys, 0, apps)[0] & 0x003f_ffff;
|
||||
for ib in 0..512 {
|
||||
let mut s = base;
|
||||
flip_bit(&mut s, ib);
|
||||
let o1 = apply_n(s, &mp, &keys, 0, apps)[0] & 0x003f_ffff;
|
||||
let d = o0 ^ o1;
|
||||
for ab in 0..22 {
|
||||
if (d >> ab) & 1 == 1 {
|
||||
counts[ib * 22 + ab] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(counts, count)
|
||||
}));
|
||||
}
|
||||
let mut counts = vec![0u64; 512 * 22];
|
||||
let mut n = 0u64;
|
||||
for h in handles {
|
||||
let (c, k) = h.join().unwrap();
|
||||
for (a, b) in counts.iter_mut().zip(c.iter()) {
|
||||
*a += b;
|
||||
}
|
||||
n += k;
|
||||
}
|
||||
let nf = n as f64;
|
||||
let sigma = 0.5 / nf.sqrt();
|
||||
let band = 8.0 * sigma;
|
||||
let (mut holes, mut strong, mut worst_dev, mut worst) = (0u64, 0u64, 0.0f64, (0usize, 0usize, 0.0f64));
|
||||
let mut total = 0u64;
|
||||
for ib in 0..512 {
|
||||
for ab in 0..22 {
|
||||
let c = counts[ib * 22 + ab];
|
||||
total += c;
|
||||
let p = c as f64 / nf;
|
||||
if c == 0 || c == n {
|
||||
holes += 1;
|
||||
} else {
|
||||
let dev = (p - 0.5).abs();
|
||||
if dev > band {
|
||||
strong += 1;
|
||||
}
|
||||
if dev > worst_dev {
|
||||
worst_dev = dev;
|
||||
worst = (ib, ab, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("lineindex day={} apps={} states={} threads={} (22 address bits x 512 input bits = 11264 cells)", day, apps, n, threads);
|
||||
println!(" mean address-bit flip probability: {:.6} (ideal 0.5); band 8 sigma = {:.6}", total as f64 / (nf * 11264.0), band);
|
||||
println!(" holes: {} of 11264; strong-bias cells: {} of 11264", holes, strong);
|
||||
println!(" worst cell: in_bit {} -> addr_bit {} p = {:.6} ({:.1} sigma)", worst.0, worst.1, worst.2, worst_dev / sigma);
|
||||
println!(" VERDICT: {}", if holes > 0 || strong > 0 { "FINDING (address bits predictable at this K)" } else { "no address-bit distinguisher at this K" });
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
// cnf (Q1): DIMACS export of two keyed applications with the real day constants, the commutation instance
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Bit-exact Tseitin encoding of M(M(x, rk1), rk2) and M(M(x, rk2), rk1) on a shared 512-variable input, with the
|
||||
// constraint that the two outputs are equal. SAT = a state on which the two key orders commute; UNSAT = none
|
||||
// exists (a proof of fold probe (c) over all 2^512 states). Adds are ripple-carry full adders, a constant
|
||||
// multiply is the shift-add chain over the set bits of MUL, a constant XOR is a literal flip, a rotation is
|
||||
// wiring. No solver reaches the box (crates.io is refused and none is installed), so this writes the model for a
|
||||
// solver elsewhere and the row stays BLOCKED on the solve.
|
||||
|
||||
struct Cnf {
|
||||
nvars: i32,
|
||||
clauses: Vec<Vec<i32>>,
|
||||
}
|
||||
const LTRUE: i32 = i32::MAX;
|
||||
const LFALSE: i32 = i32::MIN + 1;
|
||||
impl Cnf {
|
||||
fn new() -> Self {
|
||||
Cnf { nvars: 0, clauses: Vec::new() }
|
||||
}
|
||||
fn var(&mut self) -> i32 {
|
||||
self.nvars += 1;
|
||||
self.nvars
|
||||
}
|
||||
fn neg(l: i32) -> i32 {
|
||||
if l == LTRUE { LFALSE } else if l == LFALSE { LTRUE } else { -l }
|
||||
}
|
||||
fn xor(&mut self, a: i32, b: i32) -> i32 {
|
||||
if a == LFALSE { return b; }
|
||||
if b == LFALSE { return a; }
|
||||
if a == LTRUE { return Self::neg(b); }
|
||||
if b == LTRUE { return Self::neg(a); }
|
||||
let o = self.var();
|
||||
self.clauses.push(vec![-a, -b, -o]);
|
||||
self.clauses.push(vec![a, b, -o]);
|
||||
self.clauses.push(vec![a, -b, o]);
|
||||
self.clauses.push(vec![-a, b, o]);
|
||||
o
|
||||
}
|
||||
fn and(&mut self, a: i32, b: i32) -> i32 {
|
||||
if a == LFALSE || b == LFALSE { return LFALSE; }
|
||||
if a == LTRUE { return b; }
|
||||
if b == LTRUE { return a; }
|
||||
let o = self.var();
|
||||
self.clauses.push(vec![-o, a]);
|
||||
self.clauses.push(vec![-o, b]);
|
||||
self.clauses.push(vec![o, -a, -b]);
|
||||
o
|
||||
}
|
||||
fn or(&mut self, a: i32, b: i32) -> i32 {
|
||||
if a == LTRUE || b == LTRUE { return LTRUE; }
|
||||
if a == LFALSE { return b; }
|
||||
if b == LFALSE { return a; }
|
||||
let o = self.var();
|
||||
self.clauses.push(vec![o, -a]);
|
||||
self.clauses.push(vec![o, -b]);
|
||||
self.clauses.push(vec![-o, a, b]);
|
||||
o
|
||||
}
|
||||
/// 32-bit ripple-carry add of two literal words.
|
||||
fn add32(&mut self, a: &[i32; 32], b: &[i32; 32]) -> [i32; 32] {
|
||||
let mut out = [LFALSE; 32];
|
||||
let mut carry = LFALSE;
|
||||
for i in 0..32 {
|
||||
let t = self.xor(a[i], b[i]);
|
||||
out[i] = self.xor(t, carry);
|
||||
let c1 = self.and(a[i], b[i]);
|
||||
let c2 = self.and(t, carry);
|
||||
carry = self.or(c1, c2);
|
||||
}
|
||||
out
|
||||
}
|
||||
fn xor32(&mut self, a: &[i32; 32], b: &[i32; 32]) -> [i32; 32] {
|
||||
let mut o = [LFALSE; 32];
|
||||
for i in 0..32 { o[i] = self.xor(a[i], b[i]); }
|
||||
o
|
||||
}
|
||||
fn const32(v: u32) -> [i32; 32] {
|
||||
let mut o = [LFALSE; 32];
|
||||
for i in 0..32 { o[i] = if (v >> i) & 1 == 1 { LTRUE } else { LFALSE }; }
|
||||
o
|
||||
}
|
||||
fn rotl32(a: &[i32; 32], n: u32) -> [i32; 32] {
|
||||
let mut o = [LFALSE; 32];
|
||||
for i in 0..32 { o[((i as u32 + n) % 32) as usize] = a[i]; }
|
||||
o
|
||||
}
|
||||
fn shl32(a: &[i32; 32], n: u32) -> [i32; 32] {
|
||||
let mut o = [LFALSE; 32];
|
||||
for i in 0..32 { if i as u32 + n < 32 { o[(i as u32 + n) as usize] = a[i]; } }
|
||||
o
|
||||
}
|
||||
/// Multiply by a constant: shift-add over the set bits of `c`.
|
||||
fn mulc32(&mut self, a: &[i32; 32], c: u32) -> [i32; 32] {
|
||||
let mut acc: Option<[i32; 32]> = None;
|
||||
for j in 0..32 {
|
||||
if (c >> j) & 1 == 1 {
|
||||
let sh = Self::shl32(a, j);
|
||||
acc = Some(match acc { None => sh, Some(p) => self.add32(&p, &sh) });
|
||||
}
|
||||
}
|
||||
acc.unwrap_or(Self::const32(0))
|
||||
}
|
||||
fn qr(&mut self, s: &mut [[i32; 32]; 16], a: usize, b: usize, c: usize, d: usize, r: [u32; 4]) {
|
||||
s[a] = self.add32(&s[a], &s[b]); s[d] = self.xor32(&s[d], &s[a]); s[d] = Self::rotl32(&s[d], r[0]);
|
||||
s[c] = self.add32(&s[c], &s[d]); s[b] = self.xor32(&s[b], &s[c]); s[b] = Self::rotl32(&s[b], r[1]);
|
||||
s[a] = self.add32(&s[a], &s[b]); s[d] = self.xor32(&s[d], &s[a]); s[d] = Self::rotl32(&s[d], r[2]);
|
||||
s[c] = self.add32(&s[c], &s[d]); s[b] = self.xor32(&s[b], &s[c]); s[b] = Self::rotl32(&s[b], r[3]);
|
||||
}
|
||||
/// One mixer application, symbolic, the same wiring as memhard::mixer.
|
||||
fn mixer(&mut self, s: &mut [[i32; 32]; 16], rk: u32, mp: &MixParams) {
|
||||
for i in 0..16 {
|
||||
let k = Self::const32(mp.rc[i].wrapping_add(rk));
|
||||
let x = self.xor32(&s[i], &k);
|
||||
s[i] = self.mulc32(&x, mp.mul[i]);
|
||||
}
|
||||
let r = &mp.rot;
|
||||
let col = [r[0], r[1], r[2], r[3]];
|
||||
let dia = [r[4], r[5], r[6], r[7]];
|
||||
self.qr(s, 0, 4, 8, 12, col); self.qr(s, 1, 5, 9, 13, col); self.qr(s, 2, 6, 10, 14, col); self.qr(s, 3, 7, 11, 15, col);
|
||||
self.qr(s, 0, 5, 10, 15, dia); self.qr(s, 1, 6, 11, 12, dia); self.qr(s, 2, 7, 8, 13, dia); self.qr(s, 3, 4, 9, 14, dia);
|
||||
}
|
||||
fn assert_eq_lit(&mut self, a: i32, b: i32) {
|
||||
let is_const = |l: i32| l == LTRUE || l == LFALSE;
|
||||
match (a, b) {
|
||||
(LTRUE, LTRUE) | (LFALSE, LFALSE) => {}
|
||||
(x, y) if is_const(x) && is_const(y) => self.clauses.push(vec![]), // constant mismatch: UNSAT
|
||||
(LTRUE, x) | (x, LTRUE) => self.clauses.push(vec![x]),
|
||||
(LFALSE, x) | (x, LFALSE) => self.clauses.push(vec![-x]),
|
||||
_ => { self.clauses.push(vec![-a, b]); self.clauses.push(vec![a, -b]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cnf_export(day: u64, path: &str) {
|
||||
let mp = params_of_day(day);
|
||||
let keys = app_keys();
|
||||
let (rk1, rk2) = (keys[0], keys[1]);
|
||||
let mut c = Cnf::new();
|
||||
let mut x = [[LFALSE; 32]; 16];
|
||||
for w in 0..16 { for b in 0..32 { x[w][b] = c.var(); } }
|
||||
let mut s1 = x; c.mixer(&mut s1, rk1, &mp); c.mixer(&mut s1, rk2, &mp);
|
||||
let mut s2 = x; c.mixer(&mut s2, rk2, &mp); c.mixer(&mut s2, rk1, &mp);
|
||||
for w in 0..16 { for b in 0..32 { c.assert_eq_lit(s1[w][b], s2[w][b]); } }
|
||||
let mut out = String::new();
|
||||
let _ = writeln!(out, "c adv-mixer commutation instance: exists x with M(M(x,rk1),rk2) == M(M(x,rk2),rk1), day {} rk1 {:#010x} rk2 {:#010x}", day, rk1, rk2);
|
||||
let _ = writeln!(out, "c internal adversarial pass, not an independent review; frozen mixer 017e7037; input vars 1..512 = s[w] bit b at 1 + 32 w + b");
|
||||
let _ = writeln!(out, "p cnf {} {}", c.nvars, c.clauses.len());
|
||||
for cl in &c.clauses {
|
||||
for &l in cl { let _ = write!(out, "{} ", l); }
|
||||
let _ = writeln!(out, "0");
|
||||
}
|
||||
std::fs::write(path, out).expect("write cnf");
|
||||
println!("cnf day={} vars={} clauses={} written {}", day, c.nvars, c.clauses.len(), path);
|
||||
println!(" BLOCKED on the solve: no SAT solver reaches the box (crates.io refused, none installed); the model is the artefact");
|
||||
}
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
// startup self-check: the genesis test vector of the spec
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -449,10 +789,32 @@ fn main() {
|
|||
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);
|
||||
let days = arg_u64(&args, "--days", 1);
|
||||
for d in day..day + days { integral(d, apps, dmax, placements, threads); }
|
||||
}
|
||||
"fold-sweep" => {
|
||||
let trials = arg_u64(&args, "--trials", 20_000);
|
||||
let days = arg_u64(&args, "--days", 64);
|
||||
fold_sweep(day, days, trials, threads);
|
||||
}
|
||||
"linrel" => {
|
||||
let apps = arg_u64(&args, "--apps", 2) as usize;
|
||||
let samples = arg_u64(&args, "--samples", 8192) as usize;
|
||||
let addr = args.iter().any(|a| a == "--addr");
|
||||
let days = arg_u64(&args, "--days", 1);
|
||||
for d in day..day + days { linrel(d, apps, samples, addr); }
|
||||
}
|
||||
"lineindex" => {
|
||||
let apps = arg_u64(&args, "--apps", 2) as usize;
|
||||
let states = arg_u64(&args, "--states", 500_000);
|
||||
lineindex(day, apps, states, threads);
|
||||
}
|
||||
"cnf" => {
|
||||
let path = arg_str(&args, "--out", "adv-mixer-commute.cnf").to_string();
|
||||
cnf_export(day, &path);
|
||||
}
|
||||
_ => {
|
||||
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]");
|
||||
eprintln!("usage: attack-adv-mixer diffusion|fold|fold-sweep|integral|linrel|lineindex|cnf [--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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue