verify-anchor.mjs: step 6, the proposer's Falcon-512 seal on every recent header (AIP-22 stage 5), verified against the v3 registry key of its index and bound to the coinbase; proven on testnet 28001 with two planted forgeries rejected
This commit is contained in:
parent
c0e6b62d71
commit
0f6ec82fa1
@ -22,6 +22,11 @@
|
|||||||
// 5. ON-CHAIN PIN each manifest's hash, keccak256(addr||pk over rows), equals slot 0 of the immutable
|
// 5. ON-CHAIN PIN each manifest's hash, keccak256(addr||pk over rows), equals slot 0 of the immutable
|
||||||
// anchor contract named in the index, and that contract's code is the 11-byte
|
// anchor contract named in the index, and that contract's code is the 11-byte
|
||||||
// read-only getter with no SSTORE: the manifest you hold is the one the chain pinned.
|
// read-only getter with no SSTORE: the manifest you hold is the one the chain pinned.
|
||||||
|
// 6. PROPOSER (AIP-22 stage 5, 2026-09-24) on a chain where headers carry element 6, the proposer's
|
||||||
|
// Falcon-512 seal over keccak256(RLP["AERE-PQ-PROPOSAL-1", chainId, height, round, digest])
|
||||||
|
// verifies against the v3 registry key of its index, and that index is the header's
|
||||||
|
// coinbase, on the most recent headers (PROPOSER_HEADERS, default 20). Where no header
|
||||||
|
// carries it (chain 2800 today) the tool says so and this leg does not change the verdict.
|
||||||
//
|
//
|
||||||
// What this still does NOT prove: who RUNS the validators (today: one operator, the Foundation; we
|
// What this still does NOT prove: who RUNS the validators (today: one operator, the Foundation; we
|
||||||
// publish that ourselves), or that the RPC you use is honest about chain contents - bring your own
|
// publish that ourselves), or that the RPC you use is honest about chain contents - bring your own
|
||||||
@ -510,19 +515,78 @@ for (let i = 0; i < COUNT; i++) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. PROPOSER SEALS, AIP-22 stage 5 (added 2026-09-24). From H_hdr every header carries element 6, [index, signature]: the
|
||||||
|
// proposer's Falcon-512 seal over keccak256(RLP["AERE-PQ-PROPOSAL-1", chainId, height, round, digest]), where digest is the hash
|
||||||
|
// of the header re-encoded in the EXCLUDE_COMMIT_SEALS form (SPEC 2.3: round kept, commit seals an empty list, the certificate
|
||||||
|
// and the proposer seal omitted). The index names a row of the v3 registry epoch in force at that height, and that row's id must
|
||||||
|
// be the header's coinbase. Each header is first re-encoded in the on-chain form (round 0) and must reproduce the hash the
|
||||||
|
// endpoint reports: that is the control of THIS header's encoding, and a header that fails it is not judged at all. Headers
|
||||||
|
// without element 6 (every header of a chain, or of heights, before stage 5) are counted and named, never judged.
|
||||||
|
const PROPOSER_HEADERS = Number(process.env.PROPOSER_HEADERS || 20);
|
||||||
|
const PROPOSAL_DOMAIN = 'AERE-PQ-PROPOSAL-1';
|
||||||
|
const nested = (node) => (node.list ? node.item.map(nested) : Buffer.from(node.item));
|
||||||
|
function headerRlp(b, extra) {
|
||||||
|
const f = [hex(b.parentHash), hex(b.sha3Uncles), hex(b.miner), hex(b.stateRoot), hex(b.transactionsRoot), hex(b.receiptsRoot),
|
||||||
|
hex(b.logsBloom), scalar(b.difficulty), scalar(b.number), scalar(b.gasLimit), scalar(b.gasUsed), scalar(b.timestamp), extra,
|
||||||
|
hex(b.mixHash), hex(b.nonce)];
|
||||||
|
if (b.baseFeePerGas != null) f.push(scalar(b.baseFeePerGas));
|
||||||
|
if (b.withdrawalsRoot != null) f.push(hex(b.withdrawalsRoot));
|
||||||
|
if (b.blobGasUsed != null) { f.push(scalar(b.blobGasUsed)); f.push(scalar(b.excessBlobGas)); }
|
||||||
|
if (b.parentBeaconBlockRoot != null) f.push(hex(b.parentBeaconBlockRoot));
|
||||||
|
if (b.requestsHash != null) f.push(hex(b.requestsHash));
|
||||||
|
if (b.balHash != null) f.push(hex(b.balHash));
|
||||||
|
return rlpEncode(f);
|
||||||
|
}
|
||||||
|
let propOk = 0, propBad = 0, propUnmeasured = 0, propAbsent = 0;
|
||||||
|
{
|
||||||
|
const headNow = parseInt(await rpc('eth_blockNumber', []), 16);
|
||||||
|
for (let k = 0; k < PROPOSER_HEADERS; k++) {
|
||||||
|
const h = headNow - 2 - k; if (h < 1) break;
|
||||||
|
const b = await rpc('eth_getBlockByNumber', ['0x' + h.toString(16), false]);
|
||||||
|
const items = rlpRead(hex(b.extraData), 0).item;
|
||||||
|
if (items.length < 7) { propAbsent++; continue; }
|
||||||
|
const onchain = keccak(headerRlp(b, rlpEncode([nested(items[0]), nested(items[1]), nested(items[2]), Buffer.alloc(0), []])));
|
||||||
|
if (!onchain.equals(hex(b.hash))) {
|
||||||
|
propUnmeasured++;
|
||||||
|
console.log(h + ' PROPOSER SEAL NOT CHECKED HERE: this header does not re-encode to the hash the endpoint reports, so its proposal digest cannot be computed with confidence');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const seal = items[6];
|
||||||
|
if (!seal.list || seal.item.length !== 2 || seal.item[0].list || seal.item[1].list) { propBad++; console.log(h + ' FAIL: the proposer seal is not [index, signature]'); continue; }
|
||||||
|
const idx = seal.item[0].item.length ? parseInt(Buffer.from(seal.item[0].item).toString('hex'), 16) : 0;
|
||||||
|
const digestP = keccak(headerRlp(b, rlpEncode([nested(items[0]), nested(items[1]), nested(items[2]), nested(items[3]), []])));
|
||||||
|
const msgP = keccak(rlpEncode([Buffer.from(PROPOSAL_DOMAIN, 'ascii'), scalar(CHAIN_ID), scalar(h), Buffer.from(items[3].item), digestP]));
|
||||||
|
const ep = v3EpochAt(h);
|
||||||
|
const row = ep && ep.byIndex[idx];
|
||||||
|
if (!row) { propBad++; console.log(h + ' FAIL: proposer seal index ' + idx + ' names no row of a published v3 registry epoch in force at this height'); continue; }
|
||||||
|
if (String(b.miner).toLowerCase() !== row.id) { propBad++; console.log(h + ' FAIL: proposer seal index ' + idx + ' is id ' + row.id + ', but the coinbase is ' + b.miner); continue; }
|
||||||
|
if (!row.pk || !verifyFalcon512(row.pk, msgP, new Uint8Array(seal.item[1].item))) { propBad++; console.log(h + ' FAIL: the proposer seal of index ' + idx + ' is not a valid Falcon-512 signature over the proposal message'); continue; }
|
||||||
|
propOk++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const propChecked = propOk + propBad + propUnmeasured;
|
||||||
console.log('');
|
console.log('');
|
||||||
|
console.log(propChecked === 0
|
||||||
|
? 'proposer seals (AIP-22 stage 5): none of the ' + propAbsent + ' most recent headers carries one; stage 5 is not active at these heights on this chain, so there is nothing to verify'
|
||||||
|
: 'proposer seals (AIP-22 stage 5): ' + propOk + ' of ' + propChecked + ' recent headers carry a valid Falcon-512 proposal seal by the key of their coinbase'
|
||||||
|
+ (propUnmeasured ? ', ' + propUnmeasured + ' could not be checked here' : '') + (propAbsent ? ' (' + propAbsent + ' earlier headers carry none)' : ''));
|
||||||
console.log('seals: ' + sealsValid + '/' + sealsTotal + ' cryptographically valid and validator-bound manifests: ' + (manifests.length - manifestBad) + '/' + manifests.length + ' self-consistent');
|
console.log('seals: ' + sealsValid + '/' + sealsTotal + ' cryptographically valid and validator-bound manifests: ' + (manifests.length - manifestBad) + '/' + manifests.length + ' self-consistent');
|
||||||
const all = bad === 0 && manifestBad === 0 && unmeasured === 0;
|
const all = bad === 0 && manifestBad === 0 && unmeasured === 0 && propBad === 0 && propUnmeasured === 0;
|
||||||
console.log(all
|
console.log(all
|
||||||
? 'VERIFIED: ' + ok + '/' + COUNT + ' anchor blocks carry a post-quantum certificate bound under the block hash, every seal a valid '
|
? 'VERIFIED: ' + ok + '/' + COUNT + ' anchor blocks carry a post-quantum certificate bound under the block hash, every seal a valid '
|
||||||
+ ([...schemesSeen].sort().map(x => SCHEME_NAMES[x] || ('scheme ' + x)).join(' or ') || 'post-quantum')
|
+ ([...schemesSeen].sort().map(x => SCHEME_NAMES[x] || ('scheme ' + x)).join(' or ') || 'post-quantum')
|
||||||
+ ' signature by a key that the validator of that height bound to itself with its own consensus key.'
|
+ ' signature by a key that the validator of that height bound to itself with its own consensus key.'
|
||||||
: (bad === 0 && manifestBad === 0
|
+ (propOk ? ' And ' + propOk + ' of the ' + propOk + ' most recent headers checked carry a valid Falcon-512 proposal seal by the key of their coinbase (AIP-22 stage 5).' : '')
|
||||||
? 'UNMEASURED: ' + unmeasured + ' of ' + COUNT + ' anchors could not be fully checked ON THIS MACHINE because the SLH-DSA\n'
|
: (bad === 0 && manifestBad === 0 && propBad === 0
|
||||||
+ ' verifier is missing. Nothing here says the chain is wrong; install it and run again:\n'
|
? (unmeasured
|
||||||
+ ' npm install @noble/post-quantum'
|
? 'UNMEASURED: ' + unmeasured + ' of ' + COUNT + ' anchors could not be fully checked ON THIS MACHINE because the SLH-DSA\n'
|
||||||
: 'PROBLEMS: ' + bad + ' of ' + COUNT + ' anchors and ' + manifestBad + ' manifests failed - do not take our word for anything; the failure list above is the finding.'
|
+ ' verifier is missing. Nothing here says the chain is wrong; install it and run again:\n'
|
||||||
|
+ ' npm install @noble/post-quantum'
|
||||||
|
: '')
|
||||||
|
+ (propUnmeasured ? (unmeasured ? '\n' : '') + 'UNMEASURED: ' + propUnmeasured + ' recent header(s) did not re-encode to the hash the endpoint reports, so their'
|
||||||
|
+ ' proposer seals were not judged. This says nothing about the chain; try another endpoint (RPC=...).' : '')
|
||||||
|
: 'PROBLEMS: ' + bad + ' of ' + COUNT + ' anchors and ' + manifestBad + ' manifests failed' + (propBad ? ', and ' + propBad + ' proposer seals' : '') + ' - do not take our word for anything; the failure list above is the finding.'
|
||||||
+ (unmeasured ? ' (' + unmeasured + ' more could not be checked here: npm install @noble/post-quantum)' : '')));
|
+ (unmeasured ? ' (' + unmeasured + ' more could not be checked here: npm install @noble/post-quantum)' : '')));
|
||||||
if (!slhDsa) console.log('note: @noble/post-quantum is not installed, so a v2 certificate carrying SLH-DSA seals cannot be verified here (npm install @noble/post-quantum).');
|
if (!slhDsa) console.log('note: @noble/post-quantum is not installed, so a v2 certificate carrying SLH-DSA seals cannot be verified here (npm install @noble/post-quantum).');
|
||||||
// three states, three exit codes: 0 verified, 1 the chain failed a check, 2 this machine could not measure.
|
// three states, three exit codes: 0 verified, 1 the chain failed a check, 2 this machine could not measure.
|
||||||
process.exit(all ? 0 : (bad === 0 && manifestBad === 0 ? 2 : 1));
|
process.exit(all ? 0 : (bad === 0 && manifestBad === 0 && propBad === 0 ? 2 : 1));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user