AIP-22: conformance corpus published (aips/aip22-conformance/, eight of the nine kinds of section 9, every vector judged by both clients); section 5 re-proposal correction and the 2026-09-25/26/27 stage records; erratum for section 5
This commit is contained in:
parent
c40c52d3b6
commit
fa920a45a5
@ -159,7 +159,7 @@ From `H_fin` (a height at or above `H_id`), on every node that validates or read
|
|||||||
|
|
||||||
### 5. ECDSA out of the header
|
### 5. ECDSA out of the header
|
||||||
|
|
||||||
From `H_hdr` (at or above `H_fin`), `extraData` gains element 6, `proposerSeal = [index, signature]`, a Falcon-512 seal by the proposer over the message the proposal already signs, `keccak256(RLP["AERE-PQ-PROPOSAL-1", chainId, height, round, proposalDigest])`, where `proposalDigest` is the QBFT proposal digest recomputable from the stored header (the round is element 3 of the stored `extraData`; the digest is the `EXCLUDE_COMMIT_SEALS` encoding hash, SPEC 2.3). Element 6 is outside both hashes (like the commit seals and the certificate) and subject to the strict codec; when it is present, element 5 is present too, an empty list where the header carries no certificate, so position alone says which element is which (a six-element header whose element 5 is an empty list is not canonical and is refused). From `H_hdr` every header, anchors included, is admitted iff its ECDSA committed seals element is empty and the proposer seal verifies for the `coinbase` `id` at that height, over the round in element 3; an anchor is judged by the anchor rule as well. Cost: +663 B and -455 B (7 x 65) per block at N = 10, net +208 B, that is **+11.6 GB per node per year** (208 x 55,854,159 / 10^9). After `H_hdr` no ECDSA signature appears in any header or is examined in any consensus message; the last classical use is the RLPx transport.
|
From `H_hdr` (at or above `H_fin`), `extraData` gains element 6, `proposerSeal = [index, signature]`, a Falcon-512 seal by the proposer over the message the proposal already signs, `keccak256(RLP["AERE-PQ-PROPOSAL-1", chainId, height, round, proposalDigest])`, where `proposalDigest` is the QBFT proposal digest recomputable from the stored header (the round is element 3 of the stored `extraData`; the digest is the `EXCLUDE_COMMIT_SEALS` encoding hash, SPEC 2.3). Element 6 is outside both hashes (like the commit seals and the certificate) and subject to the strict codec; when it is present, element 5 is present too, an empty list where the header carries no certificate, so position alone says which element is which (a six-element header whose element 5 is an empty list is not canonical and is refused). From `H_hdr` every header, anchors included, is admitted iff its ECDSA committed seals element is empty and the proposer seal verifies, over the round in element 3, for the `coinbase` `id` at that height or, when that round is above zero, for the `id` of the proposer of that round (the proposer rotation computed from the parent's `coinbase` and the header's validator list); the round in element 3 is at most 1024 (no honest round gets near it, the round timer doubling each round), and, the parent being known, the `coinbase` is the proposer of some round from 0 to the round in element 3, that is, the block's builder; an anchor is judged by the anchor rule as well. The second case is the block prepared in one round and re-proposed unchanged in a later one: QBFT keeps the locked block, so its `coinbase` stays its builder's, and the accepted proposal, whose seal every node writes, is the later round's proposer's. (Corrected 2026-09-25: the text until then named the `coinbase` only, both clients implemented it so, and on testnet 28001 block 3,314,873, prepared at round 0 under load, was re-proposed at rounds 1 to 4 and refused by every node each time, 128 s, until round 5 was its builder's turn again; at N = 10 that wait is ten rounds, and with the builder down after the prepare it does not end.) Cost: +663 B and -455 B (7 x 65) per block at N = 10, net +208 B, that is **+11.6 GB per node per year** (208 x 55,854,159 / 10^9). After `H_hdr` no ECDSA signature appears in any header or is examined in any consensus message; the last classical use is the RLPx transport.
|
||||||
|
|
||||||
### 6. Equivocation evidence and consequences without stake
|
### 6. Equivocation evidence and consequences without stake
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ Per block: the hash chain (one keccak of the header, already done), the proposer
|
|||||||
|
|
||||||
### 9. Cross-client determinism and conformance
|
### 9. Cross-client determinism and conformance
|
||||||
|
|
||||||
Both clients of chain 2800 MUST: compute the same `id` from the same key; load, hash and refuse the same v3 registries; translate the set identically at `H_id`; order the validator list identically; accept and refuse the same anchors under the same scheme-interval schedule and thresholds; roll back the same provisional tail on the same anchor; verify the same equivocation objects. Conformance vectors are to be published with the reference implementation (not yet published at 2026-09-24; the stage proofs on chain 28001 so far are the two clients checked against each other on a live chain, which is not the same deliverable): a v3 registry with rotation, a set translation at a fixed height, a Falcon-only anchor, a hybrid anchor, a short certificate, a certificate with a surplus scheme, a proposer seal, an equivocation pair, a classical-only fork of 31 blocks; each invalid vector is refused by both clients for its stated reason (the lesson of AIP-20: a negative vector differs from the valid one in exactly one field, and the refusal names the field).
|
Both clients of chain 2800 MUST: compute the same `id` from the same key; load, hash and refuse the same v3 registries; translate the set identically at `H_id`; order the validator list identically; accept and refuse the same anchors under the same scheme-interval schedule and thresholds; roll back the same provisional tail on the same anchor; verify the same equivocation objects. Conformance vectors are to be published with the reference implementation (none was published at 2026-09-24; from 2026-09-27 the corpus is in `aere-research/aips/aip22-conformance/`, every vector with both clients' verdict, and it holds eight of the nine kinds below, the classical-only fork not yet as a static vector): a v3 registry with rotation, a set translation at a fixed height, a Falcon-only anchor, a hybrid anchor, a short certificate, a certificate with a surplus scheme, a proposer seal, an equivocation pair, a classical-only fork of 31 blocks; each invalid vector is refused by both clients for its stated reason (the lesson of AIP-20: a negative vector differs from the valid one in exactly one field, and the refusal names the field).
|
||||||
|
|
||||||
## Rationale
|
## Rationale
|
||||||
|
|
||||||
@ -259,6 +259,8 @@ Activation heights on chain 2800 are the founder's (AIP-19 acceptance per stage)
|
|||||||
|
|
||||||
**2026-09-24 20:00Z, the public verifier checks the proposer seal (stage 5).** `aere-node/tools/verify-anchor.mjs` gains a sixth step: on the most recent headers that carry element 6 (20 by default), it recomputes the proposal digest from the stored header (the `EXCLUDE_COMMIT_SEALS` form, round kept), the message `keccak256(RLP["AERE-PQ-PROPOSAL-1", chainId, height, round, digest])`, verifies the Falcon-512 signature against the key of the index in the published v3 epoch in force at that height, and requires that index's `id` to be the header's `coinbase`; each header is first re-encoded in the on-chain form and must reproduce the hash the endpoint reports, otherwise it is not judged. On testnet 28001, run exactly as the public instruction on the site says: 20 of 20 recent headers verified. Negative controls through a local proxy that alters only element 6 (outside both hashes): one byte of the signature flipped, and the index moved to another validator, each fail with the reason named; on chain 2800, where no header carries element 6, the step says stage 5 is not active and does not change the verdict (four of four).
|
**2026-09-24 20:00Z, the public verifier checks the proposer seal (stage 5).** `aere-node/tools/verify-anchor.mjs` gains a sixth step: on the most recent headers that carry element 6 (20 by default), it recomputes the proposal digest from the stored header (the `EXCLUDE_COMMIT_SEALS` form, round kept), the message `keccak256(RLP["AERE-PQ-PROPOSAL-1", chainId, height, round, digest])`, verifies the Falcon-512 signature against the key of the index in the published v3 epoch in force at that height, and requires that index's `id` to be the header's `coinbase`; each header is first re-encoded in the on-chain form and must reproduce the hash the endpoint reports, otherwise it is not judged. On testnet 28001, run exactly as the public instruction on the site says: 20 of 20 recent headers verified. Negative controls through a local proxy that alters only element 6 (outside both hashes): one byte of the signature flipped, and the index moved to another validator, each fail with the reason named; on chain 2800, where no header carries element 6, the step says stage 5 is not active and does not change the verdict (four of four).
|
||||||
|
|
||||||
|
**2026-09-25, stage 5 corrected: a re-proposed block carries the seal of its round's proposer, repaired in both clients and in the public verifier, not yet deployed.** The load bench of testnet 28001 reported three incomplete runs, and the chain told why: blocks 3,160,789 (2026-09-24), 3,314,873 and 3,353,663 (2026-09-25), each carrying thousands of transactions, were prepared at round 0, not committed before its timer, and finalized only at round 5, 128 to 130 s later. The validators' logs of 3,314,873 show the mechanism: at rounds 1, 2 and 4 the round's proposer re-proposed the locked block unchanged (its coinbase stays its builder's), a quorum committed it, and every node refused the header it sealed, because element 6 is the accepted proposal's seal, the round's proposer's, while the rule of section 5 as written required the coinbase's ("a seal cannot vouch for another proposer"); round 5 was the builder's turn again (N = 5). The stage-5 rehearsal of 2026-09-23 had provoked two round-1 blocks, but fresh ones, with no locked value, so this case had not been measured. Section 5 now admits, at a round above zero, the seal of the proposer of that round as upstream's rotation computes it from the parent's coinbase and the header's validator list; an adversarial review of the repair the same morning added two rules so that both clients judge every header alike: with the parent, the coinbase must be the proposer of some round up to element 3 (the Nethermind-derived client had required this since 2026-09-03, the Besu-derived one had not), and the round is at most 1024 (upstream's rotation arithmetic is in 32-bit integers; honest rounds never approach the bound); the chain already satisfies both (measured below). Forging a header on the new branch still needs a validator's Falcon-512 key, the key that can build its own block. Probes: in the Besu-derived client seven new cases (every existing-proposer pair, three missing-proposer seeds, the exact round, the builder, the bound and its inclusive edge, the expected proposer written in the probe independently of the rule), three modules 1,154 tests, 0 failures, with the application compiled, and seven negative controls planted at run time, each red (any id admitted, the rotation without its +1, no id requirement, the rule judged without the parent, the previous round's proposer, no builder requirement, no bound). In the Nethermind-derived client the same cases, including its seal-only path (forward-sync batches and the seal check of a gossiped block carry no parent; there an in-set id at a round above zero is not refused for its id, and the full validation with the parent, which runs before any import, judges it exactly), 46 tests, 0 failures tests, with seven planted controls red. The public verifier's step 6 fetches the parent only when the seal is not the coinbase's and the round is above zero, requires that parent to re-encode to the header's parentHash before using its coinbase, applies the canonical round encoding, the bound and the builder rule to every header it judges (each header in its window serves as the parent of the next, so one extra request), and fails with the reason named otherwise (21 offline cases on a synthetic chain with real Falcon-512 keys, ten planted controls caught; as a stranger on 28001, 20 of 20 as before). Read back over the whole stage-5 history of 28001, the 228,553 headers from H_hdr to 3,359,552, 410 of them finalized above round 0, all satisfy the added rules (the same scan with the rotation missing its +1 fails 200 of 201). Not measured, and stated: a real re-proposed header sealed by someone other than its coinbase, which will exist only once the nodes run the repair; the testnet deployment goes with the Nethermind-derived binary that also carries the validator-list rule of the same week, with the published verifier and this text in the same hour.
|
||||||
|
|
||||||
**2026-09-23 22:10-22:35Z, stage 6 (equivocation evidence on chain) built and proven on testnet 28001.** First the framing was proven against the live Falcon-512 precompile before any contract was written: a consensus seal on the wire is `0x39 || nonce(40) || compressed` and becomes the precompile input `pk(897) || u16(len esig) || nonce || message || esig` with `esig = 0x29 || compressed`; the five real seals of an anchor certificate return 1, the same seals with one bit changed return 0, the same seals under another index's key return 0, and the specification's JavaScript verifier agrees on all fifteen cases. `PqEquivocationEvidence` (immutable, ownerless; its epochs and pins fixed at construction, a later epoch needs a new contract) takes the SIGNED PRE-IMAGE FIELDS of the two messages rather than the full upstream payloads (a seal binds only its pre-image, so nothing else in a payload could be checked; a stated deviation from the object as written in section 6), rebuilds both pre-images on chain exactly as the clients do, verifies both seals through `0x0AE1`, parses the v3 row pre-image for its chain, epoch, index, id and Falcon key, proves the row hash into the registry hash pinned for the epoch in force at the height, and records `(id, type, height, round)` once. Equivocation per type: PROPOSAL and PREPARE, same height and round and a different digest; COMMIT (the anchor form, which carries no round), same height and a different block hash; ROUND-CHANGE, same height and target round and a different prepared claim. The id is not re-derived from the key on chain: a rotated row keeps the id of its first registration, which the first form of the contract refused on the live epoch (row 2 of epoch 3,097,600); the id is inside the row hash, which the registry binds. On testnet 28001 the registry epochs were pinned only in genesis and in node configuration, so the two v3 epochs were pinned on chain in the same eleven-byte read-only getter as chain 2800's epochs, each reloaded and verified (possession proofs, rotation, ids) before its hash was written. Probes: locally, seven tests with a stand-in precompile (the pre-images, all four types recorded, each refusal by its reason, a rotated row accepted), and a negative control of six guards planted at run time (conflict, second seal, pin, chain id, epoch, domain), each red with the tests run. On chain 28001, with the live precompile: a DRILL instance (pre-images naming chain 28099 and an epoch of drill keys generated in memory for the run, so that no validator of 28001 or 2800 is named by a permanent record) recorded a PREPARE and a COMMIT equivocation, each sealed twice by one row with the clients' own signing code, at 322,404 and 303,103 gas, and refused, as calls, the same message twice, a second seal from another row, a Merkle path to another registry and a second submission; the REAL instance (chain 28001, the two v3 epochs) proves all five rows of the epoch in force, the rotated one included, and verifies the four seals of a live anchor certificate through the contract over the COMMIT-1 message the contract rebuilds, with the changed-bit control refused. The runtime code of both instances equals the compiled source with the immutable chain id masked, and the first form's code differs (control of the method). Not built in this pass, and stated: the second form of section 6 (two conflicting anchor headers) and any consequence, which stays a schedule decision.
|
**2026-09-23 22:10-22:35Z, stage 6 (equivocation evidence on chain) built and proven on testnet 28001.** First the framing was proven against the live Falcon-512 precompile before any contract was written: a consensus seal on the wire is `0x39 || nonce(40) || compressed` and becomes the precompile input `pk(897) || u16(len esig) || nonce || message || esig` with `esig = 0x29 || compressed`; the five real seals of an anchor certificate return 1, the same seals with one bit changed return 0, the same seals under another index's key return 0, and the specification's JavaScript verifier agrees on all fifteen cases. `PqEquivocationEvidence` (immutable, ownerless; its epochs and pins fixed at construction, a later epoch needs a new contract) takes the SIGNED PRE-IMAGE FIELDS of the two messages rather than the full upstream payloads (a seal binds only its pre-image, so nothing else in a payload could be checked; a stated deviation from the object as written in section 6), rebuilds both pre-images on chain exactly as the clients do, verifies both seals through `0x0AE1`, parses the v3 row pre-image for its chain, epoch, index, id and Falcon key, proves the row hash into the registry hash pinned for the epoch in force at the height, and records `(id, type, height, round)` once. Equivocation per type: PROPOSAL and PREPARE, same height and round and a different digest; COMMIT (the anchor form, which carries no round), same height and a different block hash; ROUND-CHANGE, same height and target round and a different prepared claim. The id is not re-derived from the key on chain: a rotated row keeps the id of its first registration, which the first form of the contract refused on the live epoch (row 2 of epoch 3,097,600); the id is inside the row hash, which the registry binds. On testnet 28001 the registry epochs were pinned only in genesis and in node configuration, so the two v3 epochs were pinned on chain in the same eleven-byte read-only getter as chain 2800's epochs, each reloaded and verified (possession proofs, rotation, ids) before its hash was written. Probes: locally, seven tests with a stand-in precompile (the pre-images, all four types recorded, each refusal by its reason, a rotated row accepted), and a negative control of six guards planted at run time (conflict, second seal, pin, chain id, epoch, domain), each red with the tests run. On chain 28001, with the live precompile: a DRILL instance (pre-images naming chain 28099 and an epoch of drill keys generated in memory for the run, so that no validator of 28001 or 2800 is named by a permanent record) recorded a PREPARE and a COMMIT equivocation, each sealed twice by one row with the clients' own signing code, at 322,404 and 303,103 gas, and refused, as calls, the same message twice, a second seal from another row, a Merkle path to another registry and a second submission; the REAL instance (chain 28001, the two v3 epochs) proves all five rows of the epoch in force, the rotated one included, and verifies the four seals of a live anchor certificate through the contract over the COMMIT-1 message the contract rebuilds, with the changed-bit control refused. The runtime code of both instances equals the compiled source with the immutable chain id masked, and the first form's code differs (control of the method). Not built in this pass, and stated: the second form of section 6 (two conflicting anchor headers) and any consequence, which stays a schedule decision.
|
||||||
|
|
||||||
**2026-09-23, stage 3 foundation: the registry v3 forms, pinned.** Section 3.2 names a "canonical serialization"; the bytes are now fixed, identically, in the Besu-derived client (`PqRegistryV3`), the Nethermind-derived client (`PqRegistryV3`) and a third, independent JavaScript implementation, and pinned by 19 shared conformance vectors (two valid epochs, the second rotating one validator's keys under its id; seventeen refusals, each differing from a valid manifest in one field with the signatures re-made over that field, one per refusal code `AERE-PQC-REG3-01` to `-16`):
|
**2026-09-23, stage 3 foundation: the registry v3 forms, pinned.** Section 3.2 names a "canonical serialization"; the bytes are now fixed, identically, in the Besu-derived client (`PqRegistryV3`), the Nethermind-derived client (`PqRegistryV3`) and a third, independent JavaScript implementation, and pinned by 19 shared conformance vectors (two valid epochs, the second rotating one validator's keys under its id; seventeen refusals, each differing from a valid manifest in one field with the signatures re-made over that field, one per refusal code `AERE-PQC-REG3-01` to `-16`):
|
||||||
@ -279,10 +281,15 @@ ecdsa claim = keccak256(0x19 || 0x00 || low20(keccak256("AERE-PQ-CLAIM-1" ||
|
|||||||
|
|
||||||
Two refinements to the text above, recorded rather than silently applied: the Merkle inner node carries the tag byte `0x01`, so an inner node can never be presented as a row hash; and a membership proof is valid only for an index below `count`, because with the odd node paired with itself the path of the last row of an odd-sized tree would otherwise also prove the index `count` (both clients and the JavaScript tool refuse it, and a probe in each asserts the refusal). Negative controls: in the Besu-derived client, removing the rotation rule, the id-derivation rule or the index bound each turns its probes red with the probes run (and the untouched source green); in the Nethermind-derived client, removing the rotation rule turns 2 of its 3 probes red and removing the index bound 1 of 3 (untouched: 3 of 3 green); in the JavaScript tool the same three plantings are caught by 2, 2 and 1 vectors. Nothing of stage 3 is wired into consensus yet: the set translation at `H_id`, authorship by row, `coinbase` and votes by id, and the forwarding predicate are the next parts, and the v3 epoch of testnet 28001 needs the PROBA keys on its hosts to sign possession, claim and rotation.
|
Two refinements to the text above, recorded rather than silently applied: the Merkle inner node carries the tag byte `0x01`, so an inner node can never be presented as a row hash; and a membership proof is valid only for an index below `count`, because with the odd node paired with itself the path of the last row of an odd-sized tree would otherwise also prove the index `count` (both clients and the JavaScript tool refuse it, and a probe in each asserts the refusal). Negative controls: in the Besu-derived client, removing the rotation rule, the id-derivation rule or the index bound each turns its probes red with the probes run (and the untouched source green); in the Nethermind-derived client, removing the rotation rule turns 2 of its 3 probes red and removing the index bound 1 of 3 (untouched: 3 of 3 green); in the JavaScript tool the same three plantings are caught by 2, 2 and 1 vectors. Nothing of stage 3 is wired into consensus yet: the set translation at `H_id`, authorship by row, `coinbase` and votes by id, and the forwarding predicate are the next parts, and the v3 epoch of testnet 28001 needs the PROBA keys on its hosts to sign possession, claim and rotation.
|
||||||
|
|
||||||
|
**2026-09-26, the light client AIP-22 names for after `H_id`, rehearsed on chain 28001 (not deployed on 2800).** Backwards Compatibility says a light client "either verifies anchor certificates directly ... or waits for a hash-based proof system". The direct path now exists as contracts and was measured: `AerePqAnchorFinalityVerifier` re-derives an anchor's block hash, requires the header's validator list to be the pinned v3 epoch's ids in the header's ascending order, requires the vanity to be the v2 digest of the supplied certificate (the certificate under the block hash), parses the certificate and verifies its Falcon-512 seals with the precompile at `0x0AE1` over the parent's commit message, counting distinct indexes against K; `AerePqLightClient` records the anchor's parent as final and extends finality below it through the parent-hash chain over headers in the hashed form. Measured: on a real anchor of 28001 (stage 5, seven elements, five Falcon-512 seals), the contract's hash, digest and commit message equal the reference tool's; the exact precompile input the contract builds for each seal is accepted by the REAL precompile of 28001 (`eth_call`, 5 of 5 on a hybrid anchor of 4 Falcon + 4 SLH-DSA seals) and refused with one byte changed; in the local EVM, which has no Falcon precompile, the quorum path and ten refusals are driven with a format-exact mock (29 tests). Stated, not measured: only Falcon-512 seals are verified (SLH-DSA seals are digested, not verified, in this version); a registry epoch is pinned per deployment (a rotation needs a new deployment); the contracts are deployable only where the precompile exists, which today means Aere chains, so for foreign chains section 7's hash-based proof remains the path. Alongside, a conformance corpus in the stage-5 header form was generated from twelve live headers of 28001 (proposer seal verified against the v3 epoch, forty-eight negative vectors each differing in one field), the first AIP-22 vectors published in the repository (section 9 asked for them); the round > 0 case did not occur in the sampled window and is covered only by the offline re-proposal probe.
|
||||||
|
|
||||||
|
**2026-09-27, three records.** (1) *The re-proposal repair of 2026-09-25 runs on testnet 28001; the public verifier and this text followed it a day late.* The six nodes of chain 28001 have judged headers by the corrected section 5 since 2026-09-26: the Nethermind-derived validator from 05:15Z, the five Besu-derived nodes one at a time after it (from 2026-09-27 00:00-00:11Z on a later distribution that keeps the rule). The record of 2026-09-25 put the public verifier and this text in the same hour as that deployment; they did not go then. Until this publication, step 6 of the published `aere-node/tools/verify-anchor.mjs` still required every proposer seal to be the coinbase's, so a re-proposed header inside the window it checks would have been reported as a failure of the chain. Measured over headers 3,473,624 to 3,611,791 of chain 28001 (from before the deployment to this record: 138,168 headers, 31 of them finalized above round 0): one was re-proposed and sealed by someone other than its builder, block 3,508,874 (2026-09-26 09:58:40Z, 3,325 transactions, prepared by the validator of row 2 and finalized at round 2 under the seal of row 0, that round's proposer), admitted by every node. Judged through a local proxy that makes it the tip, the verifier published until today fails it ("proposer seal index 0 is id 0xa0a5..., but the coinbase is 0xe7e6..."), so whoever ran it in the seconds that block stayed in its window was told the chain had failed; the verifier published now names it as a re-proposal and passes. It is also the first real re-proposed header sealed by someone other than its coinbase, which the record of 2026-09-25 listed as not measured. Published now: the verifier with the corrected step 6; the 21 offline cases of 2026-09-25 pass on the published file, and ten planted defects each turn them red; as a stranger, 20 of 20 anchors and 20 of 20 proposer seals on 28001, 20 of 20 anchors on 2800. The tool now sets its exit code at the end instead of calling `process.exit()`: on Windows, exiting while its HTTP sockets were closing could abort Node with a code that is neither 0, 1 nor 2 (two of the 21 offline cases on the first run); its early refusals still exit through `process.exit(1)` and can show the same code there, with the reason printed as before. The check that the nodes run the repair now also requires the served verifier to carry it. (2) *Conformance corpus.* Published in `aere-research/aips/aip22-conformance/`: eight of the nine kinds of section 9, every vector with the verdict of both clients and, for a refusal, the text each wrote. The certificate with a surplus scheme was the kind no node had produced (on 28001 SLH-DSA-SHA2-128s is required only on its 128-block grid, and no anchor of the 32-block grid carries it): its valid form carries a real SLH-DSA-SHA2-128s seal over `M(parent)` of anchor 3,288,320, made on 2026-09-27 by the key of the validator holding row 0 of the v3 epoch in force, on that validator's host with the node's own classes, after a real Falcon-512 seal of the same certificate was checked to verify over the same `M`. Both clients admit it, and refuse it with one byte of that seal changed, each for the reason that the seal does not verify: a surplus seal is verified and counted, never ignored (section 2). (3) *The classical-only fork, recorded.* The behaviour section 4 rule 4 names was measured on the rehearsal network 28099 (one validator V; an attacker X holding V's ECDSA key and no post-quantum key; readers linked only to X until the fork stops). On 2026-09-23 a Besu-derived reader followed X's fork for 26 blocks, which stopped at the block before the next anchor because X cannot certify it, and moved to the anchored chain (+27/-26) in the second it was linked to V; with the anchor rule disarmed on the reader and on X, the fork crossed the anchor height and the reader stayed on it. On 2026-09-24, on the chain rebuilt with `H_fin` = 600, readers of both clients followed a 24-block fork that stopped at the block before the anchor, and both moved to the anchored chain when linked to V; with the anchor unarmed (the Nethermind-derived client has no disarm switch; its anchor rule left unconfigured is the equivalent), both stayed on the fork. So the Nethermind-derived reader under attack, not measured in the stage 4 record, was measured the next day. The static vector section 9 names is not yet in the corpus; a head rule for validators fed such a fork (it needs N >= 4) is not measured.
|
||||||
|
|
||||||
## Errata
|
## Errata
|
||||||
|
|
||||||
- 2026-09-24, Backwards Compatibility: the text said that ECDSA-quorum light clients need another path "after `H_fin`". Read in their guest program (`verify_finality` in the QBFT finality crate of the ZK light client), they stop earlier, at `H_id`, because the set they match seals against becomes a list of `id`s there. Corrected in place; nothing else in the section changes.
|
- 2026-09-24, Backwards Compatibility: the text said that ECDSA-quorum light clients need another path "after `H_fin`". Read in their guest program (`verify_finality` in the QBFT finality crate of the ZK light client), they stop earlier, at `H_id`, because the set they match seals against becomes a list of `id`s there. Corrected in place; nothing else in the section changes.
|
||||||
- 2026-09-24, section 9: the text said that conformance vectors "are published" with the reference implementation. No AIP-22 vector corpus is published yet; the sentence now says they are to be, and says what has been done instead (both clients checked against each other on chain 28001 at every stage).
|
- 2026-09-24, section 9: the text said that conformance vectors "are published" with the reference implementation. No AIP-22 vector corpus is published yet; the sentence now says they are to be, and says what has been done instead (both clients checked against each other on chain 28001 at every stage).
|
||||||
|
- 2026-09-25, section 5: the text admitted a header's proposer seal only for the `coinbase`'s `id`. A block prepared in one round and re-proposed unchanged in a later one keeps its builder's `coinbase` and carries the later round's proposer's seal, and every node refused it (stage record of 2026-09-25). Corrected in place in the working text on 2026-09-25; the published text carried the old rule until 2026-09-27 (stage record of that date).
|
||||||
|
|
||||||
## Post-Acceptance Outcome Record
|
## Post-Acceptance Outcome Record
|
||||||
|
|
||||||
|
|||||||
35
aips/aip22-conformance/README.md
Normal file
35
aips/aip22-conformance/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# AIP-22 conformance vectors
|
||||||
|
|
||||||
|
The corpus that section 9 of [AIP-22](../AIP-22.md) asks for: header and registry cases on which every client of chain 2800 must reach the same verdict, each case with the verdict the two existing clients reached on it. Published 2026-09-27. `VECTORS.json` is generated from the working corpus in one place; do not edit it by hand.
|
||||||
|
|
||||||
|
## What a vector is
|
||||||
|
|
||||||
|
| field | meaning |
|
||||||
|
|---|---|
|
||||||
|
| `kind` | one of the nine kinds listed in section 9 |
|
||||||
|
| `valid` | `true`: a conforming client admits it; `false`: a conforming client refuses it |
|
||||||
|
| `blocks` | what is judged: for a header rule `[parent, header]` (a rule on the validator list or on an anchor needs the parent); block fields as `eth_getBlockByNumber` returns them |
|
||||||
|
| `mutatedField` | for a generated vector, the single field in which it differs from the vector it is `derivedFrom` |
|
||||||
|
| `expectedReason`, `reasonTag` | for an invalid vector, the reason a conforming client must refuse it for; refusing it for another reason does not conform |
|
||||||
|
| `registryVector` | for the registry kind: the manifest judged and the one before it |
|
||||||
|
| `verdicts` | what each client did: `admitted` or `refused`, the refusal text it wrote, the tag that text maps to, and `conforms` |
|
||||||
|
|
||||||
|
When a vector changes something under the block hash (the anchor certificate, through the digest in `vanityData`), the header hash is recomputed from the header in its on-chain form, and the digest is re-bound, so that the vector fails only for the field it names. The `kinds` list at the top of `VECTORS.json` counts, per kind, the valid and invalid vectors and how many of them both clients judged conformingly.
|
||||||
|
|
||||||
|
## Where the vectors come from
|
||||||
|
|
||||||
|
- **Valid vectors** are real blocks of the public testnet 28001, each read from the nodes of both clients and identical there (hash and `extraData`): the set translation at `H_id`, an anchor on the Falcon-512 grid, a hybrid anchor, a stage-5 header with its proposer seal, a registry epoch with a key rotation.
|
||||||
|
- **Generated vectors** are derived from them, one field each: every invalid one, and the short certificate cut to exactly K rows, which is valid.
|
||||||
|
- **Registry vectors** are the 19 shared vectors of the v3 registry forms (section 3; two valid epochs, seventeen refusals, one per refusal code).
|
||||||
|
- **Equivocation pairs** are the objects recorded on chain 28001 by the stage-6 drill, judged by the evidence contract through each client's EVM and Falcon-512 precompile (`eth_call`, no transaction).
|
||||||
|
- **The certificate with a surplus scheme** needed a seal that no node had made: on chain 28001, SLH-DSA-SHA2-128s is required only on its 128-block grid, so no anchor on the 32-block Falcon-512 grid carries one. It was made for this corpus on 2026-09-27: the SLH-DSA-SHA2-128s key of the testnet validator holding row 0 of the v3 epoch in force signed `M(parent)` of anchor 3,288,320, on that validator's own host, with the node's own classes; before the signature was kept, a real Falcon-512 seal of the same certificate was checked to verify over the same `M` (and not over a changed one), and the new signature to verify under row 0's published key (and not over a changed `M`). The provenance is inline in the vector (`sealProvenance`).
|
||||||
|
|
||||||
|
## How the verdicts were reached
|
||||||
|
|
||||||
|
Each client's own rule classes were run on the vectors, in each client's test build (a Besu-derived and a Nethermind-derived client), with the published configuration of chain 28001: the v3 registry epochs, the anchor-interval and scheme schedules, the stage heights. The verdicts are those of the rules in the clients' source at the date; that the nodes of chain 28001 run those rules is shown separately, on the live chain, in the stage records of AIP-22. The two harnesses are not part of this directory.
|
||||||
|
|
||||||
|
A third client conforms on a vector when it admits a valid one, and refuses an invalid one for the reason in `reasonTag`.
|
||||||
|
|
||||||
|
## Not yet in the corpus (2026-09-27)
|
||||||
|
|
||||||
|
Section 9 names nine kinds; eight are here. The ninth, a classical-only fork of 31 blocks, is not yet a static vector. Its behaviour was measured on a live rehearsal network, with a Besu-derived reader on 2026-09-23 and with readers of both clients on 2026-09-24 (recorded in AIP-22's stage record of 2026-09-27): a reader fed the fork follows it until the height of the next anchor, which the fork cannot certify, and moves to the anchored chain when it reaches it; with the anchor rule disarmed, the same reader stays on the fork. A static form needs the fork's blocks, which only a rehearsal network's keys can make, and a configuration other than chain 28001's.
|
||||||
3991
aips/aip22-conformance/VECTORS.json
Normal file
3991
aips/aip22-conformance/VECTORS.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user