Merge reference-apps-f04 4e954be8 into master (gate: green on 4e954be8, recorded by tools/ci/pre-push.sh; landed on the box mirror)

This commit is contained in:
igneum-labs 2026-10-08 19:46:20 +00:00
commit 94889a6561
29 changed files with 241 additions and 107 deletions

View file

@ -244,11 +244,10 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
- Box class: harness (network run on the 2.0 devnet plus Sepolia reads)
- Fixtures: none
- Cases:
- VER-03 Prove successful payment rather than inclusion: partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction
- VER-03 Prove successful payment rather than inclusion: partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction; Review B F04: a receipt claiming lock_state final under a certificate the node reports as a recovery lock is refused, a recovery lock prints as recovery lock, not final (Node and browser negative cases on the fixtures; the field is not live on devnet-4 tonight, so no live recovery lock was seen)
- VER-04 Bound cross-chain oracle trust and replay: partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's
- VER-05 Reconstruct required state without founder storage: partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise
- VER-06 Detect withholding, corruption and stale data: partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run
- VER-08 Keep every user-facing state truthful: partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows
- VER-08 Keep every user-facing state truthful: partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows; Review B F04: /light, /receipt, the receipt file and the one-file verifier show a node-reported recovery lock as recovery lock, not final, /oracle says recovery locks are not accepted by its verifier, the terms block defines the recovery lock
### harness:reference-apps-ver-trust-anchor
@ -268,6 +267,14 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
- UX-05 Keep voting keys with the miner through pooling: partial: the vote-key commitment on the wire (verify::tests::a_share_under_another_key_is_refused_before_the_hash: known-pass the member's key, known-fail another key on the share and a job naming another key, refused with code vote_key before the hash), the open pool's sidechain::check_key (the claimed key, the header's key and the reveal are one key), the same-nonce-other-template wrong_hash test, the TLS binding test; the malicious-pool run and the leave-and-retain run on the devnet-4 pair are the UX-05 batch's other evidence
- UX-04 Pay small operators without hidden custody: partial: PPLNS distribution tests (fee first, never overpays, late joiner), the payout key file round trip, the signed transfer decode; the live payout path on the devnet-4 pair is the UX-04 batch's evidence
### harness:reference-apps-ver-availability
- Command: `node tools/reference-apps/ver/run.mjs (the same run; VER-05 reads from tools/reference-apps/ver/evidence/VER-05.json: the public read node reports its start mode through igneum_getExecStatus, the reference reader on build-1 is the build-server lane's read)`
- Box class: harness (network run on the 2.0 devnet)
- Fixtures: none
- Cases:
- VER-05 Reconstruct required state without founder storage: partial: the public read node must report startedFrom genesis (no snapshot) and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot; the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise. Run ra-20261008T1915-ver: FAIL, the public node reports startedFrom snapshot (restarted from a snapshot after 17:46 UK); the reader on build-1 still from genesis
## Automated cases with no harness in the matrix (NOT RUN, the reason)
- GOV-02 Approve thresholds before results: the approval is recorded in the registry's approval field; the automated half (thresholds frozen before any run_status) is the gate rule landing by 21:00

View file

@ -3715,8 +3715,8 @@
"owner_lane": "reference-apps lane (a2060899d2a27d31c)",
"run_status": "FAIL",
"evidence_path": "tools/reference-apps/ver/evidence/VER-01.json",
"run_id": "ra-20261008T1746-ver",
"updated": "2026-10-08T19:13:20.937Z",
"run_id": "ra-20261008T1915-ver",
"updated": "2026-10-08T19:46:20.696Z",
"evidence_record": {
"cell": "harness:reference-apps-ver-trust-anchor",
"manifest_sha": "4cdcc488",
@ -3724,7 +3724,7 @@
"VER-01": "fails today, disclosed: the light client authenticates the certificate's signatures and the header path but the voter table is a node-supplied trust anchor named on /light; a forged table signature and a wrong-network certificate are refused",
"VER-02": "fails today, disclosed: the execution statement's proof is verified by the nodes and the browser verifies the aggregator signature and the post root under it; an in-browser verifier for the permitted proof and a table commitment close it"
},
"at": "2026-10-08T19:13:20.937Z"
"at": "2026-10-08T19:46:20.696Z"
}
},
{
@ -3755,8 +3755,8 @@
"owner_lane": "reference-apps lane (a2060899d2a27d31c)",
"run_status": "FAIL",
"evidence_path": "tools/reference-apps/ver/evidence/VER-01.json",
"run_id": "ra-20261008T1746-ver",
"updated": "2026-10-08T19:13:20.937Z",
"run_id": "ra-20261008T1915-ver",
"updated": "2026-10-08T19:46:20.696Z",
"evidence_record": {
"cell": "harness:reference-apps-ver-trust-anchor",
"manifest_sha": "4cdcc488",
@ -3764,7 +3764,7 @@
"VER-01": "fails today, disclosed: the light client authenticates the certificate's signatures and the header path but the voter table is a node-supplied trust anchor named on /light; a forged table signature and a wrong-network certificate are refused",
"VER-02": "fails today, disclosed: the execution statement's proof is verified by the nodes and the browser verifies the aggregator signature and the post root under it; an in-browser verifier for the permitted proof and a table commitment close it"
},
"at": "2026-10-08T19:13:20.937Z"
"at": "2026-10-08T19:46:20.696Z"
}
},
{
@ -3795,19 +3795,18 @@
"owner_lane": "reference-apps lane (a2060899d2a27d31c)",
"run_status": "RUNNING",
"evidence_path": "tools/reference-apps/ver/evidence/summary.json",
"run_id": "ra-20261008T1746-ver",
"updated": "2026-10-08T19:13:20.937Z",
"run_id": "ra-20261008T1915-ver",
"updated": "2026-10-08T19:46:20.696Z",
"evidence_record": {
"cell": "harness:reference-apps-ver",
"manifest_sha": "4cdcc488",
"coverage": {
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction",
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction; Review B F04: a receipt claiming lock_state final under a certificate the node reports as a recovery lock is refused, a recovery lock prints as recovery lock, not final (Node and browser negative cases on the fixtures; the field is not live on devnet-4 tonight, so no live recovery lock was seen)",
"VER-04": "partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's",
"VER-05": "partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise",
"VER-06": "partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run",
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows"
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows; Review B F04: /light, /receipt, the receipt file and the one-file verifier show a node-reported recovery lock as recovery lock, not final, /oracle says recovery locks are not accepted by its verifier, the terms block defines the recovery lock"
},
"at": "2026-10-08T19:13:20.937Z"
"at": "2026-10-08T19:46:20.696Z"
}
},
{
@ -3838,19 +3837,18 @@
"owner_lane": "reference-apps lane (a2060899d2a27d31c)",
"run_status": "RUNNING",
"evidence_path": "tools/reference-apps/ver/evidence/summary.json",
"run_id": "ra-20261008T1746-ver",
"updated": "2026-10-08T19:13:20.937Z",
"run_id": "ra-20261008T1915-ver",
"updated": "2026-10-08T19:46:20.696Z",
"evidence_record": {
"cell": "harness:reference-apps-ver",
"manifest_sha": "4cdcc488",
"coverage": {
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction",
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction; Review B F04: a receipt claiming lock_state final under a certificate the node reports as a recovery lock is refused, a recovery lock prints as recovery lock, not final (Node and browser negative cases on the fixtures; the field is not live on devnet-4 tonight, so no live recovery lock was seen)",
"VER-04": "partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's",
"VER-05": "partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise",
"VER-06": "partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run",
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows"
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows; Review B F04: /light, /receipt, the receipt file and the one-file verifier show a node-reported recovery lock as recovery lock, not final, /oracle says recovery locks are not accepted by its verifier, the terms block defines the recovery lock"
},
"at": "2026-10-08T19:13:20.937Z"
"at": "2026-10-08T19:46:20.696Z"
}
},
{
@ -3879,21 +3877,17 @@
"owner": "Wallet + light-client lead; independent security review",
"manual_page": 52,
"owner_lane": "reference-apps lane (a2060899d2a27d31c)",
"run_status": "RUNNING",
"evidence_path": "tools/reference-apps/ver/evidence/summary.json",
"run_id": "ra-20261008T1746-ver",
"updated": "2026-10-08T19:13:20.937Z",
"run_status": "FAIL",
"evidence_path": "tools/reference-apps/ver/evidence/VER-05.json",
"run_id": "ra-20261008T1915-ver",
"updated": "2026-10-08T19:46:20.696Z",
"evidence_record": {
"cell": "harness:reference-apps-ver",
"cell": "harness:reference-apps-ver-availability",
"manifest_sha": "4cdcc488",
"coverage": {
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction",
"VER-04": "partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's",
"VER-05": "partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise",
"VER-06": "partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run",
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows"
"VER-05": "partial: the public read node must report startedFrom genesis (no snapshot) and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot; the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise. Run ra-20261008T1915-ver: FAIL, the public node reports startedFrom snapshot (restarted from a snapshot after 17:46 UK); the reader on build-1 still from genesis"
},
"at": "2026-10-08T19:13:20.937Z"
"at": "2026-10-08T19:46:20.696Z"
}
},
{
@ -3924,19 +3918,18 @@
"owner_lane": "reference-apps lane (a2060899d2a27d31c)",
"run_status": "RUNNING",
"evidence_path": "tools/reference-apps/ver/evidence/summary.json",
"run_id": "ra-20261008T1746-ver",
"updated": "2026-10-08T19:13:20.937Z",
"run_id": "ra-20261008T1915-ver",
"updated": "2026-10-08T19:46:20.696Z",
"evidence_record": {
"cell": "harness:reference-apps-ver",
"manifest_sha": "4cdcc488",
"coverage": {
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction",
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction; Review B F04: a receipt claiming lock_state final under a certificate the node reports as a recovery lock is refused, a recovery lock prints as recovery lock, not final (Node and browser negative cases on the fixtures; the field is not live on devnet-4 tonight, so no live recovery lock was seen)",
"VER-04": "partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's",
"VER-05": "partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise",
"VER-06": "partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run",
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows"
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows; Review B F04: /light, /receipt, the receipt file and the one-file verifier show a node-reported recovery lock as recovery lock, not final, /oracle says recovery locks are not accepted by its verifier, the terms block defines the recovery lock"
},
"at": "2026-10-08T19:13:20.937Z"
"at": "2026-10-08T19:46:20.696Z"
}
},
{
@ -4001,19 +3994,19 @@
"manual_page": 53,
"owner_lane": "reference-apps lane (a2060899d2a27d31c)",
"run_status": "RUNNING",
"evidence_path": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/miner-9c844503/box2-app.log",
"run_id": "201-7cfa422a-aa0e0f45",
"updated": "2026-10-08T19:32:50.856Z",
"evidence_path": "tools/reference-apps/ver/evidence/summary.json",
"run_id": "ra-20261008T1915-ver",
"updated": "2026-10-08T19:46:20.696Z",
"evidence_record": {
"cell": "suite:app",
"manifest_sha": "7cfa422a",
"cell": "harness:reference-apps-ver",
"manifest_sha": "4cdcc488",
"coverage": {
"UX-02": "partial: the engine state machine's pause, stop and knob tests; the operator study is UX-01's",
"UX-03": "partial: the card and earnings rendering tests; the net-earnings model against real bills is COM/ECO evidence",
"UX-07": "partial: the refused-update and manifest tests; the update-return lane's install classes are its own rows",
"VER-08": "partial: the app's own state-word tests; the wallet and light client rows are VER-01 to VER-03"
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction; Review B F04: a receipt claiming lock_state final under a certificate the node reports as a recovery lock is refused, a recovery lock prints as recovery lock, not final (Node and browser negative cases on the fixtures; the field is not live on devnet-4 tonight, so no live recovery lock was seen)",
"VER-04": "partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's",
"VER-06": "partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run",
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows; Review B F04: /light, /receipt, the receipt file and the one-file verifier show a node-reported recovery lock as recovery lock, not final, /oracle says recovery locks are not accepted by its verifier, the terms block defines the recovery lock"
},
"at": "2026-10-08T19:32:50.856Z"
"at": "2026-10-08T19:46:20.696Z"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -450,7 +450,7 @@
<div class="grid c2">
<div class="dl-list" style="margin:0">
<a data-dl="miner-windows" href="https://dl.igneum.network/public/igneum-miner-windows.exe" class="primary">Windows <span data-dl-meta="miner-windows">v2.0.0 · 63.8 MB</span></a>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg">macOS <span data-dl-meta="miner-mac">v2.0.0 · 45.9 MB</span></a>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg">macOS <span data-dl-meta="miner-mac">v2.0.1 · 45.9 MB</span></a>
<a href="/miner#hive">Linux · HiveOS <span>the tarball and the flight sheet, on the miner page</span></a>
</div>
<div>

View file

@ -254,8 +254,8 @@
<div class="body">
<p>Open the disk image, drag the app across, press Start. A Mac mines on its GPU at about a fifth of a flagship card and proves on its CPU, slowly.</p>
<p data-mac-first-open>On first open macOS will say it cannot verify the app. Click Done, open System Settings, Privacy and Security, and click Open Anyway, then open Igneum Miner again. A signed and notarized build follows.</p>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg" class="btn primary"><span class="osmark bare" data-os="mac" title="macOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M16.4 12.6c0-2.5 2-3.6 2.1-3.7-1.2-1.7-3-1.9-3.6-2-1.5-.2-3 .9-3.8.9-.8 0-2-.9-3.3-.8-1.7 0-3.2 1-4.1 2.5-1.8 3-.5 7.6 1.3 10.1.9 1.2 1.9 2.6 3.2 2.5 1.3 0 1.8-.8 3.3-.8 1.6 0 2 .8 3.3.8 1.4 0 2.3-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9 0 0-2.7-1-2.9-4.1zM13.9 5.3c.7-.8 1.2-2 1-3.2-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.3z"/></svg></span>Download for macOS <span class="meta" data-dl-meta="miner-mac">v2.0.0 · 45.9 MB</span></a>
<div class="sha"><b>sha256</b> <span data-dl-sha="miner-mac">d7dff598444bc27274eb35a5276803b7bbc72876e5d7abeb154afd2cf5a5d4b2</span></div>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg" class="btn primary"><span class="osmark bare" data-os="mac" title="macOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M16.4 12.6c0-2.5 2-3.6 2.1-3.7-1.2-1.7-3-1.9-3.6-2-1.5-.2-3 .9-3.8.9-.8 0-2-.9-3.3-.8-1.7 0-3.2 1-4.1 2.5-1.8 3-.5 7.6 1.3 10.1.9 1.2 1.9 2.6 3.2 2.5 1.3 0 1.8-.8 3.3-.8 1.6 0 2 .8 3.3.8 1.4 0 2.3-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9 0 0-2.7-1-2.9-4.1zM13.9 5.3c.7-.8 1.2-2 1-3.2-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.3z"/></svg></span>Download for macOS <span class="meta" data-dl-meta="miner-mac">v2.0.1 · 45.9 MB</span></a>
<div class="sha"><b>sha256</b> <span data-dl-sha="miner-mac">7059a31c29b9798f32c04402f40c1e02db9674d15b497b08d51e2cc8e6845f45</span></div>
</div>
</article>
<article class="dl-card" id="linux">
@ -263,16 +263,16 @@
<div><h3>Ember for Linux</h3><div class="sub">one tarball, the miner and the node inside</div></div>
<div class="body">
<p>Unpack it, run <code>igneum</code> with your payout address. The same signed manifest as the desktop apps. NVIDIA through CUDA, AMD through OpenCL.</p>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><span class="osmark bare" data-os="linux" title="Linux"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M12 2c-2.4 0-4 1.9-4 4.6 0 1.2.2 2 0 2.8-.6 1.4-2.1 2.9-2.6 4.9-.3 1.1-.1 2 .3 2.6-.6.4-1.3 1-1.1 1.7.3 1 2.1 1.2 3.2 1.8.7.4 1.5.6 2.1.1.6.2 1.3.3 2.1.3s1.5-.1 2.1-.3c.6.5 1.4.3 2.1-.1 1.1-.6 2.9-.8 3.2-1.8.2-.7-.5-1.3-1.1-1.7.4-.6.6-1.5.3-2.6-.5-2-2-3.5-2.6-4.9-.2-.8 0-1.6 0-2.8C16 3.9 14.4 2 12 2zm-1.4 4.2c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zm2.8 0c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zM12 9.3c.9 0 1.9.5 1.9 1s-1 1.2-1.9 1.2-1.9-.7-1.9-1.2 1-1 1.9-1zm0 3.4c2.2 0 3.6 2.6 3.6 4.4 0 1.5-1.6 2.3-3.6 2.3s-3.6-.8-3.6-2.3c0-1.8 1.4-4.4 3.6-4.4z"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v2.0.0 · 53.3 MB</span></a>
<div class="sha"><b>sha256</b> <span data-dl-sha="miner-hive">5c1369fc5559e8c85534da08cb6eafff9f688336f8993b44e0512fe86d6284d1</span></div>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><span class="osmark bare" data-os="linux" title="Linux"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M12 2c-2.4 0-4 1.9-4 4.6 0 1.2.2 2 0 2.8-.6 1.4-2.1 2.9-2.6 4.9-.3 1.1-.1 2 .3 2.6-.6.4-1.3 1-1.1 1.7.3 1 2.1 1.2 3.2 1.8.7.4 1.5.6 2.1.1.6.2 1.3.3 2.1.3s1.5-.1 2.1-.3c.6.5 1.4.3 2.1-.1 1.1-.6 2.9-.8 3.2-1.8.2-.7-.5-1.3-1.1-1.7.4-.6.6-1.5.3-2.6-.5-2-2-3.5-2.6-4.9-.2-.8 0-1.6 0-2.8C16 3.9 14.4 2 12 2zm-1.4 4.2c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zm2.8 0c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zM12 9.3c.9 0 1.9.5 1.9 1s-1 1.2-1.9 1.2-1.9-.7-1.9-1.2 1-1 1.9-1zm0 3.4c2.2 0 3.6 2.6 3.6 4.4 0 1.5-1.6 2.3-3.6 2.3s-3.6-.8-3.6-2.3c0-1.8 1.4-4.4 3.6-4.4z"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v2.0.1 · 52.7 MB</span></a>
<div class="sha"><b>sha256</b> <span data-dl-sha="miner-hive">a16add6a9082747bc11d9bb9a69d193530f723dd2671fe1352ad59b1ae0a46ab</span></div>
</div>
</article>
<article class="dl-card" id="hive">
<span class="osmark" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span>
<div><h3>Ember on HiveOS</h3><div class="sub">for the rig people, the same tarball</div></div>
<div class="body">
<div class="hive-sheet"><b>Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-2.0.0.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn"><span class="osmark bare" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v2.0.0 · 53.3 MB</span></a>
<div class="hive-sheet"><b>Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-2.0.1.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn"><span class="osmark bare" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v2.0.1 · 52.7 MB</span></a>
</div>
</article>
</div>

View file

@ -237,7 +237,7 @@
<p class="lead">A GPU-secured network for Ethereum-compatible applications and verifiable computation.</p>
<div class="fold-actions" id="fold-actions">
<a data-dl="miner-windows" href="https://dl.igneum.network/public/igneum-miner-windows.exe" data-os-button="windows" class="btn primary"><span class="osmark bare" data-os="windows" title="Windows"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M3 5.6l7.3-1v7.1H3zM11.4 4.4L21 3v8.7h-9.6zM3 12.3h7.3v7.1L3 18.4zM11.4 12.3H21V21l-9.6-1.4z"/></svg></span>Download for Windows<span class="meta" data-dl-meta="miner-windows">v2.0.0 · 63.8 MB</span></a>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg" data-os-button="mac" class="btn"><span class="osmark bare" data-os="mac" title="macOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M16.4 12.6c0-2.5 2-3.6 2.1-3.7-1.2-1.7-3-1.9-3.6-2-1.5-.2-3 .9-3.8.9-.8 0-2-.9-3.3-.8-1.7 0-3.2 1-4.1 2.5-1.8 3-.5 7.6 1.3 10.1.9 1.2 1.9 2.6 3.2 2.5 1.3 0 1.8-.8 3.3-.8 1.6 0 2 .8 3.3.8 1.4 0 2.3-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9 0 0-2.7-1-2.9-4.1zM13.9 5.3c.7-.8 1.2-2 1-3.2-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.3z"/></svg></span>Download for macOS<span class="meta" data-dl-meta="miner-mac">v2.0.0 · 45.9 MB</span></a>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg" data-os-button="mac" class="btn"><span class="osmark bare" data-os="mac" title="macOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M16.4 12.6c0-2.5 2-3.6 2.1-3.7-1.2-1.7-3-1.9-3.6-2-1.5-.2-3 .9-3.8.9-.8 0-2-.9-3.3-.8-1.7 0-3.2 1-4.1 2.5-1.8 3-.5 7.6 1.3 10.1.9 1.2 1.9 2.6 3.2 2.5 1.3 0 1.8-.8 3.3-.8 1.6 0 2 .8 3.3.8 1.4 0 2.3-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9 0 0-2.7-1-2.9-4.1zM13.9 5.3c.7-.8 1.2-2 1-3.2-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.3z"/></svg></span>Download for macOS<span class="meta" data-dl-meta="miner-mac">v2.0.1 · 45.9 MB</span></a>
</div>
<p class="fold-more fold-one">One click: node, miner, prover. The card starts.</p>
<p class="fold-more"><a href="/download">Linux and HiveOS</a></p>

View file

@ -80,12 +80,13 @@ export async function runLight(address) {
const res = verifyBalance(cp, proof, deps);
window.__igneumLight = { cp, proof, neg, res };
const top = res.verified
? `<div class="headline"><div class="eyebrow ember">Proven balance · Devnet 3, no value</div><div class="big">${esc(formatIgn(res.balance_wei))} <span>IGN</span></div>
<p class="pt">${esc(res.balance_wei)} wei at chain block ${res.block}, under checkpoint ${res.checkpoint} (locked ${esc(ago(Number(proof.headers[proof.headers.length - 1].timestamp)))}). Verified here in ${res.ms} ms, ${res.headers} headers checked.</p></div>`
? `<div class="headline${res.lock_state === 'recovery' ? ' recovery' : ''}"><div class="eyebrow ember">${res.lock_state === 'recovery' ? 'Proven balance under a RECOVERY LOCK, not final' : 'Proven balance'} · Devnet 3, no value</div><div class="big">${esc(formatIgn(res.balance_wei))} <span>IGN</span></div>
<p class="pt">${esc(res.balance_wei)} wei at chain block ${res.block}, under checkpoint ${res.checkpoint} (${res.lock_state === 'recovery' ? 'recovery lock' : 'locked'} ${esc(ago(Number(proof.headers[proof.headers.length - 1].timestamp)))}). Verified here in ${res.ms} ms, ${res.headers} headers checked.</p>
${res.lock_state === 'recovery' ? `<p class="pt"><b>Recovery lock.</b> The node reports this checkpoint locked under the recovery rule: more than half of the anchored weight after a full window with no lock, not the two-thirds final rule. Nothing under it is final. The state is the node's report; the certificate bytes carry none.</p>` : ''}</div>`
: `<div class="headline bad"><div class="eyebrow">Not verified · Devnet 3, no value</div><p class="pt">${esc(res.reason)}</p></div>`;
out.innerHTML = top + negativeHtml('a copy of this proof with one byte of a trie node altered', neg) + stepsHtml(res)
+ `<p class="note">Data served by ${esc(API)} (a read service in front of a Devnet 3 node). Nothing it answered was taken on trust: the certificate, every header hash and parent link, the coinbase inclusion, the segment record's signature and the account proof were recomputed in this tab. What is trusted: that the aggregator's statement is the true execution result (every node checks it natively before paying the record; the SP1 proof behind it is verified by nodes, not in this tab yet), and the voter list with weights, which came from the node (spec 10.1).</p>`;
setStatus(res.verified ? 'verified' : 'refused', res.verified ? 'ok' : 'bad');
setStatus(res.verified ? (res.lock_state === 'recovery' ? 'verified under a recovery lock, not final' : 'verified') : 'refused', res.verified ? (res.lock_state === 'recovery' ? 'warn' : 'ok') : 'bad');
return res;
}
@ -110,18 +111,22 @@ export async function runReceipt(tx) {
if (res.payment) { receipt.kind = 'payment receipt'; receipt.authenticates = 'inclusion of the signed transaction in a finalised block and its successful execution outcome (status and logs) through the proven segment\'s receipts commitment'; }
let negPay = null;
if (res.payment) { const b2 = clone(receipt); const r0 = b2.segment.receipts[Number(b2.segment.receipt_position)]; r0.status = '0x0'; negPay = verifyPaymentReceipt(b2, deps); }
window.__igneumReceipt = { receipt, neg, res };
if (res.lock_state) receipt.lock_state = res.lock_state;
// negative case under a recovery lock: a copy claiming lock_state final must be refused
let negLock = null;
if (res.lock_state === 'recovery') { const b3 = clone(receipt); b3.lock_state = 'final'; negLock = verifyReceipt(b3, deps); }
window.__igneumReceipt = { receipt, neg, res, negLock };
const t = res.tx || {};
const when = new Date(Number(res.block_time || 0)).toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
const top = res.verified
? `<div class="headline"><div class="eyebrow ember">${res.payment ? 'Payment receipt · included, executed, proven and finalised' : 'Transaction inclusion receipt · included and finalised'} · Devnet 3, no value</div><div class="big">${esc(formatIgn(t.value || '0'))} <span>IGN</span></div>
? `<div class="headline"><div class="eyebrow ember">${res.lock_state === 'recovery' ? (res.payment ? 'Payment receipt · included, executed, proven, under a RECOVERY LOCK (not final)' : 'Transaction inclusion receipt · included, under a RECOVERY LOCK (not final)') : res.payment ? 'Payment receipt · included, executed, proven and finalised' : 'Transaction inclusion receipt · included and finalised'} · Devnet 3, no value</div><div class="big">${esc(formatIgn(t.value || '0'))} <span>IGN</span></div>
${res.payment ? `<p class="pt"><b>Outcome authenticated:</b> ${esc(res.outcome.asset)}, ${esc(formatIgn(res.outcome.amount_wei))} IGN to ${esc(res.outcome.recipient || 'contract creation')}, executed with status success, through the receipts commitment of the proven segment ending at chain block ${esc(String(receipt.execution.chain_block))}.</p>` : `<p class="pt"><b>This is the inclusion receipt.</b> ${esc(res.payment_unavailable || receipt.payment_unavailable || 'the outcome is not authenticated')}${res.receipt_status === 'failed' ? ' The authenticated status is FAILED: no transfer took place.' : ''}</p>`}
<div class="kv"><div class="k">To</div><div class="mono">${esc(t.to || 'contract creation')}</div><div class="k">From</div><div class="mono">${esc(receipt.tx_as_reported.from)} <small>(as the node reports it; the signature is the chain's check)</small></div>
<div class="k">Transaction</div><div class="mono">0x${esc(receipt.tx_hash)}</div><div class="k">Block</div><div class="mono">${esc(res.block)} <small>at ${esc(when)}, DAA ${esc(res.block_daa)}</small></div>
<div class="k">Finality</div><div>checkpoint ${res.checkpoint}, ${esc(res.certificate)}; ${res.headers} headers from the block to the checkpoint, verified here in ${res.ms} ms</div>
<div class="k">${res.lock_state === 'recovery' ? 'Recovery lock' : 'Finality'}</div><div>${res.lock_state === 'recovery' ? '<b>recovery lock, not final</b> (the node reports the checkpoint locked under the recovery rule; the certificate bytes carry no state); ' : ''}checkpoint ${res.checkpoint}, ${esc(res.certificate)}; ${res.headers} headers from the block to the checkpoint, verified here in ${res.ms} ms</div>
${receipt.execution ? `<div class="k">Executed</div><div>status ${receipt.execution.status === '0x1' ? 'success' : 'failed'}, gas ${parseInt(receipt.execution.gas_used, 16)}, ${receipt.execution.logs} log(s) <small>${res.payment ? '(authenticated: the receipt sits in the shard receipts trie whose root the proven segment statement commits to)' : '(as the node reports it: executed is reported, not authenticated by this receipt; a payment receipt would authenticate the outcome)'}</small></div>` : ''}</div></div>`
: `<div class="headline bad"><div class="eyebrow">Not verified · Devnet 3, no value</div><p class="pt">${esc(res.reason)}</p></div>`;
out.innerHTML = top + negativeHtml('a copy of this receipt with one nibble of the raw transaction altered', neg) + (negPay ? negativeHtml('a copy of this payment receipt with the receipt status flipped to failed', { verified: negPay.verified && negPay.payment, reason: negPay.reason }) : '') + stepsHtml(res)
out.innerHTML = top + negativeHtml('a copy of this receipt with one nibble of the raw transaction altered', neg) + (negLock ? negativeHtml('a copy of this receipt claiming lock_state final under a certificate the node reports as a recovery lock', negLock) : '') + (negPay ? negativeHtml('a copy of this payment receipt with the receipt status flipped to failed', { verified: negPay.verified && negPay.payment, reason: negPay.reason }) : '') + stepsHtml(res)
+ (res.verified ? `<p><button class="btn" type="button" data-download>Download the ${res.payment ? 'payment' : 'inclusion'} receipt (JSON, ${Math.round(JSON.stringify(receipt).length / 1024)} KB)</button></p>
<p class="note">The file carries the raw transaction, the including block's header and merkle path, every header up to the certified checkpoint, the certificate and the voter table. Anyone re-verifies it offline with the one-file verifier: <code>node verify-receipt.js receipt.json</code> (<a href="/lc/verify-receipt.js" download>verify-receipt.js</a>, plain JavaScript, no npm, no network). A tampered file fails there the same way the copy above failed here.</p>` : '');
const dl = $('[data-download]');
@ -129,7 +134,7 @@ export async function runReceipt(tx) {
const blob = new Blob([JSON.stringify(receipt, null, 1)], { type: 'application/json' });
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `igneum-${res.payment ? 'payment' : 'inclusion'}-receipt-${receipt.tx_hash.slice(0, 12)}.json`; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 5000);
});
setStatus(res.verified ? 'verified' : 'refused', res.verified ? 'ok' : 'bad');
setStatus(res.verified ? (res.lock_state === 'recovery' ? 'verified under a recovery lock, not final' : 'verified') : 'refused', res.verified ? (res.lock_state === 'recovery' ? 'warn' : 'ok') : 'bad');
return res;
}

View file

@ -254,6 +254,21 @@ export function verifyHeaderPath(headers, blake2b, fromHash, toHash) {
return headers.length;
}
// ---- the lock state (Review B F04, 8 October 2026): a recovery lock is never presented as a final lock ---------------
// The certificate bytes carry no state; the node reports it beside the checkpoint (lockKind "final" or "recovery" and
// recoveryLock on igneum_getFinalityCheckpoints, 2.0.2 line; the service maps it to lock_state; absent on nodes before the field). A recovery lock is a lock under the recovery rule (more than
// half of the anchored weight after a full window with no lock), reported by the node, not provable from the certificate.
export function lockStateOf(cp) {
const v = cp && (cp.lock_state ?? cp.lockKind ?? (cp.recoveryLock === true ? 'recovery' : cp.recoveryLock === false ? 'final' : undefined));
if (v === undefined || v === null || v === '') return null;
if (v !== 'final' && v !== 'recovery') throw new Error(`unknown lock state "${v}" reported for checkpoint ${cp.index}; refused`);
return v;
}
export const lockStateDetail = st => st === 'recovery'
? 'RECOVERY LOCK as the node reports it: a lock under the recovery rule, not the final rule; nothing under it is final'
: st === 'final' ? 'final lock as the node reports it' : 'no lock state reported by this node (a node before the field)';
// ---- the balance proof ------------------------------------------------------------------------------------------
// `cp` is the /api/checkpoint body (certificate, voters, headers to the previous lock); `proof` is the /balance body:
// { address, chain_id, checkpoint: {hash, index}, headers: [carrier .. checkpoint], carrier: { coinbase: <RpcTransaction>,
@ -320,8 +335,9 @@ export function verifyBalance(cp, proof, deps) {
if (!a.exists && BigInt(proof.account.balance) !== 0n) throw new Error('the node reports a balance for an account the trie does not hold');
return `${proof.account.accountProof.length} nodes, ${a.exists ? 'account present' : 'account absent (exclusion proof)'}`;
});
const lockState = lockStateOf(cp); step('lock state: the node\'s report beside the certificate (the certificate bytes carry none)', () => lockStateDetail(lockState));
const balance = acct.exists ? acct.balance : 0n;
return done({ verified: true, balance, balance_wei: balance.toString(), nonce: acct.nonce.toString(), block: Number(st.number), post_root: st.post_root, checkpoint: Number(cp.index), headers: proof.headers.length, aggregator: agg, certificate: cert });
return done({ verified: true, balance, balance_wei: balance.toString(), nonce: acct.nonce.toString(), block: Number(st.number), post_root: st.post_root, checkpoint: Number(cp.index), lock_state: lockState, headers: proof.headers.length, aggregator: agg, certificate: cert });
} catch (e) {
return done({ verified: false, reason: String(e.message || e) });
}
@ -344,6 +360,11 @@ export function verifyReceipt(receipt, deps) {
if (strip(cp.hash) !== strip(receipt.checkpoint.hash) || receipt.chain_id !== cp.chain_id) throw new Error('the receipt names another checkpoint or chain than its certificate');
return `checkpoint ${r.index} on ${cp.chain_id}, ${r.signers} of ${r.voters} voters, ${(r.weight_fraction_total * 100).toFixed(1)}% of total weight`;
});
const lockState = step('lock state: the node\'s report beside the certificate (the certificate bytes carry none)', () => {
const st = lockStateOf(cp);
if (receipt.lock_state !== undefined && receipt.lock_state !== null && receipt.lock_state !== (st || 'final')) throw new Error(`the receipt says lock_state "${receipt.lock_state}" but its certificate is reported as ${st || 'final (no state field)'}; refused`);
return lockStateDetail(st);
}) && lockStateOf(cp);
let tx = null;
step('the transaction hash is keccak256 of the raw signed transaction', () => {
const raw = hexToBytes(strip(receipt.raw_tx_hex));
@ -361,7 +382,7 @@ export function verifyReceipt(receipt, deps) {
if (bytesToHex(root) !== strip(receipt.headers[0].hash_merkle_root)) throw new Error('the merkle path does not reach the including block\'s hash_merkle_root');
return `leaf ${ib.leaf_index} of ${ib.leaf_count}`;
});
return done({ verified: true, tx, headers: receipt.headers.length, checkpoint: Number(cp.index), certificate: cert, block: strip(receipt.headers[0].hash), block_daa: receipt.headers[0].daa_score, block_time: receipt.headers[0].timestamp });
return done({ verified: true, tx, headers: receipt.headers.length, checkpoint: Number(cp.index), lock_state: lockState, certificate: cert, block: strip(receipt.headers[0].hash), block_daa: receipt.headers[0].daa_score, block_time: receipt.headers[0].timestamp });
} catch (e) {
return done({ verified: false, reason: String(e.message || e) });
}

