Reference apps F04: the one-file verifier rebuilt on the box from the lock-state source; the oracle README names the refused recovery locks
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
bd39a7c18a
commit
9e689c3051
2 changed files with 20 additions and 2 deletions
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue