adv-accept-3: harness adv3 (idcheck, ids, margins, lastresort, exhaust-mirror, verdicts, steer, static, era-steer)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 19:04:33 +00:00
parent 3033331b94
commit 81b4aaca74
3 changed files with 1006 additions and 0 deletions

View file

@ -0,0 +1,21 @@
[package]
name = "adv-accept-3"
version = "0.1.0"
edition = "2021"
description = "Adversarial lane adv-accept-3: exhaustion and steering of the program draw, program id collisions, determinism of the acceptance rule (internal adversarial pass, not an independent review)"
license = "MIT"
publish = false
[[bin]]
name = "adv3"
path = "src/main.rs"
[dependencies]
igneum-pow = { path = "../../../igneum-pow" }
[workspace]
[profile.release]
opt-level = 3
lto = true
codegen-units = 1

View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# adv-accept-3: start one sweep on the box under the per-box sweep lock (coordinator rule, 7 Oct 2026 19:55 BST: one sweep per
# box at a time, nice 10, cores 8 to 95, up to 88 threads). Logs, pid files and results live under /srv/builds/_adv-accept-3/,
# never under the worktree mirror. Kill by pid file only.
# run-box.sh <name> <bin> <args...> log /srv/builds/_adv-accept-3/<name>.log, pid file beside it
set -u
name="$1"; shift
dir=/srv/builds/_adv-accept-3; mkdir -p "$dir"
log="$dir/$name.log"
q=""; for a in "$@"; do q="$q $(printf '%q' "$a")"; done
nohup setsid flock /srv/builds/_adv/locks/sweep.lock -c "echo \"start \$(date -u +%FT%TZ) pid \$\$\" > '$log.start'; nice -n 10 taskset -c 8-95 $q > '$log' 2>&1" > /dev/null 2>&1 &
echo $! > "$log.pid"
echo "queued $name: pid $(cat "$log.pid") (waits on the sweep lock), log $log"

View file