View file

@ -56,6 +56,8 @@ try {
['receipt names another checkpoint than its certificate', d => { d.checkpoint.hash = flipHex(d.checkpoint.hash, 60); }],
];
for (const [name, mutate] of R) { const d = clone(receipt); mutate(d); row('r', name, verifyReceipt(d, deps), false); }
{ const d = clone(receipt); d.checkpoint.certificate.lock_state = 'recovery'; d.lock_state = 'final'; row('r', 'a receipt claiming lock_state final under a certificate the node reports as a recovery lock (F04)', verifyReceipt(d, deps), false); }
{ const d = clone(receipt); d.checkpoint.certificate.lock_state = 'recovery'; const r = verifyReceipt(d, deps); row('r', 'a receipt under a recovery lock: verifies with lock_state recovery, never final (F04)', { ...r, verified: r.verified && r.lock_state === 'recovery' }, true); }
row('r', `genuine receipt for ${tx.slice(0, 14)} (${receipt.headers.length} headers, checkpoint ${cp.index})`, verifyReceipt(receipt, deps), true);
// a balance to prove
let address = q.get('address');
@ -77,6 +79,9 @@ try {
];
for (const [name, mutate] of B) { const d = clone(proof); mutate(d); row('b', name, verifyBalance(cp, d, deps), false); }
{ const c = clone(cp); c.certificate.aggregate_signature_hex = flipHex(c.certificate.aggregate_signature_hex, 20); row('b', 'certificate signature altered', verifyBalance(c, proof, deps), false); }
// Review B F04: a recovery lock is never presented as a final lock (the node's report beside the certificate)
{ const c = clone(cp); c.lock_state = 'recovery'; const r = verifyBalance(c, proof, deps); row('b', 'under a node-reported recovery lock: verifies with lock_state recovery, never final', { ...r, verified: r.verified && r.lock_state === 'recovery' }, true); }
{ const c = clone(cp); c.lock_state = 'anchored-maybe'; row('b', 'an unknown lock kind reported', verifyBalance(c, proof, deps), false); }
const g = verifyBalance(cp, proof, deps);
row('b', `genuine balance of ${address.slice(0, 12)} at chain block ${proof.segment.last} (${proof.headers.length} headers, checkpoint ${cp.index})`, g, true);
}

