aere-research/aips/AIP-21.md
Aere Network 856c5df2f8 AIPs: dated notes for AIP-20, AIP-21 and AIP-22 on chain 2800; AIP-22 stages 1 to 3 active
AIP-22 described chain 2800 "today" as the chain before stages 1 to 3. A note dated 2026-09-30 says which
stages are active from which block; the index row and the README summary carry the same heights.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:27:18 +03:00

266 lines
19 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).
## 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 &ge; 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
None.
## Post-Acceptance Outcome Record
Not accepted; nothing to record.
## Copyright
Copyright and related rights waived via CC0.