Merge mhpow-b2 15bbcf17 into master (gate: green on 7638774f, recorded by tools/ci/pre-push.sh; landed on the build mirror)

This commit is contained in:
igneum-labs 2026-10-09 09:23:06 +00:00
commit e8b084c64f
5 changed files with 518 additions and 0 deletions

View file

@ -0,0 +1,157 @@
# B2: binding the economically expensive work (Track B, the 1.5x research programme)
Lane: B2, the binding spec lane. Written 9 October 2026, 10:1x to 12:00 UK. Documents only. Every row here is NOT RUN in the
registry until the panel reads it; this lane writes no PASS. The status words in the table are this lane's reading of a paper
(ANSWERED), a written counter-example to a candidate rule (FAIL), or an open question with its owner (BLOCKED).
## 0. Sources, by sha
| Source | Identity | What was read |
|---|---|---|
| The founder's research plan, "IGNEUM - The 1.5x Research Programme" | sha256 269ceaa5824d09140a1bcc124dba59438db5a5e68fddc18960db7141fefc0305 | sections 2, 3, 4, 6 (B2 above all), 8, 9, 10 |
| Blocki and Smearsoll, "Provably Memory-Hard Proofs of Work With Memory-Easy Verification", TCC 2025, eprint 2025/1456 (the plan's R1) | the FULL PDF, sha256 13c3646e7d85c1aa58a92914582caab5798d90cf2a3cad33e58d81d49c1d31db, 608,919 bytes; fetched on build-9 from the Internet Archive capture of `https://eprint.iacr.org/2025/1456.pdf` at 2025-12-31 15:20:15Z (the eprint host answers a Cloudflare challenge, HTTP 403, from the box and from the Mac); the capture's CDX digest equals the 2025-08-12 capture's, so one version is on record | sections 1.3, 2, 3, 4, 5, 6, 7 and Appendix A (Lemmas 7 to 10) in full; Appendix B (the proofs of Theorems 9 and 10) skimmed. Copy at build-9:/srv/builds/b2/2025-1456.pdf for the B1 lane |
| The frozen class's header binding | igneum-pow `src/bind.rs` at the class-v6 freeze tree 1a938abe4 (generator fingerprint 5f4d6dc6...) | the init words, the pre-PoW hash rule, the interim day rule |
| The node's header hash | fork branch class-v6-node-review e8773ff5, `consensus/core/src/hashing/header.rs`, `hash_override_nonce_time` | every field the pre-PoW hash absorbs |
| The spec | `docs/spec/01-lottery-hash.md` on master bc6bfa75e: 1.0 (the frozen object), 1.6, 1.10, 1.12, 1.13.3 | the dataset policy digest in full, the epoch, day and era clocks |
| The signing pair | `igneum-pow/tests/composition.rs` at class-v6 755c2dbcf | epoch seed af89be5d..., era edc4fa84..., day 20730, program id 0x2a1d6caab4c24564 |
| Figures | `tools/mhpow/b2/b2_figures.py` (sha256 e7d69c1ee651252bfe8ccafc838995e81c18ca9813c5124d3ed659e2ee0bb457) | output `figures.txt` (sha256 007dfe0f571d009f9911c572eaa7e3132a62cd86dc344922a58bc42644d37c90), run on build-9 under `/srv/builds/b2/b2-run.pid`: `python3 tools/mhpow/b2/b2_figures.py > docs/analysis/mhpow/b2/figures.txt` |
## 1. What the paper proves, and what it does not
The construction (paper section 3.2). `Prove^H(chi, N, k)` labels a DAG `G` on `N = 2^n` nodes: `l_1 = H(chi, 1)`,
`l_v = H(chi, v, l_v1, ..., l_vk)` over the parents in ascending order; commits to the labels with a Merkle tree salted by `chi`;
derives `k` challenges `c_i = H(chi, i, tau) mod N` from the root `tau`; opens `l_(c_i + 1)` and its parents. `Verify` recomputes
the challenges from `chi` and `tau`, checks every opening against `tau` and checks local consistency. The certificate is
`tau` plus the openings.
| Paper statement | Content | Constants |
|---|---|---|
| Definition 5 | an MHPoW is `(Prove, Verify)` with prover efficiency `cmc <= N^2 lambda`, `O(N)` rounds, verifier efficiency, perfect completeness, and `(eps, C)`-soundness for every input `chi` in `{0,1}^lambda` | there is no difficulty parameter and no target: one certificate per input |
| Lemma 1 (oracle prediction) | a predictor with a hint of `|h|` bits outputs `|S|` fresh correct `lambda`-bit pairs with probability at most `|h| 2^(-lambda |S|)` | |
| Lemmas 7, 10 (Appendix A) | COLLISION at most `C(q,2) 2^-lambda`; BADORDER at most `q^2 n 2^-lambda`, `n` the query length in bits | |
| Lemma 2 | MISCOLOR at most `|V(G)| 2^-lambda` | |
| Theorem 4 | without COLLISION, BADORDER, MISCOLOR, the ex-post-facto pebbling is a legal pebbling of the ex-post-facto (green-only) graph `G'` | |
| Theorem 5, Lemma 3 | the extractor recovers `|P'_i|` oracle pairs from `sigma_i` and a hint of `(2 log2 q + log2 indeg) |P'_i|` bits; IE at most `t 2^(-lambda/2)` | precondition `lambda/4 >= 2 log2 q + log2 indeg(G')` |
| Theorem 6 | `cmc(trace) >= cc(G') lambda / 4` absent the bad events | the 1/4 is the rounding of `lambda - 2 log2 q - log2 indeg` under Lemma 3's precondition |
| Lemma 4 | LUCKYQUERY (a fresh root whose `k` challenges all land on green nodes while at least `beta N` nodes are red) at most `q (1 - beta)^k` | |
| Theorem 7, Corollary 1 | `cmc >= (lambda/4) min over |S| <= beta N of cc(G - S)` except with `2^-lambda C(q,2) + n 2^-lambda q^2 + 2^-lambda |V| + 2^(-lambda/2) t + q (1 - beta)^k` | `q` queries, `t` rounds |
| Lemma 5, Corollary 2 | for `(e, d)`-depth-robust `G`, `beta = e/(2N)`, `k = (2 N ln 2 / e) lambda`: `cmc >= (lambda/4)(e d / 2)` except with the sum above, last term `q 2^-lambda` | the statement writes `q 2^-lambda`, the proof's last line `q e^-lambda`; harmless, noted for B4 |
| Fact 8, Corollary 3 | DRSample: indeg 2, `(c3 N / log N, c4 N)`-depth-robust, so `cmc >= (lambda/4)(c3 c4 / 2) N^2 / log N`; `k = 2 lambda log N / c3` (the statement's `c1` is `c3/2`) | `c3`, `c4` exist (ABH17, ABP17); the paper gives no value |
| Section 7, Theorems 9, 10 | attacks on any MTP-style MHPoW: delete `e = N/k` nodes, pebble `G - S`; for constant indegree `k` must be `omega(log N / log log N)` | Attack 1: `cmc <= O(N d lambda + N lambda log N)`, success `(1 - e/N)^k` |
Outside the paper: a lottery or difficulty target; more than one accepted certificate per input; amortisation over many inputs
(every statement fixes one `chi`); an input chosen after seeing the oracle; energy, bandwidth, SRAM against DRAM (the measure
is cumulative memory, bits times PROM rounds); quantum adversaries (the model is the classical parallel random oracle).
## 2. The canonical instance input (work version 1, draft)
Every field has a fixed width, integers are little-endian, 328 bytes in all (figures section 1, self-checked contiguous).
The VERIFIER builds the instance from the header and from the chain's own state. The certificate carries only `tau` and the
openings (paper section 3.2, step 4); no instance field is ever read from the certificate.
| Offset | Width | Field | Value rule | Example (the signing pair) |
|---|---|---|---|---|
| 0 | 24 | tag | ASCII `igneum-mhpow/instance/1/` | |
| 24 | 32 | network_id | u8 length, ASCII, zero padded | `igneum-devnet-4` |
| 56 | 8 | chain_id | u64 | 4465 |
| 64 | 2 | header_version | the header's `version` (object byte in bits 8 to 13) | PLACEHOLDER |
| 66 | 2 | work_version | 1 for this draft | 1 |
| 68 | 4 | reserved_a | zero | |
| 72 | 32 | dataset_policy | the consensus digest arm over `class_v6_dataset_steps` and `class_v6_family_flags` (spec 1.0, 1.13.3) | 70a6c703787d5d75cdbc486b34acf3eb901f4188bae3e948c94274d5f4ac4bba |
| 104 | 8 | program_id | the class's program id for the epoch (the signing id) | 0x2a1d6caab4c24564 |
| 112 | 32 | epoch_seed | the epoch's program seed bytes (devnet: the epoch block hash, spec 1.12) | af89be5ddbadb6f6b4aee28ac8f249713be5d4c12621e3cea7f83ceada3c66b3 |
| 144 | 8 | epoch_start_daa | the epoch's start score `s = 86,400 d + L e` (spec 1.12) | PLACEHOLDER |
| 152 | 32 | era_seed | the era seed bytes | edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 |
| 184 | 8 | day_index | the day as the frozen class derives it (`bind.rs`: `timestamp_ms / 86,400,000`) | 20730 |
| 192 | 32 | state_root | the state stream root the class's dataset is keyed on | PLACEHOLDER (the node1 state file is sha256 abb5800350b02dd9...; the root bytes: BLOCKED B-N3) |
| 224 | 32 | header_prehash | `hash_override_nonce_time(header, 0, header.timestamp)` (`bind.rs`, the fork at e8773ff5) | PLACEHOLDER (`00...01`, the composition test's prehash) |
| 256 | 8 | daa_score | the header's | PLACEHOLDER |
| 264 | 8 | timestamp_ms | the header's | PLACEHOLDER (first millisecond of day 20730) |
| 272 | 4 | bits | the header's | PLACEHOLDER |
| 276 | 4 | reserved_b | zero | |
| 280 | 8 | trial_id | the header nonce, all 64 bits | 0 |
| 288 | 1 | log2_n | consensus parameter | PLACEHOLDER 24 |
| 289 | 1 | indeg | 2 (DRSample) | 2 |
| 290 | 2 | label_bytes | `lambda / 8` | PLACEHOLDER 32 (section 3, O-12) |
| 292 | 2 | k | challenges | PLACEHOLDER 0 (O-11) |
| 294 | 2 | reserved_c | zero | |
| 296 | 32 | graph_seed | consensus constant: the DRSample sampling seed | PLACEHOLDER zero |
The header fields the brief names are all bound. `header_prehash` absorbs, at e8773ff5: `version`, `parents_by_level` (the
parent set, every level), `hash_merkle_root` (the coinbase commitment: the coinbase is in the transaction root),
`accepted_id_merkle_root`, `utxo_commitment`, `timestamp`, `bits`, the nonce as zero, `daa_score`, `blue_score`, `blue_work`,
`pruning_point`, `vote_key_hash`. The timestamp window is the node's acceptance rule; the instance binds the exact timestamp, so
each admissible timestamp is a different instance. `daa_score` and `timestamp_ms` also stand as explicit fields because the
epoch, day and era rules read them; the verifier checks each explicit field against the header and the chain.
The oracle and the domain separation:
| Use | Query | Paper form |
|---|---|---|
| Oracle | `H_lambda(role, x)` = BLAKE2b, digest length `lambda/8` bytes, personalisation `igneum-mhpow/1` plus two zero bytes, input `role || x`; `lambda <= 512` (above 512 needs an XOF: B-T6) | one random oracle `H: {0,1}* -> {0,1}^lambda` |
| Instance digest | `chi = H_lambda('I', instance)` | the paper's input `chi` in `{0,1}^lambda` (Definition 5) |
| Source label | `l_1 = H_lambda('L', chi || u32 1)` | `l_1 = H(chi, 1)` |
| Label | `l_v = H_lambda('L', chi || u32 v || l_p1 || l_p2)`, parents ascending, the parent count fixed by the graph | `l_v = H(chi, v, l_v1, ..., l_vk)` |
| Merkle node | `tau_x = H_lambda('M', chi || tau_x0 || tau_x1)`; leaf `x` in `{0,1}^n` is `l_(1 + bin(x))`; root `tau = tau_empty` | section 4.1, salt `chi` |
| Challenge | `c_i = H_lambda('C', chi || u16 i || tau) mod N`, `i = 1 .. k`; open `l_(c_i + 1)` and its parents | `c_i = H(chi, i, tau) mod N` |
| Lottery value | NONE in this draft (O-07) | the paper has none |
Every query is fixed width once the role and the node are known, which keeps Theorem 5's parse unique (the extractor reads the
`h`-th parent at a fixed offset after `chi` and `v`). The role byte keeps the four query families disjoint. The paper uses one
oracle with structured inputs and no role byte; the role byte is a refinement that leaves every event the proofs bound
(COLLISION, BADORDER, MISCOLOR, LUCKYQUERY) defined as before.
The example instance's bytes, its sha256 (7e24f382...), `chi` (3eb972d6... at trial 0, ef38f6bc... at trial 1) and `l_1` are in
`figures.txt` section 2. They check the layout only: eleven fields are PLACEHOLDER until a live template (the node lane) and the
parameter choice (B3, B4) fill them. They are not conformance vectors; the B1 lane owns the primitive's known-answer fixtures.
## 3. The obligations table
| Id | Obligation | The construction's answer | Paper reference and constants | Reading |
|---|---|---|---|---|
| O-01 | The instance encoding is injective: two different (network, version, epoch, template, trial) tuples never give one `chi` | fixed widths, explicit lengths, a tag and a work version; `chi` from the same oracle | a `chi` collision is a COLLISION event: Lemma 7, `C(q,2) 2^-lambda` | ANSWERED |
| O-02 | Domain separation of the oracle's roles | the role byte and the personalisation (section 2) | Theorem 5's parse needs fixed offsets; Lemmas 7, 8, 10 hold for any query form | ANSWERED (the role byte is not in the paper; B-T7 asks B4 to confirm no step uses a cross-role coincidence) |
| O-03 | Reuse on another template: an accepted certificate replayed with another parent set, DAA score, coinbase or timestamp | every one of those changes `header_prehash`, so `chi`; every label, Merkle and challenge query carries `chi`, so the openings fail local consistency and the challenges move | Definition 5 soundness is per `chi`; Corollary 3 fixes `chi`; a replay needs `chi = chi'` (O-01) | ANSWERED |
| O-04 | Reuse on another epoch, day, era, network or protocol version | the same: those fields are in the instance, and the verifier recomputes each from the chain (never from the certificate) | as O-03 | ANSWERED, conditional on B-N1 (the verifier's recomputation rule and the live day rule) |
| O-05 | The graph is fixed before the prover acts (the Dinur and Nadler lesson) | DRSample sampled once from `graph_seed`, a consensus constant; never from `chi`, the epoch, the era or the template | the paper's own condition (section 1.2): MTP is sound "as long as the underlying graph is fixed a priori"; Fact 8 is an existence statement | BLOCKED B-T1: the probability that one sampled DRSample instance at the chosen `N` fails the `(c3 N / log N, c4 N)` depth-robustness, and whether a per-era re-seed is admissible |
| O-06 | Grinding the commitment to dodge the challenges (try roots until all `k` challenges land on honestly computed nodes) | `k` from Corollary 2 | Lemma 4: `q (1 - beta)^k`; Corollary 2: `beta = e/(2N)`, `k = (2 N ln 2 / e) lambda` gives `q 2^-lambda`; figures section 4 | ANSWERED (the constants of `k` wait on `c3`: B-T2) |
| O-07 | Grinding the lottery: what the prover can vary per draw, at what cost, once one labelling exists | no lottery rule is adopted. Two candidate rules fail: (a) `W = H('W' || chi || tau) <= target`: after one honest labelling the prover sets the sink label `l'_N` to fresh bytes (the sink turns red), rehashes its Merkle path and draws again, `log2 N + 1` calls a draw, and the certificate still verifies unless a challenge lands on the sink, probability `(1 - 1/N)^k` (0.99988 at `N = 2^24`, `k = 2,000`); honest work per trial is `2N` calls, so one labelling buys about `1.3 x 10^6` draws at `N = 2^24` (figures section 5). Varying any unchallenged node with few descendants does the same; a fixed always-checked set of the last `m` nodes moves the cost to about `m` labels a draw, and only full verification removes it. (b) `W = H(chi) <= target`: the prover screens trials for free and labels only the winner, so the memory work per block is one labelling whatever the difficulty | the paper's theorems lower-bound the cost of a trace that outputs AN accepted certificate (Theorem 7); they say nothing about how many distinct accepted certificates one trace outputs for one `chi`, and Definition 5 has no target. The re-roll is consistent with every theorem in the paper | (a) FAIL, (b) FAIL; the replacement BLOCKED B-T3 |
| O-08 | Grinding through the template: timestamp inside the window, coinbase extranonce, transaction set, parent choice, the nonce | each is a new `chi`, hence a full new labelling (no label query is shared across `chi`) | per `chi`: Corollary 3, `(lambda/4)(c3 c4 / 2) N^2 / log N` cumulative memory; across many `chi`: O-10 | ANSWERED per trial; the many-trial bound BLOCKED (O-10) |
| O-09 | Chosen instance: the prover picks a favourable `chi` | the graph is data-independent and fixed (O-05), so no `chi` changes the graph's structure; the only freedom is which `chi` to label | Corollary 3 fixes `chi` before the oracle; a `chi` chosen after querying the oracle needs a union over the candidates (at most `q`), a loss the paper does not state | BLOCKED B-T4 |
| O-10 | Amortisation: one labelling, or one shared state, serving many trials or many templates | none can share labels across `chi` (O-03); the lower bound for producing accepted certificates on `m` distinct inputs is the open part | not in the paper (every statement fixes one `chi`); the natural route is the disjoint union of `m` copies (cumulative pebbling cost adds over components), the extractor run on the union, LUCKYQUERY bounded per copy | BLOCKED B-T5 |
| O-11 | Partial evaluation: answering the challenges without the full labelling | the red-node budget `beta N` and the challenge count `k` | Theorem 7 (`beta`), Lemma 5 (`cc(G - S) >= (e - |S|) d`), Corollary 2 (`|S| <= e/2`, `cc(G') >= e d / 2`); Section 7: `k` must be `omega(log N / log log N)`, Theorem 10 attacks below that; certificate sizes in figures section 4: 19.5 MiB at `lambda = 256`, `N = 2^24`, `c3 = 1`; 194.9 MiB at `c3 = 0.1`; with a lottery-sized luck term (`q = 2^80`, `eps = 2^-40`) 9.1 MiB and 91.4 MiB | ANSWERED as formulas; the numbers wait on `c3` (B-T2) and the network budget (B6) |
| O-12 | The concrete constants: no hidden big-O in a numerical claim (plan section 8) | `lambda` chosen against the adversary's query count `q` | Lemma 3 needs `lambda/4 >= 2 log2 q + 1`: `lambda = 256` covers `q <= 2^31.5`, `512` covers `2^63.5`, `1024` covers `2^127.5` (figures section 3). Without the rounding the charge per pebble is `lambda - 2 log2 q - 1` bits: 127 of 256 at `q = 2^64` (0.496), 95 at `q = 2^80`. The proved ratio of the honest prover's cumulative memory (about `N^2 lambda / 2`) to the bound is `4 log N / (c3 c4)` | BLOCKED B-T2 (`c3`, `c4` for DRSample at the chosen `N`) and B-T6 (`lambda` for a network-scale `q`, and the XOF above 512 bits) |
| O-13 | The cost measure is the one the 1.5x target needs | none in the paper: the bound is cumulative memory in the parallel random oracle model, not joules, bandwidth or SRAM against DRAM | the plan's section 2 and B4: a theorem in bits times rounds is level 2 evidence; the physical translation (level 3) and the full-SRAM design (B5) are separate obligations | BLOCKED B-T8 |
| O-14 | Rejected trials and cancellation: a trial in flight when the template changes | the instance binds the parent set, so a new block on the network stales every trial in flight; the honest trial takes `O(N)` sequential rounds (Definition 5, item 1) | not a soundness matter; it is a feasibility bound: `N x t_seq <= rho x T_block`, with `T_block` 1 s on the devnet (spec 1.12) and `rho` the stale fraction the chain accepts. The bound caps `N`, and a small `N` fits one instance in SRAM, which is the B5 question | BLOCKED B-N2 (the node lane: `T_block` and `rho` on devnet-4) and B3 (`t_seq` measured, never assumed) |
| O-15 | The frozen class stays the chain's binding: the dataset policy 70a6c703 and the signing id 2a1d6caab4c24564 | the instance carries `dataset_policy` (all 32 bytes), `program_id`, `epoch_seed`, `epoch_start_daa`, `era_seed`, `day_index` and `state_root`; the verifier recomputes each from the chain and the header and rejects a mismatch, so no certificate carries across a class object, a policy or an epoch | by construction plus O-01, O-03. Two facts for the node lane: the policy digest sits outside the consensus digest until the class v6 floor is set (f0 manifest), so the instance is the only place a certificate binds it today; and the day rule differs between `bind.rs` (the interim `timestamp_ms / 86,400,000`) and spec 1.12 (DAA seconds) | ANSWERED by construction, conditional on B-N1 and B-N3 |
| O-16 | The epoch-seed lead and the day-pack: no precomputation across trials | the labelling needs `chi`, which needs the template's parents; the 600 DAA-second epoch-seed lead and the day key (known before the day) give no label in advance. Static per-day work (the v6 dataset) stays amortisable by design; the instance removes it from the MHPoW part only | O-03 | ANSWERED |
## 4. The BLOCKED questions, with their owners
| Id | Owner | The exact question |
|---|---|---|
| B-T1 | B4 theory lane (to be named) | For DRSample sampled from a fixed public seed at `N = 2^20` to `2^24`, what is the probability that the sampled graph is not `(c3 N / log N, c4 N)`-depth-robust, with the constants written out? Is a per-era re-seed from the era seed admissible, given that a party able to bias the era seed could search for a weak graph? |
| B-T2 | B4 | The values of `c3` and `c4` (Fact 8, from ABH17 or ABP17) for the sampled DRSample at the chosen `N`, proved, so that Corollary 3's bound and `k = 2 lambda log N / c3` become numbers. Without them every certificate size and every lower bound in this README is a sensitivity row. |
| B-T3 | B4 | The lottery. Give a lottery value `W` (or prove none exists) such that the expected number of distinct accepted pairs (`chi`, certificate) with `W` under the target, output by an adversary of cumulative memory `C`, is at most `C` divided by a constant fraction of Corollary 3's bound, with sampled verification of polylog cost. Known: any `W` that depends on labels the verifier samples can be re-rolled through an unchallenged node at the cost of that node's descendants (O-07 (a)); any `W` of `chi` alone is screened for free (O-07 (b)). Candidates for the panel: a succinct proof of the full labelling (a unique output, so Alwen and Serbinenko's bound per trial applies, at a prover cost the paper's own section 1 calls non-egalitarian); or a construction that is not MTP. |
| B-T4 | B4 | The adaptive-input version of Corollary 3: the adversary chooses `chi` among at most `q` candidates after querying `H`. State the loss in the bad-event sum. |
| B-T5 | B4 | The multi-instance version: producing accepted certificates for `m` distinct inputs costs at least `m (lambda/4)(c3 c4 / 2) N^2 / log N` except with what probability, and how the `q` and `t` terms scale with `m`. |
| B-T6 | B4 | The label width `lambda` for an adversary of `q = 2^64` to `2^96` queries: either `lambda >= 8 log2 q + 4` under Lemma 3 as stated (516 to 772 bits, so an XOF in place of BLAKE2b's 512-bit maximum), or a tighter statement of Theorem 6 with the charge `lambda - 2 log2 q - log2 indeg` per pebble at `lambda = 256`. |
| B-T7 | B4 | Confirm that no step in Theorems 4 to 7 or Lemmas 2 to 4 uses a coincidence between a label query and a Merkle or challenge query that the role byte would remove (the role byte only splits the domain; the check is that the extractor's hint and parse still hold). |
| B-T8 | B4 with the adversary lane | The translation from cumulative memory to energy and bandwidth (the plan's R2, R3), including the full-SRAM design at the `N` that O-14 allows. |
| B-N1 | node lane a283f5f0d364ceef0 | Which day rule is live on devnet-4 (`bind.rs`'s timestamp day or spec 1.12's DAA day), and the rule the verifier uses to recompute `epoch_seed`, `epoch_start_daa`, `era_seed`, `program_id` and the policy digest from a header and the chain. |
| B-N2 | node lane | The block interval on devnet-4, the timestamp acceptance window (past median and future bound), and the stale fraction the chain accepts, so O-14's bound on `N x t_seq` is a number. |
| B-N3 | node lane | The 32 bytes of the state stream root the class v6 dataset is keyed on for the signing pair (epoch af89be5d, era edc4fa84, day 20730), and whether that root is in the header's past by the epoch boundary. |
## 5. What this means for Track B
1. The input binding is complete for reuse: no certificate carries across a template, a trial, an epoch, a day, an era, a network
or a class object (O-01 to O-04, O-15, O-16).
2. The economic binding is not: the reference construction has no lottery, and the two direct ways to add one either buy about a
million draws per labelling at `N = 2^24` or screen trials for free (O-07). Until B-T3 is answered, an MTP certificate per
block adds memory work per block, never per lottery trial, and cannot move the 1.5x ratio.
3. Binding the template forces each trial to finish inside a fraction of the block interval (O-14), which caps `N` and pushes the
instance toward a size that fits in SRAM. B3 and B5 own that collision; it is recorded here because the binding causes it.
4. Even granted a lottery, the proved bound sits a factor `4 log N / (c3 c4)` under the honest prover's cumulative memory with
unknown `c3 c4` (O-12), and its unit is memory-time, not energy (O-13).
No row here is a PASS. The registry batch is not run.

View file

@ -0,0 +1,96 @@
== 1. canonical instance layout (work_version 1, draft)
offset width field encoding
0 24 tag ASCII 'igneum-mhpow/instance/1/'
24 32 network_id u8 length, then ASCII, zero padded
56 8 chain_id u64 LE
64 2 header_version u16 LE, the header's version field (object byte in bits 8 to 13)
66 2 work_version u16 LE, 1 for this draft
68 4 reserved_a zero
72 32 dataset_policy the consensus digest arm's 32 bytes
104 8 program_id u64 LE, the class's program (signing) id
112 32 epoch_seed the epoch's program seed bytes
144 8 epoch_start_daa u64 LE, the epoch's start DAA score
152 32 era_seed the era seed bytes
184 8 day_index u64 LE, the day as the frozen class derives it
192 32 state_root the state stream root the class's dataset is keyed on
224 32 header_prehash hash_override_nonce_time(header, 0, header.timestamp)
256 8 daa_score u64 LE
264 8 timestamp_ms u64 LE
272 4 bits u32 LE
276 4 reserved_b zero
280 8 trial_id u64 LE, the header nonce, all 64 bits
288 1 log2_n u8
289 1 indeg u8, 2 for DRSample
290 2 label_bytes u16 LE, lambda / 8
292 2 k u16 LE, challenges
294 2 reserved_c zero
296 32 graph_seed consensus constant: the DRSample sampling seed
total 328
== 2. example instance (layout check only; PLACEHOLDER fields named in the docstring)
0 69676e65756d2d6d68706f772f696e7374616e63652f312f0f69676e65756d2d
32 6465766e65742d34000000000000000000000000000000007111000000000000
64 010801000000000070a6c703787d5d75cdbc486b34acf3eb901f4188bae3e948
96 c94274d5f4ac4bba6445c2b4aa6c1d2aaf89be5ddbadb6f6b4aee28ac8f24971
128 3be5d4c12621e3cea7f83ceada3c66b30000000000000000edc4fa844da9dc98
160 d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07fa50000000000000
192 0000000000000000000000000000000000000000000000000000000000000000
224 0000000000000000000000000000000000000000000000000000000000000001
256 000000000000000000d83504a101000000000000000000000000000000000000
288 1802200000000000000000000000000000000000000000000000000000000000
320 0000000000000000
instance_sha256 7e24f382cef23fa1e70a401c9a1501aa8ffaf0f53876079ad9801e3eb7ecf446
chi = H_256('I' || instance) 3eb972d69f23312da84ac302cfefdd30dc176a7970bd2fe403749f7372d87d97
chi at trial_id 1 ef38f6bcc3cbf2ea68df98bab7b736fb394ef0a1d9436283f03f94b6dd55c9d4
l_1 = H_256('L' || chi || u32 1) bb29fd04fd6fd36ffc2fecc632455286bd98f1c259de0e3eb4d5c85a5ae56ff8
== 3. Lemma 3 precondition and the per-pebble charge (indeg 2)
lambda log2_q precondition lambda/4 >= 2 log2 q + 1 charge bits lambda - 2 log2 q - 1 charge / lambda max log2 q under the precondition
256 32 FAILS 191 0.746 31.5
256 48 FAILS 159 0.621 31.5
256 64 FAILS 127 0.496 31.5
256 80 FAILS 95 0.371 31.5
256 96 FAILS 63 0.246 31.5
512 32 holds 447 0.873 63.5
512 48 holds 415 0.811 63.5
512 64 FAILS 383 0.748 63.5
512 80 FAILS 351 0.686 63.5
512 96 FAILS 319 0.623 63.5
1024 32 holds 959 0.937 127.5
1024 48 holds 927 0.905 127.5
1024 64 holds 895 0.874 127.5
1024 80 holds 863 0.843 127.5
1024 96 holds 831 0.812 127.5
== 4. Corollary 2: k = (2 N ln 2 / e) lambda with e = c3 N / log2 N, so k = 2 ln 2 lambda log2 N / c3
certificate bytes ~ k * (1 + indeg) * (lambda/8) * (log2 N + 1) (one label and its Merkle path per opened label;
no sharing of paths; the root and the encoding overhead omitted). c3 is DRSample's depth-robustness constant:
the paper (Fact 8) states it exists and gives no value; the rows are a sensitivity table, not a parameter.
lambda log2_N c3 k certificate_MiB
256 20 1.0 7098 13.6
256 20 0.5 14196 27.3
256 20 0.1 70979 136.5
256 24 1.0 8518 19.5
256 24 0.5 17035 39.0
256 24 0.1 85174 194.9
the same luck term for a lottery target eps instead of 2^-lambda: k = ln(q / eps) / beta, beta = c3 / (2 log2 N)
log2_q log2_eps log2_N c3 k certificate_MiB (lambda 256)
64 -40 20 1.0 2884 5.5
64 -40 20 0.1 28835 55.4
64 -40 24 1.0 3461 7.9
64 -40 24 0.1 34602 79.2
80 -40 20 1.0 3328 6.4
80 -40 20 0.1 33272 64.0
80 -40 24 1.0 3993 9.1
80 -40 24 0.1 39926 91.4
== 5. O-07 counter-example: one labelling, many lottery draws, when the lottery value is W = H('W' || chi || tau)
honest calls per trial: N labels + (N - 1) Merkle nodes + 1 lottery hash
re-roll per draw: set the sink label l'_N to fresh bytes (sink red), rehash its Merkle path (log2 N calls),
one lottery hash; the certificate passes iff no challenge lands on the sink: (1 - 1/N)^k
log2_N k honest_calls_per_trial reroll_calls_per_draw amortisation pass_probability
20 2000 2097152 21 9.99e+04 0.998094
20 9000 2097152 21 9.99e+04 0.991454
24 2000 33554432 25 1.34e+06 0.999881
24 9000 33554432 25 1.34e+06 0.999464

View file

@ -0,0 +1,37 @@
{
"run_id": "mhpow-b2-20261009-01",
"manifest_sha": "13c3646e",
"evidence_dir": "docs/analysis/mhpow/b2",
"method": "model",
"note": "B2, the binding spec lane of the 1.5x programme's Track B (9 October 2026): the canonical instance input for an MTP/DRSample memory-hard proof of work in Igneum's setting (328 bytes, work version 1, draft) and the obligations table O-01 to O-16 read against Blocki and Smearsoll, eprint 2025/1456 (the full PDF, sha256 13c3646e...). Reuse across template, trial, epoch, day, era, network and class object: answered by the paper's per-input soundness plus the layout. The lottery: two candidate rules FAIL with written counter-examples (re-roll through a red sink, about 1.3 x 10^6 draws per labelling at N = 2^24; free screening on chi alone); the replacement and the multi-instance, adaptive-input, constants and energy questions BLOCKED to the B4 theory lane and the node lane. Figures by tools/mhpow/b2/b2_figures.py on build-9. NOT RUN until the panel reads it; this lane writes no PASS.",
"cells": [
{
"cell": "model:mhpow-b2-binding",
"cases": [
"POW-05",
"POW-06"
],
"status": "NOT RUN",
"method": "model",
"evidence": "docs/analysis/mhpow/b2/README.md",
"note": "POW-05: obligations O-06 to O-11 and O-16 (grinding, chosen instance, amortisation, partial evaluation, precomputation); O-07 records two FAIL rules and B-T3 the open question. POW-06: O-11's certificate sizes as functions of DRSample's unproved constant c3 (13.6 to 194.9 MiB at lambda 256), a sensitivity table only; the verifier budget is B6's.",
"claim_impact": "none: no public figure moves; Track B's construction is not adopted and nothing activates",
"in_progress": true
}
],
"map_cell_requested": {
"model:mhpow-b2-binding": {
"command": "python3 tools/mhpow/b2/b2_figures.py > docs/analysis/mhpow/b2/figures.txt (a build box under a pid file; byte-identical output, sha256 007dfe0f...)",
"box_class": "build box, CPU only (build-9)",
"fixtures": [],
"cases": [
"POW-05",
"POW-06"
],
"coverage": {
"POW-05": "partial: the binding obligations for the Track B work unit only; the lottery binding is BLOCKED (B-T3) and the multi-instance bound BLOCKED (B-T5)",
"POW-06": "partial: certificate size as a formula; no verifier timing, no malformed-input cost"
}
}
}
}

View file

@ -52,3 +52,6 @@ docs/analysis/class-v6/reference-population.md
docs/analysis/class-v6/eco-05-scenarios.md
docs/analysis/class-v6/eco-05-results.md
docs/analysis/proving-outcome-ledger.md
# 9 October 2026: the 1.5x programme Track B, the B2 binding spec lane (research documents and their generator)
docs/analysis/mhpow/b2
tools/mhpow/b2

View file

@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""B2 (Track B, the binding spec lane): every figure in docs/analysis/mhpow/b2/README.md, code-generated.
Standard library only. Runs on a build box under a pid file, never on the Mac (the founder's stop rule 2):
python3 tools/mhpow/b2/b2_figures.py > docs/analysis/mhpow/b2/figures.txt
Sections:
1. the canonical instance layout (offsets, widths), self-checked contiguous;
2. the example instance (igneum-devnet-4, the frozen class's signing pair) as hex and its digest chi under the draft
oracle H_lambda (BLAKE2b, personal "igneum-mhpow/1", one role byte); PLACEHOLDER fields are named as such;
3. Lemma 3's precondition lambda/4 >= 2 log2 q + log2 indeg and the per-pebble charge lambda - 2 log2 q - log2 indeg
(the extractor's bound before the paper rounds it to lambda/4);
4. Corollary 2's challenge count and the certificate size, as functions of DRSample's unproved constant c3;
5. the lottery re-roll counter-example (obligation O-07): honest oracle calls per trial against the re-roll's.
Nothing here is a measurement. Every row is arithmetic on the paper's statements (eprint 2025/1456, the copy with
sha256 13c3646e7d85c1aa58a92914582caab5798d90cf2a3cad33e58d81d49c1d31db) and on the draft layout.
"""
import hashlib
import math
import struct
import sys
PERSONAL = b"igneum-mhpow/1\x00\x00" # 16 bytes, the BLAKE2b personalisation of every B2 oracle call
assert len(PERSONAL) == 16
ROLE_INSTANCE = b"I"
ROLE_LABEL = b"L"
ROLE_MERKLE = b"M"
ROLE_CHALLENGE = b"C"
TAG = b"igneum-mhpow/instance/1/"
assert len(TAG) == 24
# (name, width in bytes, encoding note)
LAYOUT = [
("tag", 24, "ASCII 'igneum-mhpow/instance/1/'"),
("network_id", 32, "u8 length, then ASCII, zero padded"),
("chain_id", 8, "u64 LE"),
("header_version", 2, "u16 LE, the header's version field (object byte in bits 8 to 13)"),
("work_version", 2, "u16 LE, 1 for this draft"),
("reserved_a", 4, "zero"),
("dataset_policy", 32, "the consensus digest arm's 32 bytes"),
("program_id", 8, "u64 LE, the class's program (signing) id"),
("epoch_seed", 32, "the epoch's program seed bytes"),
("epoch_start_daa", 8, "u64 LE, the epoch's start DAA score"),
("era_seed", 32, "the era seed bytes"),
("day_index", 8, "u64 LE, the day as the frozen class derives it"),
("state_root", 32, "the state stream root the class's dataset is keyed on"),
("header_prehash", 32, "hash_override_nonce_time(header, 0, header.timestamp)"),
("daa_score", 8, "u64 LE"),
("timestamp_ms", 8, "u64 LE"),
("bits", 4, "u32 LE"),
("reserved_b", 4, "zero"),
("trial_id", 8, "u64 LE, the header nonce, all 64 bits"),
("log2_n", 1, "u8"),
("indeg", 1, "u8, 2 for DRSample"),
("label_bytes", 2, "u16 LE, lambda / 8"),
("k", 2, "u16 LE, challenges"),
("reserved_c", 2, "zero"),
("graph_seed", 32, "consensus constant: the DRSample sampling seed"),
]
def offsets():
out, off = [], 0
for name, width, note in LAYOUT:
out.append((off, width, name, note))
off += width
return out, off
def h(role, data, out_bytes):
"""The draft oracle H_lambda: BLAKE2b, digest_size = lambda / 8 (<= 64), personal PERSONAL, input role || data."""
assert len(role) == 1 and 1 <= out_bytes <= 64
return hashlib.blake2b(role + data, digest_size=out_bytes, person=PERSONAL).digest()
def network_field(name):
b = name.encode("ascii")
assert len(b) <= 31
return bytes([len(b)]) + b + bytes(31 - len(b))
def example_instance():
"""The example of the brief: igneum-devnet-4, chain id 4465, the frozen class's signing pair.
PLACEHOLDER fields: header_version, state_root, header_prehash, daa_score, timestamp_ms, bits, epoch_start_daa,
log2_n, label_bytes, k, graph_seed. Their real values come from a live template (the node lane) and from B4's
parameter choice; the digest below is a layout check, not a conformance vector.
"""
epoch = bytes.fromhex("af89be5ddbadb6f6b4aee28ac8f249713be5d4c12621e3cea7f83ceada3c66b3")
era = bytes.fromhex("edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07")
policy = bytes.fromhex("70a6c703787d5d75cdbc486b34acf3eb901f4188bae3e948c94274d5f4ac4bba")
prehash = bytes(31) + b"\x01" # the composition test's prehash (byte 31 = 1), PLACEHOLDER
fields = {
"tag": TAG,
"network_id": network_field("igneum-devnet-4"),
"chain_id": struct.pack("<Q", 4465),
"header_version": struct.pack("<H", 0x0801), # PLACEHOLDER: object byte 8 (class v6), low byte 1
"work_version": struct.pack("<H", 1),
"reserved_a": bytes(4),
"dataset_policy": policy,
"program_id": struct.pack("<Q", 0x2A1D6CAAB4C24564),
"epoch_seed": epoch,
"epoch_start_daa": struct.pack("<Q", 0), # PLACEHOLDER
"era_seed": era,
"day_index": struct.pack("<Q", 20730),
"state_root": bytes(32), # PLACEHOLDER: the class's state root (node lane)
"header_prehash": prehash,
"daa_score": struct.pack("<Q", 0), # PLACEHOLDER
"timestamp_ms": struct.pack("<Q", 20730 * 86_400_000), # PLACEHOLDER: the first millisecond of day 20730
"bits": struct.pack("<I", 0), # PLACEHOLDER
"reserved_b": bytes(4),
"trial_id": struct.pack("<Q", 0),
"log2_n": bytes([24]), # PLACEHOLDER (B3/B4)
"indeg": bytes([2]),
"label_bytes": struct.pack("<H", 32), # PLACEHOLDER (lambda = 256; see section 3)
"k": struct.pack("<H", 0), # PLACEHOLDER (section 4)
"reserved_c": bytes(2),
"graph_seed": bytes(32), # PLACEHOLDER (B1/B4)
}
parts = []
for name, width, _ in LAYOUT:
v = fields[name]
assert len(v) == width, (name, len(v), width)
parts.append(v)
return b"".join(parts)
def section1():
rows, total = offsets()
print("== 1. canonical instance layout (work_version 1, draft)")
print("offset\twidth\tfield\tencoding")
for off, width, name, note in rows:
print(f"{off}\t{width}\t{name}\t{note}")
print(f"total\t{total}")
return total
def section2(total):
inst = example_instance()
assert len(inst) == total
chi = h(ROLE_INSTANCE, inst, 32)
print()
print("== 2. example instance (layout check only; PLACEHOLDER fields named in the docstring)")
for i in range(0, len(inst), 32):
print(f"{i:4d} {inst[i:i + 32].hex()}")
print(f"instance_sha256\t{hashlib.sha256(inst).hexdigest()}")
print(f"chi = H_256('I' || instance)\t{chi.hex()}")
# one trial step: chi changes with the trial id, every label query carries chi
inst2 = bytearray(inst)
off = [o for o, w, n, _ in offsets()[0] if n == "trial_id"][0]
inst2[off:off + 8] = struct.pack("<Q", 1)
print(f"chi at trial_id 1\t{h(ROLE_INSTANCE, bytes(inst2), 32).hex()}")
l1 = h(ROLE_LABEL, chi + struct.pack("<I", 1), 32)
print(f"l_1 = H_256('L' || chi || u32 1)\t{l1.hex()}")
def section3():
print()
print("== 3. Lemma 3 precondition and the per-pebble charge (indeg 2)")
print("lambda\tlog2_q\tprecondition lambda/4 >= 2 log2 q + 1\tcharge bits lambda - 2 log2 q - 1\tcharge / lambda\tmax log2 q under the precondition")
for lam in (256, 512, 1024):
qmax = (lam / 4 - 1) / 2
for lq in (32, 48, 64, 80, 96):
pre = lam / 4 >= 2 * lq + 1
charge = lam - 2 * lq - 1
print(f"{lam}\t{lq}\t{'holds' if pre else 'FAILS'}\t{charge}\t{charge / lam:.3f}\t{qmax:.1f}")
def section4():
print()
print("== 4. Corollary 2: k = (2 N ln 2 / e) lambda with e = c3 N / log2 N, so k = 2 ln 2 lambda log2 N / c3")
print(" certificate bytes ~ k * (1 + indeg) * (lambda/8) * (log2 N + 1) (one label and its Merkle path per opened label;")
print(" no sharing of paths; the root and the encoding overhead omitted). c3 is DRSample's depth-robustness constant:")
print(" the paper (Fact 8) states it exists and gives no value; the rows are a sensitivity table, not a parameter.")
print("lambda\tlog2_N\tc3\tk\tcertificate_MiB")
for lam in (256,):
for ln in (20, 24):
for c3 in (1.0, 0.5, 0.1):
k = math.ceil(2 * math.log(2) * lam * ln / c3)
b = k * 3 * (lam // 8) * (ln + 1)
print(f"{lam}\t{ln}\t{c3}\t{k}\t{b / 2**20:.1f}")
print()
print(" the same luck term for a lottery target eps instead of 2^-lambda: k = ln(q / eps) / beta, beta = c3 / (2 log2 N)")
print("log2_q\tlog2_eps\tlog2_N\tc3\tk\tcertificate_MiB (lambda 256)")
for lq in (64, 80):
for le in (-40,):
for ln in (20, 24):
for c3 in (1.0, 0.1):
beta = c3 / (2 * ln)
k = math.ceil(((lq - le) * math.log(2)) / beta)
b = k * 3 * 32 * (ln + 1)
print(f"{lq}\t{le}\t{ln}\t{c3}\t{k}\t{b / 2**20:.1f}")
def section5():
print()
print("== 5. O-07 counter-example: one labelling, many lottery draws, when the lottery value is W = H('W' || chi || tau)")
print(" honest calls per trial: N labels + (N - 1) Merkle nodes + 1 lottery hash")
print(" re-roll per draw: set the sink label l'_N to fresh bytes (sink red), rehash its Merkle path (log2 N calls),")
print(" one lottery hash; the certificate passes iff no challenge lands on the sink: (1 - 1/N)^k")
print("log2_N\tk\thonest_calls_per_trial\treroll_calls_per_draw\tamortisation\tpass_probability")
for ln in (20, 24):
n = 2 ** ln
for k in (2000, 9000):
honest = n + (n - 1) + 1
reroll = ln + 1
p = (1 - 1 / n) ** k
print(f"{ln}\t{k}\t{honest}\t{reroll}\t{honest / reroll:.3g}\t{p:.6f}")
def main():
total = section1()
section2(total)
section3()
section4()
section5()
return 0
if __name__ == "__main__":
sys.exit(main())