View file

@ -1,5 +1,5 @@
#!/usr/bin/env node
// Igneum receipt verifier (transaction inclusion receipt or payment receipt), one file, offline. Source: tools/reference-apps/receipt/verify-receipt.src.mjs and site/lc/core.js
// Igneum transaction inclusion receipt verifier, one file, offline. Source: tools/reference-apps/receipt/verify-receipt.src.mjs and site/lc/core.js
// (the same checks the browser page runs), bundled with @noble/hashes 2.4.0 and @noble/curves 2.4.0 (MIT). Usage: node verify-receipt.js receipt.json [--tamper]
// tools/reference-apps/receipt/verify-receipt.src.mjs
@ -5226,6 +5226,13 @@ function verifyHeaderPath(headers, blake2b2, fromHash, toHash) {
if (toHash && strip(headers[headers.length - 1].hash) !== strip(toHash)) throw new Error("the last header is not the certified checkpoint");
return headers.length;
}
function lockStateOf(cp) {
const v = cp && (cp.lock_state ?? cp.lockKind ?? (cp.recoveryLock === true ? "recovery" : cp.recoveryLock === false ? "final" : void 0));
if (v === void 0 || v === null || v === "") return null;
if (v !== "final" && v !== "recovery") throw new Error(`unknown lock state "${v}" reported for checkpoint ${cp.index}; refused`);
return v;
}
var lockStateDetail = (st) => st === "recovery" ? "RECOVERY LOCK as the node reports it: a lock under the recovery rule, not the final rule; nothing under it is final" : st === "final" ? "final lock as the node reports it" : "no lock state reported by this node (a node before the field)";
function verifyReceipt(receipt2, deps2) {
const { blake2b: blake2b2, bls: bls2, keccak } = deps2;
const steps = [];
@ -5249,6 +5256,11 @@ function verifyReceipt(receipt2, deps2) {
if (strip(cp.hash) !== strip(receipt2.checkpoint.hash) || receipt2.chain_id !== cp.chain_id) throw new Error("the receipt names another checkpoint or chain than its certificate");
return `checkpoint ${r2.index} on ${cp.chain_id}, ${r2.signers} of ${r2.voters} voters, ${(r2.weight_fraction_total * 100).toFixed(1)}% of total weight`;
});
const lockState = step("lock state: the node's report beside the certificate (the certificate bytes carry none)", () => {
const st = lockStateOf(cp);
if (receipt2.lock_state !== void 0 && receipt2.lock_state !== null && receipt2.lock_state !== (st || "final")) throw new Error(`the receipt says lock_state "${receipt2.lock_state}" but its certificate is reported as ${st || "final (no state field)"}; refused`);
return lockStateDetail(st);
}) && lockStateOf(cp);
let tx = null;
step("the transaction hash is keccak256 of the raw signed transaction", () => {
const raw = hexToBytes3(strip(receipt2.raw_tx_hex));
@ -5265,7 +5277,7 @@ function verifyReceipt(receipt2, deps2) {
if (bytesToHex3(root) !== strip(receipt2.headers[0].hash_merkle_root)) throw new Error("the merkle path does not reach the including block's hash_merkle_root");
return `leaf ${ib.leaf_index} of ${ib.leaf_count}`;
});
return done({ verified: true, tx, headers: receipt2.headers.length, checkpoint: Number(cp.index), certificate: cert, block: strip(receipt2.headers[0].hash), block_daa: receipt2.headers[0].daa_score, block_time: receipt2.headers[0].timestamp });
return done({ verified: true, tx, headers: receipt2.headers.length, checkpoint: Number(cp.index), lock_state: lockState, certificate: cert, block: strip(receipt2.headers[0].hash), block_daa: receipt2.headers[0].daa_score, block_time: receipt2.headers[0].timestamp });
} catch (e) {
return done({ verified: false, reason: String(e.message || e) });
}
@ -5316,12 +5328,14 @@ if (args.includes("--tamper")) {
}
var r = verifyReceipt(receipt, deps);
console.log(`TRANSACTION INCLUSION RECEIPT 0x${receipt.tx_hash} on ${receipt.chain_id} (devnet, no value)`);
if (receipt.lock_state === "recovery") console.log("RECOVERY LOCK: the node reported this checkpoint locked under the recovery rule (more than half of the anchored weight after a full window with no lock), not the final rule. Nothing in this file is final.");
console.log("What this file authenticates: that the signed transaction is included in a block that is finalised. What it does not: the execution outcome (status, gas), which is carried as the node reported it. A payment receipt, which authenticates the transfer outcome, is a different file.");
print(r);
if (!r.verified) {
console.log(`REFUSED: ${r.reason}`);
process.exit(1);
}
if (r.lock_state === "recovery") console.log("LOCK STATE: recovery, as the node reported it; this receipt is included under a recovery lock, not a final one.");
var t = r.tx;
console.log(`INCLUSION VERIFIED in ${r.ms} ms: a signed transaction of ${formatIgn(t.value)} IGN to ${t.to || "contract creation"} (${t.value} wei) is included in block ${r.block.slice(0, 16)} at DAA ${r.block_daa} (${new Date(Number(r.block_time)).toISOString()}), finalised under checkpoint ${r.checkpoint}; ${r.headers} headers checked; ${r.certificate}.`);
console.log("Reported by the node, not authenticated by this file: from " + (receipt.tx_as_reported && receipt.tx_as_reported.from) + (receipt.execution ? `, executed with status ${receipt.execution.status === "0x1" ? "success" : "failed"}` : "") + ". The voter table with weights came from the node (spec 10.1). In the four words: included and finalised are authenticated, executed is reported, proven is not claimed.");

