diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index ca3002af7..c95f3e846 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -2501,6 +2501,15 @@ Owed (recorded, not run, by the founder's word): G2 (the CPU verifier on 1,024 h Status: Fixed in part, finding bounded, stated (7 October 2026, night, the Counter ASIC lane's words): class v4 sub-version 3 (igneum-pow 017e7037, the audit-freeze tag) is frozen with the dataflow rule, the shared-operand rule, the 0.98 ratio and the total draw; the in-house pass's F8 re-gate reads 60 of 64 seeds under 1.2x with the four-seed tail accepted by the coordinator as the window model's unattributed residue (no chip consequence); the pass then attributed the class by value (eight live hot sets at 1.54x to 2.24x in the lowest 30 of 29,032 accepted programs, each about 1 MB of items at 0.3 percent of reads, 1.002x to a chip); class v5 (1c420786, frozen 21:53 UK) carries the fix as rule (c'''), the per-site distinct-index floor at 0.995 on the state flag (its census refuses 2.435 percent of accepted programs; seven of seven live hot sets refused at 0.9821 to 0.9919; the eighth's ratio owed tonight), with a named residual (three mild shadow-block-written concentrations at 0.9992 to 0.9997, about 1.0004x, a value-level test in the next class); the record is `docs/plans/counter-asic-3-status.md` section 7c and the class v5 design's section 14. The eighth live hot set (seed 122960, id 4be7393ab6c84802, the deepest found: X_f +0.111 percent, 1.54x the window model, its hottest item at 475,616 reads from an all-ones source) reads minimum site 12 at 0.9824 at the acceptance's own 2^20 sample (live 0.9822), refused by class v5's (c''') floor at 0.995; so the floor refuses eight of eight live hot sets by X_f at or above f found in the tail of 88,051 accepted programs (minimum sites 0.9821 to 0.9919) against 0 hot sets in 20 random programs; what it misses stays the three mild shadow-block-written concentrations at 0.9992 to 0.9997 (Devnet 3's first program among them), about 1.0004x to a chip, the value-level test in the next class (22:41 BST; the logs under `docs/analysis/cryptanalysis/logs/adv-accept/` on branch adv-accept; the v5 design's section 14). The RTX 5080 grid's knee is not in tonight; X37 keeps the 5080's stock premium only. +### AP-F8-5. The public specification did not describe the shipped acceptance rule (documentary; no object change) +"An implementation written from docs/spec/01-lottery-hash.md section 1.4.6 at 017e7037 mines a different program from the node on 264 of 400 epochs." Found by the in-house pass adv-accept-3 (report-acceptance-rule-3.md at 0c150e3c, finding 3, section 6.2, 7 October 2026, night): the text described (a), (b) and (c) with a 32-attempt cap and an id without a suffix, while the code adds (a') with the shared-operand rule, (c'), (c'') at 2^20 evaluations, the 256-attempt cap keyed on the class v4 shape, the total draw with the last resort, generator 4 and the sub-version suffix, and executes the 256-instruction shadow block 27 times per iteration inside the acceptance interpreter. Measured (sweep 97, 400 seeds, 1,317 attempt verdicts): 759 verdicts differ (758 the code rejects and the text accepts: (a') 733, (c'') 15, (c) 10; 1 the other way, the shadow block changing a (c) statistic); 264 of 400 seeds choose another attempt; the parts the text did carry, (a) and (b), agree on every row. + +Status: Spec fixed (7 October 2026, night, the site audit lane by main's order, branch spec-accept-23): sections 1.4.3 and 1.4.6 of `docs/spec/01-lottery-hash.md` rewritten to the shipped rule at igneum-pow 017e7037 with every constant named and every order of operations stated (1.4.3: the two per-register states of the draw, the dataflow table, the shared-operand rule, the shadow block's draw and the draw counts per class; 1.4.6.1 to 1.4.6.6: (a) cyclic over two passes, (b), (a') to the fixpoint then a checking pass, (c) with the shadow executed and its limits in a table, (c') and (c'') with the integer form of the ratio compare, the attempts and the two caps with the measured per-part rates, the last resort stated as unreachable and unverified, the program id with the generator-4 suffix, a constants table in the shape the crate's read-back test parses, the pinned ids a reader must reproduce); section 1.7 gains the shadow block's execution; section 1.13.1 names the two consumed era draws. The hash lane's `igneum-pow/tests` read-back test parses the two tables against the crate's `pub const` items and derives every pinned id (its landing is the row's check; AP-F8-4 carries the derivation-text half of the same finding). + +Answer: Correct, and the largest divergence source the pass found was documentary. The rule the chain runs was right; the text a second implementer would read was three sub-versions behind it. The fix is the text, written to the code line by line, and a test that fails when the two drift again. + +Evidence: `docs/analysis/cryptanalysis/report-acceptance-rule-3.md` sections 6.2 to 6.4 (branch adv-accept-3); `docs/spec/01-lottery-hash.md` 1.4.3, 1.4.6, 1.7, 1.13.1 (branch spec-accept-23); `igneum-pow/src/accept.rs`, `igneum-pow/src/generator.rs` at 017e7037. + ## Genesis forward-compatibility entries (7 October 2026, mission item 8, branch `genesis-forward`) The three genesis fields of `docs/analysis/mission/mission.md` section 2.8, built on the node fork branch `genesis-forward` (from release-0.3.19-node dc141409) and the repo branch `genesis-forward`; the design and the gates in `docs/design/genesis-forward.md`. Every switch is never on the devnet (its digest c562d70e... does not move); the testnet genesis sets all three (the testnet lane re-pins and re-digests). diff --git a/docs/ledger-public.md b/docs/ledger-public.md index 2cdcaa68e..529637680 100644 --- a/docs/ledger-public.md +++ b/docs/ledger-public.md @@ -2,7 +2,7 @@ Generated by `tools/ledger/export-public.mjs` from `docs/fud-ledger.md`; a gate check fails when the two drift. One row per item: the claim or criticism, its status, what was done, and the evidence. Internal identifiers, times of day and team-member names are left out on purpose; the full ledger is published with the repository. -191 items. By status: Conceded, stated 52; Fixed 30; Decided 22; Fixed on a branch, pending merge 14; Answered by design 9; Answered with evidence 6; Fixed, stated 4; Closed by rule 3; Open 3; Answered by design, with a correction to our own text 1; Answered with evidence, stated 1; Answered by design for finality, Conceded for the lottery 1; Conceded, implemented, stated 1; Rule implemented and measured; launch month simulated 1; Answered by design, with the concession stated 1; Conceded, stated in the litepaper and the design doc 1; Answered with evidence at 1 block/s 1; Conceded, stated in the simulation report 1; Answered by design, with the dependency conceded. Update 7… 1; Conceded, stated in the litepaper, with the dial explained 1; Closed by spec 1; Conceded by decision, stated in the design doc 1; Answered by design, with the founder's edge conceded 1; Answered by design, with a metrics caveat 1; Closed by removal, 3 October 2026 1; Conceded, stated in the litepaper 1; Conceded, stated in the design doc 1; Measured on the live node line, and the overlay does NOT… 1; Conceded, stated in the simulation 1; Conceded in part, labelled, stated 1; Fixed in the node 1; Answered with evidence for the largest body the rules allow 1; Spec fixed 1; Fixed in the proving code 1; Fixed in the spec 1; Rule fixed 1; Rule written 1; Fixed, logged 1; Answered with evidence for the test half 1; Fixed in the node and shipped, rule not yet activated on… 1; Simulation half run 1; Answered with evidence for all four 1; Written 1; Designed 1; Fixed and confirmed 1; Rolled out 1; Conceded by decision 1; Conceded, scheduled, stated 1; Conceded, contained by rule, stated 1; Fixed on a branch and verified locally 1; Answered with evidence and stated 1; Answered with evidence for PC 2 1; Answered by design and with evidence 1; Fixed, stated; restated 1; Fixed, stated; restated further 1; Fixed in part, finding bounded, stated 1; Fixed as a genesis lever, measurement owed 1. +192 items. By status: Conceded, stated 52; Fixed 30; Decided 22; Fixed on a branch, pending merge 14; Answered by design 9; Answered with evidence 6; Fixed, stated 4; Closed by rule 3; Open 3; Spec fixed 2; Answered by design, with a correction to our own text 1; Answered with evidence, stated 1; Answered by design for finality, Conceded for the lottery 1; Conceded, implemented, stated 1; Rule implemented and measured; launch month simulated 1; Answered by design, with the concession stated 1; Conceded, stated in the litepaper and the design doc 1; Answered with evidence at 1 block/s 1; Conceded, stated in the simulation report 1; Answered by design, with the dependency conceded. Update 7… 1; Conceded, stated in the litepaper, with the dial explained 1; Closed by spec 1; Conceded by decision, stated in the design doc 1; Answered by design, with the founder's edge conceded 1; Answered by design, with a metrics caveat 1; Closed by removal, 3 October 2026 1; Conceded, stated in the litepaper 1; Conceded, stated in the design doc 1; Measured on the live node line, and the overlay does NOT… 1; Conceded, stated in the simulation 1; Conceded in part, labelled, stated 1; Fixed in the node 1; Answered with evidence for the largest body the rules allow 1; Fixed in the proving code 1; Fixed in the spec 1; Rule fixed 1; Rule written 1; Fixed, logged 1; Answered with evidence for the test half 1; Fixed in the node and shipped, rule not yet activated on… 1; Simulation half run 1; Answered with evidence for all four 1; Written 1; Designed 1; Fixed and confirmed 1; Rolled out 1; Conceded by decision 1; Conceded, scheduled, stated 1; Conceded, contained by rule, stated 1; Fixed on a branch and verified locally 1; Answered with evidence and stated 1; Answered with evidence for PC 2 1; Answered by design and with evidence 1; Fixed, stated; restated 1; Fixed, stated; restated further 1; Fixed in part, finding bounded, stated 1; Fixed as a genesis lever, measurement owed 1. | Id | Claim or criticism | Status | What was done | Evidence | |---|---|---|---|---| @@ -193,6 +193,7 @@ Generated by `tools/ledger/export-public.mjs` from `docs/fud-ledger.md`; a gate | N2 | Any peer could crash any pruned node with a sync request below its retention | Fixed | `SyncManager::antipast_hashes_between` (the IBD headers path, `RequestHeaders`) unwrapped the GHOSTDAG reads of the requested low block and of every chain block of the walk; a pruned node holds no GHOSTDAG data below… | unit test `a_sync_request_below_retention_is_an_error_not_a_panic` (a chain of six headers, the genesis's GHOSTDAG… | | P23 | An unwound transaction leaves the node's view until its sender resends it | Fixed on a branch, pending merge | a fork a commit (the P23 commit, on the merge of `ledger-fixes` and `ledger-fixes-2` onto the 0.3.11 fork tip a commit); `EvmPool::on_chain_removed` (igneum/exec/src/pool.rs) and `ExecService::requeue_unwound`… | [igneum/exec/src/pool.rs](../igneum/exec/src/pool.rs) | | AP-F8-1 | A load whose source was last written by `or`, `mul` or `mulhi` makes a cross-hash hot set | Fixed in part, finding bounded, stated | Class v4 sub-version 3 (igneum-pow a commit, the audit-freeze tag) is frozen with the dataflow rule, the shared-operand rule, the 0.98 ratio and the total draw; the in-house pass's F8 re-gate reads 60 of 64 seeds under… | [docs/analysis/ca3-v4-uniform.md](../docs/analysis/ca3-v4-uniform.md) | +| AP-F8-5 | The public specification did not describe the shipped acceptance rule (documentary; no object change) | Spec fixed | Sections 1.4.3 and 1.4.6 of `docs/spec/01-lottery-hash.md` rewritten to the shipped rule at igneum-pow a commit with every constant named and every order of operations stated (1.4.3: the two per-register states of the… | [docs/analysis/cryptanalysis/report-acceptance-rule-3.md](../docs/analysis/cryptanalysis/report-acceptance-rule-3.md) | | GF1 | A post-quantum signature scheme would need a hard fork, and every vote key is a public BLS12-381 point | Fixed | The byte costs nothing now and a fork later. | none named | | GF2 | A vote key cannot move: a miner who changes keys re-earns 30 days of weight, and so does the post-quantum migration | Fixed | The successor inherits the window, not a fresh one, so a key rotation costs no weight and the migration of GF1 is one item per key. | none named | | GF3 | A 256 MB on-chip cache makes the lottery hash 2 to 3x cheaper for the card that has it, and the cache size is a constant | Fixed as a genesis lever, measurement owed | Consumer LLC is 96 to 128 MB today and datacentre 256 MB (`chip-model-v3`, approximate), so the shortcut is a datacentre card's today and a consumer card's in a generation or two. | none named | diff --git a/docs/spec/01-lottery-hash.md b/docs/spec/01-lottery-hash.md index 1366440d1..7b03ea9bf 100644 --- a/docs/spec/01-lottery-hash.md +++ b/docs/spec/01-lottery-hash.md @@ -118,30 +118,52 @@ The version 1 lever measurement (`load_weight = 17`, `proto-metal/MEMHARD.md` se ### 1.4.3 Draw order -From the program stream of 1.3.3, in this order, whether or not an op uses a value. +Implemented (`igneum-pow/src/generator.rs`, `candidate_from_words_class`; every path the chain and the packs take). From the program stream of 1.3.3, in this order, whether or not an op uses a value. The same procedure draws every class; a class changes what a draw is used for, never whether it is taken, so the stream stays aligned across classes. (1) Load slots. Let `p[0..62] = 1..63` (instruction 0 is never a load: nothing is fresh before it). For `i` in 0..15 draw `j = i + below(63 - i)` and swap `p[i]` and `p[j]`. The load slots are `p[0..15]`, a uniform 16-subset of 1..63. -(2) For each instruction `k` in 0..63, nine draws: +(2) For each instruction `k` in 0..63, in this order: ``` roll = below(75); op = first entry of the table of 1.4.2 whose cumulative weight exceeds roll (on a load slot the roll is drawn and ignored and op = load) dst = below(8) src: on an ALU slot a = below(7); src = a + (a >= dst) - on a load slot E = the registers other than dst, in register order, that an earlier instruction of this - program has written and that no later load has used as its source (E is empty before - instruction 0); if E is not empty, a = below(|E|) and src = E[a]; - if E is empty, a = below(7) and src = a + (a >= dst), and 1.4.6 (a) rejects the program + on a load slot E = the eligible registers (below), in register order; + if E is not empty, a = below(|E|) and src = E[a]; + if E is empty, a = below(7) and src = a + (a >= dst) (the fallback; 1.4.6 (a) or (a') rejects the program) b = below(8) (src2) imm = low32(next()) imm2 = low32(next()) rot = 1 + below(31) bit = below(32) mask = 1 << below(5) +under an era (every chain program of class v3 and v4; section 1.13.1): +k_off = below(3) +o = low32(next()) AND (2^k_off - 1) (k_off and o are kept on a load slot and are (0, 0) on an ALU slot) ``` -16 slot draws plus 64 x 9: 592 draws per program. A program is fully determined by its eight seed words. A load's source holds a value written in the same iteration that no earlier load has read, so no load repeats the address of an earlier load of the same hash, across the iteration boundary included (the first form of the rule, with every register eligible at instruction 0, left the wrap open and failed 1.4.6 (a) on 36 percent of programs; census section 7.1). +Eligible registers. Two per-register states are carried through the draw, updated after every instruction is drawn: + +| State | Start | After an instruction with `dst = d`, `src = a` | Used by | +|---|---|---|---| +| `fresh[r]` | all false | `fresh[a] = false` if the op is a load; then `fresh[d] = true` | every class | +| `fresh_value[r]` | all true | the table below, then the shared-operand rule | class v4 only | + +Under class v2 and class v3, `E` is every register `r != dst` with `fresh[r]`: written by an earlier instruction of this program and not read by a later load. Under the class v4 shape (`is_class_v4_shape`: the 256-instruction shadow block over the class v3 base, the pass count and the era set aside), `E` is every register `r != dst` with `fresh[r]` and `fresh_value[r]`: fresh by dataflow as well. The dataflow table (AP-F8-1, sub-version 2; `docs/analysis/ca3-v4-uniform.md`), evaluated with the values before the instruction: + +| Op drawn | `fresh_value[d]` after it | +|---|---| +| `load` | `fresh_value[a]` (a saturated source reads one fixed word and leaves a constant) | +| `add`, `sub`, `xor`, `mad`, `shfl` | `fresh_value[d] OR fresh_value[a]` | +| `rotl`, `rotr` | `fresh_value[d]` (a rotate maps all-ones and zero to themselves) | +| `or`, `mul`, `mulhi` | false | + +The shared-operand rule (sub-version 3): after `or d |= s`, a later `xor d ^= s` or `sub d -= s` with neither `d` nor `s` written in between computes `d AND NOT s`; after `xor d ^= s`, a later `or d |= s` computes `d OR s`. Either pair is lossy though its second op would count as injecting on its own (F8's p23: `or r6 |= r4; xor r6 ^= r4`). So the draw keeps `pair[d] = (op, s)` for the last `or` or `xor` written to `d`, and when the instruction drawn is the second half of such a pair on the same `s`, `fresh_value[d]` is set false whatever the table says. Then: `pair[d]` becomes `(op, a)` if the op is `or` or `xor` and the pair rule did not fire, else none; and every `pair[r]` whose `s` equals `d` is cleared (a write to a register clears every pair that names it as the operand). The `src2` register `b` takes no part in either state. + +(3) The latency-shadow block (class v4; Counter ASIC 3.0 item 8). After the 64 base instructions, `V4_SHADOW_INSTRS = 256` further instructions are drawn from the same stream, so the base program of a class v4 seed is the class v3 program of that seed draw for draw. Every shadow slot is an ALU slot: `roll = below(75)` and the op from the table of 1.4.2, `dst = below(8)`, `a = below(7); src = a + (a >= dst)`, `b = below(8)`, `imm`, `imm2`, `rot`, `bit`, `mask` as above; under an era `below(3)` and `next()` are drawn and ignored. No shadow instruction is a load, and the shadow block takes no part in the two states above during the draw (the acceptance rule's (a') reads it, section 1.4.6.3). The block runs `V4_SHADOW_REPS = 27` times at the end of every iteration (section 1.7); the latency ladder (`docs/design/latency-ladder.md`) moves the pass count alone and never the draw. + +Draw counts: 16 slot draws plus 64 x 9 = 592 under class v2; 64 x 11 + 16 = 720 under class v3 with an era; 720 + 256 x 11 = 3,536 under class v4 with an era. A program is fully determined by its eight seed words and its class. A load's source holds a value written in the same iteration that no earlier load has read, so no load repeats the address of an earlier load of the same hash, across the iteration boundary included (the first form of the rule, with every register eligible at instruction 0, left the wrap open and failed 1.4.6 (a) on 36 percent of programs; census section 7.1). Test vector: for seed `igneum-genesis` (attempt 0, program id `bcc1248b10cc90f2`, section 1.4.6) the first eight instructions are (`proto-cuda/packs/igneum-genesis-mh/program.json`): @@ -168,21 +190,115 @@ A program is transmitted as the seed bytes, never as instructions. A node hands ### 1.4.6 Program acceptance -Implemented (`igneum-pow/src/accept.rs`, `proto-metal/main.swift`; ledger M6 Fixed). A candidate program is accepted only if all of the following hold, and every conforming implementation MUST evaluate them identically. +Implemented (`igneum-pow/src/accept.rs`; `proto-metal/main.swift` for the class v2 parts; ledger M6 Fixed, AP-F8-1 to AP-F8-3, AP-F8-5). A candidate program is accepted only if every part below that applies to its class holds, and every conforming implementation MUST evaluate them identically. The verdict is accept or reject; the reason is the first failing part in the order of this section and is reported, never relied on. -(a) For every `load`, some instruction between the previous `load` from the same source register and this one, in cyclic order over the 64 instructions, writes that register. +| Part | Name | Class v2 and v3 | Class v4 | +|---|---|---|---| +| (a) | stale load source, cyclic | yes | yes | +| (b) | an injecting write per register | yes | yes | +| (a') | dataflow freshness to a fixpoint, with the shared-operand rule | no | yes | +| (c) | the dynamic test over 2,048 hashes | yes, the 64 base instructions | yes, with the shadow block executed | +| (c') | saturated load sources | no | yes | +| (c'') | the distinct-index ratio at 2^20 evaluations | no | yes | -(b) Every register `r0..r7` is the destination of at least one `add`, `sub`, `xor`, `mad`, `shfl` or `load`. +The class v4 parts and the class v4 cap are keyed on the class v4 shape (`accept::is_class_v4_shape`): the 256-instruction shadow block over the class v3 base, the pass count and the era set aside, so a rung of the ladder keeps every rule and class v2 and v3 verdicts never move. -(c) The program is interpreted (section 1.7) for 64 units at base nonces `low32(next()) AND NOT 31` from a SplitMix64 stream seeded with `FNV-1a-64("igneum-accept/" || seed words as little-endian bytes)`, with init words `I` equal to the seed words and dataset words `dataset_elem(idx, S[0], S[1])` of `verify.rs` (the six-operation closed form of the version 0.1 packs) at 2^28 words (`idx = src AND 0x0fffffff`, a constant of this rule whatever the live dataset size) in place of the memory-hard dataset. Over those 2,048 evaluations: no register has a bit equal in every final value; no `load` site (iteration, instruction) reads one address in all 32 lanes of any unit; the number of final register values equal to 0 or 2^32 - 1 is below 164 (1 percent of 16,384); every output bit's ones count is within 136 of 1,024 (6 sigma); and the number of distinct masked dataset addresses read by one lane in one evaluation, summed over the 2,048 evaluations, exceeds 245,760 (a mean above 120 of the 128 loads). +#### 1.4.6.1 Part (a): no stale load source -Attempts. Attempt 0 of a program seed `b` (the 32-byte epoch seed, or the UTF-8 of a seed string) is the candidate drawn from `seed_words_from_bytes(b)`. If it fails, attempt `k = 1, 2, ...` is drawn from `seed_words_from_bytes(b || k_le32)`; the first accepted candidate is the program of the epoch. Measured rejection rate under this generator: 5.14 percent over 100,000 seeds (census section 7) and the 20,000-seed confirmation of `docs/bench-log.md` (4 October 2026), so the probability that 32 consecutive candidates fail is below 2^-136, and an implementation MAY treat 32 consecutive failures as a consensus fault (`MAX_ATTEMPTS`). +Over the 64 base instructions, twice in a row (so the second pass sees the state carried over the iteration boundary): keep `pending[r]`, set when a load reads `r` and cleared when any instruction writes `r` (its `dst`). A load that reads `r` while `pending[r]` is set rejects the program. The shadow block is not read here. -Program id. `FNV-1a-64("igneum-program/" || generator_le32 || seed words as little-endian bytes || attempt_le32)` with `generator = 2` under class v2 and `generator = 3` under class v3, written into every pack. Two implementations that agree on the id agree on the generator version, the seed words and the attempt. +#### 1.4.6.2 Part (b): an injecting write per register -Why the closed form: the test is then a pure function of the program (no cache, no day), costs 1.3 to 3.4 ms on one core, and the census checked on 100,000 programs that its verdict agrees with the memory-hard dataset's on all but 39 threshold-edge cases (section 7.3). What the three parts catch: (a) the empty-list fallback of 1.4.3; (b) registers that saturate to all ones (2.4 percent of candidates); (c) zero-absorbing register sets, lane-constant load sites, output bias and value-level address repeats (2.1 percent). Not in the rule, and why: a contraction as the last write (80 percent of programs) and the `or` count are too common and (c) already catches the cases that matter; the load critical path is a hash-rate question, not a weakness. +Every register `r0..r7` is the `dst` of at least one `add`, `sub`, `xor`, `mad`, `shfl` or `load` among the 64 base instructions. The shadow block is not read here. -Test vectors for the rule (`igneum-pow accept --seed ...`): +#### 1.4.6.3 Part (a'): dataflow freshness in the steady state (class v4) + +The draw of 1.4.3 keeps in-pass sources fresh; this part closes the iteration boundary (a source last written late in the previous iteration or in the shadow block, which the draw's empty-list fallback can pick: F8's p11, an `or` at 63 feeding a load at 1). One pass walks the 64 base instructions then the 256 shadow instructions, in that order (the order of one iteration), applying the `fresh_value` table and the shared-operand rule of 1.4.3 to the states `fresh_value[0..7]` and `pair[0..7]`. Start with every register fresh and no pair (the init words are a per-lane hash of the nonce). Run passes until a pass changes neither state (the state only ever falls, so at most 8 passes change it; the implementation runs at most 9 and stops at the first unchanged one). Then run one checking pass in the same order: a load whose source is not fresh at that point rejects the program (`UnfreshLoadSource`, the instruction index counted over base then shadow). + +#### 1.4.6.4 Part (c): the dynamic test + +The program is interpreted for `ACCEPT_UNITS = 64` units of 32 lanes, `ACCEPT_HASHES = 2,048` hashes, with these inputs and nothing of the live chain: + +| Input | Value | +|---|---| +| Base nonces | the first 64 values of SplitMix64 seeded with `FNV-1a-64("igneum-accept/" || seed words as little-endian bytes)`, each `low32(next()) AND NOT 31`; unit `u` runs lanes `base_u + 0 .. base_u + 31` | +| Init words `I` | the program's eight seed words (section 1.6) | +| Dataset | `dataset_elem(idx, S[0], S[1])` of `verify.rs` (the closed form of the version 0.1 packs; `S[0]`, `S[1]` are seed words 0 and 1) at `ACCEPT_DATASET_LOG2 = 28`, 2^28 words, `MASK = 0x0fffffff`, whatever the live dataset size | +| Address of a load with source value `x` | without an era `idx = x AND MASK`; under an era the form of 1.13.1 at `D = 28`: `k = min(k_off, 2)`, `y = rotl(x * M, R)`, `idx = ((y AND (MASK >> k)) OR ((o AND (2^k - 1)) << (28 - k))) AND MASK` | +| Execution | section 1.7 exactly, the shadow block included under class v4: after instruction 63 of every iteration the 256 shadow instructions run 27 times with that iteration's `sel` (sub-version 3, AP-F8-3; until it, the test ran the base instructions alone and judged a program the chain never hashes) | + +During the run: a `load` whose 32 lanes compute one address in any unit rejects the program (`LaneConstantSite`, checked at every load of every iteration and unit, the shadow block has none). After the run, over the 2,048 final register states and 2,048 outputs, in this order: + +| Check | Limit | Reject | +|---|---|---| +| Nonce-independent bits | no register has a bit equal in all 2,048 final values | `ConstantBit` | +| Saturated finals | the number of final register values equal to 0 or 2^32 - 1 is below `MAX_SATURATED = 164` (1 percent of 16,384) | `Saturated` | +| (c'), (c'') | class v4 only, section 1.4.6.5 | | +| Output bias | every output bit's ones count is within `BIAS_TOLERANCE = 136` of 1,024 (6 sigma) | `OutputBias` | +| Distinct addresses | the number of distinct `idx` values one lane read in one hash, summed over the 2,048 hashes, exceeds `MIN_DISTINCT_SUM = 245,760` (a mean above 120 of the 128 loads; `loads x 2,048 x 120 / 128` for a class with another load count) | `DistinctAddresses` | + +#### 1.4.6.5 Parts (c') and (c''): the load sources (class v4) + +A load site is a load's ordinal within the iteration, 0 to 15, in instruction order. Both parts read the same interpreter and the same nonce stream as (c). + +(c') Saturated sources. Over the 64 units of (c), every site is evaluated 64 x 32 x 8 = 16,384 times. A site whose source value `x` was 0 or 2^32 - 1 in `MAX_SATURATED = 164` or more of them rejects the program (`SaturatedSource`, the first such site in order). A saturated source reads one fixed word whatever delivered the saturation; the draw's rule removes the writers it can see and this count catches every delivery. + +(c'') The distinct-index ratio. The one test that runs past the 64 units: `ACCEPT_UNITS_DISTINCT_V4 = 4,096` units, the first 4,096 base nonces of the same stream (the 64 of (c) are its first 64), so every site is evaluated `N = 2^20` times. For each site `s`, `d_s` is the number of distinct `idx` values it computed over those evaluations, `W_s = 2^28 >> min(k_off_s, 2)` is its window in words, and the expectation of a uniform source on that window is `E_s = N - N^2 / (2 W_s)` (an integer at these constants: 2^20 - 2^11, 2^20 - 2^12, 2^20 - 2^13). The site's ratio `d_s / E_s` must reach `MIN_DISTINCT_RATIO_V4 = 0.98`; the first site under it, in order, rejects the program (`LowEntropySite`). The implementation compares in f64; the integer comparison `50 d_s >= 49 E_s` gives the same verdict for every value of `d_s` at these constants (the margin is at least 0.32 of a count; adv-accept-3 section 6.1), and an implementation MAY use it. The floor sits in a measured gap: the accepted population's minimum is 0.983 to 0.989 and the rejected population's maximum 0.966 over 20,275 draws of two lanes, so a floor anywhere in 0.967 to 0.988 gives the same verdicts on every program seen (adv-accept-3 section 6.4). `MAX_SOURCE_REPEAT_V4 = 8` exists in the file and is not part of the rule. + +What the parts catch: (a) the empty-list fallback of 1.4.3; (b) registers that saturate to all ones (2.4 percent of class v2 candidates); (c) zero-absorbing register sets, lane-constant load sites, output bias and value-level address repeats (2.1 percent); (a') the cross-hash hot set of a load fed by `or`, `mul` or `mulhi` through the iteration boundary (AP-F8-1); (c') the same set delivered any other way; (c'') a low-entropy index band the lineage rules cannot see (F8's p23, p18, p19, p15, p56). Why (c) uses the closed form: the test is a pure function of the program (no cache, no day), costs about 3 ms on one core for the 64 units and 2.8 s with (c'') on the chosen candidate, and the census checked on 100,000 class v2 programs that its verdict agrees with the memory-hard dataset's on all but 39 threshold-edge cases (section 7.3). Not in the rule, and why: a contraction as the last write (80 percent of programs) and the `or` count are too common and (c) already catches the cases that matter; the load critical path is a hash-rate question, not a weakness; a 2^24 stage of (c'') does not separate the open F8 tail (p4, p8, p10, p34 read the clean seeds' values there; ledger AP-F8-1). + +#### 1.4.6.6 Attempts, the cap, the last resort and the program id + +Attempts. Attempt 0 of a program seed `b` (the 32-byte epoch seed, or the UTF-8 of a seed string) is the candidate drawn from `seed_words_from_bytes(b)`. If it is rejected, attempt `k = 1, 2, ...` is drawn from `seed_words_from_bytes(b || k_le32)`; the first accepted candidate is the program of the epoch. The cap is `MAX_ATTEMPTS = 32` under class v2 and v3 and `MAX_ATTEMPTS_V4 = 256` under the class v4 shape (`max_attempts_for`). + +| Rate, per candidate | Class v2 (census, 100,000 seeds, 4 October 2026) | Class v4 sub-version 3 (adv-accept-3, 3,009,928 candidates of 10^6 seeds and 62,240 of 19,975 full-rule seeds, 7 October 2026) | +|---|---|---| +| Accepted | 0.9486 | 0.323 | +| (a') | not a part | 0.568 | +| (a) | | 0.079 | +| (b) | | 0.022 | +| (c), (c'), (c'') together | | about 0.009 | +| Seeds reaching the cap | 0 of 100,000 | 0 of 1,019,975 | +| Probability a seed reaches the cap | below 2^-136 | 0.677^256, about 4.6 x 10^-44 | + +Under class v2 and v3 an implementation MAY treat the cap as a consensus fault. Under class v4 the draw is total (AP-F8-2): a seed whose 256 candidates are all rejected takes the last-resort program, the candidate at attempt 256 with every `or`, `mul` and `mulhi` of its base program and its shadow block rewritten to `xor` (`dst`, `src` and every other field kept), handed to the chain as drawn with no further check. With no lossy op left, (a') holds by construction. The rest of the rule is not checked on it, and does not hold on every seed: on 3,000 seeds the real rule rejects the last-resort program of 271 (251 by (a), the fallback-drawn load source the rewrite does not touch; 14 by (b); 6 by (c)'s distinct sum), so sub-version 3's last resort is stated here as unreachable and unverified (adv-accept-3 finding 1, ledger AP-F8-3); class v5 carries the verified construction (section 1.4.7). + +Program id. `FNV-1a-64("igneum-program/" || generator_le32 || seed words as little-endian bytes || attempt_le32 || suffix)` with `generator = 2` under class v2 and `generator = 3` under class v3 (no suffix), and `generator = 4` under class v4 with the suffix `"sub/" || sub_version_le16` (the class v4 stream's sub-version, `PROGRAM_SUBVERSION_V4 = 3` since 7 October 2026: `IGNEUM_PROGRAM_SUBVERSION` in program.h, `"sub_version"` in program.json; without the suffix Devnet 3's epoch-0 id reads 30956569d8f3d8d7 where the chain and the packs say fce15bf61030be57); class v5 is `generator = 5` with no suffix (section 1.4.7). A class v4 program at rung 0 of the ladder uses that form; a program of any other class or rung (a read-width, scratch, mixer, derivation, era, shadow or hot rung other than class v4's rung 0) uses the tag `"igneum-program-rw/"` and appends the class's fields after `attempt_le32` (`generator::program_id_class`). Every pack prints its own derivation as `program_id_derivation` in program.json, built from the byte recipe the id is hashed from, and a worker MUST refuse a pack whose generator version, class, era seed or sub-version is not its own (`packcheck.rs`). Two implementations that agree on the id agree on the generator version, the seed words, the attempt and, under class v4, the sub-version. + +Constants of the shipped rule. One row per constant the rule depends on, the value as the crate has it; a reader implementing from this text uses these and nothing else, and the crate's own test reads this table back against its `pub const` items. + +| Constant | Value | Where | +|---|---|---| +| generator::INSTR_COUNT | 64 | instructions per base program | +| generator::LOAD_SLOTS | 16 | load slots per program | +| generator::ITERATIONS | 8 | iterations per hash | +| generator::GENERATOR_VERSION_V4 | 4 | the class v4 generator | +| generator::PROGRAM_SUBVERSION_V4 | 3 | the id suffix "sub/" || le16 | +| generator::MAX_ATTEMPTS | 32 | the cap under class v2 and v3 | +| generator::MAX_ATTEMPTS_V4 | 256 | the cap under the class v4 shape | +| generator::V4_SHADOW_INSTRS | 256 | shadow instructions per program | +| generator::V4_SHADOW_REPS | 27 | shadow passes per iteration at rung 0 | +| accept::ACCEPT_UNITS | 64 | (c) units | +| accept::ACCEPT_HASHES | 2048 | (c) hashes | +| accept::ACCEPT_DATASET_LOG2 | 28 | log2 of the closed-form dataset | +| accept::MAX_SATURATED | 164 | (c) saturated finals and (c') saturated sources, exclusive limit | +| accept::BIAS_TOLERANCE | 136 | (c) output bias, inclusive | +| accept::MIN_DISTINCT_SUM | 245760 | (c) distinct-address sum, exclusive | +| accept::ACCEPT_UNITS_DISTINCT_V4 | 4096 | (c'') units, 2^20 evaluations per site | +| accept::MIN_DISTINCT_RATIO_V4 | 0.98 | (c'') ratio floor, inclusive | + +Pinned program ids. A reader who follows 1.4.3 and this section reproduces these from the seed, the attempt and the sub-version above; the crate's test derives each from the recipe and from the printed derivation text. + +| Seed | Attempt | Id | Note | +|---|---|---|---| +| edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 | 1 | a785001687d8688a | the shared devnet's epoch-0 seed under class v4 sub-version 3 (generator 4, the suffix); attempt 0 is rejected under class v4 | +| edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 | 0 | 73bcbfe8ccf988f1 | the same seed under class v3 (generator 3, no suffix): the class v3 control | +| edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 | 1 | c120d7963abdcd96 | must differ: generator 4 without the suffix, the stream of 6 October 2026 (sub-version 0, never stamped) | +| edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 | 1 | 1a4230699a6b9c60 | must differ: sub-version 1, the stream 0.3.20 and 0.3.21 shipped as object byte 5 | +| edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 | 1 | a788661687db4bb3 | must differ: sub-version 2, never shipped | +| 4020cb4382e3fe4b281c817c02582e147d8f851f566ae9172b28912b8e68b925 | 0 | fce15bf61030be57 | Devnet 3's epoch-0 seed (its genesis hash) under class v4 sub-version 3, accepted at attempt 0; 30956569d8f3d8d7 without the suffix | + +Test vectors for the class v2 rule (`igneum-pow accept --seed ...`): | Seed | Attempt 0 | Attempt 1 | |---|---|---| @@ -192,7 +308,7 @@ Test vectors for the rule (`igneum-pow accept --seed ...`): | `igneum-census-2026-10-03/37` | rejected, (c) 245,230 distinct addresses (mean 119.74) | accepted, `947705cc4eb1df0a` | | `igneum-census-2026-10-03/51` | rejected, (b) r4 has no injecting write | accepted, `9869afcc028bf9f1` | -The Rust crate and the Swift prototype derive identical instruction lists and identical 96-vector sets on all five seeds (`docs/bench-log.md`, 4 October 2026). +The Rust crate and the Swift prototype derive identical instruction lists and identical 96-vector sets on all five seeds (`docs/bench-log.md`, 4 October 2026). For the class v4 rule, a second implementation written from the module table of `accept.rs` and this section agreed with the crate on 5,748 of 5,748 attempt verdicts over 1,792 seeds, part for part, with its known-failed control fired (adv-accept-3 section 6.4); an implementation written from the text of this section as it stood before 7 October 2026 mined a different program on 264 of 400 epochs (adv-accept-3 section 6.2, ledger AP-F8-5), which is why every constant and every order of operations is now stated here. ## 1.5 Fixed memory footprint @@ -244,6 +360,8 @@ The output folding rotations (7, 14, 21; 9, 18, 27) are Implemented, prototype v `shfl` makes the 32 lanes of a unit interdependent: the hash of one nonce is defined only as a member of its aligned group of 32 (section 1.9). +Class v4 (Counter ASIC 3.0, the latency shadow): after instruction 63 of every iteration, and before the next iteration samples `sel`, the program's 256-instruction shadow block (section 1.4.3 (3)) is applied `V4_SHADOW_REPS` times with the iteration's `sel`, 27 at rung 0 of the latency ladder, so one hash runs 64 x 8 base instructions and 256 x 27 x 8 = 55,296 shadow instructions. The shadow holds no load. The acceptance interpreter of 1.4.6.4 runs the same block the same way (sub-version 3). The ladder (`docs/design/latency-ladder.md`) moves the pass count by miner signal and nothing else in this section. + ## 1.8 The memory-hard dataset Implemented (`memhard.rs`), construction and measurements in `proto-metal/MEMHARD.md`. Every constant below is a prototype value, to be fixed at gate 1, unless marked Definition. What fixes them is the shortcut-ratio and time-memory trade-off measurement on NVIDIA and AMD discrete cards (section 1.16 items 2 and 3) and an external review of the primitives (ledger M7). @@ -321,6 +439,8 @@ item(t) = s Eight dependent cache reads (`ITEM_ROUNDS = 8`, prototype value): the address of read `r` depends on every earlier read. Nine mixer applications under program class v2. +Under class v5 the item's init words carry the leaf of section 1.8.6. + Program class v3 (Counter ASIC 2.0, decided 5 October 2026, delegated; the founder confirms for the public testnet genesis) applies the mixer `m = 8` times per round with distinct round keys (`LoadClass::mixer_mult`; `docs/plans/mixer-x4.md` section 2), the eight dependent reads unchanged: ``` @@ -441,7 +561,7 @@ The era seed `E_n` is the 32-byte output of the 1-hour VDF of section 4.4 (in th `epoch_len` is the one era-table parameter set by miners rather than by the draw: 90% of blue blocks over a 7-day window carrying the same ladder index (3 bits of the header version, encoding Open in section 5.8) sets that length from the first day boundary at least 2 days after the window closes (section 5.7). It is not a code upgrade: the rule, the ladder and the window are genesis constants, and the chain carries no release. The era stream consumes its draw so that a future draw of this parameter changes no other parameter's value. The threat it answers is a per-program hard datapath (an FPGA fleet: 42 to 160 minutes per compile on a mid-size part, PRflow, FPT 2019, hours on large parts; at 600 s nothing it compiles ever runs); it does not answer a programmable chip, which the other layers answer. The floor 600 is set by the slowest compile-ahead measured (the Metal variant race, 38 s on the M5 Max, 6.3% of a 600-s epoch and inside the 600-s seed window; `docs/plans/epoch-length.md` section 6). -The table layout and the working-set window (Counter ASIC 2.0 layers 4 and 8, decided IN on 5 October 2026, delegated: the six-era hash-rate spread is 1.3% on the RTX 5090, 3.2% on the RX 9070 XT and 0.8% on the M5 Max, under the 5% rule; `docs/plans/era-layout.md`) are drawn under program class v3 by a second stream `S` seeded with words 0 and 1 of `seed_words_from_bytes("igneum-era/" || E_n)` (the index is not in the preimage: `E_n` commits to `n` through the VDF input), seven draws in this order whether or not a value is used: +The table layout and the working-set window (Counter ASIC 2.0 layers 4 and 8, decided IN on 5 October 2026, delegated: the six-era hash-rate spread is 1.3% on the RTX 5090, 3.2% on the RX 9070 XT and 0.8% on the M5 Max, under the 5% rule; `docs/plans/era-layout.md`) are drawn under program class v3 by a second stream `S` seeded with words 0 and 1 of `seed_words_from_bytes("igneum-era/" || E_n)` (the index is not in the preimage: `E_n` commits to `n` through the VDF input), nine draws in this order whether or not a value is used (the eighth and ninth, `epoch_len` and the latency ladder, are consumed and not read: both are set by miner signal, and consuming their slots means a later use of either changes no other draw; `generator::era_draw`): 1. `W = allowed[below(|allowed|)]`: the width in words of every dataset load of the era, from the genesis-fixed set `allowed`; the set is `{1}` (4 bytes, the read-width decision of 5 October 2026), so the draw is consumed and the width pinned. 2. `M = low32(next()) OR 1`: the stride multiplier, odd, so `x -> x * M` is a bijection.