Dated corrections (2026-10-27): anchor/README.md, the number of Falcon-512 seals an anchor carried in practice is stated with its measured ranges and dates (up to five, most three to five, under the write cap of five until 2026-08-16; eight or nine at nine validators; eight to ten at ten validators, over 400 anchors measured 2026-10-27). tools/verify-anchor.mjs: the ROUND leg names where it is active (testnet 28001 since block 6,157,024; chain 2800 since block 24,148,752), element 6 is counted from 0, and the tool exits 2 (UNMEASURED, with the reason) when the endpoint serves another chain than CHAIN_ID or does not say which. Correction to the message of 9820765 (2026-10-27): it also listed 'the follower's rewrite bound', which belonged to the previous round, and said 'three to five' where the text says 'three or more'.
This commit is contained in:
parent
9820765ebb
commit
cdc523ea64
@ -18,10 +18,11 @@ should not be confused:
|
||||
replacement: a rewrite of anchored history must also forge the post-quantum signatures.
|
||||
2. **The enforced minimum.** Since 2026-08-14 a validator does not finalize an anchor block unless
|
||||
its certificate holds at least three valid Falcon-512 seals (f+1 of nine, the guarantee that at
|
||||
least one honest validator signed; five were carried in practice under the write cap of five until 2026-08-16, then
|
||||
eight or nine at nine validators, and nine or ten at ten validators, measured 2026-10-27). A node started from the
|
||||
least one honest validator signed; up to five, most of them three to five, were carried under the write cap of five until
|
||||
2026-08-16 (one or two on some anchors before 2026-08-14), then eight or nine at nine validators, and eight to ten at ten
|
||||
validators, measured 2026-10-27 over 400 anchors). A node started from the
|
||||
published package (`run-node.sh`) lowers that count to one seal, at the cost stated in `SPEC.md`
|
||||
section 6.4 (corrected 2026-10-27: this item said "a node", and "eight or nine are carried in practice" in the present tense).
|
||||
section 6.4 (corrected 2026-10-27: this item said "a node", and "eight or nine are carried in practice" in the present tense; then, the same day, "five were carried in practice" under the write cap and "nine or ten" at ten validators).
|
||||
Raised at block
|
||||
14,961,456 (August 21, 2026): the enforced minimum became **six**, which at that time was as many as the QBFT quorum of the nine-validator set, the same count QBFT itself required to agree on the block.
|
||||
From 2026-09-11 the set was ten and QBFT's quorum seven, so the same six was above f+1 and
|
||||
|
||||
@ -24,16 +24,17 @@
|
||||
// 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
|
||||
// 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
|
||||
// 6. PROPOSER (AIP-22 stage 5, 2026-09-24) on a chain where headers carry element 6 (counting from 0), 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, or, in a block re-proposed in a round r > 0, the proposer QBFT expects for
|
||||
// round r (2026-09-25, D-406), on the most recent headers (PROPOSER_HEADERS, default 20);
|
||||
// and, as the nodes judge every header from H_hdr, the round is a canonical integer
|
||||
// at most 1024 and the coinbase is the proposer of some round 0..r at that height.
|
||||
// Where no header carries it (chain 2800 today) the tool says so and this leg does not
|
||||
// Where no header carries it (chain 2800 below its stage 5 height) the tool says so and this leg does not
|
||||
// change the verdict.
|
||||
// 7. ROUND (1(b), D-470, learned 2026-10-07, active on no chain yet) from a chain's round height H_r the certificate is
|
||||
// 7. ROUND (1(b), D-470, learned 2026-10-07; active on testnet 28001 since block 6,157,024 and on chain 2800 since
|
||||
// block 24,148,752, 2026-10-24) from a chain's round height H_r the certificate is
|
||||
// v3, RLP[3, round, [[scheme, idx, sig], ...]] under "AERE-PQ-ANCHOR-3", and every seal in it is over
|
||||
// keccak256(RLP["AERE-PQ-COMMIT-2", chainId, parentNumber, round, parentHash]) for the ONE round it names, so a
|
||||
// seal made in another round, or over another block, does not verify. The tool dispatches on the certificate's
|
||||
@ -68,7 +69,9 @@
|
||||
// RPC=http://127.0.0.1:8545 node verify-anchor.mjs
|
||||
// ANCHOR_V2_BLOCK=17047600 node verify-anchor.mjs # the v2 height V (default: the chain's own, from the table; "none" = never)
|
||||
//
|
||||
// Exit 0 only when every leg holds on every anchor checked. Any failure is the finding.
|
||||
// Exit 0 only when every leg holds on every anchor checked. Any failure is the finding. Exit 2 (since 2026-10-27): UNMEASURED, a line
|
||||
// that starts with "UNMEASURED:" says why: the endpoint serves another chain than CHAIN_ID or does not say which, or the manifests do
|
||||
// not load or are of another chain; nothing about the chain was judged, and that is not a finding about it.
|
||||
|
||||
import { keccak_256 } from '@noble/hashes/sha3';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
@ -113,7 +116,7 @@ const COMMIT_DOMAIN = 'AERE-PQ-COMMIT-1';
|
||||
// v3; at an anchor at or above H_r only v3 is admitted, below H_r a v3 is refused, and every refusal of a v3 anchor names the code
|
||||
// both clients name (AERE-PQ-V3-01..08, in the fixed order of PROIECT-1B 1.4), so a stranger's verdict can be compared with theirs.
|
||||
// 2026-10-09: three margins of the v3 rule, each with its note where it applies: R2 in two passes (every Falcon row, then every extra
|
||||
// row), a row index above 2^31-1 refused 03 at the decoding, and an element 6 that is present but is not a list (03 from H_r).
|
||||
// row), a row index above 2^31-1 refused 03 at the decoding, and an element 5 (the certificate slot, counting from 0) that is present but is not a list (03 from H_r).
|
||||
// They follow the fixed order of the codes named above (R1: the form by height 01 / 02, then the decoding 03, then the digest 04; R2: the
|
||||
// Falcon rows, then the extra rows). This tool runs neither client: how a client binary answers these margins is not measured here.
|
||||
const COMMIT_DOMAIN_2 = 'AERE-PQ-COMMIT-2';
|
||||
@ -124,7 +127,11 @@ const MAX_V3_ROWS = 64;
|
||||
// for a chain it is written here, in the same hour, before H_r (D-372: the new verifier is published BEFORE the first v3 anchor).
|
||||
// COMMIT_ROUND_BLOCK overrides (a height, or "none"); cloud-gateway/orar-ancore.mjs carries the same value for the consumers.
|
||||
const COMMIT_ROUND_DEFAULT = CHAIN_ID === 28001 ? 6157024 : 24148752;
|
||||
const COMMIT_ROUND_BLOCK = process.env.COMMIT_ROUND_BLOCK === undefined ? COMMIT_ROUND_DEFAULT
|
||||
// 2026-10-27: the line above names 28001 and gives every OTHER id the H_r of 2800 (its form is read by the activation tools, so it stays
|
||||
// as it is); a chain that is neither has no H_r, which both clients read as never, as V below for another chain. From 2026-10-21 until
|
||||
// this date every third chain got 2800's H_r and, with no V, the tool refused to start ("COMMIT_ROUND_BLOCK=24148752 needs the v2 height
|
||||
// below it"): found by the probes of this tool on synthetic chains (verify-anchor-ferestre-proba.mjs, ALT-LANT).
|
||||
const COMMIT_ROUND_BLOCK = process.env.COMMIT_ROUND_BLOCK === undefined ? (CHAIN_ID === 28001 || CHAIN_ID === 2800 ? COMMIT_ROUND_DEFAULT : null)
|
||||
: (/^(none|unset|)$/i.test(process.env.COMMIT_ROUND_BLOCK) ? null : Number(process.env.COMMIT_ROUND_BLOCK));
|
||||
if (COMMIT_ROUND_BLOCK !== null && !(Number.isInteger(COMMIT_ROUND_BLOCK) && COMMIT_ROUND_BLOCK > 0)) { console.error('FAIL: COMMIT_ROUND_BLOCK must be a positive block height or "none"'); stop(1); }
|
||||
// V per chain (2026-10-19, AIP-25 C5, D-539): aere.pq.anchorV2Block, the height from which every anchor below H_r carries the
|
||||
@ -274,7 +281,7 @@ const quorumOf = (n) => Math.floor((2 * n + 2) / 3);
|
||||
// re-encode to the bytes received; PqAnchorV2.decode: version 2, at most 64 rows, scheme tag within a byte and known, index not negative),
|
||||
// the second client through PqAnchor.DecodeCertificate (v1: no leading zero byte, the lone 0x00 included, index at most int.MaxValue,
|
||||
// signature not empty) and PqAnchorV2.Decode (v2: exactly [2, rows], rows of exactly three scalars, the same scalar rules, known tags,
|
||||
// signature not empty, the canonical round trip). `node` is element 6 as read by rlpRead, `raw` its bytes as carried. Returns
|
||||
// signature not empty, the canonical round trip). `node` is element 5 (the certificate, counting from 0) as read by rlpRead, `raw` its bytes as carried. Returns
|
||||
// { rows: [{ scheme, idx, sig }] } or { why }, the first refusal in the order below. The ORDER of the rows is judged by the caller, as before.
|
||||
function decodeV1V2(node, isV2, raw) {
|
||||
const tn = (x) => (x.list ? x.item.map(tn) : Buffer.from(x.item));
|
||||
@ -330,11 +337,21 @@ async function loadText(loc) {
|
||||
const isUrl = /^https?:\/\//.test(MANIFESTS);
|
||||
const join = (a, b) => isUrl ? a.replace(/\/$/, '') + '/' + b : path.join(a, b);
|
||||
|
||||
// 2026-10-27 (R9-S2): the endpoint is only a courier, and a courier of ANOTHER chain says nothing about this one. Until this date the
|
||||
// tool never asked which chain the endpoint serves: pointed at an endpoint of another chain, it measured that chain's grid and
|
||||
// reported "FAIL: ... The chain is not anchoring on a regular grid" about a healthy chain (exit 1), which verify-proof.mjs turned into
|
||||
// an INVALID envelope. Now the chain is read FIRST: another chain, or no answer, is UNMEASURED (exit 2), named, and nothing is judged.
|
||||
// The same for inputs that are not about this chain: a manifest index of another chain, or none that loads, measures nothing.
|
||||
let servedChain = null;
|
||||
try { servedChain = Number(BigInt(await rpc('eth_chainId', []))); }
|
||||
catch (e) { console.error('UNMEASURED: the endpoint ' + RPC + ' did not say which chain it serves (eth_chainId: ' + String(e && e.message).slice(0, 120) + '); nothing about chain ' + CHAIN_ID + ' was measured'); stop(2); }
|
||||
if (servedChain !== CHAIN_ID) { console.error('UNMEASURED: the endpoint ' + RPC + ' serves chain ' + servedChain + ', not chain ' + CHAIN_ID + ' (CHAIN_ID); nothing about chain ' + CHAIN_ID + ' was measured. Point RPC at an endpoint of chain ' + CHAIN_ID + ', or set CHAIN_ID=' + servedChain + ' with the manifests of that chain.'); stop(2); }
|
||||
|
||||
let index;
|
||||
try { index = JSON.parse(await loadText(join(MANIFESTS, 'index.json'))); }
|
||||
catch (e) { console.error('FAIL: cannot load manifest index from ' + MANIFESTS + ': ' + e.message); stop(1); }
|
||||
catch (e) { console.error('UNMEASURED: cannot load the manifest index from ' + MANIFESTS + ': ' + e.message + '; nothing about chain ' + CHAIN_ID + ' was measured'); stop(2); }
|
||||
if (!index || !Array.isArray(index.manifests) || !index.manifests.length) { console.error('FAIL: manifest index has no entries'); stop(1); }
|
||||
if (Number(index.chainId) !== CHAIN_ID) { console.error('FAIL: manifest index is for chain ' + index.chainId); stop(1); }
|
||||
if (Number(index.chainId) !== CHAIN_ID) { console.error('UNMEASURED: the manifest index at ' + MANIFESTS + ' is for chain ' + index.chainId + ', not chain ' + CHAIN_ID + ' (CHAIN_ID); pass MANIFESTS=<the manifests of chain ' + CHAIN_ID + '>; nothing was measured'); stop(2); }
|
||||
|
||||
console.log('chain ' + CHAIN_ID + ' rpc ' + RPC + ' manifests ' + MANIFESTS);
|
||||
console.log('');
|
||||
@ -640,13 +657,13 @@ for (let i = 0; i < COUNT; i++) {
|
||||
+ ' is accepted as written; checked only that its ' + pairs.length + ' seal indices are distinct (' + (pairs.join(',') || 'no seals') + '). Not verified.');
|
||||
continue;
|
||||
}
|
||||
// 2026-10-09: an element 6 that is PRESENT but is not an RLP list (a byte string, or the empty string 0x80) is not "no certificate":
|
||||
// 2026-10-09: an element 5 (the certificate slot, counting from 0; corrected 2026-10-27: this said element 6) that is PRESENT but is not an RLP list (a byte string, or the empty string 0x80) is not "no certificate":
|
||||
// it is a certificate slot that does not decode, so from H_r this tool refuses it with 03 (the decoding), not 02. Until this date it
|
||||
// was named 02 ("no certificate element"). Below H_r this tool names no v3 code for it: it stays a FAIL without a code, named for what
|
||||
// it is. Which code, if any, a client binary names for it is not measured here.
|
||||
if (items.length >= 6 && !items[5].list) {
|
||||
if (certificateFormAt(h) === 3) console.log(h + ' FAIL: AERE-PQ-V3-03 (certificate v3): element 6 of extraData is present but is not an RLP list (' + items[5].item.length + ' byte string), so it does not decode as a certificate');
|
||||
else console.log(h + ' FAIL: element 6 of extraData (the certificate) is present but is not an RLP list (' + items[5].item.length + ' byte string)');
|
||||
if (certificateFormAt(h) === 3) console.log(h + ' FAIL: AERE-PQ-V3-03 (certificate v3): element 5 of extraData (counting from 0, the certificate slot) is present but is not an RLP list (' + items[5].item.length + ' byte string), so it does not decode as a certificate');
|
||||
else console.log(h + ' FAIL: element 5 of extraData (counting from 0, the certificate) is present but is not an RLP list (' + items[5].item.length + ' byte string)');
|
||||
bad++; continue;
|
||||
}
|
||||
if (items.length < 6 || items[5].item.length === 0) {
|
||||
@ -688,7 +705,7 @@ for (let i = 0; i < COUNT; i++) {
|
||||
// v1 / v2 it is the plain FAIL it always was. (2026-10-07, review of step 8: until then a v3 anchor failed here without a code.)
|
||||
if (!vset && !judgeV3) { bad++; console.log(h + ' FAIL: the validator list in element 1 of the header of block ' + (h - 1) + ' (the block these seals sign) is empty or malformed, so no seal can be bound to a validator'); continue; }
|
||||
if (judgeV3) {
|
||||
// The bytes of element 6 as carried (element 6 starts where element 5 ends): D3 is over these bytes, and the canonical round trip
|
||||
// The bytes of element 5 as carried (the certificate, counting from 0; it starts where element 4 ends): D3 is over these bytes, and the canonical round trip
|
||||
// below requires them to be exactly the re-encoding of what was read, so no second RLP form of the same certificate is admitted.
|
||||
const rawCert = ex.subarray(items[4].next, items[5].next);
|
||||
const v3 = { code: null, why: null, round: null, rows: [] };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user