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

This commit is contained in:
igneum-labs 2026-10-09 09:33:18 +00:00
commit cb524ca6ed
4 changed files with 30 additions and 27 deletions

View file

@ -1,6 +1,6 @@
# 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
Lane: B2, the binding spec lane. Written 9 October 2026, 10:0x to 10:3x UK; re-cut at 10:3x to the B1 lane's encoding pins. 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).
@ -9,12 +9,13 @@ registry until the panel reads it; this lane writes no PASS. The status words in
| 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 |
| 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 | all 46 pages: sections 1.3, 2, 3, 4, 5, 6, 7 and Appendices A (Lemmas 7 to 10) and C (Merkle reveal and check) 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` |
| The B1 lane's encoding pins (R15-05, a4490b2dfe9114a55, 10:2x UK) | unkeyed BLAKE2b-256, `lambda = 256`, one ASCII role byte per query kind, u64 LE integers, raw-label leaves, path bit `j` = bit `j - 1` of the leaf index (LSB at the root), no path sharing, the graph from `'G' || graph_seed || u64 v || u64 ctr` by rejection sampling (DRSample from ABH17 Algorithm 1) | adopted here whole; B1 owns the primitive and its fixtures |
| Figures | `tools/mhpow/b2/b2_figures.py` (sha256 7b8664ea44b770c9f6af246898aa576f3f0a63e65e7bf50a0b278c5727a1ee8b) | output `figures.txt` (sha256 27bb414c8534a438fdeae98cba2d4b9ece9c21d0487aa872673b0da465d9702a), 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
@ -88,20 +89,21 @@ 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` |
| Oracle | `H(role, x)` = unkeyed BLAKE2b, digest `lambda/8` = 32 bytes at B1's `lambda = 256`, input `role || x`; BLAKE2b stops at 512 bits (a larger `lambda` needs an XOF: B-T6) | one random oracle `H: {0,1}* -> {0,1}^lambda` |
| Instance digest | `chi = H('I', instance)`, 32 bytes | the paper's input `chi` in `{0,1}^lambda` (Definition 5) |
| Source label | `l_1 = H('L', chi || u64 1)` | `l_1 = H(chi, 1)` |
| Label | `l_v = H('L', chi || u64 v || l_p1 || l_p2)`, parents ascending; node 2 has the one parent 1; the parent count is fixed by the graph | `l_v = H(chi, v, l_v1, ..., l_vk)` |
| Merkle node | `tau_x = H('M', chi || tau_x0 || tau_x1)`; leaves are the raw labels, node `w` at leaf index `w - 1`; root `tau = tau_empty`; a reveal is the label and `n` siblings | section 4.1, Appendix C, salt `chi` |
| Challenge | `c_i = int_le(H('C', chi || u64 i || tau)) mod N`, `i = 1 .. k`; open node `c_i + 1` and each parent, each with its own path | `c_i = H(chi, i, tau) mod N` |
| Graph | DRSample, indeg 2, its draws from `H('G', graph_seed || u64 v || u64 ctr)` by rejection sampling; `graph_seed` a 32-byte public consensus constant, never `chi` | Fact 8; section 1.2 (the graph fixed a priori) |
| 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
`h`-th parent at a fixed offset after `chi` and `v`). The role byte keeps the query families disjoint (B1's note: without it the challenge query `H(chi, i, tau)` has the shape of
an indegree-1 node's prelabel). 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
The example instance's bytes, its sha256 (7e24f382...), `chi` (12b3c35a... at trial 0, 3a02f2f6... 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.
@ -110,7 +112,7 @@ parameter choice (B3, B4) fill them. They are not conformance vectors; the B1 la
| 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-02 | Domain separation of the oracle's roles | the role byte (section 2; B1's pin, 'G' for graph sampling keyed by `graph_seed`, never `chi`) | 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 |
@ -120,7 +122,7 @@ parameter choice (B3, B4) fill them. They are not conformance vectors; the B1 la
| 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-12 | The concrete constants: no hidden big-O in a numerical claim (plan section 8) | `lambda = 256` as B1 pins it; it must be 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 |

View file

@ -40,9 +40,9 @@ total 328
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
chi = H_256('I' || instance) 12b3c35a844467d4d290bc396c4a369d531fce8d33480d1f1148f488fb681943
chi at trial_id 1 3a02f2f609d6d0216cf0a0e9294d6c96e9902ae04ce2420883eba287991a708b
l_1 = H_256('L' || chi || u64 1) 732d54bfc39303ce001cf5dc29e1434eba4ff01a01ce36cccdb0366be8bc5ae3
== 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

View file

@ -21,7 +21,7 @@
],
"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...)",
"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 27bb414c...)",
"box_class": "build box, CPU only (build-9)",
"fixtures": [],
"cases": [

View file

@ -7,8 +7,8 @@ Standard library only. Runs on a build box under a pid file, never on the Mac (t
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;
2. the example instance (igneum-devnet-4, the frozen class's signing pair) as hex and its digest chi under the oracle
B1 pinned for the reference (unkeyed BLAKE2b-256, one ASCII role byte, u64 LE integers); PLACEHOLDER fields named;
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;
@ -22,8 +22,9 @@ 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
# The oracle is B1's pin (R15-05, 10:2x UK 9 October 2026): unkeyed BLAKE2b, 32-byte digest, lambda = 256, one leading ASCII
# role byte per query kind ('L' label, 'M' Merkle inner node, 'C' challenge, 'G' graph sampling; 'I' instance is B2's),
# integers u64 little-endian.
ROLE_INSTANCE = b"I"
ROLE_LABEL = b"L"
@ -71,10 +72,10 @@ def offsets():
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."""
def h(role, data, out_bytes=32):
"""The oracle H: unkeyed BLAKE2b, digest_size = lambda / 8 (32 at lambda = 256), input role || data."""
assert len(role) == 1 and 1 <= out_bytes <= 64
return hashlib.blake2b(role + data, digest_size=out_bytes, person=PERSONAL).digest()
return hashlib.blake2b(role + data, digest_size=out_bytes).digest()
def network_field(name):
@ -154,8 +155,8 @@ def section2(total):
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()}")
l1 = h(ROLE_LABEL, chi + struct.pack("<Q", 1), 32)
print(f"l_1 = H_256('L' || chi || u64 1)\t{l1.hex()}")
def section3():