309 lines
26 KiB
Markdown
309 lines
26 KiB
Markdown
# F4. The weak-day census: 2^24 day keys through `MixParams::with_shape`
|
|
|
|
Attack pass row F4 (`docs/plans/cryptanalysis.md` section 4.2; the gate is section 1.4 (3) and `funding.md` B5
|
|
rank 3; the threat is `funding.md` B2 rank 3). Run 7 October 2026, 09:10 to 09:55 UK, on igneum-build-1 by the
|
|
attack-f4 agent (the verifier timing row of 6.6 queued behind other lanes' holds). Every number below cites its log.
|
|
|
|
## Verdict
|
|
|
|
**PASS on the gate read against M2, the DSP-bound per-day datapath (0 days over 1.1x in 2^28), and on every named
|
|
weak class; the generous bound M1 (every multiply in LUT adders) exceeds the gate at 3.26e-4 of days as the tail of a
|
|
sum, not a class, and is routed to main as a bound finding with a rejection-and-redraw rule for the next class.
|
|
Class v4 is not changed.**
|
|
|
|
Which metric the 1.1x gate reads against, and why: M2. The gate (plan 1.4 (3)) asks for the fraction of days in a
|
|
weak class, and M1's excess has no class behind it (section 6.2: the exact 16-fold convolution of one random NAF
|
|
weight predicts the census to 0.6 percent). A per-day FPGA attacker who builds the 16 multiplies in LUT shift-add
|
|
trees is building the slower design: those trees are 72 percent of M1's cost (167 of 231 adders), and DSP blocks
|
|
take that cost off the fabric, so the design that wins is DSP-bound, where the day's constants move nothing unless a
|
|
word has NAF weight at most 3, which happens on no day in 2^28 for two words. M1 is still reported in full because
|
|
the brief asks for the generous bound, and because a two-line rule closes it for nothing.
|
|
|
|
| Metric | Days over 1.1x in 2^24 | Fraction | Days over 1.1x in 2^28 | Fraction | Gate 2^-20 = 9.54e-7 | Log |
|
|
|---|---|---|---|---|---|---|
|
|
| M1: per-day LUT datapath, adders per mixer application, against the census median | 5,476 | 3.264e-4 | 87,426 | 3.257e-4 | OVER, by 342x | `census-2p24.md`, `census-2p28.md` gate table |
|
|
| M1 exact expectation (16-fold convolution of the NAF-weight table over all 2^31 odd constants) | 5,441 | 3.243e-4 | | | the census is the tail of a smooth sum, not a class | `expect-231.log` last line |
|
|
| M2: DSP-bound datapath, 16/(16 - k), k = words of NAF weight at most 3 | 0 | 0 | 0 | 0 | under | `census-2p24.md`, `census-2p28.md` M2 table |
|
|
| ROT value and RC value on a per-day datapath | 0 | 0 | 0 | 0 | under (exact 0 ops moved, section 3) | section 3 |
|
|
|
|
The gate as written fails under M1 only. What M1 finds is not a weak class: the per-day cost of the 16 constant
|
|
multipliers is a sum of 16 NAF weights (mean 231.1 adder-equivalents per application, sd 6.19), and 1 day in 3,070
|
|
sits 3.4 sigma below the median, where a bitstream synthesised for that day pays 10 to 19 percent fewer adders. The
|
|
worst day in 2^28 reads 1.19x (day 27,952,752, cost 194). The exact expectation predicts the census to 0.6 percent.
|
|
Section 7 prices the consequence (0.004 percent more hashes a year for an all-LUT FPGA that re-synthesises every
|
|
day, nothing for a chip or a GPU) and section 8 gives the rejection-and-redraw rule that closes it.
|
|
|
|
## 1. Target
|
|
|
|
| Item | Value |
|
|
|---|---|
|
|
| Commit | 924288d1 (the brief); the worktree HEAD moved to 11b375a0 during the pass (F5 and F6 records); `git diff 924288d1 11b375a0 --stat -- igneum-pow/src` is empty, so the target code is the same |
|
|
| Code | `igneum-pow/src/memhard.rs` `MixParams::with_shape` (lines 189 to 215): `SplitMix64::new(key[0] as u64 \| (key[1] as u64) << 32)`, then `ROT[0..7] = 1 + below(31)`, `MUL[0..15] = next() as u32 \| 1`, `RC[0..15] = next() as u32`; no rejection rule |
|
|
| Day key | `bind::day_bytes(d) = "igneum-day/" \|\| d_le64`, `key = seed_words_from_bytes(day_bytes)` (the interim day rule, `bind.rs` lines 30 to 68); the genesis day index is 20,729 (`bind.rs` test `day_bytes_layout`) |
|
|
| Shape | `Shape::for_class(&V4_CLASS)`: mixer x8, cache 2^26 words, no derivation program (asserted by the harness) |
|
|
| Mixer | `memhard::mixer`: per word `(s ^ (RC + rk)) * MUL`, then one ChaCha double round with `ROT[0..3]` on the columns and `ROT[4..7]` on the diagonals; 72 applications per item under x8 |
|
|
| Census set | 2^24 consecutive chain days from 20,729 (the gate run), and 2^28 (the extended run); the first 36,525 of them are the chain's public calendar for the next 100 years under the interim rule |
|
|
|
|
The 64-bit seeding fact (F7 covers the spec's intent): the 40 draws depend on `key[0] | key[1] << 32` alone, so the
|
|
stream can produce at most 2^64 distinct parameter sets whatever the key's other 192 bits hold. Over the 2^24 census
|
|
days the 64-bit seeds were all distinct (0 collisions, expected 7.6e-6; `census-2p24.md` "64-bit seeding" line).
|
|
`below(31)` is `next() % 31` without rejection: the bias per rotation value is 2^-64 and is ignored.
|
|
|
|
## 2. Known-failed shape
|
|
|
|
A day key whose drawn `ROT`, `MUL` or `RC` gives a fixed datapath a gain over 1.1x: all-equal `ROT` (31^-7 per day,
|
|
MEMHARD.md section 3 item 3, untested until now), `MUL = 1` (2^-31 per word), pairs summing to 32, small rotation
|
|
amounts, low-weight multipliers, `RC + rk = 0`.
|
|
|
|
## 3. The gain metrics (exact, structural)
|
|
|
|
The verifier and every GPU run the same instructions on every day (`rotate_left` by a register amount, `wrapping_mul`,
|
|
no branch on a drawn value), so wall time cannot move with the draw; the only attacker a weak day helps is one who
|
|
builds the day's constants into logic. That is an FPGA bitstream synthesised per day (hours of compile against a
|
|
public calendar), never a taped-out chip. Costs are in 32-bit adder-equivalents per mixer application:
|
|
|
|
| Element of one application | Generic datapath | Per-day datapath |
|
|
|---|---|---|
|
|
| 16 x `s ^ (RC + rk)` | 16 | 0 (constant XOR: inverters, absorbed into the next LUT) |
|
|
| 16 x `* MUL` | 16 multipliers (value-independent) | M1: `NAF(MUL_i) - 1` adders each (canonical signed-digit shift-add); M2: a DSP block each, value-independent, except a word of NAF weight at most 3 moves to 2 LUT adders and frees its DSP |
|
|
| 8 quarter rounds: 32 adds, 32 XORs | 64 | 64 |
|
|
| 32 rotations | 32 barrel shifters | 0 (wiring) |
|
|
|
|
* **M1** `cost = 64 + sum_i (NAF(MUL_i) - 1)`; gain of a day = census median cost / the day's cost. The generous
|
|
bound: optimal single-constant multiplication is below NAF for every constant and the ratio between days is what
|
|
is measured.
|
|
* **M2** gain = `16 / (16 - k)` on a DSP-bound design, k the words of NAF weight at most 3.
|
|
* **ROT** and **RC** hand a per-day datapath exactly 0 ops at any value (wiring and inverters); on a generic
|
|
datapath a rotation costs the same at every amount and `RC + rk = 0` removes one XOR of 10,368 ops per item
|
|
(1.0001x). They are censused as structure, and the worst members are measured for diffusion (section 6), the
|
|
only other thing a rotation draw could move; a bit-exact verifier never lets a chip skip an application, so
|
|
diffusion is reported and is not a gain.
|
|
|
|
## 4. Harness
|
|
|
|
| Item | Path or line |
|
|
|---|---|
|
|
| Crate | `tools/attack/f4-weakday/` (`Cargo.toml` with `igneum-pow = { path = "../../../igneum-pow" }` and an empty `[workspace]`; `src/main.rs`); `igneum-pow` untouched |
|
|
| Build | `cd tools/attack/f4-weakday && IGNEUM_AGENT=attack-f4 bash /Users/joshm/Projects/igneum/tools/build-remote.sh --artefacts "target/release/attack-f4" --out <scratch> -- build --release`; box binary `/srv/builds/igneum-wt-attack/tools/attack/f4-weakday/target/release/attack-f4`: sha256 `fda006d7...835f52` ran every census and firing (`build-1.log`); the rebuild `5eb081cf...7f0355` (`build-2.log`) removes one unused import and nothing else |
|
|
| Unit tests | `build-remote.sh --no-fetch -- test --release` on the box (`test-1.log`): 2 passed, 0 failed (`naf_weights`: 0, 1, 3, 7, 2^32 - 1, the alternating maximum 17, and the planted weight-3 constant; `genesis_day_draw_matches_memhard_md`: the string day `2026-10-03` draws `ROT 20 20 19 4 26 3 3 27`, MEMHARD.md section 1.1, through the same `with_shape` path the census uses) |
|
|
| Census (gate) | `flock -s /srv/builds/_locks/measure -c 'nice -n 10 taskset -c 16-21,64-69 attack-f4 census --from 20729 --count 16777216 --threads 12 --dedupe --out census-2p24.md'`; 4.2 s |
|
|
| Census (extended) | the same with `--count 268435456 --out census-2p28.md`; 68.6 s |
|
|
| Expectation tables | `attack-f4 expect --threads 12 --median 231` (every odd 32-bit constant: NAF weight and popcount, then the 16-fold convolution); 14.9 s |
|
|
| One day | `attack-f4 day --index <d> --median 231` |
|
|
| Firings | `attack-f4 plant alleq\|mul1\|mul1all\|mulnaf\|rc0\|rcrk0 --median 231` (the day 20,729 draw with one field forced through the crate's own hook) |
|
|
| Diffusion | `attack-f4 avalanche --index <d> --states 2048 [--plant-alleq r]` |
|
|
| Timing (exclusive hold) | `timing.sh` on the box under nohup: `flock -x -w 7200 /srv/builds/_locks/measure -c 'nice -n 19 taskset -c 16,64 igneum-pow bench --seed x --epoch-hex edc4fa84...fb07 --day-hex <day bytes> --program-class v4 --warps 100'` for day 20,729 and the worst day, A B A B. Process note: withdrawing the first attempt, one ad hoc ssh line used `pkill -f "<literal>"`, the banned shape, and killed its own shell (self-match); the relaunch used the bracket form. Nothing else was touched |
|
|
| Calendar | `attack-f4 census --from 20729 --count 36525 --threads 12 --out census-100y.md` (the chain's first 100 years) |
|
|
| Box logs | `/srv/builds/igneum-wt-attack/attack-f4/{run2.log, census-2p24.md, census-2p28.md, census-100y.md, expect-231.log, firings.log, avalanche.log, timing.log}` |
|
|
| Mac copies | `/private/tmp/claude-501/-Users-joshm/cd75457f-4858-4f86-9634-7481ee056b7b/scratchpad/attack-f4/box/` (the box directory was deleted once from under the pass at about 09:14 UK by another agent's worktree sync; everything was re-run and copied to the Mac the moment it ended; the re-run reproduced the first run line for line) |
|
|
|
|
## 5. The two firings (`firings.log`)
|
|
|
|
| Case | Classifier | Gain | Result |
|
|
|---|---|---|---|
|
|
| Known-pass: day 20,729 (the genesis day), `ROT [6, 25, 5, 25, 29, 11, 9, 21]`, NAF sum 178 | no weak class (only "pair sums to 32", 12 and 60 percent of all days) | M1 0.978x, M2 1.000x | passes, as it must |
|
|
| Known-fail: `plant mul1all` (all 16 `MUL = 1`) | `MUL any = 1` FIRED | M1 3.453x, M2 unbounded | FIRED over 1.1x |
|
|
| Known-fail: `plant mulnaf` (four words at NAF weight 3) | `MUL any NAF weight <= 3` FIRED | M1 1.145x, M2 1.333x | FIRED over 1.1x |
|
|
| `plant mul1` (one word `MUL = 1`) | `MUL any = 1` FIRED | M1 1.023x, M2 1.067x | flagged, under the gate: one word of 16 |
|
|
| `plant alleq` (`ROT` all 7) | `ROT all equal` FIRED | M1 0.978x (0 ops moved) | flagged; diffusion in section 6 |
|
|
| `plant rc0`, `plant rcrk0` | `RC any = 0`, `RC + rk = 0` FIRED | M1 0.978x (0 ops moved) | flagged |
|
|
|
|
## 6. Numbers
|
|
|
|
### 6.1 Classes over 2^24 days (`census-2p24.md`), with the 2^28 count (`census-2p28.md`)
|
|
|
|
Expected per day is analytic (independent draws); the NAF rows come from the exact table of `expect-231.log`.
|
|
|
|
| Class | Count 2^24 | Fraction | Expected per day | Expected count 2^24 | Count 2^28 | Worst member (day, M1 cost, M1 gain, M2 gain) |
|
|
|---|---|---|---|---|---|---|
|
|
| ROT all equal | 0 | 0 | 3.64e-11 (31^-7) | 0.001 | 0 | none |
|
|
| ROT distinct <= 3 | 534 | 3.18e-5 | 3.07e-5 | 515 | 8,229 | 2^28: day 49,986,853, 206, 1.121x, 1.000x |
|
|
| ROT distinct <= 4 | 26,010 | 1.55e-3 | 1.54e-3 | 25,783 | 412,698 | 2^28: day 208,103,482, 197, 1.173x, 1.000x |
|
|
| ROT max multiplicity >= 4 | 35,631 | 2.12e-3 | 2.35e-3 (first order) | 39,421 | 568,423 | 2^28: day 115,569,197, 200, 1.155x, 1.000x |
|
|
| ROT same-word pair sums to 32 | 2,062,481 | 0.1229 | 0.1229 | 2,062,288 | 32,997,484 | 2^28: day 97,502,921, 196, 1.179x, 1.000x |
|
|
| ROT any pair sums to 32 | 10,022,037 | 0.5974 | 0.6007 (approx., pairs not independent) | 10,078,561 | 160,353,891 | 2^28: day 27,952,752, 194, 1.191x, 1.000x |
|
|
| ROT all 8 in {1, 2, 30, 31} | 2 | 1.19e-7 | 7.68e-8 | 1.29 | 19 | day 14,330,190, 217, 1.064x, 1.000x |
|
|
| ROT >= 6 in {1, 2, 30, 31} | 1,761 | 1.05e-4 | 1.02e-4 | 1,716 | 27,651 | 2^28: day 181,528,254, 204, 1.132x, 1.000x |
|
|
| ROT >= 4 in {8, 16, 24} | 74,541 | 4.44e-3 | 4.46e-3 | 74,756 | 1,196,376 | day 5,517,722, 198, 1.167x, 1.000x |
|
|
| MUL any = 1 | 0 | 0 | 7.45e-9 | 0.125 | 4 | 2^28: day 196,441,106, 221, 1.045x, 1.067x |
|
|
| MUL any = 2^32 - 1 | 0 | 0 | 7.45e-9 | 0.125 | 1 | 2^28: day 39,988,645, 215, 1.074x, 1.067x |
|
|
| MUL any popcount <= 2 | 0 | 0 | 2.38e-7 | 4.0 | 57 | 2^28: day 218,029,468, 209, 1.105x, 1.067x |
|
|
| MUL any popcount <= 4 | 612 | 3.65e-5 | 3.72e-5 | 624 | 10,132 | day 7,275,755, 200, 1.155x, 1.000x |
|
|
| MUL any NAF weight <= 2 | 4 | 2.38e-7 | 4.62e-7 | 7.75 | 125 | 2^28: day 63,704,833, 205, 1.127x, 1.067x |
|
|
| MUL any NAF weight <= 3 | 216 | 1.29e-5 | 1.30e-5 | 218 | 3,515 | 2^28: day 247,161,685, 200, 1.155x, 1.067x |
|
|
| MUL any NAF weight <= 4 | 3,637 | 2.17e-4 | 2.20e-4 | 3,683 | 58,667 | 2^28: day 81,133,010, 198, 1.167x, 1.000x |
|
|
| MUL any < 256 | 22 | 1.31e-6 | 9.54e-7 | 16 | 262 | 2^28: day 241,187,962, 203, 1.138x, 1.067x |
|
|
| MUL two equal | 0 | 0 | 5.59e-8 | 0.94 | 18 | 2^28: day 223,900,428, 226, 1.022x, 1.000x |
|
|
| MUL M2 k >= 2 (gain >= 1.143x) | 0 | 0 | 7.9e-11 (C(16,2) x (8.12e-7)^2, approx.) | 0.0013 | 0 | none |
|
|
| RC any = 0 | 0 | 0 | 3.73e-9 | 0.062 | 1 | 2^28: day 109,542,046, 243, 0.951x, 1.000x |
|
|
| RC any popcount <= 4 or >= 28 | 5,186 | 3.09e-4 | 3.09e-4 | 5,180 | 82,804 | 2^28: day 53,303,116, 206, 1.121x, 1.000x |
|
|
| RC + rk = 0 for any of the 72 keys | 10 | 5.96e-7 | 2.68e-7 | 4.5 | 87 | day 3,194,363, 218, 1.060x, 1.000x |
|
|
| RC two equal | 1 | 5.96e-8 | 2.79e-8 | 0.47 | 8 | 2^28: day 182,857,055, 222, 1.040x, 1.000x |
|
|
|
|
Every class sits at its expectation (the largest deviation, "ROT max multiplicity >= 4", is against a first-order
|
|
bound). The worst member of every class owes its gain to its MUL draw (M1 is a MUL-only quantity); the class itself
|
|
moves nothing. No day in 2^28 has two words of NAF weight at most 3, so M2 never exceeds 1.067x.
|
|
|
|
### 6.2 The M1 tail: census against the exact expectation (`census-2p24.md`, `expect-231.log`)
|
|
|
|
| M1 cost per application | Gain vs median 231 | Days in 2^24 | Cumulative fraction, census | Cumulative fraction, exact |
|
|
|---|---|---|---|---|
|
|
| 197 (the 2^24 minimum, day 4,819,563) | 1.173x | 1 | 5.96e-8 | 8.18e-8 |
|
|
| 200 | 1.155x | 10 | 8.34e-7 | 8.62e-7 |
|
|
| 205 | 1.127x | 250 | 2.94e-5 | 2.87e-5 |
|
|
| 208 | 1.111x | 1,382 | 1.84e-4 | 1.83e-4 |
|
|
| 209 | 1.105x | 2,387 | 3.26e-4 | 3.24e-4 |
|
|
| 210 | 1.100x | 3,887 | 5.58e-4 | 5.64e-4 |
|
|
| 231 (median) | 1.000x | 1,079,174 | 0.522 | 0.522 |
|
|
|
|
Mean cost 231.113 (exact 231.111), sd 6.190 (exact 6.190). The 2^28 minimum is 194 (1.191x, day 27,952,752). A
|
|
single NAF weight has mean 11.44 and sd 1.55 over the 2^31 odd constants (`expect-231.log`).
|
|
|
|
### 6.3 ROT structure (`census-2p24.md` histograms)
|
|
|
|
| Distinct rotation amounts a chip must wire | Days in 2^24 | Fraction | Expected S(8,d) 31_d / 31^8 |
|
|
|---|---|---|---|
|
|
| 1 | 0 | 0 | 3.63e-11 |
|
|
| 2 | 4 | 2.4e-7 | 1.4e-7 |
|
|
| 3 | 530 | 3.16e-5 | 3.05e-5 |
|
|
| 4 | 25,476 | 1.52e-3 | 1.51e-3 |
|
|
| 5 | 421,405 | 0.0251 | 0.0251 |
|
|
| 6 | 2,773,843 | 0.1653 | 0.1653 |
|
|
| 7 | 7,302,781 | 0.4353 | 0.4351 |
|
|
| 8 | 6,253,177 | 0.3727 | 0.3729 |
|
|
|
|
Small amounts {1, 2, 30, 31} and byte-aligned amounts {8, 16, 24} follow Binomial(8, 4/31) and Binomial(8, 3/31) to
|
|
within 3 percent in every bin.
|
|
|
|
### 6.4 Diffusion of the worst members (`avalanche.log`: 2,048 states x 512 input bits, mean and minimum per-output-bit flip probability)
|
|
|
|
| Day | Why | ROT | After 1 application, mean / min | After 2, mean / min |
|
|
|---|---|---|---|---|
|
|
| 20,729 | genesis, same-word pair 11 + 21 = 32 | 6 25 5 25 29 11 9 21 | 0.461 / 0.383 | 0.500 / 0.498 |
|
|
| 4,819,563 | M1 worst in 2^24 | 26 18 8 30 24 24 6 9 | 0.467 / 0.426 | 0.500 / 0.499 |
|
|
| 27,952,752 | M1 worst in 2^28 | 11 26 11 7 6 20 20 3 | 0.460 / 0.392 | 0.500 / 0.498 |
|
|
| 11,482,247 | 3 distinct amounts, multiplicity 5 | 12 19 19 4 19 12 19 19 | 0.453 / 0.374 | 0.500 / 0.499 |
|
|
| 14,330,190 | all 8 amounts in {1, 2, 30, 31} | 1 1 2 31 1 31 1 31 | 0.331 / 0.196 | 0.4995 / 0.497 |
|
|
| 332,924 | NAF weight 3 word, three amounts of 1 | 1 23 1 1 12 11 16 18 | 0.458 / 0.370 | 0.500 / 0.498 |
|
|
| 196,441,106 | `MUL = 1` word (2^28) | 30 24 23 5 11 28 12 9 | 0.461 / 0.364 | 0.500 / 0.499 |
|
|
| 109,542,046 | `RC = 0` word (2^28) | 28 5 4 31 25 28 12 4 | 0.459 / 0.348 | 0.500 / 0.499 |
|
|
| planted all 1 | the worst all-equal draw | 1 x 8 | 0.345 / 0.216 | 0.500 / 0.499 |
|
|
| planted all 16 | half-word swaps | 16 x 8 | 0.387 / 0.312 | 0.500 / 0.499 |
|
|
| planted all 7 | | 7 x 8 | 0.464 / 0.400 | 0.500 / 0.498 |
|
|
|
|
The slowest draw that can exist (all rotations by 1, probability 31^-8 per day) reaches full avalanche after 2 of the
|
|
8 applications between cache reads; the worst real day in 2^28 (all amounts in {1, 2, 30, 31}) the same. No draw
|
|
gives an attacker a shorter dependency between reads than the round margin F2 measures.
|
|
|
|
### 6.5 The chain's first 100 years (`census-100y.md`: days 20,729 to 57,253 under the interim day rule)
|
|
|
|
| Item | Value |
|
|
|---|---|
|
|
| Days over 1.1x under M1 | 6 of 36,525 (1.64e-4; the 2^24 rate predicts 12) |
|
|
| First such day | 22,633 (genesis + 1,904 days, about 5.2 years in), cost 208, 1.111x |
|
|
| Worst day | 29,337 (genesis + 8,608 days, about 23.6 years in), cost 206, 1.121x |
|
|
| Days at exactly 1.100x (cost 210) | 6 more: 25,605; 28,102; 31,573; 33,710; 42,573; 54,884 |
|
|
| M2 k >= 2 | 0 |
|
|
| Genesis day 20,729 | cost 226, 0.978x; the next four devnet days (20,730 to 20,733) read 0.987x, 1.036x, 0.947x, 0.979x |
|
|
| Rotation structure | 1 day with 2 distinct amounts (57,146, genesis + 36,417, cost 225, 1.027x), 46 with 4, none with 3 or fewer otherwise; no day with a `MUL` of NAF weight under 4 |
|
|
|
|
### 6.6 Verifier time (exclusive hold, `timing.log`)
|
|
|
|
A confirmation row only: the verifier's code path is value-independent, so the exact metric is the op count above
|
|
and a wall-time difference between days can only be noise. Queued on the box at 09:47 UK (`timing.sh`, nohup, an
|
|
exclusive `flock -x -w 7200` behind the shared holds of F1, F2, F8, F9, F10 and F7 and the queued exclusive hold of
|
|
F6; the first attempt, queued 09:14 UK, was attached to a Mac ssh session and was withdrawn in favour of the nohup
|
|
job). Cores 16 and 64, nice 19, `--warps 100`, day 20,729 against day 4,819,563 (the 2^24 M1 worst), A B A B.
|
|
|
|
| Day | Cold warp 0 (ms) | Average per warp, 100 warps (ms) |
|
|
|---|---|---|
|
|
| 20,729 (genesis) | pending (`timing.log`) | pending |
|
|
| 4,819,563 (M1 worst, 1.173x) | pending (`timing.log`) | pending |
|
|
|
|
The verdict does not rest on this row.
|
|
|
|
### 6.7 The worst days in full (`worst-days.log`, `export-29337.log`)
|
|
|
|
The worst day in adders per application against the census median, in each set. M1 is the sum of the 16 NAF weights
|
|
less 16 plus 64. Every one is an ordinary draw whose 16 weights happen to sum low; none has a word under NAF weight 7.
|
|
|
|
| Set | Chain day | Years after genesis | ROT | MUL words (hex) | NAF weights | M1 cost | Gain vs median 231 | M2 |
|
|
|---|---|---|---|---|---|---|---|---|
|
|
| The public calendar, first 36,525 days (what an auditor runs) | 29,337 | 23.6 | 24 12 18 11 14 26 29 21 | 3fe4d03b 227c2043 06011627 40c10137 00234d99 063071d9 91e5abb7 035240b1 f40bfe47 809251b9 1ce999ef 940b381d da13a021 f75f8ba7 3f59bca7 01310e05 | 9 8 9 8 10 10 12 10 9 10 12 11 10 11 11 8 (sum 158) | 206 | 1.121x | 1.000x |
|
|
| 2^24 (the gate census) | 4,819,563 | 13,139 | 26 18 8 30 24 24 6 9 | a0653c83 a09de525 810085fb 6a00eba1 bf8205ff bba82079 f27da4c3 2cb80223 6001efcf 1c2814f7 ae9d09d7 ffedd7b7 943dde01 39ff47e1 0513a83f c028eef9 | 11 11 7 10 7 10 12 10 7 9 13 8 8 8 9 9 (sum 149) | 197 | 1.173x | 1.000x |
|
|
| 2^28 (extended) | 27,952,752 | 76,481 | 11 26 11 7 6 20 20 3 | f15eb273 227a08f1 20f822e1 6d477779 8d9b3aff 03040503 27fff521 bfd9ce7d 7708000d 5d60ba11 2d40005b f07e10d7 1deefdb1 4881e821 01e1fc71 3ee7c39b | 13 9 8 11 11 7 7 11 7 11 9 9 8 8 7 10 (sum 146) | 194 | 1.191x | 1.000x |
|
|
|
|
Reproduction, through the harness: `attack-f4 day --index 29337 --median 231` (and 4819563, 27952752). Through
|
|
`igneum-pow` itself, with the day bytes `"igneum-day/" || d_le64` as hex (day 29,337 = 0x7299):
|
|
`igneum-pow export --seed x --epoch-hex edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 --day-hex 69676e65756d2d6461792f9972000000000000 --program-class v4 --out <dir>`
|
|
writes the day's constants into the pack's `memhard.h` as `IGNEUM_MIX_ROT_INIT` and `IGNEUM_MIX_MUL_INIT`; run on the
|
|
box at 09:52 UK (`export-29337.log`, OVERALL PASS, cache FNV-1a 64 `1979492fb76b52ce`), the pack's 8 rotations and
|
|
16 multipliers equal the harness's word for word. The day-hex strings of the other two days are in section 6.2's
|
|
source list (`census-2p24.md` and `census-2p28.md`, "The 16 lowest-cost days"): `...2f6b8a490000000000` and
|
|
`...2f7086aa0100000000`.
|
|
|
|
## 7. Gate line and consequences
|
|
|
|
Gate (plan 1.4 (3)): the fraction of days with any gain over 1.1x under 2^-20.
|
|
|
|
| Model | Fraction over 1.1x | Gate | What the number means per tier |
|
|
|---|---|---|---|
|
|
| M1 (per-day LUT bitstream) | 3.26e-4 (1 day in 3,070; 2^24 and 2^28 agree; exact expectation 3.24e-4) | FAIL by 342x | An FPGA farm that re-synthesises its bitstream every day gains 10 to 19 percent on those days: 3.26e-4 x about 0.12 = 4e-5 of a year's hashes, 0.004 percent. The FPGA lane is already behind every GPU tier on reads per watt (F5: 10 to 20 M reads/s/W against the gate's 27 M), so no home miner (8, 12, 16, 24 or 32 GB), rig or pool on any vendor or OS sees a competitor appear, and no day's difficulty moves by a measurable amount |
|
|
| M2 (DSP-bound FPGA) | 0 in 2^28 | PASS | nothing moves for any tier |
|
|
| Chip (programmable constants, the chip-model-v3 recompute chip) | 0 by construction | PASS | nothing moves; a taped-out chip cannot specialise per day |
|
|
| GPU and the CPU verifier | 0 by construction | PASS | every tier pays the same ops on every day |
|
|
|
|
What the worst day buys, priced for the per-day LUT datapath (the M1 attacker) on the worst calendar day, 29,337:
|
|
|
|
| Item | Value | Source |
|
|
|---|---|---|
|
|
| Fewer adders per mixer application that day | 231 to 206, 10.8 percent fewer | section 6.7 |
|
|
| Item derivations per unit of fabric that day | 1.121x (M1 gain) | section 6.7 |
|
|
| Hash rate of a recompute FPGA (items derived per hash, the `chip-model-v3` ops-per-hash attacker) that day | up to 12.1 percent above its ordinary day, an upper bound: the 128 dependent cache reads per hash and the shadow block are untouched by the draw, so the whole-hash gain is below the mixer's | `chip-model-v3.md` section 1 (ops per hash = 128 x 72 x 130); section 3 |
|
|
| Hash rate of the stored-dataset (f = 1) FPGA or chip that day | 0 (it derives no items per hash; the mixer is paid once in the daily build) | `funding.md` B2 rank 2 |
|
|
| Days a century at or over 1.1x | 12 (6 over, 6 at exactly 1.100x) | section 6.5 |
|
|
| Share of a century's hashes the M1 attacker gains | 12 / 36,525 x about 0.11 = 3.6e-5, 0.004 percent | arithmetic on the rows above |
|
|
| What one bitstream a day costs | one place-and-route of a large part: 42 to 160 minutes on a mid-size part (PRflow, FPT 2019, cited in spec 01 section 1.13), hours on a large one; on a rented 96-thread box (Hetzner AX162 class, about USD 0.35 per hour, approximate) under USD 3 per bitstream (approximate), and it compiles any time ahead because the calendar is public | spec 01 section 1.13; price approximate |
|
|
|
|
So the bitstream is cheap and the gain is 0.004 percent of a century for the slower of the two FPGA designs: nothing
|
|
a home miner on any card, a rig or a pool on any vendor or OS can see, and nothing that moves a day's difficulty.
|
|
|
|
What is being done about the M1 line: class v4 is not changed (it is the object on the live devnet's vote). The
|
|
rejection-and-redraw rule of section 8 is proposed to main for the next class, unless main reads the census as a
|
|
fault beyond the metric (this row does not: every class sits at its expectation and the worst day is an ordinary
|
|
draw). The rule costs one redraw on 5.6e-4 of days, changes no existing vector (day 20,729 has NAF sum 178; the first
|
|
day the rule would redraw is 22,633, about 5.2 years after genesis, section 6.5), and makes the M1 gate pass by
|
|
construction. `igneum-pow` is untouched by this row.
|
|
|
|
## 8. Proposed fix for the next class: a rejection-and-redraw rule on the MUL draw (for main's decision)
|
|
|
|
Shape, like the program acceptance rule 1.4.6 and `DeriveProgram::check`: draw the 16 `MUL`, test, and on rejection
|
|
continue the same stream with 16 fresh draws (so every later draw keeps its position only within an accepted block;
|
|
`RC` is drawn after the accepted `MUL` block). Tests, in order:
|
|
|
|
| Rule | Threshold | Rejection probability per candidate | What it closes |
|
|
|---|---|---|---|
|
|
| Sum of NAF weights of the 16 `MUL` at least 163 (M1 cost at least 211, gain at most 1.095x against the median 231) | `sum_i NAF(MUL_i) >= 163` | 5.64e-4 (`expect-231.log` cumulative at cost 210) | the M1 tail: no day over 1.1x by construction |
|
|
| Every `MUL` of NAF weight at least 4 | `NAF(MUL_i) >= 4` | 1.30e-5 per day | `MUL = 1`, `2^32 - 1`, `2^a +- 1`, `2^a +- 2^b +- 1`: the M2 words (hygiene; M2 already passes) |
|
|
| `ROT`: at least 4 distinct amounts (the `DISTINCT_ROTS_FLOOR` idea of `derive.rs`) | `distinct >= 4` | 3.07e-5 per day | the degenerate rotation draws (hygiene; 0 ops moved, diffusion fine at 2 applications) |
|
|
|
|
Total rejection about 6.1e-4 per day: one redraw every 4.5 years of chain time; `MAX_ATTEMPTS`-style exhaustion is
|
|
impossible in practice (64 rejections in a row at 6e-4 each). Class check to land with it: a unit test in `memhard.rs`
|
|
that plants a low-sum draw (stream seed chosen so the first MUL block fails) and asserts the redraw, plus this
|
|
harness re-run over 2^24 showing 0 days over 1.1x under M1 after the rule. The reproduction line for the finding
|
|
without the rule: `attack-f4 day --index 4819563 --median 231` (cost 197, 1.173x) and
|
|
`attack-f4 day --index 27952752 --median 231` (cost 194, 1.191x).
|
|
|
|
Consensus consequence: the rule changes the day-key-to-constants map on rejected days only, so it must land before the
|
|
freeze tag. In the chain's first 100 years the sum rule redraws 12 days (6 under 1.1x and 6 at exactly 1.100x,
|
|
section 6.5), the first of them 22,633, about 5.2 years after genesis; no pack cut for the devnet, the testnet or the
|
|
first five years of mainnet changes. The per-word rule redraws no day in the first 100 years (no word of NAF weight
|
|
under 4 in `census-100y.md`); the `ROT` rule redraws one, day 57,146 (2 distinct amounts, 99.7 years in).
|
|
|
|
## 9. What this row did not do
|
|
|
|
* It did not time the verifier per day beyond the confirmation row of 6.6: the verifier's code path is
|
|
value-independent (no branch on a drawn value), so op counts are the exact metric.
|
|
* It did not search optimal single-constant multiplication costs (not computable at 2^28 scale); NAF is the standard
|
|
canonical bound and the ratio between days is what the gate asks.
|
|
* It did not census the era draw (F7) or the spec's intent for the 64-bit seeding (F7); the fact is stated in section 1.
|