53 lines
14 KiB
Markdown
53 lines
14 KiB
Markdown
# Attack plan: exhaustion and steering of the program draw (lane adv-accept-3)
|
|
|
|
internal adversarial pass, not an independent review
|
|
|
|
Lane adv-accept-3. Branch adv-accept-3 off build/master. Written 7 October 2026, 19:51 to 19:58 BST (clock: `TZ=Europe/London date`; first push 57fd32ea at 19:58 BST). Target commit 017e70376489251e18564c0abce7e466e606c8b3 (class v4 sub-version 3, object byte 7). I am an outside attacker with the public kit; I have never worked on the hash code. Every sentence here that could be quoted in public carries the label above.
|
|
|
|
## 0. The outsider rule, applied
|
|
|
|
| Check | Result |
|
|
|---|---|
|
|
| Worktree HEAD | 45845b09d98bbf3e25ed17c2bb5a95542b8ab295 (build/master at 19:51 BST) |
|
|
| `git diff --quiet 017e7037 HEAD -- igneum-pow` | IDENTICAL. The crate I build is the frozen object. |
|
|
| Kit zip packs-ca3-v4-sub3-20261007.zip on build-1 | sha256 4f2445c50c58d76a5544023492d8b858d0b07c5e372d31f9c90c4ce51f829154: matches the brief |
|
|
| Devnet 3 pack v4-devnet3-epoch0.zip on build-1 | sha256 e025750f71175ed14d6e2a24e387ebbf1979b1cd0faee9139c41a7671165b334: matches; program id 0xfce15bf61030be57 at attempt 0, era label af3a9139, era seed bytes = epoch seed bytes 4020cb43...b925 |
|
|
| Shared devnet epoch-0 pack (kit) | id 0xa785001687d8688a at attempt 1, seed edc4fa84...fb07, era = the same bytes |
|
|
| Queue files | 90 (exhaustion census) was already claimed by lane adv-accept before I started; I read its row and cite it. 91 (seed steering) and 92 (program-id determinism) claimed by me at 19:5x BST (mkdir under claims/). |
|
|
| Boxes | build-1 load 426, build-2 load 560 at 19:52 BST. Both at nice 10 on cores 8 to 95. Coordinator rule at 19:55 BST: one sweep per box at a time under `flock /srv/builds/_adv/locks/sweep.lock`, up to 88 threads. No GPU: any GPU row is BLOCKED. |
|
|
| rustc on the boxes | 1.99.0 (build-remote.sh's own check); `rustc` is not on the plain ssh PATH, so every build goes through tools/build-remote.sh |
|
|
|
|
### Files opened (complete list)
|
|
|
|
igneum-pow/: Cargo.toml, src/lib.rs, src/seed.rs (full), src/accept.rs (full), src/generator.rs (lines 40 to 70, 300 to 365, 860 to 900, 1060 to 1140, 1196 to 1580: the constants, the era draw, the generate entry points, the program id, attempt_words, the candidate draw, the attempt loop, the last resort, attempts_class), src/verify.rs (lines 1 to 60 and 130 to 160: load_index, window, fold_words, dataset_elem, splitmix32), src/main.rs (lines 380 to 430: the accept and show commands). docs/spec/01-lottery-hash.md at 017e7037: 1.2, 1.3, 1.4 to 1.4.6, 1.12, 1.13 to 1.16. docs/analysis/chip-model-v3.md at HEAD: sections 1, 2, 5, 6. Kit: proto-cuda/packs-ca3-v4/v4-devnet-epoch0/program.json and v4-era-0/seeds.txt at 017e7037 (listing of the eight pack directories); the Devnet 3 program.json on build-1 (read over ssh). Harnesses: tools/attack/f4-weakday/Cargo.toml and src/main.rs (first 150 lines) from build/attack-pass; tools/attack/f8-uniform/Cargo.toml and src/main.rs (index, program_spec, run_program head, usage) from the attack-regate worktree. Operating files: tools/build-remote.sh, infra/build-server/lib.sh (lines 1 to 80 and 255 to 335), infra/build-server/remote-run.sh (first 120 lines), tools/ci/export-exclude.txt. Siblings: build/adv-accept docs/analysis/cryptanalysis/report-acceptance-rule.md and the head of docs/plans/cryptanalysis/plan-acceptance-rule.md; build/adv-accept-2 report-acceptance-rule-2.md and plan-acceptance-rule-2.md. The three queue definition files 90, 91, 92. Not opened: anything else under docs/, site/, proto-metal/, git log, other branches' sources.
|
|
|
|
## 1. The target, restated from the spec and the code
|
|
|
|
The epoch program is the first candidate the rule accepts, over attempts k = 0, 1, 2, ... of the 32-byte epoch seed b: attempt 0 draws from `seed_words_from_bytes(b)`, attempt k from `seed_words_from_bytes(b || k_le32)` (spec 1.4.6 Attempts; generator.rs `attempt_words`). The draw (spec 1.4.3, `candidate_from_words_class`) takes words 0 to 3 into one SplitMix64 state `lo ^ (hi * 0x9E3779B97F4A7C15)`; words 4 to 7 enter only the register init and the acceptance stream. The rule (accept.rs module table) is (a) cyclic stale-load, (b) injecting write per register, (a') dataflow freshness to a fixpoint over base plus shadow with the shared-operand idiom, (c) 64 units on the seed-keyed closed form (constant bits, lane-constant sites, saturation under 164, bias within 136, distinct sum over 245,760), (c') per-site saturated source under 164, (c'') per-site distinct-index ratio at 2^20 evaluations at or above 0.98 against `N - N^2 / 2W`. The cap is 256 attempts for the class v4 shape (`MAX_ATTEMPTS_V4`), 32 otherwise; a class v4 seed whose 256 candidates all fail takes `last_resort_v4(candidate(b, 256))`: the attempt-256 candidate with every or, mul and mulhi of base and shadow rewritten to xor, accepted with no check. The program id is FNV-1a-64 over `"igneum-program/" || generator_le32 || seed words LE || attempt_le32`, and for generator 4 also `"sub/" || 3_le16`.
|
|
|
|
What the spec at this commit says and the code does not: spec 1.4.6 lists (a), (b), (c) only, a 5.14 percent rejection rate, a 32-attempt consensus fault, and an id without the sub suffix. The code adds (a'), (c'), (c''), a 256 cap, a total draw with a last resort, generator 4 and the sub suffix. That gap is itself a determinism question (Q4 below).
|
|
|
|
## 2. Questions, in order
|
|
|
|
| # | Question | Method | Tool (harness `adv3`, own crate at tools/attack/adv-accept-3, igneum-pow by path) | Known-failed shape (must fire first) | Gate | Box-hours |
|
|
|---|---|---|---|---|---|---|
|
|
| Q1 | Exhaustion (queue 90, owned by adv-accept): P(a seed exhausts 256 attempts), per-part rejection rate, attempt-count distribution | READ adv-accept's row and cite it; add my own per-attempt rejection rate r from the Q2 sweep's attempt log and the independence check (ratio of successive histogram bins against geometric); P(exhaust) = r^256 with its interval | adv-accept's census; `adv3 steer` attempt log | the planted reject-everything loop of Q1b | P(exhaust) bounded with its count | 0 (cited) |
|
|
| Q1b | The last-resort program: what it is, whether weak, constant or predictable, what a chip gains | For 10^4 seeds build `last_resort_v4(candidate(b, 256, class))` through the library and run the REAL rule on it (`accept::check`): pass rate and the failing part; its op mix, static critical path, independent-load count, predictable-address sites; it is a deterministic function of the seed (attempt-256 words), so "predictable" means what everyone can compute; constant across seeds is a count of distinct programs. A mirror of `try_generate_class` with a patched reject-everything rule must stop at attempt 256 and print the same program byte for byte | `adv3 lastresort --seeds 10000`; `adv3 exhaust-mirror --seed s` | the reject-everything mirror hits the cap and prints the last resort | the last resort characterised; reachable only at r^256; no predictable or weak accepted program | 0.4 |
|
|
| Q2 | Steering (queue 91): an attacker who re-rolls the epoch seed searches for a program with a property it wants; how many tries buy each quantile | Chain-shaped seeds `seed_words_from_bytes("igneum-adv-accept-3/steer/<i>")` as 32 LE bytes, era fixed to the Devnet 3 era bytes (the era is a 180-day constant the epoch attacker does not re-roll); per seed the REAL chain draw `generate_era_generator(V4_CLASS, era, V3_ALLOWED, 4)` (the full attempt loop and the full rule). Per accepted program: attempt, id, min per-site distinct ratio at 256 units (the live hot-set proxy adv-accept validated: its five lowest of 4,600 all failed the f8 1.2x gate live, the worst a 1.002x chip gain), the (c) report, static properties: longest dependent chain per iteration through the 64 base instructions (loads counted as depth 1 ALU + 1 memory), independent loads per iteration (loads whose source has no dataflow path from an earlier load of the same iteration), op-mix skew (chi-square of the 48 non-load ops against the weights; the fixed-datapath friendliness), count of load sites whose address depends on init words only (predictable addresses), lossy last write. Quantiles 0.5, 10^-1, 10^-2, 10^-3, 10^-4 and the minimum over the sample; tries per quantile = 1/q by definition, so the table's content is the VALUE at each quantile and the chip gain it implies (chip-model-v3 5.7: the f = 1 chip is lanes over latency; a hot set saves reads; a shorter chain saves nothing while both sides sit at the activate ceiling) | `adv3 steer --from i --count n --threads t --out log` sharded by seed range, one log per shard; `adv3 quantiles` over the logs | `adv3 steer --plant hot` rewrites the two writers before one load's source to `or` on an accepted program: the planted set must sit in the top (lowest-ratio) quantile at once | tries per property quantile tabulated; no property reaches a 1.1x chip gain under 10^5 tries | full rule: about 3 s per seed per core (the 2^20 pass dominates). 3 x 10^4 seeds with the real rule = 25 core-hours = about 1 box-hour at the lock's 88 threads if I get them, 2 to 3 under the load; then as many more as the lock allows |
|
|
| Q2b | Static steering at 10^6 seeds | The static properties of Q2 over 10^6 seeds on the first candidate that passes the STATIC rule (a), (b), (a') (microseconds per seed), as the cheap proxy for the accepted program's static shape; the dynamic rule's conditioning is checked by comparing the 10^6 static distribution with Q2's accepted sample | `adv3 static --from 0 --count 1000000 --threads t` | the plant: a forced zero-depth program (every load source written by instruction 0) must land at the chain-length minimum | the 10^-5 and 10^-6 quantiles of each static property | 0.3 |
|
|
| Q2c | Live hot set of the top steering seeds | The five lowest-ratio seeds of Q2 through the unmodified f8 `warps` census at 10^6 and 2^24 nonces (f8 takes `--program k` of its own label space, so I add a label mode to my copy that reads an epoch-seed hex; the census code itself untouched) | copy of f8-uniform under tools/attack/adv-accept-3/f8-copy | f8's own `--plant const-item` must FLAG and the clean control pass | the chip gain of the worst steered seed found, priced as adv-accept did (reads saved by a 1 MB SRAM copy) | 0.5 |
|
|
| Q3 | The program id (queue 92): collisions and derivation agreement | (i) FNV-1a-64 ids over 10^7 (seed, attempt) pairs (10^6 seeds x attempts 0..9): sort, count equal ids; also equal `program_rng` states (the 64-bit value that fixes the base program: two seeds with equal state draw the same instructions) and equal seed-word octets. (ii) The derivation two implementations could disagree on: recompute the Devnet 3 id 0xfce15bf61030be57 and the kit id 0xa785001687d8688a from the seed words by the derivation program.json STATES (no sub suffix), by spec 1.4.6's text (no sub suffix), and by the code (sub suffix): report which match | `adv3 ids --seeds 1000000 --attempts 10`; `adv3 idcheck` | flipping one bit of the attempt must change the id; a derivation without the suffix must give another id | 0 collisions; the derivation that matches the packs named; disagreement sources listed | 0.1 |
|
|
| Q4 | Determinism traps: where two conforming implementations diverge | (i) A second interpretation of the rule written from the module table and the spec text, not copied: (a) cyclic, (b), (a') to fixpoint, (c), (c'), (c'') with integer arithmetic for the ratio (`1000 * d >= 980 * E` form against the code's f64); verdict by verdict against `accept::check` on 10^4 seeds x every attempt until acceptance. (ii) A spec-1.4.6-only implementation ((a), (b), (c), cap 32, no last resort, id without suffix): the verdict and program disagreement rate against the crate on the same seeds. (iii) The f64 in (c''): the margin between 0.98 x E and the nearest integer for the three windows (E = 2^20 - 2^40 / 2W), so whether f64 or f32 can ever flip a verdict. (iv) Read list: `attempts_class` (the CLI accept command) caps at 32 with no last resort; early returns; panics; allocation of the 2^20 pass | `adv3 verdicts --seeds 10000 --threads t`; `adv3 margins`; the read list | a reordered rule (the (a) check made non-cyclic, single pass) must change at least one verdict on the 10^4 seeds | 0 disagreements between the code and the faithful second interpretation; the spec-only disagreement rate measured; every divergence source listed | 0.5 |
|
|
| Q5 | Anything else | Era steering: the era attacker re-rolls E_n (one draw per 180 days) and gets a stride and window layout; the steering cost table of Q2 applies with the era bytes as the lever; measured only if time allows (`adv3 steer --vary era`). Seed-word collisions through `seed_words_from_bytes` suffix structure (attempt k words share the FNV prefix state with attempt 0): the ids sweep's equal-octet count covers it | as above | as above | noted or measured | 0.3 if run |
|
|
|
|
Total planned: about 4 to 6 box-hours. 8 is the reading line (I say when it is crossed and keep going), 16 the ask line.
|
|
|
|
## 3. Running
|
|
|
|
Build: `tools/build-remote.sh --box 2 -- build --release` from tools/attack/adv-accept-3 (box 1 if 2 has no slot). Runs over 10 minutes start on the box from `tools/attack/adv-accept-3/run-box.sh`: `flock /srv/builds/_adv/locks/sweep.lock -c "nice -n 10 taskset -c 8-95 <bin> <args> > <log> 2>&1"` under nohup setsid, a pid file beside the log, one sweep per box at a time (the coordinator's rule of 20:0x BST). Logs, pid files and results under /srv/builds/_adv-accept-3/ on each box, never under the worktree mirror; finished logs copied into docs/analysis/cryptanalysis/logs/adv-accept-3/ on this branch. Kill by pid file only. No GPU. The 10^6-seed sweeps are sharded by seed range, one log per shard, and every further sweep is an executable queue file /srv/builds/_adv/accept/queue/9N-adv-accept-3-<name>.sh on build-2, claimed by mkdir before it runs. When my queue is empty I claim the next unclaimed sibling file in name order and name the owner in my report.
|
|
|
|
Commit as igneum-labs; push only `git push build adv-accept-3`. GitHub is dark. Copy law: no em dashes, short sentences, numbers in tables. Every time I write is current UK time.
|
|
|
|
## 4. What a result is
|
|
|
|
A BREAK names the method, the counted gain against chip-model-v3 section 5, the exact command and the seed. A BOUND names what was searched, the tool, how far, and the margin. "Nothing found" counts only with its effort in box-hours.
|