AIP-21: a record that reaches `post-quantum` shows that the commit quorum of distinct validators sealed the block post-quantum in some round; it does not by itself prove that the block was decided (follows the AIP-22 erratum of the same day). AIP-23: dated note on what the finality level rests on; level names unchanged. Three formal models carry a dated note where they treated certificate uniqueness as a property of the code rather than of the model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
289 lines
22 KiB
Markdown
289 lines
22 KiB
Markdown
# AIP-21: Verifiable Post-Quantum Finality per Block (Side Store, Proof RPC, Anchor as Permanent Record)
|
|
|
|
> **Note added 2026-09-30.** Where this document says that only every 128th block carries a certificate, it describes
|
|
> chain 2800 from block 17,225,968 to block 20,746,736 (2026-09-05 to 2026-09-29). Since block 20,746,736 (AIP-22
|
|
> stage 2) a Falcon-512 certificate is carried every 32nd block and SLH-DSA seals every 128th, with at least seven of
|
|
> ten seals per scheme since block 20,715,632 (AIP-22 stage 1).
|
|
>
|
|
> **Correction added 2026-10-01.** The commit seal this record counts carries no round, and the store keeps seals by block
|
|
> hash across rounds. A record that reaches `post-quantum` shows that at least the commit quorum of distinct validators
|
|
> sealed the block post-quantum, in some round; it does not by itself prove that the block was decided. See the Errata entry
|
|
> of that date, which follows the AIP-22 erratum of the same day. Chain 2800 today, below AIP-22 stage 5, is not affected:
|
|
> a node imports a block only with the ECDSA commit quorum of a single round.
|
|
|
|
## Preamble
|
|
|
|
| Field | Value |
|
|
| --- | --- |
|
|
| AIP | 21 |
|
|
| Title | Verifiable Post-Quantum Finality per Block (Side Store, Proof RPC, Anchor as Permanent Record) |
|
|
| Author | Aere Network Foundation |
|
|
| Type | Standards Track |
|
|
| Category | Interface (non-consensus) |
|
|
| Status | Draft (first half implemented in the Besu-derived client on 2026-09-18, not yet on any network at the Created date; both halves in both clients on testnet 28001 the same day; on chain 2800 on all ten validators since 2026-09-22, see the dated records at the end) |
|
|
| Created | 2026-09-18 |
|
|
| Requires | AIP-15 (post-quantum anchor certificates), the per-Commit post-quantum seal enforced on chain 2800 since 2026-09-05 |
|
|
| Supersedes | None |
|
|
| Superseded-By | None |
|
|
| Ratification | Not ratified; testnet-gated (chain 28001 first), second-client-gated (both clients answer the same record for the same block), no activation height needed on chain 2800 because nothing here changes block validity |
|
|
|
|
## Abstract
|
|
|
|
Every block of an Aere QBFT chain is committed with a Falcon-512 seal on each Commit message, and
|
|
every validator already holds, for recent blocks, the seals it heard. Those seals never reach the
|
|
header: only every 128th block carries a certificate, over its parent, so the immediate post-quantum
|
|
finality of a block has been true and not observable. This AIP makes it observable without growing the
|
|
header: a bounded side store of heard seals per block hash, a JSON-RPC method `aere_getPqFinality`
|
|
that returns the seals of a block re-verified now against the anchored registry over the exact message
|
|
the validators signed, with the verdict "verified distinct signers ≥ the chain's commit quorum",
|
|
and the anchor certificate as the permanent record once the window has passed.
|
|
|
|
## Motivation
|
|
|
|
A user, an exchange or a bridge that asks "is this block final under post-quantum assumptions right
|
|
now?" could until now only be answered "yes, trust us" or "wait for the next anchor". The seals that
|
|
prove it exist on every validator and are thrown away after a few hundred blocks. Keeping them a
|
|
little longer and answering a query costs kilobytes, and turns a claim into a measurement that a
|
|
third party can repeat: fetch the record, verify each Falcon-512 seal against the published registry
|
|
epoch, count distinct signers, compare with the quorum. It also makes "immediate post-quantum
|
|
finality" a gate we can run against our own nodes, on the block of ten seconds ago, instead of a
|
|
sentence on a website.
|
|
|
|
## Specification
|
|
|
|
### The record
|
|
|
|
For a block `B` at height `h` with on-chain hash `H` (round forced to zero), a node answers:
|
|
|
|
```
|
|
blockNumber h
|
|
blockHash H
|
|
validators n, the size of the validator set that validated B
|
|
quorum the chain's commit quorum for n (Besu: ceil(2n/3))
|
|
sealForm "anchor" when the signed message is M = commitMessage(chainId, h, H) (AIP-15,
|
|
the form in force on every live network today), "commit-digest" below it
|
|
signedMessage M in hex, or null when it cannot be rebuilt from the header
|
|
falconSeals [{scheme:"falcon-512", index, verified, signature}] as heard, index-sorted
|
|
falconSealsHeard count
|
|
falconSealsVerified distinct validator indexes whose seal verifies NOW against the registry epoch
|
|
bound at height h (verifyAtHistoric)
|
|
verifiedIndexes that set
|
|
hybridSeals [{scheme, index, verified:null, signature}] heard extras (SLH-DSA on anchor
|
|
parents); listed, not verified by this method: the anchor certificate is theirs
|
|
finality "post-quantum" | "partial" | "none" | "unavailable" | "unverifiable-form"
|
|
retentionBlocks the node's window
|
|
note one sentence that says what the verdict rests on
|
|
```
|
|
|
|
`finality` is `post-quantum` iff `falconSealsVerified >= quorum > 0`; `partial` when at least one
|
|
seal verifies but fewer than the quorum; `none` when seals were heard and none verifies; `unavailable`
|
|
when no seal was heard (a reader node, or a block outside the window); `unverifiable-form` below the
|
|
anchor form. A method that cannot verify says so; it never counts an unverified seal.
|
|
|
|
### The side store
|
|
|
|
The existing per-node seal cache (keyed on the on-chain block hash, populated from Commit messages)
|
|
gains a configurable retention window, system property `aere.pq.finality.window` (blocks). The
|
|
consensus default stays the previous value (256); the value is per node and never a consensus value:
|
|
two nodes with different windows import the same chain. Memory cost is about 7 kB per block for ten
|
|
seals.
|
|
|
|
### The window on disk (second half, 2026-09-18)
|
|
|
|
The window survives a restart. Each client appends what it heard for a height to a journal when the NEXT
|
|
height begins, and reads the journal back at startup. Both clients write the SAME format, pinned by a byte
|
|
vector present literal for literal in both proof suites: one directory, segment files of 64 heights
|
|
(`w-<height / 64>.rlp`), each a sequence of frames `length(4, big endian) || payload || crc32(payload)(4, big
|
|
endian)`, the payload being the RLP list `["AERE-PQ-SEALWINDOW-1", chainId, blockNumber, blockHash,
|
|
[[index, signature], ...]]`. Frames are only appended; segments wholly below the window are deleted, so the
|
|
footprint is bounded by the window (a few megabytes at the default) whatever the age of the node. Writes are
|
|
best effort and unsynced: a disk fault costs the answer for those blocks after a restart, never the node.
|
|
|
|
Nothing read from the journal is believed. A seal is self-verifying: at startup every seal is verified again
|
|
against the registry epoch bound at its height, over the commit message rebuilt from the block hash, and what
|
|
does not verify is dropped. The Besu-derived client also drops, before looking at a signature, any frame whose
|
|
hash is not the canonical hash its own chain holds at that height; the Nethermind-derived client keeps the
|
|
hash beside each seal and applies the same canonical filter at query time. A forged journal obtains the empty
|
|
window an absent journal already gives. A torn tail (the node died mid-append) or a damaged frame costs that
|
|
frame and what follows it in the same segment, nothing else.
|
|
|
|
Switches, per node, never consensus values: Besu-derived client, system property `aere.pq.finality.persist`
|
|
(`false` turns the journal off; default on wherever the head-seal store is on), directory
|
|
`<data>/aere-pq-seal-window`; Nethermind-derived client, environment variable `AERE_PQ_FINALITY_DIR` (unset
|
|
= off). What is NOT persisted: the hybrid extras (SLH-DSA seals on anchor parents); after a restart they are
|
|
listed only for blocks heard since, and the anchor certificate remains their permanent record.
|
|
|
|
### The method
|
|
|
|
`aere_getPqFinality([blockParameter])`, `blockParameter` a number, `latest` or `pending`; default
|
|
`latest`. Registered in the QBFT API group. Returns the record above, or `null` for an unknown block.
|
|
|
|
### Who can answer
|
|
|
|
Seals travel on Commit messages between validators, so only validators hear them. A public reader
|
|
node answers `unavailable` honestly. A network that wants the record public exposes a read-only door
|
|
that accepts only this method and asks a validator: on testnet 28001 that door is
|
|
`https://testnet-rpc.aere.network/finality`, a proxy that tries the four Besu validators in turn and
|
|
refuses every other method.
|
|
|
|
### The permanent record
|
|
|
|
Once the block leaves every node's window, its post-quantum finality is recorded by the next anchor
|
|
certificate (AIP-15), which is verifiable forever with the published verifier. The two are the same
|
|
claim at two ages: the seals prove it in the second, the certificate proves it for the life of the
|
|
chain.
|
|
|
|
## Rationale
|
|
|
|
Growing the header with a post-quantum seal per validator per block was measured at 247 GB to 2.9 TB
|
|
per node per year on 2026-09-17 and rejected; anchors every 128 blocks cost about 32 GB per year. A
|
|
side store that keeps what validators already hold, for a bounded window, costs kilobytes and changes
|
|
nothing about validity, so it needs no coordination, no fork and no signature to deploy.
|
|
|
|
## Backwards Compatibility
|
|
|
|
None affected. Nodes with and without the method import the same chain.
|
|
|
|
## Security Considerations
|
|
|
|
The record is only as strong as its verification: a seal is counted only if it verifies now against
|
|
the registry epoch bound at that height, over the message rebuilt from the header this node holds. A
|
|
node lying about the record can be caught by any second node or by the anchor certificate. The record
|
|
exposes no secret: seals are already broadcast to every validator.
|
|
|
|
## Reference Implementation
|
|
|
|
Besu-derived client, `consensus-pqc/arbore-complet-2026-08-01/petice-aip21/`: the method
|
|
`AereGetPqFinality` (consensus/qbft), its test `AereGetPqFinalityTest` (7 cases, each with its negative
|
|
pair: a signer short of quorum, nothing verifying, nothing heard, a form that cannot be rebuilt, an
|
|
unknown block), the retention window in `PqSealCache`, three anchored edits (`aip21-aplica.py`). The
|
|
Nethermind port, the testnet deployment and the public door are recorded here, dated, as they land.
|
|
|
|
**Testnet 28001 deployment, 2026-09-18 02:35Z.** The five Besu nodes of the public testnet (four validators and the
|
|
reader) run the AIP-21 distribution `besu-aip21-26.4.0-aere.1-d203201-wt5fda18a0`, switched one at a time with a
|
|
130 s cooling window between restarts (`testnet-public/84-comuta-toate-f2.sh` over `83-binar-nod-f2.sh`, each node
|
|
back at the tip before the next). Public door: `https://testnet-rpc.aere.network/finality` (`finalitate-proxy.mjs`,
|
|
unit `aere-testnet-finalitate`; accepts only `aere_getPqFinality` and `eth_blockNumber`, asks the four validators in
|
|
turn and names which one answered). First public record: block 2,244,964, quorum 4, 5 of 5 seals verified,
|
|
`post-quantum`; after the full switch, block 2,246,017, quorum 4, 5 of 5, `post-quantum`. Aere Cloud relays it as
|
|
`GET /v1/pq/finality/{block}?network=testnet`.
|
|
|
|
**Nethermind port, written 2026-09-18** (`nethermind-pqc/nethermind-intree/`): `Nethermind.AerePqc/Consensus/Finality/AerePqFinality.cs`
|
|
(the query; the same record, the same five verdicts and notes as the Besu method), `patches/AereRpcModule.cs`
|
|
(JSON-RPC module `Aere`, `aere_getPqFinality`, enabled per endpoint like every module), `patches/aip21-finalitate-nm.py`
|
|
(anchored, idempotent: retention window `AERE_PQ_FINALITY_WINDOW`, default 256, in place of the previous fixed 8
|
|
heights; each heard seal remembers the digest of the proposal it was heard for, because this engine keys its store
|
|
on the height, so a seal of a losing proposal at the same height is never listed for the block that won; snapshots
|
|
under the engine lock for the RPC thread), `patches/AereAip21PqFinalityProofTests.cs` (10 cases, each with its
|
|
negative pair; 10 of 10 green, negative control: with verification planted to 'always true' 5 of 10 go red).
|
|
|
|
**Nethermind testnet deployment, 2026-09-18 03:18Z.** The Nethermind validator of testnet 28001 runs
|
|
build `client2-aip21-2026-09-18` (built on its host from the same overlay, 185 assemblies, nothing lost against the
|
|
previous live binary), switched by `testnet-public/88-aip21-nm-binar-nou.sh` with the `Aere` module enabled in its
|
|
JSON-RPC configuration. Functional proof on the node: `latest` (block 2,250,690) `post-quantum`, 5 of 5 seals verified,
|
|
quorum 4, `retentionBlocks` 256; `latest-64` (2,250,626) `post-quantum`; an unknown block returns `null`, a block from
|
|
before the restart returns `unavailable`, an unknown method of the module returns -32601. Public door:
|
|
`https://client2.aere.network/testnet/finality` (`89-finalitate-usa-client2.sh`, unit `aere-testnet-finalitate-nm`,
|
|
`answeredBy: nethermind@8651`; nginx location in `deploy/client2/nginx-client2-rpc.conf`).
|
|
|
|
**Two-client conformance, 2026-09-18 03:21Z** (`aips/aip21-conformitate/DOVEDESTE.sh`, repeatable, writes `ULTIMA.txt`):
|
|
block 2,250,809 asked to both doors: hash `0xab55016e3b3b5397abd6bfd8e86299f7105e95af8c9675979d4240ffe9293451` on both,
|
|
`post-quantum` on both, quorum 4, verified indexes [0,1,2,3,4] on both; negative controls: the hash of block N-1 from
|
|
Nethermind differs from the hash of N from Besu (the comparison can fail), a block outside the Nethermind window is
|
|
`unavailable` there (no invented agreement), both doors refuse every other method. Aere Cloud:
|
|
`GET /v1/pq/finality/{block}?network=testnet&client=besu|nethermind|both`; with `both` the response carries the two
|
|
records, `agreement` and `postQuantumConfirmedByBoth`. Record: `aips/aip21-conformitate/TESTNET-28001-2026-09-18.md`.
|
|
|
|
**Correction, 2026-09-18 08:17Z (Nethermind-derived client, found on testnet, fixed the same morning).** The first form of
|
|
the port remembered each heard seal under the QBFT *message* digest. That digest keeps the round; the on-chain hash zeroes
|
|
it. They coincide in round 0 and differ for any block finalised in a round above zero, which every validator restart
|
|
produces; the Falcon commit seal signs the on-chain hash. So for those blocks the client answered `unavailable` while
|
|
holding five valid seals, and the window restore dropped them. Measured on block 2,282,930 (round 1): Besu-derived
|
|
`post-quantum`, Nethermind-derived `unavailable`; `DOVEDESTE.sh` reported NECONFORM. Fixed by remembering seals under the
|
|
on-chain hash (`CurrentOnchainHash()`), pinned by a proof (round 0 equal, round 1 different, the query under each key), and
|
|
the conformance proof now looks for the newest round-above-zero block and demands agreement on it. Implementers: the key
|
|
under which anything about a BLOCK is kept is the on-chain hash, never the message digest.
|
|
|
|
**Second half on testnet 28001, 2026-09-18 08:24Z to 08:49Z (both clients).** Besu-derived client: distribution
|
|
`besu-aip21b-26.4.0-aere.1-d203201-wt3f12ef0d` (33 proofs on the distribution tree, 9 of them the window persistence suite,
|
|
with a negative control that blinds the historic verification and turns the restore proof red) rolled onto the five nodes
|
|
one at a time with the host-clock cooling; a second restart of node 0 then restored `1284 verified seal(s) over 257
|
|
block(s)` from 257 frames, `0 not canonical`, `0 FAILED`, and block 2,284,257, heard before that restart, answered
|
|
`post-quantum` after it. Nethermind-derived client: build `client2-aip21c` (17 proofs, 6 of them the journal suite with the
|
|
shared byte vector; negative control on both the query and the journal verification), `AERE_PQ_FINALITY_DIR` set in the
|
|
unit's environment; after its second restart `1255 verified seal(s) over 251 block(s)` restored from 265 frames and block
|
|
2,285,317 (heard before it) stayed `post-quantum`, `0 FAILED`, while a block outside the window stayed `unavailable`.
|
|
|
|
**The round-above-zero case, measured live (08:48Z).** Block 2,285,333 was finalised in round 1. Both doors answer
|
|
`post-quantum` on the same hash (`0xcfecf9d0...8d2c3b`): the Besu-derived validator with signers `[0,1,2,3]`, the
|
|
Nethermind-derived validator with `[0,1,2,3,4]`, its own seal included (a validator finalises on quorum, so it may not
|
|
have heard the last Commit; the two counts legitimately differ, the verdict and the hash do not). The full
|
|
Nethermind-side record, signatures included, is kept in `aips/aip21-conformitate/runda1-bloc-2285333-nethermind.json`.
|
|
The same question on the first form of the port gave `unavailable` (the 08:17Z correction below). `DOVEDESTE.sh`:
|
|
CONFORM at 08:49:38Z, round-1 block included.
|
|
|
|
**Rebuild, 2026-09-18 11:54Z.** The Nethermind-derived testnet validator now runs build `client2-aip21d`: the same AIP-21
|
|
code, rebuilt after the host build script was found to copy only one of the five files of the consensus subprotocol from
|
|
the repository (the difference was one diagnostic line's log level; nothing functional). Switched with the same script and
|
|
proofs: window restored after the second restart (`1255 verified seal(s) over 251 block(s)`, 0 failed), round-1 block
|
|
2,305,309 `post-quantum` in both clients on the same hash, `DOVEDESTE.sh` CONFORM at 11:57Z.
|
|
|
|
**A probe that manufactures its own rare case measures its action, not the code (08:42Z).** The switch script first
|
|
looked for the round-above-zero block that ITS OWN restart of the validator produces, demanded `post-quantum` there, and
|
|
rolled a good binary back: the round had changed precisely because that validator was down, so it could not have heard
|
|
the seals of that block, and `unavailable` was the correct answer. A round-above-zero block says something about a client
|
|
only if the client answers for it, or if the OTHER client's record lists its seal index among the verified signers (then
|
|
it took part and must hold the record). Three cases, in the switch script and in `DOVEDESTE.sh`: answers `post-quantum`
|
|
on the same hash = measured; does not, but its index is in the other record = failure; does not, and its index is absent
|
|
= it was down, nothing measured.
|
|
|
|
**Interoperability note (measured 2026-09-18 03:16Z).** Send `blockParameter` as a hex string (`"0x2256b9"`), `latest`
|
|
or `pending`. The Besu-derived client also accepts a bare JSON number; the Nethermind-derived client rejects it with
|
|
`-32602 Invalid params` (`unknown block parameter type`). The public doors pass parameters through unchanged.
|
|
|
|
**Chain 2800 deployment, 2026-09-22 20:42Z to 21:45Z (both clients).** Under the founder's general mandate of
|
|
2026-09-22 (given in chat; the AIP-19 signed acceptance is still to be produced, so the ratification row above stays as
|
|
it is), the distribution `26.4.0-aere.1-d203201-wt3f12ef0d` was proven to be the live mainnet binary (`wtde7ea1c5`,
|
|
series d358) plus exactly AIP-20 and AIP-21: class by class in both directions (49,481 classes live, 49,492 new, 0 lost,
|
|
11 new, 46 with a different CRC of which 20 differ in code, all inside the patch footprint, and 21 identical in code,
|
|
line tables only). It was rolled onto the nine Besu-derived validators one at a time with the host-clock cooling (five
|
|
minutes and an anchor between restarts; every node returned `LATE-ANCHOR ACTIVATED`, kept pace, kept every property and
|
|
gained exactly `-Daere.pqtx.forkBlock=19900000`), then onto the two public readers and the (stopped) archive unit, and
|
|
the Nethermind-derived validator (the tenth) moved to a build made on its own host (`client2-aip2021-2026-09-22`,
|
|
nothing lost against the live binary, drop-in `zz-binar`, `AERE_PQ_FINALITY_DIR`, `AERE_PQTX_FORK_BLOCK=19900000`).
|
|
Measured at 21:45Z with `deploy/validators/aip2021-2026-09-22/dovedeste-aip2021-2800.sh`: every validator answers
|
|
`aere_getPqFinality` with `post-quantum`, 10 of 10 Falcon seals verified, quorum 7, `retentionBlocks` 256; the two
|
|
readers answer `unavailable` (they hear no seals, which is the honest answer); the Nethermind-derived validator agrees
|
|
with a Besu-derived one on the same block (same hash, same quorum, both `post-quantum`); all ten validators keep proposing
|
|
in rotation (20 of the last 200 blocks each). There is no public finality door on chain 2800 yet: the validators' RPCs are
|
|
local, so the record is read on the validator, or through Aere Cloud when that route is added.
|
|
|
|
## Errata
|
|
|
|
- 2026-10-01, Abstract, Motivation, "The record" (the verdict `post-quantum`) and "The permanent record": the record counts
|
|
Falcon-512 seals over `M(B) = keccak256(RLP["AERE-PQ-COMMIT-1", chainId, number(B), hash(B)])`, kept by the block's on-chain
|
|
hash; neither the seal nor the key carries the round. In QBFT an honest validator can commit a block in one round while a
|
|
different block is decided in a later round, and seals over `M(B)` made in different rounds cannot be told apart (AIP-22,
|
|
Errata, 2026-10-01). Measured on 2026-10-01 in the reference client: seven valid Falcon-512 seals over `M(p)` from rounds 0
|
|
and 2 on a block `p` that no round decided (test class `AereRoundFreeCertificateTest`), and a quorum of one round assembled
|
|
from the post-quantum seals of rounds 0 and 2 in the decision collector, which counts the same seals, under the adversary of
|
|
AIP-22 who forges ECDSA (`AereRoundFreeCommitTest`, case RF2-Q1). For `aere_getPqFinality` itself this is derived from the
|
|
code (the same seal store, kept by hash), not run against the method. Withdrawn as written, until the commit seal binds the
|
|
round: (1) "the immediate post-quantum finality of a block has been true" (Abstract) and the reading of the record as the
|
|
answer to "is this block final under post-quantum assumptions" (Motivation); (2) the verdict `post-quantum` as finality: the
|
|
field and its values stay (clients read them) and mean that at least the commit quorum of distinct validators' seals over
|
|
`M(B)` verify now, which is evidence of participation, not of the decision; (3) "The permanent record": the anchor
|
|
certificate is the permanent record of the same participation, and the AIP-22 erratum says what it proves. Not affected:
|
|
chain 2800 below AIP-22 stage 5, where a node imports a block only with the ECDSA commit quorum of one round, so the block of
|
|
a record is final there against an adversary who cannot forge ECDSA; the record as evidence of which validators sealed the
|
|
block; and the agreement of the two clients on the same record. The remedy is the one the AIP-22 erratum proposes: a commit
|
|
seal that binds the round, with a quorum of one round; it is a coordinated fork and is not scheduled.
|
|
|
|
## Post-Acceptance Outcome Record
|
|
|
|
Not accepted; nothing to record.
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via CC0.
|