View file

@ -293,6 +293,7 @@
<div class="k">Executed</div><div>A node ran it at a chain block and reports a result (status, gas, logs). On these pages an execution result is reported by the node, not authenticated, unless the page says it is.</div>
<div class="k">Proven</div><div>An aggregator's segment record, carried in a block's coinbase and signed with its vote key, commits to the state root after that chain block; nodes check the statement against their own execution before paying it. The SP1 proof behind the statement is verified by nodes, not in the browser or on Sepolia.</div>
<div class="k">Finalised</div><div>A certified checkpoint has the block in its past: an aggregate BLS signature by voters holding two thirds of active weight and at least 17/30 of total weight over the checkpoint, checked here against the voter table the node supplies.</div>
<div class="k">Recovery lock</div><div>Not a fifth state and never shown as finalised. After a full weight window with no lock, the finality rule accepts a checkpoint signed by more than half of the anchored weight (the recovery rule, Review B F04). The node reports each lock's kind beside its checkpoint (<code>lockKind</code>: final or recovery); the certificate bytes carry none, so the kind is the node's report. These pages print a recovery lock as <b>recovery lock, not final</b> on the result, in the receipt file (<code>lock_state</code>) and in the one-file verifier, and refuse a receipt that claims final under a reported recovery lock. On a node before the field nothing is shown. Devnet 3 and the 2.0 devnet, no value.</div>
</div>
<p class="note">The same four definitions sit on <a href="/light">/light</a>, <a href="/receipt">/receipt</a> and <a href="/oracle">/oracle</a> (one source: <code>site/partials/terms.html</code>). The devnet, no value.</p>
</section>

