igneum/docs/plans/cryptanalysis/plan-mixer-2.md
2026-10-07 20:32:27 +00:00

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 017e70376489251e18564c0abce7e466e606c8b3 (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.

  1. 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.rs test). Nothing from the chain enters the day key: every future day's parameters are computable today.
  2. 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. Only K[0] | K[1] << 32 (salt 0, 64 bits) seeds the mixer stream. K[2..7] enter the item init only.
  3. The draw. One SplitMix64 stream from that 64-bit seed: ROT[i] = 1 + next() mod 31 for i in 0..7, then MUL[i] = low32(next()) | 1 for 0..15, then RC[i] = low32(next()) for 0..15. Forty draws, in that order.
  4. The mixer. M(s, rk): per word s[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 is MX8 plus 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.
  5. The price. chip-model-v3.md 5.2: 128 ops per application with RC[i] + rk hoisted, 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