10 KiB
Attack plan: the day-key weakness class of the mixer M_r (lane adv-mixer-2)
Internal adversarial pass, not an independent review. Every sentence below that could be quoted carries that label.
| Field | Value |
|---|---|
| Lane | adv-mixer-2, question class: weak parameter draws of M_r (the day-key weakness class) |
| Target commit | 017e703764 (class v4 sub-version 3, object byte 7) |
| Branch | adv-mixer-2, cut from build/master 7a7caa34 at 19:22 UK, 7 October 2026; merged build/master 04c4d9bc at 19:40 UK |
| Byte identity | git diff --quiet 017e7037... HEAD -- igneum-pow printed IDENTICAL at both 7a7caa34 and 04c4d9bc (the whole crate, every file) |
| Attacker | an outsider with the public kit; nothing read under docs/ beyond the spec, chip-model-v3.md sections 1, 2, 5, 6 and the siblings' cryptanalysis files |
| Plan due | 20:25 UK, 7 October 2026. First report rows due 00:00 UK, 8 October |
| Toolchain | rustc 1.99.0 on both boxes; the Mac's PATH rustc is 1.69.0 and is never used (no cargo on the Mac) |
1. The target, restated from spec and code
Spec 01 sections 1.3, 1.8.1, 1.8.4, 1.8.5, 1.12; code igneum-pow/src/seed.rs, memhard.rs, bind.rs, generator.rs.
- The day.
bind::day_index(ts_ms) = ts_ms / 86,400,000, so the day is the Unix day count. Day bytes are"igneum-day/" || le64(day)(19 bytes). The Devnet 3 pack carries day 20733 and the public epoch-0 pack day 20730; the genesis day of the chain is 20729 (3 October 2026,bind.rstest). Nothing from the chain enters the day key: every future day's parameters are computable today. - The day key.
K[0..7] = seed_words_from_bytes(day_bytes): four FNV-1a 64 passes with salted bases, each finished with the murmur mix, split into two 32-bit words. OnlyK[0] | K[1] << 32(salt 0, 64 bits) seeds the mixer stream. K[2..7] enter the item init only. - The draw. One SplitMix64 stream from that 64-bit seed:
ROT[i] = 1 + next() mod 31for i in 0..7, thenMUL[i] = low32(next()) | 1for 0..15, thenRC[i] = low32(next())for 0..15. Forty draws, in that order. - The mixer.
M(s, rk): per words[i] = (s[i] ^ (RC[i] + rk)) * MUL[i], then one ChaCha-shaped double round (four column quarter rounds with ROT[0..3], four diagonal with ROT[4..7]). Class v4 isMX8plus a shadow block:mixer_mult = 8,derive_len = 0, so M is applied 8 times between dependent reads with round keys(r*8 + j + 1) * 0x9E3779B9, 72 applications per item, 9 rounds of 8. - The price. chip-model-v3.md 5.2: 128 ops per application with
RC[i] + rkhoisted, 9,360 ops per item (72 x 128 + 144 init and fold). Every gain below is priced against 9,360 per item, and against 130 per application where the spec's figure is the reference.
A weak day is a draw whose constants let a datapath BUILT FOR THAT DAY do less than 9,360 ops' worth of work per item. A bit-exact verifier never lets any attacker skip an application, so the per-day gain lives only in what a constant costs when it is baked into hardware: a constant rotation is wiring (0 ops on every day), a constant XOR is inverters (0 ops on every day), and a constant multiply is a shift-add chain whose length depends on the day. The only per-day attacker that exists is an FPGA bitstream synthesised for the day (an ASIC cannot be masked per day). The absolute standing of that attacker against a GPU is a separate question and is stated as unknown.
2. The questions, in order
| # | Question | Result shape |
|---|---|---|
| Q1 | Census of structural classes over 2^24 consecutive chain days from genesis (ROT, MUL, RC, cross-field) | counts, fractions, analytic expectation, worst day per class |
| Q2 | Per-day gain under three cost models (below), over the same 2^24, then 2^32 days | gain histogram, fraction over 1.1x, 1.2x, 1.5x, 2x, largest gain, its day |
| Q3 | Exact tail by convolution: per-word cost table over all 2^31 odd constants, 16-fold convolution | P(gain > 1.1x) exact under model A; compared with Q2's census |
| Q4 | The real calendar: every day from genesis for 100 years (20729 to 57253) | worst day with ISO date and gain under each model; the first ten |
| Q5 | Cross-day structure: 64-bit seed collisions, stream shifts (seeds differing by k x 0x9E3779B97F4A7C15, | k |
| Q6 | ROT functional check: avalanche of 1, 2, 4, 8 applications and of the 22 address bits, on the worst ROT days found and on the planted all-equal day | diffusion numbers against a median day; a per-day gain only if a bit-exact shortcut follows, else 0 |
| Q7 | Redraw rule if the fraction with gain over 1.1x exceeds 2^-20 per day | the rule, its own census, its cost to the honest miner |
| Q8 | Anything else seen in Q1 to Q7 | measured or stated unknown |
Cost models, defined here and not borrowed:
| Model | Cost per application | What it prices |
|---|---|---|
| A, LUT shift-add | 64 + sum_i (w32(MUL_i) - 1), with w32 the minimal signed-digit weight of MUL_i MODULO 2^32 (the smaller of the NAF weight of the constant and of its 2^32 complement, digits at position 32 and above discarded) | an FPGA datapath for the day: adders for the quarter rounds (32 add + 32 xor) plus a canonical signed-digit multiplier per word; constant rotations and XORs free |
| B, certified shift-add | 64 + sum_i scm_i, with scm_i the smallest adder count for which a chain is CERTIFIED (exact sets for 1, 2, 3 adders by bitmap; a 4-adder certificate by decomposition over those sets; otherwise w32 - 1) | the same datapath with the multiplier optimised, as a synthesiser would; an upper bound on the attacker's cost, so a lower bound on his gain |
| C, DSP | 16 / (16 - k), k the words whose constant has w32 <= 3 and leaves its DSP block for LUTs | an FPGA whose multipliers sit in DSP blocks, value-independent, with the cheap ones moved out |
Gain of a day under a model = the median day's cost over the day's cost. A day's MUL = 1 words count 0 adders under A and B (the planted shape).
3. Method and tool per question
Harness: tools/attack/adv-mixer-2/ (crate attack-adv-mixer-2, binary adv-mixer-2, igneum-pow by path,
nothing re-implemented: every key and draw comes from bind::day_bytes, seed_words_from_bytes,
MixParams::with_shape(key, Shape::for_class(&V4_CLASS)), and memhard::mixer for Q6). Commands:
| Command | Question | Known-failed shape (must fire) | Box-hours (estimate) |
|---|---|---|---|
census --from 20729 --count 2^24 --threads N |
Q1, Q2 (models A, C; B on the tail) | plant alleq, plant mul1, plant mul1all, plant rcrk0: the day-20729 draw with the field replaced, run through the same classifier, must land in its class and show its gain (mul1all: cost 64 under A, gain about 3.4x) |
0.1 |
census --from 20729 --count 2^32 --threads N |
Q2 extended | same plants | 1.5 |
exact --threads N |
Q3 | the table must give P(MUL = 1) = 2^-31 and the convolution's mean must match the census mean within 0.01 | 0.3 |
scm-build then scm-refine --days <file> |
Q2 model B on the calendar, on the census tail (lowest 2^14 days under A) and on a 2^16-day random sample | scm-refine on MUL = 3, 5, 7, 9 must certify 1 adder; on 0x9E3779B9 it must certify at most w32 - 1 |
1.5 |
calendar --from 20729 --years 100 |
Q4, Q5 (collisions, shifts) | a planted pair of days with seeds differing by one gamma must be found by the shift scan | 0.1 |
avalanche --index <day> [--plant alleq] |
Q6 | the planted all-equal ROT day runs beside a median day; a --plant rot1 (all ROT = 1) must show weaker single-application diffusion than the median |
0.5 |
redraw-census |
Q7 | the proposed rule applied to 2^24 days: fraction redrawn, fraction over 1.1x after the rule, which must be 0 | 0.2 |
Runs over 10 minutes go through tools/attack/adv-mixer-2/run-box.sh on the box: nohup nice -n 10 in a setsid
process group, pid file beside the log under /srv/builds/igneum-wt-adv-mixer-2/adv/, a 5 s poll of
/srv/builds/_locks (build-k, quiet, core-*) that SIGSTOPs the group while any is held and SIGCONTs when clear,
the pattern of infra/build-server/capacity/run.sh. Every sweep is a queue file
/srv/builds/_adv/mixer/queue/NN-adv-mixer-2-<name>.sh on build-2, claimed by mkdir .../claims/<file> before it
runs. CPU-bound sweeps run on build-1 (load 9 to 16 at 19:40 UK against build-2's 55 to 65); the binary is
built on both boxes so a queue file runs on either. No GPU is used; no row needs one.
Total estimate: about 4 box-hours. 8 is the reading line, 16 the ask line.
4. What is already known from the siblings (read, not relied on)
adv-mixer's report (branch build/adv-mixer) ran the f4 census: M1 cost mean 231, 3.26e-4 of days over 1.1x on its generous metric, 0 days with M2 gain, ROT all equal never seen. This lane recomputes with its own cost models (modular weight, certified chains), adds the exact tail, the real calendar with dates, the cross-day structure and a redraw rule. Where the two censuses agree the agreement is stated; where they differ the difference is explained.
5. Files opened (the outsider rule)
| File | Why |
|---|---|
| igneum-pow/Cargo.toml, Cargo.lock (listed), src/seed.rs, src/memhard.rs, src/bind.rs, src/generator.rs (LoadClass, MX8, V3_CLASS, V4_CLASS), tests/mixer.rs (header, contract) | the target |
docs/spec/01-lottery-hash.md at 017e7037: 1.2, 1.3, 1.8, 1.12 |
the target |
| docs/analysis/chip-model-v3.md sections 1, 2, 5, 6 | the price |
| proto-cuda/packs-ca3-v4/ (listing), v4-devnet-epoch0/program.json (day fields) | the public kit |
| /srv/artefacts/packs/v4-devnet3-epoch0.zip on build-1: sha256 e025750f... verified | the public kit |
| tools/attack/f4-weakday/{Cargo.toml, src/main.rs} from build/attack-pass | prior harness, read only |
| tools/attack/f8-uniform/{Cargo.toml, src/main.rs} (header) | prior harness, read only |
| tools/build-remote.sh, infra/build-server/lib.sh, remote-run.sh, capacity/lib.sh, capacity/run.sh, capacity/ (listing) | operating files |
| build/adv-mixer: docs/plans/cryptanalysis/plan-mixer.md, docs/analysis/cryptanalysis/report-mixer.md; build/adv-accept and build/adv-cache: file listings only | siblings |