View file

@ -290,16 +290,16 @@ pre b{color:var(--molten-text);font-weight:500}
<div class="download-platform"><span class="osmark" data-os="mac" title="macOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M16.4 12.6c0-2.5 2-3.6 2.1-3.7-1.2-1.7-3-1.9-3.6-2-1.5-.2-3 .9-3.8.9-.8 0-2-.9-3.3-.8-1.7 0-3.2 1-4.1 2.5-1.8 3-.5 7.6 1.3 10.1.9 1.2 1.9 2.6 3.2 2.5 1.3 0 1.8-.8 3.3-.8 1.6 0 2 .8 3.3.8 1.4 0 2.3-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9 0 0-2.7-1-2.9-4.1zM13.9 5.3c.7-.8 1.2-2 1-3.2-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.3z"/></svg></span><div><h3>Ember for macOS</h3><p>a disk image, Apple silicon, Metal</p></div></div>
<p class="download-note">Open the disk image, drag the app across, press Start. A Mac mines on its GPU at about a fifth of a flagship card and proves on its CPU, slowly.</p>
<p class="download-note" data-mac-first-open>On first open macOS will say it cannot verify the app. Click Done, open System Settings, Privacy and Security, and click Open Anyway, then open Igneum Miner again. A signed and notarized build follows.</p>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg" class="btn primary"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5M5 16v4h14v-4"/></svg>Download for macOS <span data-dl-meta="miner-mac" style="font-weight:400;opacity:.85">v2.0.0 · 45.9 MB</span></a>
<div class="download-detail"><span>from dl.igneum.network</span><span>sha256 <span data-dl-sha="miner-mac">d7dff598444bc27274eb35a5276803b7bbc72876e5d7abeb154afd2cf5a5d4b2</span></span></div>
<a data-dl="miner-mac" href="https://dl.igneum.network/public/igneum-miner-mac.dmg" class="btn primary"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5M5 16v4h14v-4"/></svg>Download for macOS <span data-dl-meta="miner-mac" style="font-weight:400;opacity:.85">v2.0.1 · 45.9 MB</span></a>
<div class="download-detail"><span>from dl.igneum.network</span><span>sha256 <span data-dl-sha="miner-mac">7059a31c29b9798f32c04402f40c1e02db9674d15b497b08d51e2cc8e6845f45</span></span></div>
</div>
<div class="platform-body" id="panel-hive" role="tabpanel" aria-labelledby="tab-hive" data-platform-panel="hive" hidden>
<div class="download-platform"><span class="osmark" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span><div><h3>Ember for Linux and HiveOS</h3><p>a tarball for rigs and Hive flight sheets</p></div></div>
<p class="download-note">For the rig people. One tarball, the miner and the node inside, the same signed manifest as the desktop apps.</p>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5M5 16v4h14v-4"/></svg>Download the tarball <span data-dl-meta="miner-hive" style="font-weight:400;opacity:.85">v2.0.0 · 53.3 MB</span></a>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5M5 16v4h14v-4"/></svg>Download the tarball <span data-dl-meta="miner-hive" style="font-weight:400;opacity:.85">v2.0.1 · 52.7 MB</span></a>
<p class="fair" id="versions">Current build: Windows <span data-rm="versions.miner-windows.version">0.3.26</span>, macOS <span data-rm="versions.miner-mac.version">2.0.0</span>, HiveOS <span data-rm="versions.miner-hive.version">0.3.26</span>, the wallet <span data-rm="versions.wallet-mac.version">0.1.6</span>, <span data-rm="read_at_short">read 8 October 2026, 17:2x UK</span>. The machine-readable list, with every file’s SHA-256, is <a href="/release.json">/release.json</a>.</p>
<div class="hive-sheet" id="hive"><b>HiveOS Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-2.0.0.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<p class="sha">sha256 <span data-dl-sha="miner-hive">5c1369fc5559e8c85534da08cb6eafff9f688336f8993b44e0512fe86d6284d1</span></p>
<div class="hive-sheet" id="hive"><b>HiveOS Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-2.0.1.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<p class="sha">sha256 <span data-dl-sha="miner-hive">a16add6a9082747bc11d9bb9a69d193530f723dd2671fe1352ad59b1ae0a46ab</span></p>
</div>
</div>
<div>