@ -0,0 +1,972 @@
//! adv3: lane adv-accept-3 of the lottery-hash adversarial pass (internal adversarial pass, not an independent
//! review). Target: the acceptance rule and the program draw at commit 017e7037 (class v4 sub-version 3), the
//! question class "exhaustion or steering of the draw". Nothing in `igneum-pow` is modified; the draw, the rule and
//! the id come from the library by path. `verdicts` carries a SECOND interpretation of the rule written from the
//! module table of accept.rs and spec 01 section 1.4.6, compared verdict by verdict with the library's.
//!
//! Commands (every sweep writes one TSV row per seed as it lands):
//! adv3 idcheck the two public packs re-derived; the id by three derivations
//! adv3 ids --seeds N --attempts A --threads T FNV-1a-64 id collisions over N x A (seed, attempt) pairs
//! adv3 margins the (c'') f64 ratio against an integer rule, every d
//! adv3 lastresort --from F --count N --threads T the last-resort program of N seeds under the REAL rule
//! adv3 exhaust-mirror --seed i a reject-everything loop must hit the cap and print the last resort
//! adv3 verdicts --from F --count N --threads T [--variant faithful|noncyclic|floor97|spec]
//! adv3 steer --from F --count N --threads T [--plant hot] [--check-every 50]
//! adv3 static --from F --count N --threads T
//! adv3 era-steer --from F --count N --threads T the era attacker: stride and windows over N era seeds (static)
use igneum_pow::accept::{self, check, check_static, distinct_ratio_pass, AcceptReport, Reject, ACCEPT_DATASET_LOG2, BIAS_TOLERANCE, MAX_SATURATED, MIN_DISTINCT_RATIO_V4, MIN_DISTINCT_SUM};
use igneum_pow::generator::{attempt_words, candidate_class, last_resort_v4, max_attempts_for, program_id, EraParams, Instr, LoadClass, Op, Program, ProgramClass, GENERATOR_VERSION_V4, INSTR_COUNT, ITERATIONS, LANES, PROGRAM_SUBVERSION_V4, V3_ALLOWED, V4_CLASS};
use igneum_pow::seed::{fnv1a64, program_rng, seed_words_from_bytes, SplitMix64};
use igneum_pow::verify::{dataset_elem, load_index, splitmix32, Epoch};
use std::collections::HashSet;
use std::io::Write as _;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use std::time::Instant;
/// Devnet 3 epoch-0 seed and era (the era seed is the epoch seed at epoch 0), program id 0xfce15bf61030be57 at attempt 0.
const DEVNET3_HEX: &str = "4020cb4382e3fe4b281c817c02582e147d8f851f566ae9172b28912b8e68b925";
/// The shared devnet epoch-0 seed of the kit pack, id 0xa785001687d8688a at attempt 1.
const DEVNET_KIT_HEX: &str = "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07";
fn unhex(s: &str) -> Vec<u8> {
(0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
}
fn words_bytes(w: &[u32; 8]) -> Vec<u8> {
w.iter().flat_map(|x| x.to_le_bytes()).collect()
}
/// A chain-shaped 32-byte epoch seed of this lane's label space.
fn steer_seed(i: u64) -> Vec<u8> {
words_bytes(&seed_words_from_bytes(format!("igneum-adv-accept-3/steer/{i}").as_bytes()))
}
fn era_bytes() -> Vec<u8> {
unhex(DEVNET3_HEX)
}
fn v4_era_class(era: &[u8]) -> LoadClass {
LoadClass::era(V4_CLASS, era, &V3_ALLOWED)
}
fn utc() -> String {
let s = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
let (d, t) = (s / 86400, s % 86400);
// civil from days (Howard Hinnant)
let z = d as i64 + 719468;
let era = z.div_euclid(146097);
let doe = z.rem_euclid(146097);
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let dd = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
format!("{y:04}-{m:02}-{dd:02}T{:02}:{:02}:{:02}Z", t / 3600, (t % 3600) / 60, t % 60)
}
struct Args {
cmd: String,
kv: Vec<(String, String)>,
}
impl Args {
fn parse() -> Args {
let v: Vec<String> = std::env::args().skip(1).collect();
if v.is_empty() {
usage();
}
let mut kv = Vec::new();
let mut i = 1;
while i < v.len() {
if let Some(k) = v[i].strip_prefix("--") {
if i + 1 < v.len() && !v[i + 1].starts_with("--") {
kv.push((k.to_string(), v[i + 1].clone()));
i += 2;
} else {
kv.push((k.to_string(), "1".to_string()));
i += 1;
}
} else {
usage();
}
}
Args { cmd: v[0].clone(), kv }
}
fn get(&self, k: &str, d: &str) -> String {
self.kv.iter().rev().find(|(a, _)| a == k).map(|(_, b)| b.clone()).unwrap_or_else(|| d.to_string())
}
fn u(&self, k: &str, d: u64) -> u64 {
self.get(k, &d.to_string()).parse().unwrap_or_else(|_| usage())
}
}
fn usage() -> ! {
eprintln!("adv3 idcheck | ids --seeds N --attempts A --threads T | margins | lastresort --from F --count N --threads T | exhaust-mirror --seed i | verdicts --from F --count N --threads T [--variant faithful|noncyclic|floor97|spec] | steer --from F --count N --threads T [--plant hot] [--check-every 50] | static --from F --count N --threads T | era-steer --from F --count N --threads T");
std::process::exit(2);
}
// ------------------------------------------------------------------------------------------------------------
// Static properties a seed grinder would want (Q2, Q2b, Q1b)
// ------------------------------------------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, Default)]
struct Props {
/// loads on the longest dependency path per hash (8 iterations, steady state), 128 at most
cp_loads: u32,
/// ALU instructions (base and shadow, every rep) on the longest dependency path per hash
cp_alu: u32,
/// load sites of iteration 0 whose address depends on the init words only (no earlier load on its path)
predictable_sites_it0: u32,
/// chi-square of the 48 non-load base ops against the weights of spec 1.4.2
opmix_chi2_milli: u32,
/// the largest single-op share of the 48 non-load base ops, in percent
opmix_max_pct: u32,
/// output registers whose last write of the iteration (base then shadow) is or, mul or mulhi
lossy_last_writes: u32,
/// count of or, mul, mulhi in the base program
lossy_base: u32,
/// longest dependency path through one shadow pass (256 instructions)
shadow_pass_depth: u32,
}
/// Dependency depth over a sequence of instructions, carrying per-register (alu depth, load depth). `shfl` reads
/// `src` of another lane: the same register, the same depth. `predictable` counts loads whose source has no load
/// on its path (an address computable from the init words alone).
fn depth_pass(instrs: &[Instr], st: &mut [(u32, u32); 8], mut predictable: Option<&mut u32>) {
for i in instrs {
let (d, a) = (i.dst as usize, i.src as usize);
let mut m = st[d];
if !matches!(i.op, Op::Rotl) {
m = (m.0.max(st[a].0), m.1.max(st[a].1));
}
if i.op == Op::Mad {
let b = i.src2 as usize;
m = (m.0.max(st[b].0), m.1.max(st[b].1));
}
if i.op.is_load() {
if let Some(p) = predictable.as_deref_mut() {
if st[a].1 == 0 {
*p += 1;
}
}
st[d] = (m.0 + 1, m.1 + 1);
} else {
st[d] = (m.0 + 1, m.1);
}
}
}
fn props(p: &Program) -> Props {
let mut st = [(0u32, 0u32); 8];
let reps = p.shadow_reps();
let mut pred_it0 = 0u32;
for it in 0..ITERATIONS {
depth_pass(&p.instrs, &mut st, if it == 0 { Some(&mut pred_it0) } else { None });
for _ in 0..reps {
depth_pass(&p.shadow, &mut st, None);
}
}
let cp_alu = st.iter().map(|s| s.0).max().unwrap_or(0);
let cp_loads = st.iter().map(|s| s.1).max().unwrap_or(0);
let mut sd = [(0u32, 0u32); 8];
depth_pass(&p.shadow, &mut sd, None);
let shadow_pass_depth = sd.iter().map(|s| s.0).max().unwrap_or(0);
let weights: [(Op, u32); 10] = [(Op::Add, 12), (Op::Xor, 10), (Op::Mul, 8), (Op::Mad, 8), (Op::Shfl, 8), (Op::Rotl, 7), (Op::Sub, 6), (Op::MulHi, 6), (Op::Rotr, 6), (Op::Or, 4)];
let mut counts = [0u32; 10];
let mut nonload = 0u32;
for i in &p.instrs {
if i.op.is_load() {
continue;
}
nonload += 1;
for (k, (o, _)) in weights.iter().enumerate() {
if *o == i.op {
counts[k] += 1;
}
}
}
let mut chi2 = 0.0f64;
let mut maxc = 0u32;
for (k, (_, w)) in weights.iter().enumerate() {
let e = nonload as f64 * *w as f64 / 75.0;
chi2 += (counts[k] as f64 - e).powi(2) / e;
maxc = maxc.max(counts[k]);
}
let lossy_base = p.instrs.iter().filter(|i| matches!(i.op, Op::Or | Op::Mul | Op::MulHi)).count() as u32;
let mut last = [None::<Op>; 8];
for i in p.instrs.iter().chain(p.shadow.iter()) {
last[i.dst as usize] = Some(i.op);
}
let lossy_last_writes = last.iter().filter(|o| matches!(o, Some(Op::Or | Op::Mul | Op::MulHi))).count() as u32;
Props {
cp_loads,
cp_alu,
predictable_sites_it0: pred_it0,
opmix_chi2_milli: (chi2 * 1000.0) as u32,
opmix_max_pct: if nonload > 0 { maxc * 100 / nonload } else { 0 },
lossy_last_writes,
lossy_base,
shadow_pass_depth,
}
}
fn props_header() -> &'static str {
"cp_loads\tcp_alu\tpred_it0\tchi2_milli\topmax_pct\tlossy_last\tlossy_base\tshadow_depth"
}
fn props_row(q: &Props) -> String {
format!("{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", q.cp_loads, q.cp_alu, q.predictable_sites_it0, q.opmix_chi2_milli, q.opmix_max_pct, q.lossy_last_writes, q.lossy_base, q.shadow_pass_depth)
}
fn reject_part(r: &Reject) -> &'static str {
match r {
Reject::StaleLoadSource { .. } => "a",
Reject::NoInjectingWrite { .. } => "b",
Reject::UnfreshLoadSource { .. } => "a'",
Reject::ConstantBit { .. } => "c-constbit",
Reject::LaneConstantSite { .. } => "c-lanesite",
Reject::Saturated { .. } => "c-saturated",
Reject::SaturatedSource { .. } => "c'",
Reject::RepeatedSource { .. } => "c''-repeat",
Reject::LowEntropySite { .. } => "c''",
Reject::OutputBias { .. } => "c-bias",
Reject::DistinctAddresses { .. } => "c-distinct",
}
}
fn program_fingerprint(p: &Program) -> u64 {
let mut b = Vec::new();
for i in p.instrs.iter().chain(p.shadow.iter()) {
b.push(i.op as u8);
b.push(i.dst);
b.push(i.src);
b.push(i.src2);
b.extend_from_slice(&i.imm.to_le_bytes());
b.extend_from_slice(&i.imm2.to_le_bytes());
b.push(i.rot as u8);
b.push(i.bit);
b.push(i.mask);
b.push(i.win);
b.push(i.off);
}
fnv1a64(&b)
}
// ------------------------------------------------------------------------------------------------------------
// Parallel driver: seeds [from, from + count) over T threads, rows written as they land
// ------------------------------------------------------------------------------------------------------------
fn run_seeds<F>(from: u64, count: u64, threads: usize, out: &Mutex<std::io::Stdout>, f: F)
where
F: Fn(u64) -> String + Sync,
{
let next = AtomicUsize::new(0);
std::thread::scope(|s| {
for _ in 0..threads {
s.spawn(|| loop {
let k = next.fetch_add(1, Ordering::Relaxed) as u64;
if k >= count {
break;
}
let row = f(from + k);
let mut o = out.lock().unwrap();
writeln!(o, "{row}").unwrap();
o.flush().unwrap();
});
}
});
}
// ------------------------------------------------------------------------------------------------------------
// Q3 (i): idcheck
// ------------------------------------------------------------------------------------------------------------
/// The id as program.json and spec 01 section 1.4.6 STATE it: no sub-version suffix.
fn program_id_stated(generator: u32, seed: &[u32; 8], attempt: u32) -> u64 {
let mut b = Vec::new();
b.extend_from_slice(b"igneum-program/");
b.extend_from_slice(&generator.to_le_bytes());
b.extend_from_slice(&words_bytes(seed));
b.extend_from_slice(&attempt.to_le_bytes());
fnv1a64(&b)
}
/// The id as the code computes it, re-done here byte for byte (so a change in the library would show).
fn program_id_code(generator: u32, seed: &[u32; 8], attempt: u32) -> u64 {
let mut b = Vec::new();
b.extend_from_slice(b"igneum-program/");
b.extend_from_slice(&generator.to_le_bytes());
b.extend_from_slice(&words_bytes(seed));
b.extend_from_slice(&attempt.to_le_bytes());
if generator == 4 {
b.extend_from_slice(b"sub/");
b.extend_from_slice(&PROGRAM_SUBVERSION_V4.to_le_bytes());
}
fnv1a64(&b)
}
fn idcheck() {
println!("# adv3 idcheck {} (internal adversarial pass, not an independent review)", utc());
for (name, hx, want_id, want_attempt) in [("devnet3-epoch0", DEVNET3_HEX, 0xfce15bf61030be57u64, 0u32), ("kit v4-devnet-epoch0", DEVNET_KIT_HEX, 0xa785001687d8688au64, 1u32)] {
let seed = unhex(hx);
let t0 = Instant::now();
let p = Epoch::chain_program(&seed, Some(&seed), ProgramClass::V4, &format!("igneum-epoch/{hx}"));
let id = p.program_id();
let lib = program_id(GENERATOR_VERSION_V4, &p.seed, p.attempt);
let code = program_id_code(4, &p.seed, p.attempt);
let stated = program_id_stated(4, &p.seed, p.attempt);
let stated3 = program_id_stated(3, &p.seed, p.attempt);
println!("{name}: chain draw attempt {} id {id:016x} (want {want_id:016x} at attempt {want_attempt}) class {} in {:.1} s: {}", p.attempt, p.class.name(), t0.elapsed().as_secs_f64(), if id == want_id && p.attempt == want_attempt { "MATCH" } else { "MISMATCH" });
println!(" library program_id(4, seed, attempt) = {lib:016x}; re-done with sub/3 suffix = {code:016x} ({}); as program.json and spec 1.4.6 STATE it (no suffix) = {stated:016x} ({}); the stated form with generator 3 = {stated3:016x}", if code == want_id { "matches the pack" } else { "does NOT match" }, if stated == want_id { "matches the pack" } else { "does NOT match the pack" });
// known-failed shape: one attempt bit flipped changes the id; the words of attempt k differ from attempt 0
let flipped = program_id(GENERATOR_VERSION_V4, &p.seed, p.attempt ^ 1);
println!(" attempt bit flipped: {flipped:016x} ({})", if flipped != id { "changed, as it must" } else { "UNCHANGED" });
println!(" seed words of attempt {}: {}", p.attempt, p.seed.iter().map(|w| format!("{w:08x}")).collect::<Vec<_>>().join(" "));
println!(" op mix {}; props {}", p.op_mix(), props_row(&props(&p)));
}
}
// ------------------------------------------------------------------------------------------------------------
// Q3 (ii): ids, collisions over (seed, attempt) pairs
// ------------------------------------------------------------------------------------------------------------
fn count_dups<T: Ord + Copy>(v: &mut [T]) -> usize {
v.sort_unstable();
v.windows(2).filter(|w| w[0] == w[1]).count()
}
fn ids(seeds: u64, attempts: u32, threads: usize) {
println!("# adv3 ids {} seeds {seeds} attempts {attempts} (internal adversarial pass, not an independent review)", utc());
let t0 = Instant::now();
let per = (seeds as usize + threads - 1) / threads;
let parts: Vec<(Vec<u64>, Vec<u64>, Vec<(u64, u64, u64, u64)>)> = std::thread::scope(|s| {
let hs: Vec<_> = (0..threads)
.map(|t| {
s.spawn(move || {
let lo = (t * per) as u64;
let hi = (((t + 1) * per) as u64).min(seeds);
let mut ids = Vec::with_capacity(((hi.saturating_sub(lo)) as usize) * attempts as usize);
let mut states = Vec::with_capacity(ids.capacity());
let mut octets = Vec::with_capacity(ids.capacity());
for i in lo..hi {
let b = steer_seed(i);
for k in 0..attempts {
let w = attempt_words(&b, k);
ids.push(program_id(GENERATOR_VERSION_V4, &w, k));
states.push(program_rng(&w).s);
octets.push((w[0] as u64 | (w[1] as u64) << 32, w[2] as u64 | (w[3] as u64) << 32, w[4] as u64 | (w[5] as u64) << 32, w[6] as u64 | (w[7] as u64) << 32));
}
}
(ids, states, octets)
})
})
.collect();
hs.into_iter().map(|h| h.join().unwrap()).collect()
});
let mut all_ids = Vec::new();
let mut all_states = Vec::new();
let mut all_oct = Vec::new();
for (a, b, c) in parts {
all_ids.extend(a);
all_states.extend(b);
all_oct.extend(c);
}
let n = all_ids.len();
let d_ids = count_dups(&mut all_ids);
let d_states = count_dups(&mut all_states);
let d_oct = count_dups(&mut all_oct);
// the stream state's low 32 bits and the id's low 32 bits: the birthday count at 2^32 is the control that the
// counter sees collisions when they exist (expected n^2 / 2^33)
let mut lo_ids: Vec<u32> = all_ids.iter().map(|x| *x as u32).collect();
let mut lo_states: Vec<u32> = all_states.iter().map(|x| *x as u32).collect();
let d_lo_ids = count_dups(&mut lo_ids);
let d_lo_states = count_dups(&mut lo_states);
let exp32 = (n as f64).powi(2) / 2f64.powi(33);
let exp64 = (n as f64).powi(2) / 2f64.powi(65);
println!("pairs {n}: id collisions {d_ids} (expected {exp64:.2e} at random); program-stream state collisions {d_states} (two seeds with one state draw one base program; expected {exp64:.2e}); seed-word octet collisions {d_oct}");
println!("control (known-failed shape): low-32 id collisions {d_lo_ids} and low-32 state collisions {d_lo_states} against {exp32:.1} expected at random: the counter fires when collisions exist");
println!("done in {:.1} s", t0.elapsed().as_secs_f64());
}
// ------------------------------------------------------------------------------------------------------------
// Q4 (iii): margins of the f64 ratio in (c'')
// ------------------------------------------------------------------------------------------------------------
fn margins() {
println!("# adv3 margins {} (internal adversarial pass, not an independent review)", utc());
let n: u64 = (accept::ACCEPT_UNITS_DISTINCT_V4 * LANES * ITERATIONS) as u64;
println!("n = {n} evaluations per site; floor {MIN_DISTINCT_RATIO_V4}");
for k in 0..=2u32 {
let w: u64 = (1u64 << ACCEPT_DATASET_LOG2) >> k;
let e_int: u64 = n - n * n / (2 * w);
let e_f = n as f64 - (n as f64) * (n as f64) / (2.0 * w as f64);
let thr = 0.98 * e_int as f64;
let nearest = thr.round();
println!("window 2^{} words: E = {e_int} (f64 {e_f}); 0.98 E = {thr:.3}; nearest integer {nearest}; margin {:.3} counts; relative margin {:.2e}", ACCEPT_DATASET_LOG2 - k, (thr - nearest).abs(), (thr - nearest).abs() / thr);
// every d: the code's f64 compare against the exact integer compare 50 d >= 49 E, and an f32 compare
let (mut flips64, mut flips32) = (0u64, 0u64);
for d in 0..=n {
let code_pass = (d as f64 / e_f) >= MIN_DISTINCT_RATIO_V4;
let int_pass = 50 * d >= 49 * e_int;
let f32_pass = (d as f32 / e_f as f32) >= 0.98f32;
flips64 += (code_pass != int_pass) as u64;
flips32 += (f32_pass != int_pass) as u64;
}
println!(" over all d in 0..={n}: f64 compare disagrees with the integer rule on {flips64} values; an f32 compare on {flips32}");
}
}
// ------------------------------------------------------------------------------------------------------------
// Q1b: the last resort under the real rule; the reject-everything mirror
// ------------------------------------------------------------------------------------------------------------
fn lastresort(from: u64, count: u64, threads: usize) {
let out = Mutex::new(std::io::stdout());
println!("# adv3 lastresort {} seeds {from}..{} (internal adversarial pass, not an independent review)", utc(), from + count);
println!("seed\tverdict\tpart\tfingerprint\tid\tkept_mad\t{}\tsecs", props_header());
let era = era_bytes();
let class = v4_era_class(&era);
run_seeds(from, count, threads, &out, |i| {
let b = steer_seed(i);
let t0 = Instant::now();
let c = candidate_class(&format!("adv3/steer/{i}"), &b, max_attempts_for(&class), class);
let lr = last_resort_v4(c);
let v = check(&lr);
let (verdict, part) = match &v {
Ok(_) => ("accept", "-"),
Err(r) => ("reject", reject_part(r)),
};
let mads = lr.instrs.iter().chain(lr.shadow.iter()).filter(|x| x.op == Op::Mad).count();
format!("{i}\t{verdict}\t{part}\t{:016x}\t{:016x}\t{mads}\t{}\t{:.1}", program_fingerprint(&lr), program_id(GENERATOR_VERSION_V4, &lr.seed, lr.attempt), props_row(&props(&lr)), t0.elapsed().as_secs_f64())
});
}
fn exhaust_mirror(i: u64) {
println!("# adv3 exhaust-mirror {} seed {i} (internal adversarial pass, not an independent review)", utc());
let era = era_bytes();
let class = v4_era_class(&era);
let b = steer_seed(i);
let label = format!("adv3/steer/{i}");
// the mirror of try_generate_class with a rule patched to reject everything
let cap = max_attempts_for(&class);
let reject_all = |_p: &Program| -> Result<AcceptReport, Reject> { Err(Reject::NoInjectingWrite { reg: 0 }) };
let mut tried = 0u32;
let mut mirror = None;
for attempt in 0..cap {
let p = candidate_class(&label, &b, attempt, class);
tried += 1;
if reject_all(&p).is_ok() {
mirror = Some(p);
break;
}
}
let mirror = mirror.unwrap_or_else(|| last_resort_v4(candidate_class(&label, &b, cap, class)));
let lib = last_resort_v4(candidate_class(&label, &b, cap, class));
println!("cap {cap}; attempts tried {tried}; mirror reached the cap: {}; mirror program == library last_resort_v4(candidate(seed, {cap})): {}", tried == cap, mirror == lib);
println!("last-resort attempt {} id {:016x} fingerprint {:016x} op mix {}; shadow op mix {}", mirror.attempt, mirror.program_id(), program_fingerprint(&mirror), mirror.op_mix(), mirror.shadow_op_mix());
let real = check(&mirror);
println!("the REAL rule on the last resort: {}", match &real { Ok(r) => format!("accept (distinct mean {:.3}, saturated {}, bias max {})", r.distinct_mean(), r.saturated, r.bias_max), Err(r) => format!("reject: {r}") });
println!("props {}", props_row(&props(&mirror)));
for (k, ins) in mirror.instrs.iter().enumerate() {
println!("{k:2}: {:5} dst={} src={} src2={} rot={} bit={} mask={} win={} off={}", ins.op.name(), ins.dst, ins.src, ins.src2, ins.rot, ins.bit, ins.mask, ins.win, ins.off);
}
// and the real rule's own path: the chain draw of the same seed (the accepted attempt, not the last resort)
let chain = Epoch::chain_program(&b, Some(&era), ProgramClass::V4, &label);
println!("the chain's own program of this seed: attempt {} id {:016x}", chain.attempt, chain.program_id());
}
// ------------------------------------------------------------------------------------------------------------
// Q4 (i), (ii): the second interpretation of the rule
// ------------------------------------------------------------------------------------------------------------
#[derive(Clone, Copy, PartialEq, Eq)]
enum Variant {
/// the module table of accept.rs, written again from its text, integer ratio compare
Faithful,
/// known-failed shape: (a) checked in a single pass (not cyclic)
NonCyclic,
/// known-failed shape: the (c'') floor at 0.97
Floor97,
/// spec 01 section 1.4.6 as written at 017e7037: (a), (b), (c) only, no shadow block in the execution
Spec,
}
struct Second {
variant: Variant,
}
impl Second {
fn base_nonces(seed: &[u32; 8], n: usize) -> Vec<u32> {
let mut b = b"igneum-accept/".to_vec();
b.extend_from_slice(&words_bytes(seed));
let mut rng = SplitMix64::new(fnv1a64(&b));
(0..n).map(|_| (rng.next() as u32) & !31).collect()
}
/// (a): cyclic (two passes) or single pass.
fn rule_a(&self, p: &Program) -> bool {
let passes = if self.variant == Variant::NonCyclic { 1 } else { 2 };
let mut read_unwritten = [false; 8];
for _ in 0..passes {
for ins in &p.instrs {
if ins.op.is_load() && read_unwritten[ins.src as usize] {
return false;
}
read_unwritten[ins.dst as usize] = false;
if ins.op.is_load() {
read_unwritten[ins.src as usize] = true;
}
}
}
true
}
fn rule_b(&self, p: &Program) -> bool {
let mut inj = [false; 8];
for ins in &p.instrs {
if matches!(ins.op, Op::Add | Op::Sub | Op::Xor | Op::Mad | Op::Shfl | Op::Load) {
inj[ins.dst as usize] = true;
}
}
inj.iter().all(|&x| x)
}
/// (a'): freshness by dataflow over base then shadow, from all-fresh, to a fixpoint, then a checking pass.
/// Written from the table in accept.rs: load keeps its source's freshness; add, sub, xor, mad, shfl fresh if dst
/// or src was; rotl, rotr keep dst's; or, mul, mulhi never. The shared-operand idiom: or then xor or sub on one
/// operand, or xor then or, is lossy.
fn rule_a_prime(&self, p: &Program) -> bool {
if self.variant == Variant::Spec {
return true;
}
let seq: Vec<&Instr> = p.instrs.iter().chain(p.shadow.iter()).collect();
let mut fresh = [true; 8];
let mut pair: [Option<(Op, u8)>; 8] = [None; 8];
let step = |fresh: &mut [bool; 8], pair: &mut [Option<(Op, u8)>; 8], checking: bool| -> bool {
for ins in &seq {
let (d, a) = (ins.dst as usize, ins.src);
if checking && ins.op.is_load() && !fresh[a as usize] {
return false;
}
let lossy_pair = match (pair[d], ins.op) {
(Some((Op::Or, s)), Op::Xor) | (Some((Op::Or, s)), Op::Sub) | (Some((Op::Xor, s)), Op::Or) => s == a,
_ => false,
};
let f = if lossy_pair {
false
} else {
match ins.op {
Op::Load => fresh[a as usize],
Op::Add | Op::Sub | Op::Xor | Op::Mad | Op::Shfl => fresh[d] || fresh[a as usize],
Op::Rotl | Op::Rotr => fresh[d],
_ => false,
}
};
fresh[d] = f;
pair[d] = if matches!(ins.op, Op::Or | Op::Xor) && !lossy_pair { Some((ins.op, a)) } else { None };
for r in 0..8 {
if r != d {
if let Some((_, s)) = pair[r] {
if s as usize == d {
pair[r] = None;
}
}
}
}
}
true
};
for _ in 0..16 {
let before = (fresh, pair);
step(&mut fresh, &mut pair, false);
if (fresh, pair) == before {
break;
}
}
step(&mut fresh, &mut pair, true)
}
/// One evaluation of one lane group, scalar per lane. Returns per-lane final registers; records per load
/// position the 32 indices through `on_load(position, iteration, instr, &idx[32], &src_values[32])`.
fn run_unit<F: FnMut(usize, usize, usize, &[u32; 32], &[u32; 32])>(&self, p: &Program, era: Option<&EraParams>, base: u32, mut on_load: F) -> [[u32; 8]; 32] {
let s = &p.seed;
let mask = (1u32 << 28) - 1;
let mut r = [[0u32; 8]; 32];
for lane in 0..32u32 {
let nonce = base.wrapping_add(lane);
for i in 0..8 {
let x = splitmix32((nonce ^ s[i]).wrapping_add(0x9e3779b9u32.wrapping_mul(i as u32 + 1)));
r[lane as usize][i] = x ^ s[(i + 1) & 7];
}
}
let reps = if self.variant == Variant::Spec { 0 } else { p.shadow_reps() };
let mut pos = 0usize;
for it in 0..ITERATIONS {
let sel: [u32; 32] = std::array::from_fn(|l| r[l][0]);
let mut exec = |ins: &Instr, k: usize, r: &mut [[u32; 8]; 32], pos: &mut usize| {
let (d, a) = (ins.dst as usize, ins.src as usize);
match ins.op {
Op::Add => {
for l in 0..32 {
let c = if (sel[l] >> ins.bit) & 1 == 1 { ins.imm2 } else { ins.imm };
r[l][d] = r[l][d].wrapping_add(r[l][a]).wrapping_add(c);
}
}
Op::Sub => (0..32).for_each(|l| r[l][d] = r[l][d].wrapping_sub(r[l][a])),
Op::Mul => (0..32).for_each(|l| r[l][d] = r[l][d].wrapping_mul(r[l][a])),
Op::MulHi => (0..32).for_each(|l| r[l][d] = ((r[l][d] as u64 * r[l][a] as u64) >> 32) as u32),
Op::Xor => (0..32).for_each(|l| r[l][d] ^= r[l][a]),
Op::Or => (0..32).for_each(|l| r[l][d] |= r[l][a]),
Op::Rotl => (0..32).for_each(|l| r[l][d] = r[l][d].rotate_left(ins.rot)),
Op::Rotr => (0..32).for_each(|l| r[l][d] = r[l][d].rotate_right(r[l][a] & 31)),
Op::Mad => (0..32).for_each(|l| r[l][d] = r[l][a].wrapping_mul(r[l][ins.src2 as usize]).wrapping_add(r[l][d])),
Op::Shfl => {
let src: [u32; 32] = std::array::from_fn(|l| r[l][a]);
for l in 0..32 {
r[l][d] ^= src[l ^ ins.mask as usize];
}
}
Op::Load => {
let srcv: [u32; 32] = std::array::from_fn(|l| r[l][a]);
let idx: [u32; 32] = std::array::from_fn(|l| load_index(era, ins, srcv[l], mask, 28));
on_load(*pos, it, k, &idx, &srcv);
for l in 0..32 {
r[l][d] ^= dataset_elem(idx[l], s[0], s[1]);
}
*pos += 1;
}
_ => panic!("op outside the lottery hash"),
}
};
for (k, ins) in p.instrs.iter().enumerate() {
exec(ins, k, &mut r, &mut pos);
}
for _ in 0..reps {
for (k, ins) in p.shadow.iter().enumerate() {
exec(ins, INSTR_COUNT + k, &mut r, &mut pos);
}
}
}
r
}
/// (c), (c') and (c''). Returns accept or the name of the failing part.
fn dynamic(&self, p: &Program) -> Result<(), &'static str> {
let era = p.class.era;
let era = era.as_ref();
let bases = Self::base_nonces(&p.seed, 64);
let mut and_acc = [u32::MAX; 8];
let mut or_acc = [0u32; 8];
let mut saturated = 0u32;
let mut ones = [0u32; 64];
let mut distinct_sum = 0u64;
let mut sat_source = [0u32; 16];
let mut lane_const = false;
for &base in &bases {
let mut per_lane: Vec<Vec<u32>> = vec![Vec::with_capacity(128); 32];
let regs = self.run_unit(p, era, base, |pos, _it, _k, idx, srcv| {
if idx.iter().all(|&x| x == idx[0]) {
lane_const = true;
}
for l in 0..32 {
per_lane[l].push(idx[l]);
if srcv[l] == 0 || srcv[l] == u32::MAX {
sat_source[pos % 16] += 1;
}
}
});
if lane_const {
return Err("c-lanesite");
}
for l in 0..32 {
for i in 0..8 {
let v = regs[l][i];
and_acc[i] &= v;
or_acc[i] |= v;
saturated += (v == 0 || v == u32::MAX) as u32;
}
let lo = regs[l][0] ^ regs[l][1].rotate_left(7) ^ regs[l][2].rotate_left(14) ^ regs[l][3].rotate_left(21);
let hi = regs[l][4] ^ regs[l][5].rotate_left(9) ^ regs[l][6].rotate_left(18) ^ regs[l][7].rotate_left(27);
let h = (hi as u64) << 32 | lo as u64;
for j in 0..64 {
ones[j] += ((h >> j) & 1) as u32;
}
let set: HashSet<u32> = per_lane[l].iter().copied().collect();
distinct_sum += set.len() as u64;
}
}
for i in 0..8 {
if (and_acc[i] | !or_acc[i]) != 0 {
return Err("c-constbit");
}
}
if saturated >= MAX_SATURATED {
return Err("c-saturated");
}
if self.variant != Variant::Spec {
if sat_source.iter().any(|&c| c >= MAX_SATURATED) {
return Err("c'");
}
// (c''): 4096 units, per-site distinct indices against N - N^2 / 2W, integer compare
let bases = Self::base_nonces(&p.seed, 4096);
let mut per_site: Vec<Vec<u32>> = vec![Vec::with_capacity(4096 * 32 * 8); 16];
for &base in &bases {
self.run_unit(p, era, base, |pos, _it, _k, idx, _s| {
per_site[pos % 16].extend_from_slice(idx);
});
}
let n: u64 = 4096 * 32 * 8;
let wins: Vec<u8> = p.instrs.iter().filter(|i| i.op == Op::Load).map(|i| i.win.min(2)).collect();
for (site, v) in per_site.iter_mut().enumerate() {
v.sort_unstable();
v.dedup();
let w: u64 = (1u64 << 28) >> wins[site];
let e = n - n * n / (2 * w);
let pass = match self.variant {
Variant::Floor97 => 100 * v.len() as u64 >= 97 * e,
_ => 50 * v.len() as u64 >= 49 * e,
};
if !pass {
return Err("c''");
}
}
}
for &o in &ones {
if o.abs_diff(1024) > BIAS_TOLERANCE {
return Err("c-bias");
}
}
if distinct_sum <= MIN_DISTINCT_SUM {
return Err("c-distinct");
}
Ok(())
}
fn verdict(&self, p: &Program) -> Result<(), &'static str> {
if !self.rule_a(p) {
return Err("a");
}
if !self.rule_b(p) {
return Err("b");
}
if !self.rule_a_prime(p) {
return Err("a'");
}
self.dynamic(p)
}
}
fn verdicts(from: u64, count: u64, threads: usize, variant: &str) {
let v = match variant {
"faithful" => Variant::Faithful,
"noncyclic" => Variant::NonCyclic,
"floor97" => Variant::Floor97,
"spec" => Variant::Spec,
_ => usage(),
};
let second = Second { variant: v };
let out = Mutex::new(std::io::stdout());
println!("# adv3 verdicts {} variant {variant} seeds {from}..{} (internal adversarial pass, not an independent review)", utc(), from + count);
println!("seed\tattempt\tlib_verdict\tlib_part\tsecond_verdict\tsecond_part\tagree\tchosen_lib\tchosen_second\tsecs");
let era = era_bytes();
let class = v4_era_class(&era);
let cap = if v == Variant::Spec { 32 } else { max_attempts_for(&class) };
run_seeds(from, count, threads, &out, |i| {
let b = steer_seed(i);
let label = format!("adv3/steer/{i}");
let t0 = Instant::now();
let mut rows = Vec::new();
let (mut chosen_lib, mut chosen_second) = (None::<u32>, None::<u32>);
let mut attempt = 0u32;
while attempt < cap && (chosen_lib.is_none() || chosen_second.is_none()) {
let p = candidate_class(&label, &b, attempt, class);
let lv = check(&p);
let sv = second.verdict(&p);
let (lverd, lpart) = match &lv { Ok(_) => ("accept", "-"), Err(r) => ("reject", reject_part(r)) };
let (sverd, spart) = match &sv { Ok(_) => ("accept", "-"), Err(e) => ("reject", *e) };
if lv.is_ok() && chosen_lib.is_none() {
chosen_lib = Some(attempt);
}
if sv.is_ok() && chosen_second.is_none() {
chosen_second = Some(attempt);
}
rows.push(format!("{i}\t{attempt}\t{lverd}\t{lpart}\t{sverd}\t{spart}\t{}", if lv.is_ok() == sv.is_ok() { "yes" } else { "NO" }));
attempt += 1;
}
let secs = t0.elapsed().as_secs_f64();
let cl = chosen_lib.map(|x| x.to_string()).unwrap_or_else(|| "cap".into());
let cs = chosen_second.map(|x| x.to_string()).unwrap_or_else(|| "cap".into());
rows.iter().map(|r| format!("{r}\t{cl}\t{cs}\t{secs:.1}")).collect::<Vec<_>>().join("\n")
});
}
// ------------------------------------------------------------------------------------------------------------
// Q2: steering, the real chain draw per seed
// ------------------------------------------------------------------------------------------------------------
/// The plant for the property measurement: on an accepted program, the two instructions that last wrote the source
/// register of one load (scanning back cyclically) become `or`, so the source saturates toward all ones and the
/// site's distinct-index ratio must fall far below the population. The real rule is run on the planted program too.
fn plant_hot(p: &Program) -> Program {
let mut q = p.clone();
let n = q.instrs.len();
let site = q.instrs.iter().position(|i| i.op == Op::Load && i.src != 0).or_else(|| q.instrs.iter().position(|i| i.op == Op::Load)).unwrap();
let reg = q.instrs[site].src;
let mut changed = 0;
let mut k = (site + n - 1) % n;
while changed < 2 && k != site {
if q.instrs[k].dst == reg && !q.instrs[k].op.is_load() {
q.instrs[k].op = Op::Or;
changed += 1;
}
k = (k + n - 1) % n;
}
q
}
fn steer(from: u64, count: u64, threads: usize, plant: bool, check_every: u64) {
let out = Mutex::new(std::io::stdout());
println!("# adv3 steer {} seeds {from}..{} plant {plant} (internal adversarial pass, not an independent review)", utc(), from + count);
println!("seed\tattempt\tid\tmin_ratio256_milli\tmin_site\tdistinct_mean_milli\tsaturated\tbias_max\trejects\t{}\tchain_eq\tsecs{}", props_header(), if plant { "\tplant_ratio_milli\tplant_verdict" } else { "" });
let era = era_bytes();
let class = v4_era_class(&era);
let cap = max_attempts_for(&class);
run_seeds(from, count, threads, &out, |i| {
let b = steer_seed(i);
let label = format!("adv3/steer/{i}");
let t0 = Instant::now();
let mut rejects = String::new();
let mut accepted = None;
for attempt in 0..cap {
let p = candidate_class(&label, &b, attempt, class);
match check(&p) {
Ok(r) => {
accepted = Some((p, r));
break;
}
Err(r) => {
if !rejects.is_empty() {
rejects.push(',');
}
rejects.push_str(reject_part(&r));
}
}
}
let (p, r) = match accepted {
Some(x) => x,
None => {
let lr = last_resort_v4(candidate_class(&label, &b, cap, class));
return format!("{i}\tLASTRESORT\t{:016x}\t-\t-\t-\t-\t-\t{rejects}\t{}\t-\t{:.1}", lr.program_id(), props_row(&props(&lr)), t0.elapsed().as_secs_f64());
}
};
let (min_ratio, min_site) = distinct_ratio_pass(&p, 256, 0.0).unwrap_or((0.0, 99));
let chain_eq = if check_every > 0 && i % check_every == 0 {
let c = Epoch::chain_program(&b, Some(&era), ProgramClass::V4, &label);
if c.instrs == p.instrs && c.shadow == p.shadow && c.attempt == p.attempt && c.program_id() == p.program_id() { "eq" } else { "DIFF" }
} else {
"-"
};
let mut row = format!(
"{i}\t{}\t{:016x}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{chain_eq}\t{:.1}",
p.attempt,
p.program_id(),
(min_ratio * 1000.0) as u32,
min_site,
(r.distinct_mean() * 1000.0) as u32,
r.saturated,
r.bias_max,
if rejects.is_empty() { "-".to_string() } else { rejects },
props_row(&props(&p)),
t0.elapsed().as_secs_f64()
);
if plant {
let q = plant_hot(&p);
let (pr, _) = distinct_ratio_pass(&q, 256, 0.0).unwrap_or((0.0, 99));
let pv = match check(&q) { Ok(_) => "accept".to_string(), Err(e) => format!("reject:{}", reject_part(&e)) };
row.push_str(&format!("\t{}\t{pv}", (pr * 1000.0) as u32));
}
row
});
}
// ------------------------------------------------------------------------------------------------------------
// Q2b: static steering over many seeds (the first candidate that passes the static rule)
// ------------------------------------------------------------------------------------------------------------
fn static_sweep(from: u64, count: u64, threads: usize) {
let out = Mutex::new(std::io::stdout());
println!("# adv3 static {} seeds {from}..{} (internal adversarial pass, not an independent review)", utc(), from + count);
println!("seed\tstatic_attempt\tstatic_rejects\t{}", props_header());
let era = era_bytes();
let class = v4_era_class(&era);
let cap = max_attempts_for(&class);
run_seeds(from, count, threads, &out, |i| {
let b = steer_seed(i);
let label = format!("adv3/steer/{i}");
let mut rejects = [0u32; 3];
for attempt in 0..cap {
let p = candidate_class(&label, &b, attempt, class);
match check_static(&p) {
Ok(()) => return format!("{i}\t{attempt}\t{}/{}/{}\t{}", rejects[0], rejects[1], rejects[2], props_row(&props(&p))),
Err(Reject::StaleLoadSource { .. }) => rejects[0] += 1,
Err(Reject::NoInjectingWrite { .. }) => rejects[1] += 1,
Err(_) => rejects[2] += 1,
}
}
format!("{i}\tcap\t{}/{}/{}\t-", rejects[0], rejects[1], rejects[2])
});
}
// ------------------------------------------------------------------------------------------------------------
// Q5: the era attacker (static): the stride and the windows of N era seeds
// ------------------------------------------------------------------------------------------------------------
fn era_steer(from: u64, count: u64, threads: usize) {
let out = Mutex::new(std::io::stdout());
println!("# adv3 era-steer {} eras {from}..{} (internal adversarial pass, not an independent review)", utc(), from + count);
println!("era\tstride_mul\tstride_rot\tmul_popcount\tmul_naf\tinterleave\tepoch0_attempt\tepoch0_min_ratio256_milli");
run_seeds(from, count, threads, &out, |i| {
let e = words_bytes(&seed_words_from_bytes(format!("igneum-adv-accept-3/era/{i}").as_bytes()));
let class = v4_era_class(&e);
let era = class.era.unwrap();
// the first epoch of the era under this lane's epoch seed 0: the stride's effect on the accepted program
let b = steer_seed(0);
let p = Epoch::chain_program(&b, Some(&e), ProgramClass::V4, &format!("adv3/era/{i}"));
let (mr, _) = distinct_ratio_pass(&p, 256, 0.0).unwrap_or((0.0, 99));
format!("{i}\t{:#010x}\t{}\t{}\t{}\t{:?}\t{}\t{}", era.stride_mul, era.stride_rot, era.stride_mul.count_ones(), naf_weight(era.stride_mul), era.pos, p.attempt, (mr * 1000.0) as u32)
});
}
fn naf_weight(v: u32) -> u32 {
let mut n = v as u64;
let mut w = 0;
while n != 0 {
if n & 1 == 1 {
if n & 3 == 3 { n += 1 } else { n -= 1 }
w += 1;
}
n >>= 1;
}
w
}
fn main() {
let a = Args::parse();
let threads = a.u("threads", 8) as usize;
match a.cmd.as_str() {
"idcheck" => idcheck(),
"ids" => ids(a.u("seeds", 1_000_000), a.u("attempts", 10) as u32, threads),
"margins" => margins(),
"lastresort" => lastresort(a.u("from", 0), a.u("count", 1000), threads),
"exhaust-mirror" => exhaust_mirror(a.u("seed", 0)),
"verdicts" => verdicts(a.u("from", 0), a.u("count", 1000), threads, &a.get("variant", "faithful")),
"steer" => steer(a.u("from", 0), a.u("count", 1000), threads, a.get("plant", "none") == "hot", a.u("check-every", 50)),
"static" => static_sweep(a.u("from", 0), a.u("count", 1000), threads),
"era-steer" => era_steer(a.u("from", 0), a.u("count", 1000), threads),
_ => usage(),
}
}