View file

@ -277,6 +277,7 @@
<li>The coinbase transaction under the carrier's <code>hash_merkle_root</code>, and the segment record parsed out of its extra data: the record's <code>post_root</code> for its block is stored under the certificate's index.</li>
<li>Every read: a keccak-keyed Merkle Patricia proof against the stored root, verified on chain.</li>
</ol>
<p class="note"><b>Recovery locks are not accepted by this verifier.</b> Both Sepolia verifiers apply the two-thirds rule only: a certificate signed under the recovery rule (more than half of the anchored weight after a full window with no lock, Review B F04) carries under two thirds of the installed table and <code>submitCertificate</code> reverts, so every root this oracle answers passed the final rule. A recovery lock is never presented here as a final lock because it is never stored. The verifier reads weight against its installed table and nothing else; a re-installed table moves the threshold with it.</p>
<div class="trust"><h3>Trust anchors and unchecked signatures, named (also returned by the contract's <code>trust()</code>)</h3><ul>
<li><b>Deployer-installed trust anchor, disclosed, not removed:</b> the shared verifier's voter table. It stays because no header field commits to the table yet; the day one does, the table is read from the chain and this anchor goes. The devnet-4 verifier's table installs at the chain's first lock; the roots and certificate recorded below are the earlier devnet's, under its verifier's table (checkpoint 2127).</li>
<li>The voter table and weights the shared verifier checks certificates against were installed by the verifier's deployer (read from a node at checkpoint 2127), not read from the chain. A certificate is verified against that installed table; a later table that drifts past the rule's margin needs a new install by that deployer.</li>
@ -295,6 +296,7 @@
<div class="k">Executed</div><div>A node ran it at a chain block and reports a result (status, gas, logs). On these pages an execution result is reported by the node, not authenticated, unless the page says it is.</div>
<div class="k">Proven</div><div>An aggregator's segment record, carried in a block's coinbase and signed with its vote key, commits to the state root after that chain block; nodes check the statement against their own execution before paying it. The SP1 proof behind the statement is verified by nodes, not in the browser or on Sepolia.</div>
<div class="k">Finalised</div><div>A certified checkpoint has the block in its past: an aggregate BLS signature by voters holding two thirds of active weight and at least 17/30 of total weight over the checkpoint, checked here against the voter table the node supplies.</div>
<div class="k">Recovery lock</div><div>Not a fifth state and never shown as finalised. After a full weight window with no lock, the finality rule accepts a checkpoint signed by more than half of the anchored weight (the recovery rule, Review B F04). The node reports each lock's kind beside its checkpoint (<code>lockKind</code>: final or recovery); the certificate bytes carry none, so the kind is the node's report. These pages print a recovery lock as <b>recovery lock, not final</b> on the result, in the receipt file (<code>lock_state</code>) and in the one-file verifier, and refuse a receipt that claims final under a reported recovery lock. On a node before the field nothing is shown. Devnet 3 and the 2.0 devnet, no value.</div>
</div>
<p class="note">The same four definitions sit on <a href="/light">/light</a>, <a href="/receipt">/receipt</a> and <a href="/oracle">/oracle</a> (one source: <code>site/partials/terms.html</code>). The devnet, no value.</p>
</section>

View file

@ -5,6 +5,7 @@
<div class="k">Executed</div><div>A node ran it at a chain block and reports a result (status, gas, logs). On these pages an execution result is reported by the node, not authenticated, unless the page says it is.</div>
<div class="k">Proven</div><div>An aggregator's segment record, carried in a block's coinbase and signed with its vote key, commits to the state root after that chain block; nodes check the statement against their own execution before paying it. The SP1 proof behind the statement is verified by nodes, not in the browser or on Sepolia.</div>
<div class="k">Finalised</div><div>A certified checkpoint has the block in its past: an aggregate BLS signature by voters holding two thirds of active weight and at least 17/30 of total weight over the checkpoint, checked here against the voter table the node supplies.</div>
<div class="k">Recovery lock</div><div>Not a fifth state and never shown as finalised. After a full weight window with no lock, the finality rule accepts a checkpoint signed by more than half of the anchored weight (the recovery rule, Review B F04). The node reports each lock's kind beside its checkpoint (<code>lockKind</code>: final or recovery); the certificate bytes carry none, so the kind is the node's report. These pages print a recovery lock as <b>recovery lock, not final</b> on the result, in the receipt file (<code>lock_state</code>) and in the one-file verifier, and refuse a receipt that claims final under a reported recovery lock. On a node before the field nothing is shown. Devnet 3 and the 2.0 devnet, no value.</div>
</div>
<p class="note">The same four definitions sit on <a href="/light">/light</a>, <a href="/receipt">/receipt</a> and <a href="/oracle">/oracle</a> (one source: <code>site/partials/terms.html</code>). The devnet, no value.</p>
</section>

View file

@ -292,6 +292,7 @@
<div class="k">Executed</div><div>A node ran it at a chain block and reports a result (status, gas, logs). On these pages an execution result is reported by the node, not authenticated, unless the page says it is.</div>
<div class="k">Proven</div><div>An aggregator's segment record, carried in a block's coinbase and signed with its vote key, commits to the state root after that chain block; nodes check the statement against their own execution before paying it. The SP1 proof behind the statement is verified by nodes, not in the browser or on Sepolia.</div>
<div class="k">Finalised</div><div>A certified checkpoint has the block in its past: an aggregate BLS signature by voters holding two thirds of active weight and at least 17/30 of total weight over the checkpoint, checked here against the voter table the node supplies.</div>
<div class="k">Recovery lock</div><div>Not a fifth state and never shown as finalised. After a full weight window with no lock, the finality rule accepts a checkpoint signed by more than half of the anchored weight (the recovery rule, Review B F04). The node reports each lock's kind beside its checkpoint (<code>lockKind</code>: final or recovery); the certificate bytes carry none, so the kind is the node's report. These pages print a recovery lock as <b>recovery lock, not final</b> on the result, in the receipt file (<code>lock_state</code>) and in the one-file verifier, and refuse a receipt that claims final under a reported recovery lock. On a node before the field nothing is shown. Devnet 3 and the 2.0 devnet, no value.</div>
</div>
<p class="note">The same four definitions sit on <a href="/light">/light</a>, <a href="/receipt">/receipt</a> and <a href="/oracle">/oracle</a> (one source: <code>site/partials/terms.html</code>). The devnet, no value.</p>
</section>

View file

@ -0,0 +1,26 @@
{
"run_id": "ra-20261008T1915-ver",
"manifest_sha": "4cdcc488",
"cut_tip": "igneumd 2.0.0 on the 2.0 devnet (igneum-devnet-4, chain id 4465); the reference apps with the F04 lock-state code (branch reference-apps-f04); Devnet 3 fixtures for the payment receipt; Sepolia oracle 0xbb345004 on the devnet-4 verifier",
"evidence_dir": "tools/reference-apps/ver/evidence (on master; summary.json plus one file per case)",
"boxes": [
"mac-reference-apps-lane (network client only; no build)"
],
"cells": [
{
"cell": "harness:reference-apps-ver",
"status": "RUNNING",
"evidence": "tools/reference-apps/ver/evidence/summary.json"
},
{
"cell": "harness:reference-apps-ver-trust-anchor",
"status": "FAIL",
"evidence": "tools/reference-apps/ver/evidence/VER-01.json"
},
{
"cell": "harness:reference-apps-ver-availability",
"status": "FAIL",
"evidence": "tools/reference-apps/ver/evidence/VER-05.json"
}
]
}

View file

@ -419,16 +419,14 @@
"cases": [
"VER-03",
"VER-04",
"VER-05",
"VER-06",
"VER-08"
],
"coverage": {
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction",
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction; Review B F04: a receipt claiming lock_state final under a certificate the node reports as a recovery lock is refused, a recovery lock prints as recovery lock, not final (Node and browser negative cases on the fixtures; the field is not live on devnet-4 tonight, so no live recovery lock was seen)",
"VER-04": "partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's",
"VER-05": "partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise",
"VER-06": "partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run",
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows"
"VER-08": "partial: every served state on /light, /receipt and /oracle uses one of included, executed, proven, finalised from the shared definition block, the lock line serves the live DAA before the first certificate, and the trust assumptions are listed on each page; the wallet and explorer surfaces are their lanes' rows; Review B F04: /light, /receipt, the receipt file and the one-file verifier show a node-reported recovery lock as recovery lock, not final, /oracle says recovery locks are not accepted by its verifier, the terms block defines the recovery lock"
}
},
"harness:reference-apps-ver-trust-anchor": {
@ -459,6 +457,17 @@
"UX-05": "partial: the vote-key commitment on the wire (verify::tests::a_share_under_another_key_is_refused_before_the_hash: known-pass the member's key, known-fail another key on the share and a job naming another key, refused with code vote_key before the hash), the open pool's sidechain::check_key (the claimed key, the header's key and the reveal are one key), the same-nonce-other-template wrong_hash test, the TLS binding test; the malicious-pool run and the leave-and-retain run on the devnet-4 pair are the UX-05 batch's other evidence",
"UX-04": "partial: PPLNS distribution tests (fee first, never overpays, late joiner), the payout key file round trip, the signed transfer decode; the live payout path on the devnet-4 pair is the UX-04 batch's evidence"
}
},
"harness:reference-apps-ver-availability": {
"command": "node tools/reference-apps/ver/run.mjs (the same run; VER-05 reads from tools/reference-apps/ver/evidence/VER-05.json: the public read node reports its start mode through igneum_getExecStatus, the reference reader on build-1 is the build-server lane's read)",
"box_class": "harness (network run on the 2.0 devnet)",
"fixtures": [],
"cases": [
"VER-05"
],
"coverage": {
"VER-05": "partial: the public read node must report startedFrom genesis (no snapshot) and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot; the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise. Run ra-20261008T1915-ver: FAIL, the public node reports startedFrom snapshot (restarted from a snapshot after 17:46 UK); the reader on build-1 still from genesis"
}
}
},
"not_run": {

View file

@ -134,6 +134,21 @@ async function headerPath(fromHash, chainNumber, cpNumber, cpHash) {
}
// ---- the checkpoint -----------------------------------------------------------------------------------------------
// Review B F04 (8 October 2026): the node reports a lock state beside each checkpoint (igneum_getFinalityCheckpoints: lockKind and recoveryLock
// per checkpoint, latestLockKind at the top); the service passes it through as lock_state so the pages and receipts show a recovery lock as a
// recovery lock, never as final. Absent on nodes before the field: the answer carries no lock_state and nothing changes.
async function withLockState(cp) {
if (!cp) return cp;
try {
const r = await exec('igneum_getFinalityCheckpoints', [{ last: 2000 }]);
const c = (r && r.checkpoints || []).find(x => Number(x.index) === Number(cp.index) && strip(x.hash) === strip(cp.hash));
// the node lane's names (2.0.2 line, e9ab052f): lockKind "final" or "recovery" and recoveryLock per checkpoint
const v = c ? (c.lockKind ?? (c.recoveryLock === true ? 'recovery' : c.recoveryLock === false ? 'final' : undefined)) : undefined;
if (v !== undefined && v !== null) return { ...cp, lock_state: String(v) };
} catch {}
return cp;
}
async function checkpoint() {
if (process.env.DATABASE_URL) {
// the Devnet 3 observer's rows, read the way the site's /api/checkpoint reads them (DATABASE_URL from the box's observer env)
@ -144,12 +159,12 @@ async function checkpoint() {
throw httpError(404, `no finality certificate yet on the Igneum 2.0 devnet (${CHAIN_ID_NAME}); the first lock comes when the weight window fills at DAA ${window ?? 7200}${daa !== null ? `, the chain reads DAA ${daa.toLocaleString('en-GB')} now` : ''}`);
}
if (cp.chain_id !== CHAIN_ID_NAME) throw httpError(409, `the stored certificate is for ${cp.chain_id}, not ${CHAIN_ID_NAME}; refused`);
return { ok: true, now: new Date().toISOString(), ...cp };
return { ok: true, now: new Date().toISOString(), ...(await withLockState(cp)) };
}
const r = await fetch(CHECKPOINT_URL, { signal: AbortSignal.timeout(15000), cache: 'no-store' });
const j = await r.json();
if (!j.ok) throw new Error('checkpoint: ' + (j.error || r.status));
return j;
return withLockState(j);
}
async function chainNumberOf(hash) {
const b = await exec('eth_getBlockByHash', ['0x' + strip(hash), false]);
@ -188,7 +203,7 @@ async function balance(q) {
if (!rec) throw httpError(500, `the carrier's coinbase holds ${records.length} segment record(s), none for segment ${chosen.first}`);
// the smallest proof: the earliest certified checkpoint at or above the carrier (the certificate used travels in the answer)
let certificate = null;
if (process.env.DATABASE_URL) certificate = await earliestCheckpointAbove(neon(), chosen.carrierNumber, chainNumberOf, `${SOURCE}_live_certificates`).catch(() => null);
if (process.env.DATABASE_URL) certificate = await withLockState(await earliestCheckpointAbove(neon(), chosen.carrierNumber, chainNumberOf, `${SOURCE}_live_certificates`).catch(() => null));
let cpN = cpNumber, cpH = cpHash, cpI = cpIndex;
if (certificate) { cpH = certificate.hash; cpI = Number(certificate.index); cpN = await chainNumberOf(cpH); }
const headers = await headerPath(chosen.carrier, chosen.carrierNumber, cpN, cpH);
@ -240,7 +255,7 @@ async function receipt(q) {
}
const mustCover = paidRecord ? Math.max(chainNumber, Number(paidRecord.carrierNumber)) : chainNumber;
let certificate = null;
if (process.env.DATABASE_URL) certificate = await earliestCheckpointAbove(neon(), mustCover, chainNumberOf, `${SOURCE}_live_certificates`).catch(() => null);
if (process.env.DATABASE_URL) certificate = await withLockState(await earliestCheckpointAbove(neon(), mustCover, chainNumberOf, `${SOURCE}_live_certificates`).catch(() => null));
if (certificate) { cpHash = certificate.hash; cpIndex = Number(certificate.index); }
const cpNumber = await chainNumberOf(cpHash);
if (chainNumber > cpNumber) throw httpError(409, `not final yet: executed at chain block ${chainNumber}, the latest certified checkpoint is chain block ${cpNumber}; try again in about ${chainNumber - cpNumber + 30} s`);

View file

@ -7,7 +7,7 @@ import { readFileSync } from 'node:fs';
import { blake2b } from '@noble/hashes/blake2.js';
import { keccak_256 } from '@noble/hashes/sha3.js';
import { bls12_381 } from '@noble/curves/bls12-381.js';
import { verifyReceipt, verifyBalance, verifyPaymentReceipt } from '../../../site/lc/core.js';
import { verifyReceipt, verifyBalance, verifyPaymentReceipt, lockStateOf } from '../../../site/lc/core.js';
const deps = { blake2b, bls: bls12_381, keccak: keccak_256 };
const here = new URL('.', import.meta.url).pathname;
@ -75,5 +75,33 @@ if (pay && pay.segment) {
P('genuine payment receipt', d => {}, true);
const g = verifyPaymentReceipt(pay, deps); if (g.payment) console.log(` outcome: ${g.outcome.amount_wei} wei of ${g.outcome.asset} to ${g.outcome.recipient}, ${g.outcome.logs.length} log(s), ${g.ms} ms`);
} else if (pay) console.log('payment fixture has no segment data: ' + pay.payment_unavailable);
// ---- Review B F04: a recovery lock is never presented as a final lock ------------------------------------------------
{
const clone = x => JSON.parse(JSON.stringify(x));
const rec = clone(receipt); rec.checkpoint.certificate.lock_state = 'recovery';
const r1 = verifyReceipt(rec, deps);
if (!r1.verified || r1.lock_state !== 'recovery') { console.log('FAIL F04: a receipt under a node-reported recovery lock must verify with lock_state recovery, got', r1.verified, r1.lock_state, r1.reason); process.exit(1); }
console.log('ok F04: a receipt under a recovery lock verifies with lock_state recovery (never final)');
const rec2 = clone(rec); rec2.lock_state = 'final';
const r2 = verifyReceipt(rec2, deps);
if (r2.verified) { console.log('FAIL F04: a receipt claiming lock_state final under a recovery-lock certificate must be refused'); process.exit(1); }
console.log('ok F04: a receipt claiming final under a recovery lock is refused ::', r2.reason);
const rec3 = clone(receipt); rec3.checkpoint.certificate.lock_state = 'anchored-maybe';
const r3 = verifyReceipt(rec3, deps);
if (r3.verified) { console.log('FAIL F04: an unknown lock state must be refused'); process.exit(1); }
console.log('ok F04: an unknown lock state is refused ::', r3.reason);
const rec4 = clone(receipt);
const r4 = verifyReceipt(rec4, deps);
if (!r4.verified || r4.lock_state !== null) { console.log('FAIL F04: no field, nothing changes (lock_state null), got', r4.lock_state); process.exit(1); }
console.log('ok F04: without the field nothing changes (lock_state null, the page renders as before)');
if (lockStateOf({ lockKind: 'recovery' }) !== 'recovery' || lockStateOf({ recoveryLock: false }) !== 'final' || lockStateOf({}) !== null) { console.log('FAIL F04: lockStateOf shape'); process.exit(1); }
if (balance) {
const cp = clone(balance.checkpoint_certificate || load(here + '../fixtures/dn3-checkpoint.json')); cp.lock_state = 'recovery';
const rb = verifyBalance(cp, balance, deps);
if (!rb.verified || rb.lock_state !== 'recovery') { console.log('FAIL F04: a balance under a recovery lock must verify with lock_state recovery, got', rb.verified, rb.lock_state, rb.reason); process.exit(1); }
console.log('ok F04: a balance under a recovery lock verifies with lock_state recovery (never final)');
}
}
console.log(failed ? `FAILED ${failed}` : 'RESULT every case behaved');
process.exit(failed ? 1 : 0);

View file

@ -50,3 +50,7 @@ node demo.mjs
The deployer key is read from `~/.config/igneum/sepolia-deployer`. Sepolia's gas schedule is repriced (a plain
transfer estimates 12,000 gas), so the numbers in `deployment.json` are what this network charges.
## Recovery locks (Review B F04, 8 October 2026)
Recovery locks are not accepted by this verifier: a certificate under half of the installed table's weight reverts in `submitCertificate` (both Sepolia verifiers apply the two-thirds rule only), so every stored root passed the final rule and nothing the oracle answers can read final for a recovery lock. `trust()` gains this sentence at the next redeploy; the page carries it now.

View file

@ -26,9 +26,11 @@ if (args.includes('--tamper')) {
}
const r = verifyReceipt(receipt, deps);
console.log(`TRANSACTION INCLUSION RECEIPT 0x${receipt.tx_hash} on ${receipt.chain_id} (devnet, no value)`);
if (receipt.lock_state === 'recovery') console.log('RECOVERY LOCK: the node reported this checkpoint locked under the recovery rule (more than half of the anchored weight after a full window with no lock), not the final rule. Nothing in this file is final.');
console.log('What this file authenticates: that the signed transaction is included in a block that is finalised. What it does not: the execution outcome (status, gas), which is carried as the node reported it. A payment receipt, which authenticates the transfer outcome, is a different file.');
print(r);
if (!r.verified) { console.log(`REFUSED: ${r.reason}`); process.exit(1); }
if (r.lock_state === 'recovery') console.log('LOCK STATE: recovery, as the node reported it; this receipt is included under a recovery lock, not a final one.');
const t = r.tx;
console.log(`INCLUSION VERIFIED in ${r.ms} ms: a signed transaction of ${formatIgn(t.value)} IGN to ${t.to || 'contract creation'} (${t.value} wei) is included in block ${r.block.slice(0, 16)} at DAA ${r.block_daa} (${new Date(Number(r.block_time)).toISOString()}), finalised under checkpoint ${r.checkpoint}; ${r.headers} headers checked; ${r.certificate}.`);
console.log('Reported by the node, not authenticated by this file: from ' + (receipt.tx_as_reported && receipt.tx_as_reported.from) + (receipt.execution ? `, executed with status ${receipt.execution.status === '0x1' ? 'success' : 'failed'}` : '') + '. The voter table with weights came from the node (spec 10.1). In the four words: included and finalised are authenticated, executed is reported, proven is not claimed.');

View file

@ -1,8 +1,8 @@
{
"case": "VER-01",
"title": "Authenticate light-client bootstrap",
"run_id": "ra-20261008T1746-ver",
"run_at": "2026-10-08T17:46:23.207Z",
"run_id": "ra-20261008T1915-ver",
"run_at": "2026-10-08T19:15:13.475Z",
"network_rpc": "https://rpc.devnet.igneum.network",
"verdict": "FAIL",
"note": "FAIL by design today: the voter table is a node-supplied trust anchor (disclosed on /light); closing it needs a header commitment to the table or a shipped trust anchor with authority-change tracking (VER-02)",

View file

@ -1,8 +1,8 @@
{
"case": "VER-02",
"title": "Verify evolving authority and execution statements",
"run_id": "ra-20261008T1746-ver",
"run_at": "2026-10-08T17:46:23.402Z",
"run_id": "ra-20261008T1915-ver",
"run_at": "2026-10-08T19:15:13.641Z",
"network_rpc": "https://rpc.devnet.igneum.network",
"verdict": "FAIL",
"note": "FAIL by design today, disclosed: statement-based (the proof is verified by nodes), table from the node; closing needs an in-browser verifier for the permitted proof and a table commitment",

View file

@ -1,8 +1,8 @@
{
"case": "VER-03",
"title": "Prove successful payment rather than inclusion",
"run_id": "ra-20261008T1746-ver",
"run_at": "2026-10-08T17:46:23.752Z",
"run_id": "ra-20261008T1915-ver",
"run_at": "2026-10-08T19:15:13.930Z",
"network_rpc": "https://rpc.devnet.igneum.network",
"verdict": "PASS",
"note": "run on the Devnet 3 fixtures; the 2.0 devnet re-run needs its first paid segment",

View file

@ -1,11 +1,11 @@
{
"case": "VER-04",
"title": "Bound cross-chain oracle trust and replay",
"run_id": "ra-20261008T1746-ver",
"run_at": "2026-10-08T17:46:23.752Z",
"run_id": "ra-20261008T1915-ver",
"run_at": "2026-10-08T19:15:13.930Z",
"network_rpc": "https://rpc.devnet.igneum.network",
"verdict": "PASS",
"note": "oracles: devnet-4 0xbb3450049926da3572e6b67cb94df312fe349e34 on the igneum-devnet-4 verifier",
"note": "oracles: devnet-4 0x06c12c0c44c952ae10dcbbe3d5450a13168baccb on the igneum-devnet-4 verifier",
"checks": [
{
"name": "the oracle discloses its trust anchors and unchecked signatures in trust()",

View file

@ -1,16 +1,16 @@
{
"case": "VER-05",
"title": "Reconstruct required state without founder storage",
"run_id": "ra-20261008T1746-ver",
"run_at": "2026-10-08T17:46:23.866Z",
"run_id": "ra-20261008T1915-ver",
"run_at": "2026-10-08T19:15:14.066Z",
"network_rpc": "https://rpc.devnet.igneum.network",
"verdict": "PASS",
"verdict": "FAIL",
"note": "",
"checks": [
{
"name": "a node reconstructs the execution state from the chain alone (the public node executed from genesis, no snapshot)",
"ok": true,
"detail": "startedFrom genesis, executedTip 3072, recordsContinuous true"
"ok": false,
"detail": "startedFrom snapshot, executedTip 4379, recordsContinuous true"
},
{
"name": "the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot, data dir fresh at 17:19 BST)",

View file

@ -1,8 +1,8 @@
{
"case": "VER-06",
"title": "Detect withholding, corruption and stale data",
"run_id": "ra-20261008T1746-ver",
"run_at": "2026-10-08T17:46:24.086Z",
"run_id": "ra-20261008T1915-ver",
"run_at": "2026-10-08T19:15:14.918Z",
"network_rpc": "https://rpc.devnet.igneum.network",
"verdict": "PASS",
"note": "",
@ -10,7 +10,7 @@
{
"name": "the read service never serves a certificate from another network (refuses with the reason when none of its own exists)",
"ok": true,
"detail": "no finality certificate yet on the Igneum 2.0 devnet (igneum-devnet-4); the first lock comes when the weight window fills at DAA 7200, the chain reads DAA 5,913 now"
"detail": "igneum-devnet-4"
},
{
"name": "a withheld header in the path is detected",

View file

@ -1,8 +1,8 @@
{
"case": "VER-08",
"title": "Keep every user-facing state truthful",
"run_id": "ra-20261008T1746-ver",
"run_at": "2026-10-08T17:46:24.087Z",
"run_id": "ra-20261008T1915-ver",
"run_at": "2026-10-08T19:15:14.918Z",
"network_rpc": "https://rpc.devnet.igneum.network",
"verdict": "PASS",
"note": "",

View file

@ -1,6 +1,6 @@
{
"run_id": "ra-20261008T1746-ver",
"at": "2026-10-08T17:46:24.088Z",
"run_id": "ra-20261008T1915-ver",
"at": "2026-10-08T19:15:14.919Z",
"cases": [
{
"id": "VER-01",
@ -20,11 +20,11 @@
{
"id": "VER-04",
"verdict": "PASS",
"note": "oracles: devnet-4 0xbb3450049926da3572e6b67cb94df312fe349e34 on the igneum-devnet-4 verifier"
"note": "oracles: devnet-4 0x06c12c0c44c952ae10dcbbe3d5450a13168baccb on the igneum-devnet-4 verifier"
},
{
"id": "VER-05",
"verdict": "PASS",
"verdict": "FAIL",
"note": ""
},
{