aere-node/SPEC.md

1081 lines
70 KiB
Markdown

# Aere Network chain 2800: full protocol specification
Version: 1.1.0, written 2026-08-25, amended 2026-09-02 (section 2.6, D8, G11). Scope: everything a third party needs to implement a client
that follows chain 2800 from block 0 to the live head and validates what the network validates,
without reading Aere source code. It specifies the follower (validation) protocol completely; it
describes seal production only where a follower must understand it to validate.
## 0. Conventions, and how every statement here is grounded
Every normative statement in this document carries one of three markers:
- **(source: `path`)**: the statement is checkable against a file published in this bundle (or,
where explicitly said, in the public Aere repositories). The file is the authority; if this
document and the file ever disagree, the file wins and the disagreement is a bug in this document.
- **[MEASURED yyyy-mm-dd]**: the statement is an observation of the live chain or of a real run,
made on that date, with the method stated. A measurement describes the chain as it was on that
date; it is not a promise about later heights.
- **[NOT MEASURED]**: the statement could not be established from the published sources and is
flagged rather than guessed. Section 9 collects all of these.
Where two sources of truth diverge, the divergence itself is specified, with dates, in section 8.
This chain has several such places and a client that "corrects" any of them will fail to follow the
chain. **Read section 8 before implementing anything.**
Number formatting: heights are written with commas for readability (13,014,000); in code and
configuration they are plain digits. All hashes are lower-case hex. "H" always means the anchor
activation height 13,014,000. No statement in this document uses the phrase "no block finalizes
without a post-quantum quorum"; the defensible claim is narrower and is stated exactly in
section 3.1.
Primary sources in this bundle:
| Topic | File |
|---|---|
| Genesis, byte for byte as the fleet runs it | `genesis.json` |
| Operator instructions, follower configuration | `RUN-A-NODE.md` |
| Client changes vs upstream Besu | `patches/0001..0005`, `precompiles/`, `anchor/` |
| Falcon validator registries | `registries/` |
| NIST test vectors for the precompiles | `vectors/` |
| From-genesis import proof and its measured boundaries | `IMPORT-PROOF-STARE-2026-08-15.md` |
| Formal verification runner and models | `../aere-research/formal-consensus/` |
---
## 1. Chain parameters
### 1.1 Identity
| Parameter | Value | Ground |
|---|---|---|
| Chain id | 2800 (0xaf0) | (source: `genesis.json` `config.chainId`) |
| Network id | 2800 | (source: `RUN-A-NODE.md`, `--network-id=2800`) |
| Genesis file sha256 | `361709dccec4e9fc85be5aec33b30b58a55dc53424087d84c1ad11bd1d944c24` | (source: `RUN-A-NODE.md`; hash of the published file with LF line endings) |
| Block 0 hash | `0xd86d57a899cbfa580669f0e1c7cd4ae5f525247c3c823f8f75df176a0c5d7f1a` | [MEASURED 2026-08-03, `eth_getBlockByNumber("0x0")` against a from-genesis node and both public endpoints; recorded in `RUN-A-NODE.md`] |
| Consensus | QBFT (Hyperledger Besu implementation) | (source: `genesis.json` `config.qbft`) |
| Native token | AERE, 18 decimals, total genesis supply 2,800,000,000 | (source: `genesis.json` `alloc`; the six balances sum to 2.8e27 wei) |
The block 0 hash covers every consensus-relevant field of `genesis.json` including the state root
of the six `alloc` accounts, so matching it is the complete genesis check (source: `RUN-A-NODE.md`,
"Check that you are on the right chain").
The `comment` strings inside `alloc` are not consensus data, are read by no client, and two of them
are textually wrong; they are published unaltered because the file is published byte for byte as the
fleet holds it. See section 8, D7 (source: `RUN-A-NODE.md`, "A note on the comment fields").
### 1.2 Genesis header fields
(source: `genesis.json`)
| Field | Value |
|---|---|
| `timestamp` | `0x69fbd960` |
| `gasLimit` | `0x1fffffffffffff` |
| `difficulty` | `0x1` |
| `mixHash` | `0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365` (the fixed BFT mix hash; constant on every block) |
| `coinbase` | `0x0000000000000000000000000000000000000000` |
| `nonce` | `0x0` |
| `extraData` | QBFT extra data (section 2.3) carrying vanity of 32 zero bytes and the three genesis validators |
Genesis validator set, decoded from `extraData` (source: `genesis.json`):
```
0xb5e768cb3117ea25175f51aa956b48691025c2a8
0xf73bbbd26a82dbc769066550d8d4585eab2fc00d
0x4bf6f91d84b37454544279cba152581ed6820044
```
The genesis validator set has N=3, so its QBFT quorum is 2. This matters to a from-genesis follower
because of the anchor threshold guard: see section 6.4.
### 1.3 EVM fork schedule
(source: `genesis.json` `config`)
| Fork | Activation |
|---|---|
| Homestead through London, Shanghai | block 0 / time 0 (all pre-London blocks fields are 0, `shanghaiTime` 0) |
| Cancun | timestamp 1780189051 |
| Prague | timestamp 1780189051 (same instant as Cancun) |
| Osaka | timestamp 1780220351 |
| futureEips (the Aere milestone) | timestamp 1783820272 |
**The `futureEips` milestone is NOT upstream Besu's futureEips.** Upstream bases it on the
unreleased Amsterdam definition (Block Access Lists, a different state-root committer). Chain 2800
isolates it on the Osaka definition, and additionally activates in that same fork (a) the five Aere
post-quantum precompiles (section 4) and (b) EIP-2935 historical block hashes, which performs one
system state write per block into the ring-buffer contract at
`0x0000F90827F1C53a10cb7A02335B175320002935` from the activation block onward (source:
`patches/0005-aere-eip2935-futureeips.patch`, header). A client that carries the standard Osaka
rules, the five precompiles and the EIP-2935 write from this milestone computes the fleet's state
roots; a client that carries upstream's Amsterdam-based futureEips does not.
The first block at or after `futureEipsTime` is block **9,189,161** [MEASURED 2026-08-15, real
from-genesis sync of a build without patch 0005 stopped at exactly that height with
`failed persisting block due to stateroot mismatch`; recorded in
`IMPORT-PROOF-STARE-2026-08-15.md` and `patches/0005` header].
Patch 0001 additionally repoints the futureEips precompile registry from the Cancun set to the full
Osaka set, which is a behaviour change beyond the five new precompiles (source: `README.md`, "The
patch").
### 1.4 Block period and the transition every reader must not "fix"
(source: `genesis.json`)
- `config.qbft.blockperiodseconds` = 1, `epochlength` = 30000, `requesttimeoutseconds` = 4.
- `config.transitions.qbft[0]` = `{ "block": 2138451, "xblockperiodmilliseconds": 500 }`: from that
height the target block period is 500 ms.
**The configured transition height 2,138,451 does not match the measured chain.** Sampling header
timestamps on the public endpoints puts the actual rate change just after block **2,137,652**:
twenty-block spans take 20 s from 2,137,600, 15 s from 2,137,656, 12 s from 2,137,657 and a flat
10 s from 2,137,700 onward [MEASURED, recorded in `RUN-A-NODE.md`, "A number in this file that the
chain does not agree with"]. The configured value is roughly eight hundred blocks late.
**Normative consequence: ship 2138451, exactly as the fleet does.** The field only governs how fast
a proposer may seal; it has no effect on a node that follows, and a from-genesis sync carrying this
exact value imported the blocks on both sides of both numbers with zero rejections
[MEASURED 2026-08-03, sync run in `RUN-A-NODE.md`]. A genesis file "corrected" to 2137652 hashes
differently, will never peer, and any coordinated change to the live value would be a consensus
infrastructure change. Answer "2,137,652" to the question "when did the chain accelerate"; answer
"2138451" to the question "what does the live configuration say"; never merge the two.
### 1.5 `pqRegistryHash` in the genesis config
(source: `genesis.json` `config.pqRegistryHash`)
```
block 13014000 hash 0xa96ac96dcd4f1671aec8b6063cee17a3341314c92e1dd2623bc6500b7e28b0cb
block 13600000 hash 0x5e6ac058b22beaa895469671308548dfd44fbaa9ca7ea30b3dafa950c359b9b3
```
This is a height-keyed schedule of canonical registry hashes (computation defined in section 3.6).
A node compares the canonical hash of the Falcon registry it actually loaded against the entry
active at its head and refuses to start on a mismatch (`AERE-PQC-REG-MISMATCH-01`); heights below
the first entry are unenforced, so nothing historical is invalidated (source:
`anchor/consensus/common/.../PqRegistryHash.java`, class javadoc). The guard runs at startup, not
per block; a per-block header rule for the same binding does not exist today (source: same file,
"WHAT THIS CLASS DOES NOT DO").
---
## 2. Consensus: QBFT
### 2.1 Engine and parameters
Chain 2800 runs QBFT as implemented in Hyperledger Besu at upstream commit
`d2032017bb3b8cb215a97303980a1e4a643f7180` (source: `README.md`, "The patch"; that commit is the
base of every patch in this bundle and the commit named in the artifact running on the validators).
This specification does not restate the QBFT message protocol (PREPREPARE / PREPARE / COMMIT /
ROUND-CHANGE); it is upstream Besu behaviour at that commit. What is specified here is everything
Aere adds, changes, or that a follower must check.
- Block period: section 1.4. Empty blocks are produced continuously; the chain does not pause when
there are no transactions.
- Difficulty is the constant 1 on every block, enforced by a `ConstantFieldValidationRule` in the
QBFT header ruleset (source: `anchor/consensus/qbft/.../QbftBlockHeaderValidationRulesetFactory.java`,
the rule at the line registering `"Difficulty" ... UInt256.ONE`). Total difficulty is therefore
height + 1, and the longest chain is the heaviest.
- Epoch length 30000: pending validator votes reset each epoch (upstream QBFT semantics; parameter
in `genesis.json`).
- Validator set changes are made by the standard QBFT voting mechanism (the vote element of
extraData, section 2.3). The set had 3 validators at genesis (source: `genesis.json`), and the
live chain seals with nine validators, seven until 2026-08-09 (source: `RUN-A-NODE.md`, "What the
two bootnodes are"). The block-by-block vote history is on the chain itself and is not restated
here.
### 2.2 Quorum
The QBFT commit quorum is `ceil(2N/3)`: 2 at N=3, 5 at N=7, 6 at N=9. This is the value the formal
models prove is the exact safety threshold (quorum intersection greater than f for all N, and the
negative control shows a majority quorum forks) (source:
`../aere-research/formal-consensus/qbft_safety_smt.py`, property L1, and
`../aere-research/formal-consensus/RULEAZA-TOT.md`). Fault tolerance at N=9 is f=2.
### 2.3 The extraData structure, with the Aere sixth element
`extraData` is an RLP list. Layout (source:
`anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java`):
| Index | Element | Encoding |
|---|---|---|
| 0 | `vanityData` | exactly 32 bytes. Historically arbitrary (the client writes a version string); at anchor heights from H it carries the anchor digest D (section 3.3) |
| 1 | `validators` | RLP list of 20-byte addresses |
| 2 | `vote` | empty RLP list when no vote; else `RLP[recipient(20 bytes), voteValue]` with the add/drop byte values defined in upstream Besu's `Vote` class |
| 3 | `round` | int scalar |
| 4 | `committedSeals` | RLP list of ECDSA (secp256k1) commit signatures |
| 5 | `falconSeals` (Aere, optional) | RLP list of `[validatorIndex intScalar, signature bytes]` pairs; present ONLY in the stored-header encoding and ONLY when non-empty |
Three encodings exist, and which elements they carry is consensus-critical (source: same file,
`encode(...)`):
- `ALL` (stored header): all six elements; element 5 omitted when the certificate is empty.
- `EXCLUDE_COMMIT_SEALS` (the ECDSA commit-seal signing pre-image): round kept, committedSeals
written as an empty list, falconSeals omitted.
- `EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER` (the block-hash pre-image): round forced to 0,
committedSeals an empty list, falconSeals omitted.
**The block hash is computed over a RE-ENCODE of the DECODED extraData in the
`EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER` form, not over the stored bytes.** Consequences a client
must implement and must not "fix":
1. The ECDSA commit seals, the round number, and the entire Falcon certificate are NOT covered by
the block hash. Stripping or replacing the certificate leaves the hash unchanged (it is caught
by validation instead: section 3.4).
2. Two correct neighbours can legally store different extraData bytes for the same block hash:
every node assembles its final header locally from the commits it heard. Measured on the live
chain: over 200 consecutive headers read simultaneously from two Aere RPC nodes with identical
block hashes 200/200, the ECDSA seal ORDER differed on 68 headers and the seal SET differed on
11 (source: `anchor/consensus/common/.../PqAnchor.java`, class javadoc, which cites this
measurement as the reason the anchor's design is what it is). A client must never treat
byte-equality of stored extraData across peers as a validity criterion.
The upstream hashing routine itself (`BftBlockHashing`) is not part of this bundle's overlay; the
property was verified by reproducing, byte for byte, the live node's reported hash for two real
chain-2800 headers from the re-encoding described above [MEASURED 2026-08-02, model recorded in the
repository's `consensus-pqc/dovezi-preimagine-element6-2026-08-02/` evidence set; see also section
9, G2].
### 2.4 Decoder strictness (Aere change, applies at every height)
Upstream Besu ends the extraData decode with a lenient list-leave that silently discards trailing
content. Chain 2800's client makes two changes, both consensus-relevant to anyone re-implementing
the codec (source: `QbftExtraDataCodec.java`, `decodeRaw`, the two commented gates):
1. **Strict leave**: any bytes left over in the outer list after the known elements is a decode
error, so a header that smuggles extra content after the certificate does not share a hash with
a clean header.
2. **Canonical round trip**: the decoded structure re-encoded in `ALL` form must reproduce the
received bytes exactly, or the header is rejected. This closes, among other shapes, the
"explicit empty Falcon list" ambiguity: an empty certificate must be OMITTED, and a header that
carries an explicit empty list is non-canonical and rejected. Every header any Aere or stock
Besu node ever produced round-trips unchanged, so the whole pre-anchor history still decodes.
### 2.5 What QBFT header validation on chain 2800 checks, and one thing it does not
The QBFT ruleset validates ancestry, timestamp, gas usage and gas-limit bounds, the constant
difficulty, mixHash, coinbase, the validator set and the ECDSA commit seals, plus the Aere rules of
section 3. It does **not** recompute the EIP-1559 base fee at import: Besu's
`BaseFeeMarketBlockHeaderGasPriceValidationRule` is registered only by the Clique and Merge
rulesets, not by QBFT, upstream or in this fork [MEASURED 2026-08-02, by reading the ruleset
factory and grepping `consensus/`; recorded in `RUN-A-NODE.md`, "Which Besu", and in
`../../docs`-level runbook history]. The base fee is ratified through the quorum's commit seals
over the block hash, which commits to it. Section 5 states what this means for a client that DOES
validate base fees.
### 2.6 Consensus messages under post-quantum seal (emission live since 2026-09-02; enforcement not yet armed)
Since 28-31 August 2026 every one of the four QBFT message types (PROPOSAL, PREPARE, COMMIT,
ROUND-CHANGE) can carry a Falcon-512 seal of its author, and since 2 September 2026 all nine
validators of chain 2800 EMIT such seals on every message they send (source:
`PqAnchor.java`, the four `*_DOMAIN` constants and `*Message` builders; the four payload classes
`ProposalPayload.java`, `PreparePayload.java`, `CommitPayload.java`, `RoundChangePayload.java`;
fleet configuration measured 2026-09-02). This section specifies the wire format, the signed
pre-images, the emission gates and the enforcement rules exactly as the shipped client implements
them. **Enforcement is a per-node height switch and is NOT active on chain 2800 at publication:**
a message without a seal, or with an invalid seal, still counts as a vote everywhere on 2800. On
the public testnet 28001 all four enforcements are active (source: `testnet-public/CITESTE-MA.md`).
**Wire format.** Each payload keeps its upstream RLP list and appends, as its LAST element, an
optional two-element list `[validatorIndex, signature]` (`validatorIndex` an integer index into the
active Falcon registry, `signature` the Falcon-512 signature bytes). The element is OMITTED when
there is no seal; an explicit empty list is non-canonical. `writeTo` DEFINES the canonical encoding
and `readFrom` refuses any encoding that does not reproduce the received bytes, so a message with
a seal and the same message without one never share a hash (source: `PreparePayload.writeTo`,
comment "This method DEFINES the canonical encoding"). A COMMIT may additionally carry a list of
non-Falcon "extra" seals for the hybrid certificate (`[schemeWireId, index, signature]`), REQUIRED
to be accompanied by a Falcon seal; no extra seals exist on the fleet today (source:
`CommitPayload.java`, constructor javadoc "empty for every commit on the fleet today").
**Signed pre-images: one domain per message type.** Every seal signs
`keccak256(RLP[DOMAIN, chainId, height, ...])` with an ASCII domain string that names the message
type, so a seal given honestly for one message type cannot be pasted onto another (source:
`PqAnchor.java`):
| message | domain | pre-image fields after the domain |
|---|---|---|
| PROPOSAL | `AERE-PQ-PROPOSAL-1` | chainId, height, round, proposed block digest |
| PREPARE | `AERE-PQ-PREPARE-1` | chainId, height, round, digest |
| ROUND-CHANGE | `AERE-PQ-ROUNDCHANGE-1` | chainId, height, target round, hasPrepared, prepared round, prepared digest |
| COMMIT | `AERE-PQ-COMMIT-1` or the committed-seal hash | see below |
The COMMIT seal is special, and the rule was corrected on 2026-09-02 (D8 in section 8): the
message a commit seal signs is decided in ONE place, `PqAnchorProducer.commitSealMessage`, and is
**the anchor form** `keccak256(RLP["AERE-PQ-COMMIT-1", chainId, height, round-independent block
hash])` whenever the anchor is armed at that height (`height + 1 >= aere.pq.anchorBlock`, which on
2800 is every height since 13,013,999), and the ECDSA committed-seal hash otherwise (source:
`PqAnchorProducer.commitSealMessage`, `QbftRound.pqSealMessageFor`,
`MessageValidator.SubsequentMessageValidator`). The anchor form is what lets the same seals be
carried into the next anchor header's certificate (section 3.3); the round-independent hash is the
on-chain hash with the round forced to 0 in the committed-seal encoding.
**Emission gates (producer side, per node).** `aere.falcon.attachBlock` arms Falcon seals on
COMMIT messages (on 2800: 13,889,296); `aere.pq.proposalPq.attachBlock`,
`aere.pq.roundChangePq.attachBlock` and `aere.pq.preparePq.attachBlock` arm the other three (on
2800: 16,620,000, 16,660,000, 16,690,000, measured 9 of 9 emitting). Absence of a property means
never. A node without a Falcon key emits nothing and is byte-for-byte upstream on the wire
(source: `FalconSealSupport.java`, `*_ATTACH_PROPERTY`; fleet options measured 2026-09-02).
**Enforcement (validator side, per node): `aere.pq.commitPq.forkBlock`,
`aere.pq.proposalPq.forkBlock`, `aere.pq.roundChangePq.forkBlock`, `aere.pq.preparePq.forkBlock`.**
From the configured height a message of that type COUNTS as a vote only if all of the following
hold; otherwise the message is logged and ignored ("the vote does not count"), it is not a
consensus fault of the sender and it does not disconnect the peer (source: `PqCommitEnforcement`,
`PqPrepareEnforcement`, `PqProposalEnforcement`, `PqRoundChangeEnforcement`, the `refusal`
methods and their message strings):
1. the message carries a seal;
2. the registry active at that height binds the seal's index to exactly the message's ECDSA
author (recovered from the upstream signature): the seal is bound to the author, so a seal
from validator A cannot be attached to validator B's message;
3. the registry accepts the index (it refuses indices outside the active set);
4. the signature verifies, with the Falcon-512 public key of that index, over the pre-image of
the table above (for COMMIT, over the message `commitSealMessage` names at that height);
5. for a COMMIT that carries extra seals, every scheme the hybrid schedule requires at that
height is present, of the same index, and verifies over the SAME message.
Below the configured height nothing changes: the upstream ECDSA rules alone decide. There is no
consensus binding on these heights (the same caveat as section 3.2): two validators armed at
different heights disagree about which votes count, and the published activation procedure arms
all nine at the same four heights, one node at a time, with the chain measured to advance between
restarts (source: `ARMAREA-IMPUNERII-MESAJELOR-2026-09-02.md`,
`armeaza-impunerea-pe-flota.sh`).
**Order of activation, and why.** The published package arms COMMIT first (a commit that does not
count only delays finality within the round; the liveness margin is measured), then PROPOSAL,
then ROUND-CHANGE (whose enforcement touches only the failure path: the happy path never emits a
round change), and PREPARE last (the strictest: without a quorum of sealed PREPAREs a round
cannot reach "prepared"). Rehearsed end to end on the seven-node kit (F95: the four heights in
one restart, a coordinated postponement, each height crossed, a muted node, a sub-quorum stop and
its healing) and, since 2026-09-02, WITH the anchor armed as on 2800 (source:
`consensus-pqc/fullpq-activare-2026-08-28/`, `F-lib.sh`, `dovezi/`). The machine-checked model of
these rules is `formal-consensus/pq_message_enforcement_smt.py` (section 7).
**What this section does not claim.** Node-to-node authentication (RLPx) is ECDSA. The hybrid
(Falcon + SLH-DSA) certificate is implemented and tested but no hybrid keys exist on 2800. None of
this is audited by a third party. Until the four `forkBlock` heights are set on the fleet, chain
2800's consensus is post-quantum in EMISSION only, and the defensible sentence remains the one in
section 3.1.
---
## 3. The post-quantum certificate anchor
### 3.1 The claim, stated exactly
From block 13,014,000, every 32nd header of chain 2800 (an "anchor block") carries a certificate
of validator Falcon-512 seals over its parent, and the anchor block's keccak block hash covers a
32-byte commitment to that certificate. The enforced minimum number of valid seals is a height
schedule: 0 from 13,014,000 (warm-up), 3 from 13,034,000 (f+1 of nine; enforced on the fleet since
2026-08-14), and **6 of 9 from block 14,961,456** (a full 2f+1 quorum, the same count QBFT itself
requires) (source: `anchor/README.md`, "Scope boundary"; `RUN-A-NODE.md`, the anchor section).
The defensible sentence, and the only form this document endorses: *every 32nd block does not
finalize without a certificate of at least six of nine valid Falcon-512 validator seals under its
hash (three, before block 14,961,456; see the schedule and the historical windows)*. Blocks between
anchors carry no Falcon seals and are finalized by ECDSA alone. Consensus (proposal, identity,
transport, per-block finality) is classical secp256k1 ECDSA; the anchor is a binding, not a
replacement, and "post-quantum consensus" without this qualification overstates (source:
`anchor/README.md`). None of this code has been audited by a third party (source: `anchor/README.md`,
"What is not proven here").
The threat model is retroactive rewriting (the long-range / posterior-corruption attack), not
"harvest now, decrypt later", which does not apply to signatures. Peer-reviewed treatment of the
threat: Azouvi, Danezis, Nikolaenko, "Winkle", IACR 2019/1440, AFT 2020; the quantum framing and
this particular defence are Aere's own (source: `anchor/README.md`, which also places the design
against the four known mitigation families and states what it does NOT claim, including slashable
safety).
Security property bought by the interval: an adversary holding every classical validator key and no
Falcon key can rewrite at most the tail since the last anchor, so fork depth <= 32 blocks (about
16 seconds); rewriting anything below an anchor changes that anchor's parent hash, forces the
anchor to be reproduced, and reproducing it requires Falcon signatures the adversary does not have.
History below H remains protected by ECDSA alone, permanently (source:
`anchor/consensus/common/.../PqAnchorConfig.java`, interval javadoc; `anchor/README.md`).
### 3.2 Activation is by node configuration, not by genesis, and that is a real caveat
All anchor parameters are read from JVM system properties (`BESU_OPTS`), with environment-variable
twins, not from the genesis file. **There is no consensus binding on these values: two nodes
configured with different H or different K schedules disagree about which headers are valid.** The
code says this about itself and logs it at every armed start (source: `PqAnchorConfig.java`, class
javadoc "HONEST LIMITATION" and the ARMED log line). The published follower configuration in
`RUN-A-NODE.md` is therefore normative for anyone joining the network, and this section specifies
exactly those values.
The canonical follower configuration (source: `RUN-A-NODE.md`, "The configuration a follower
passes"):
```
-Daere.basefee.floor.forkBlock=10141734
-Daere.basefee.floor.value=1000000000
-Daere.pq.chainId=2800
-Daere.pq.anchorBlock=13014000
-Daere.pq.anchorInterval=32
-Daere.pq.anchor.maxSeals=9
-Daere.pq.anchorMinSeals=13014000:0,13034000:3,14961456:6
-Daere.pq.anchor.minSealsCeiling=1
-Daere.falcon.anchor.address=0x5214F0e53899Dad805ef3AdF0624F5e49d2a5EB5
-Daere.falcon.anchor.block=13889290
-Daere.falcon.manifest=<registries>/manifest-13600000.json
-Daere.falcon.registry.history=<registries>/manifest-13014000.json,<registries>/registru-2800-v2-13600000.properties
```
Configuration loading is fail-closed on PRESENCE: if no `aere.pq.*` name is set at all, the node
behaves exactly like stock Besu (the anchor never activates); if at least one is set, every field
is read strictly and any fault refuses startup with code `AERE-PQC-ANCHOR-CONF-01`, except through
the `aere.pq.anchor.disable` brake (source: `PqAnchorConfig.java`, `fromSystemConfiguration`). The
loader also refuses an armed schedule whose highest effective K is zero
(`AERE-PQC-ANCHOR-CONF-01/minSealsFloor`), and a proposer seal cap below the highest scheduled K
(source: same file).
Semantics of each parameter (source: `PqAnchorConfig.java`):
- `aere.pq.anchorBlock` (H): below H both anchor rules return true before decoding anything; the
historical chain is untouched.
- `aere.pq.anchorInterval` (32): anchor heights are counted FROM H:
`(n - 13014000) % 32 == 0`, equivalently `n mod 32 == 16`. H itself is an anchor height. Between
anchor heights the rules judge nothing.
- `aere.pq.anchorMinSeals`: `height:K` steps, floor semantics via `floorEntry(height)`; a step must
exist exactly at H; steps below H refuse.
- `aere.pq.anchor.minSealsCeiling`: effective K is `min(scheduled, ceiling)`. The ceiling can only
lower K, never raise it (proved: `../aere-research/formal-consensus/pqanchor_ceiling_monotonicity_smt.py`).
Why a follower sets 1: section 6.4.
- `aere.pq.anchor.maxSeals`: proposer-side cap on how many seals are WRITTEN. K is a floor, not a
cap: a proposer includes every eligible seal it heard in time, so real anchor headers carry more
than K (measured seal counts at K=3, N=7: 42 blocks with 4 seals, 36 with 5, 5 with 6; a
Falcon-512 seal is 666 bytes) (source: `PqAnchorConfig.java`, `PROPERTY_MAX_SEALS` javadoc,
measurement dated 2026-08-07). Real anchor blocks carry three to nine seals (source:
`RUN-A-NODE.md`).
- `aere.pq.anchor.disable`: local emergency de-arm; both rules off on this node; per-block disarm
announcement keeps shouting. Local on purpose: a halted chain cannot deliver an on-chain change.
- `aere.pq.chainId`: enters the D and M pre-images (domain separation from the proving chain
442807, which runs the same binaries and may run the same keys).
- `aere.falcon.manifest` + `aere.falcon.anchor.address`/`anchor.block`: head registry wiring;
`aere.falcon.registry.history`: every earlier registry, never pruned (a from-genesis node
verifies every block ever produced) (source: `registries/README.md`, `RUN-A-NODE.md`).
- `aere.falcon.forkBlock`: a validator-side legacy per-block rule; see section 8, D6. A follower
leaves it unset.
An armed node refuses to start in any sync mode other than FULL (`PqAnchorSyncModeGuard`,
`REQUIRED_MODE = "FULL"`) (source:
`anchor/consensus/common/.../PqAnchorSyncModeGuard.java`), because under light validation modes the
seals rule does not run.
### 3.3 The two pre-images and the certificate encoding
(source: `anchor/consensus/common/.../PqAnchor.java`; these are exact and normative)
```
D = keccak256( RLP[ "AERE-PQ-ANCHOR-1", chainId, parentNumber, parentHash, C ] )
M = keccak256( RLP[ "AERE-PQ-COMMIT-1", chainId, blockNumber, blockHash ] )
C = RLP[ [idx, sig], [idx, sig], ... ] indices STRICTLY INCREASING
```
- The domain labels are exactly 16 ASCII bytes each, written as RLP byte strings. `chainId`,
`parentNumber`, `blockNumber` are RLP long scalars; the hashes are 32-byte strings.
- **D** occupies all 32 bytes of the anchor block's `vanityData`: no magic, no version byte, no
padding. Format versioning is by activation height, not by an in-band byte.
- The certificate C carried by anchor block N attests to N's PARENT: each seal's signature is a
Falcon-512 signature over `M = commitMessage(chainId, N-1, hash(N-1))`. M is a function of the
block hash, deliberately not of the QBFT commit hash (which includes the round), so any node
holding the parent header can recompute it with no state and no round memory.
- C is hashed into D IN THE ORDER CARRIED, and both validation rules require strictly increasing
indices, so for every accepted header "the order given" and "sorted" are the same list. Strictly
increasing indices also make duplicate indices unrepresentable (the "repeat one seal to inflate
k" attack is excluded by the grammar).
- The certificate is CARRIED (element 5 of extraData, outside the hash); only its 32-byte digest D
(element 0, vanityData) is under keccak. Change one byte of the certificate and D no longer
matches; strip the certificate entirely and the hash is unchanged but validation rejects the
header (digest of the empty certificate differs, and the seal count is below K).
- Seal signature bytes are in Bouncy Castle's Falcon signature encoding,
`(0x30+logn) || nonce(40) || compressedSignature` (0x39 leading byte for Falcon-512); a seal is
666 bytes (source: verification call in
`anchor/consensus/common/.../FalconSealSupport.java` (BC `FalconSigner.verifySignature` over the
32-byte M), encoding stated in `precompiles/AereFalconSupport.java` javadoc; length measured in
`PqAnchorConfig.java` javadoc). Falcon signatures are variable-length by nature; the RLP byte
string carries the exact length.
At H itself the parent predates the scheme, the certificate is legitimately EMPTY, and vanityData
carries the digest of the empty certificate; K=0 makes that acceptable only during the warm-up step
(source: `anchor/consensus/qbft/.../PqAnchorSealsRule.java`, the `k == 0` branch).
### 3.4 What an armed node rejects: the validation rules
Registered in the QBFT header ruleset alongside the classical rules (source:
`anchor/consensus/qbft/.../QbftBlockHeaderValidationRulesetFactory.java`; registration is itself
tested with a measured negative control, `QbftAnchorRuleWiringTest`, because Besu wraps detached
rules in lambdas that hide them from reflection):
**R1, `PqAnchorDigestRule`** (detached, runs in light validation; twin `PqAnchorDigestAttachedRule`
carries the same verdict on the attached path so it survives `SKIP_DETACHED` import):
1. Below H, or at a non-anchor height, or when the height is inside the disarmed historical window:
accept (window handling in section 8, D4).
2. vanityData must be exactly 32 bytes; the certificate's indices must be strictly increasing
(inside the disarmed window: distinct and non-negative suffices); recompute
`D' = anchorDigest(chainId, N-1, header.parentHash, carried certificate)` and require
`D' == vanityData`. Any mismatch, and any undecodable extraData, rejects: the exception path
returns false, not true (source: `anchor/consensus/qbft/.../PqAnchorDigestRule.java`).
**R2, `PqAnchorSealsRule`** (attached; needs the parent and state, runs on the import path). In
order (source: `anchor/consensus/qbft/.../PqAnchorSealsRule.java`, `validate`):
1. Below H or non-anchor height: accept. Inside the disarmed historical window: accept as written
after checking the certificate still decodes with distinct non-negative indices.
2. The supplied parent must be the parent the header names (number N-1, matching hash), else
reject.
3. Decode extraData (strict codec, section 2.4); let k = number of carried seals.
4. Compute the required threshold: `required = min(schedule at N, emergency ceiling)`; inside the
threshold-only historical window, `required = min(required, 1)` where 1 is the measured floor
that was actually in force (section 8, D5).
5. `k < required` rejects.
6. Indices not acceptable (strictly increasing; window relaxation as above) rejects.
7. `k == 0` accepts here (only reachable when required == 0; R1 has already bound the digest of the
empty certificate).
8. For each seal: resolve `index -> validator address` in the registry AT THE PARENT's height
(N-1); an unbound index rejects; an address not in the PARENT block's validator set rejects; two
indices resolving to one address rejects; a Falcon-512 verification failure of the signature
over M rejects. Registry resolution at the parent height is exact and has a one-block boundary
case at H: a registry bound at exactly `parentNumber + 1` answers for the parent, because the
schedule binds inclusively from H while seals commit to H-1. Without this alignment a
from-genesis node rejects the first anchor; with it, nothing else changes
[MEASURED 2026-08-15, from-genesis import; boundary and negative control recorded in
`IMPORT-PROOF-STARE-2026-08-15.md`].
9. Any exception during checking rejects (fail-closed).
**`PqRegistryBindingRule`** and **`PqEmergencyShoutRule`** are also registered: the former is part
of the registry-binding machinery, the latter only logs (it makes a disarmed-but-configured node
shout at every block) (source: `QbftBlockHeaderValidationRulesetFactory.java`).
**`FalconSealValidationRule`** (the legacy per-block rule) retires at H: its first statement
accepts any header at `number >= legacyFalconRuleRetirementBlock()`, which equals H whenever the
anchor is configured. Exactly one of the two regimes is in force at any height (source:
`PqAnchorConfig.java`, `legacyFalconRuleRetirementBlock`;
`anchor/consensus/qbft/.../FalconSealValidationRule.java`). See section 8, D6 for why this matters
historically.
The rejection is a real import failure: on a live test network the log sequence is
`PqAnchorSealsRule ... REJECTED` then `MainnetBlockValidator | Invalid block` then
`InvalidBlockException` then sync-target disconnect [MEASURED 2026-08-06, armed test network,
recorded in the repository's `consensus-pqc/controale-finale-2026-08-06/` evidence; 12 of 12
stripped-certificate headers rejected on the 4-node rehearsal network, source: `anchor/README.md`,
"What is proven"].
### 3.5 The Falcon validator registries
(source: `registries/README.md`, files in `registries/`)
A node cannot validate any anchor block without the registries: it imports fine to 13,014,000 and
then refuses every anchor with `AERE-PQC-REG-BLOCK-01`, deliberately and fail-closed, because seal
indices cannot be mapped to validator addresses.
| File | Bind height | Keys | Role | sha256 |
|---|---|---|---|---|
| `manifest-13014000.json` | 13,014,000 | 7 | registry in force from the first anchor; carried in history | `006b1b3fb814ed84622539160956263faca7c0b76585c22f2e8acb926c0bcb7f` |
| `registru-2800-v2-13600000.properties` | 13,600,000 | 9 | nine-validator registry from the set expansion; carried in history | `c9a215d294e106f69fb2f16e3af21f9787523969e4b7260a3098f4b2cf458059` |
| `manifest-13600000.json` | 13,600,000 | 9 | head registry, passed as `aere.falcon.manifest` | `23736b7d4ba1d3213496f8b5a5f756d5c958b08efebdeb1329b24d7dfa45fb6e` |
Each JSON manifest carries `formatVersion` (2), `chainId` (2800), `bindHeight`, `count`, and rows
keyed `"0".."count-1"`, each row holding:
- `pk`: the validator's Falcon-512 public key, stored as the raw `h` polynomial, 896 bytes
(NOT the 897-byte standard encoding with its one-byte header; the header is absent in these
files) (source: `PqRegistryHash.java`, `FALCON_512_PK_LENGTH` javadoc);
- `addr`: the validator's chain-2800 address;
- `pop`: a Falcon proof of possession signed by the row's own key;
- `claim`: an ECDSA signature by the validator's own account key, the half a registry writer cannot
forge, closing row re-binding and row swapping.
No private key material exists in these files. Provenance of the published bytes: each file was
read from three independently operated validator hosts and produced the same sha256 on all three
(source: `registries/README.md`).
Registry rotation is height-scheduled and every height has exactly one registry (proved:
`../aere-research/formal-consensus/registry_rotation_coverage_smt.py` and
`registry_index_binding_smt.py`). The head registry is additionally pinned by an on-chain anchor
contract at `0x5214F0e53899Dad805ef3AdF0624F5e49d2a5EB5` from block 13,889,290 (source:
`RUN-A-NODE.md`, follower configuration; the contract's own interface is not specified in this
bundle, see section 9, G6).
**Domain tags of the registry binding and of node-local persistence (named here so that a
third-party client sees every `AERE-PQ-*` tag the shipped client uses).** A registry row binds a
Falcon key to a validator address with two signatures over two distinct pre-images (source:
`PqRegistryBinding.java`, class javadoc "The canonical pre-image"):
- the **possession proof** is a Falcon-512 signature over
`"AERE-PQ-POP-1" || uint8(formatVersion) || uint64be(chainId) || uint64be(bindHeight) ||
uint32be(count) || uint32be(index) || address(20) || uint32be(len(publicKey)) || publicKey`.
The tag sits at offset 0 INSIDE the hashed pre-image, so a possession proof can never be
replayed as a seal (the seal pre-images are `AERE-PQ-COMMIT-1` and the section 2.6 domains);
the public key is inside the signed message because Falcon does not have exclusive ownership;
- the **claim** is an ECDSA signature by the validator's consensus key, in an EIP-191 context
that mixes the tag `AERE-PQ-CLAIM-1`, so a key cannot be moved under another validator's
address, indices cannot be swapped, and a key cannot appear under an address whose owner did
not ask for it.
- `AERE-PQ-SEALSTORE-1` is the label of the node-local file in which a node persists the seals it
has heard for its chain head across restarts (`aere-pq-seals.rlp`); it is not consensus data and
a client is free to implement it differently or not at all (source: `PqSealStore.java`).
### 3.6 The canonical registry hash
(source: `anchor/consensus/common/.../PqRegistryHash.java`; exact and normative)
v1 pre-image (domain `AERE-PQ-REGISTRY-1`):
```
"AERE-PQ-REGISTRY-1" 18 bytes ASCII
uint64be(chainId) 8 bytes
uint32be(count) 4 bytes
for i in 0..count-1:
uint32be(i) 4 bytes
uint8(addressBound?1:0) 1 byte
address 20 bytes, only when address-bound
uint32be(len(publicKey)) 4 bytes
publicKey len bytes
```
v2 pre-image (domain `AERE-PQ-REGISTRY-2`; address binding mandatory; adds `uint64be(bindHeight)`
after chainId, and per row appends length-prefixed `possession` and `claim` proofs). The canonical
hash is keccak256 of the pre-image, rendered as 64 lower-case hex digits without `0x`. The proofs
are INSIDE the hash so that stripping them yields a different registry with a different hash, and
the two domains ensure a v1 file can never satisfy a v2 schedule entry. The `pqRegistryHash` values
in `genesis.json` (section 1.5) are hashes under this scheme; index, chainId and bindHeight are all
inside the pre-image, so row permutation, cross-chain reuse and height re-binding all change the
hash.
Registry loading is strict: `count` must be present, indices exactly `0..count-1` with no gap and
no extra, address binding all-or-nothing, hex even-length and strictly hexadecimal; an ambiguous
file refuses rather than hashes (source: `PqRegistryHash.java`, `Registry` javadoc).
### 3.7 Guards a validator-capable build enforces at startup
Stated because a third-party implementation that produces blocks needs them, and a follower needs
to know they exist:
- `PqAnchorThresholdGuard`: refuses to arm a seal threshold at or above the QBFT quorum of the
CURRENT validator set (a proposer could never gather it), and refuses any threshold above
N - f = 7 at N=9 (source: `anchor/consensus/common/.../PqAnchorThresholdGuard.java`;
`anchor/README.md`; `RUN-A-NODE.md`, "Why the seal-threshold ceiling"). The refusal code is
`AERE-PQC-THRESHOLD-01`, and it is the reason a from-genesis follower needs the ceiling
(section 6.4).
- The min-seals floor, the max-seals cap check, and the fail-closed loader (section 3.2).
- Emergency options exist as first-class command-line options `--Xaere-pq-anchor-disarm` and
`--Xaere-pq-anchor-min-seals-max` layered onto the same properties, command line winning
(source: `anchor/app/.../AerePqEmergencyOptions.java`, `PqAnchorConfig.java` javadoc).
---
## 4. Precompiles
### 4.1 The five mainnet precompiles
Activated on mainnet at block **9,189,161** (the futureEips milestone, section 1.3) (source:
`README.md`; sources in `precompiles/`, installed by `patches/0001`).
| Address | Algorithm | Standard | Gas | Input | Output |
|---|---|---|---|---|---|
| `0x0AE1` | Falcon-512 verify | NIST Falcon round 3 | 40,000 flat | `pk(897) \|\| sm` | 32-byte word: `...01` valid, `...00` invalid |
| `0x0AE2` | Falcon-1024 verify | NIST Falcon round 3 | 75,000 flat | `pk(1793) \|\| sm` | same |
| `0x0AE3` | ML-DSA-44 verify | FIPS 204 | 55,000 flat | `pk(1312) \|\| sig(2420) \|\| message` | same |
| `0x0AE4` | SLH-DSA-SHA2-128s verify | FIPS 205 | 350,000 flat | `pk(32) \|\| sig(7856) \|\| message` | same |
| `0x0AE5` | SHAKE256 XOF | FIPS 202 | 60 + 12 per 32-byte word of (input data + output) | 32-byte big-endian `outLen` word, then data | `outLen` bytes |
(gas and lengths: source: `precompiles/Falcon512PrecompiledContract.java`,
`Falcon1024PrecompiledContract.java`, `MLDSA44PrecompiledContract.java`,
`SLHDSA128sPrecompiledContract.java`, `SHAKE256PrecompiledContract.java`)
Verification precompiles never revert on malformed input; they return the 32-byte zero word. A
too-short input (size <= pk length for Falcon; size < pk+sig for the fixed-length pair) returns
invalid (source: the `computePrecompile` methods).
### 4.2 Falcon input framing, exactly (the message sits in the MIDDLE)
(source: `precompiles/AereFalconSupport.java`; normative, and the single most common integration
mistake)
```
input = pk || sm
pk = (0x00 + logn) || packed_h 897 bytes (Falcon-512, logn=9, header 0x09)
1793 bytes (Falcon-1024, logn=10, header 0x0A)
sm = sigLen(2, big-endian) || nonce(40) || message || esig
esig = (0x20 + logn) || compressedSignature (0x29 for Falcon-512, 0x2A for Falcon-1024)
sigLen == len(esig)
```
This is the NIST reference signed-message convention. The message is between the nonce and the
signature, NOT appended after a detached signature; a caller that places the message at the end
gets a clean `0` for a perfectly valid signature and the precompile looks dead. Falcon signatures
are compressed and therefore VARIABLE length (614 and 616 bytes were measured for two signatures
by the same key), so `sigLen` must be read per call, never hard-coded [MEASURED 2026-08-09,
recorded in the repository operating notes; framing itself: source as above]. The precompile
checks the pk header byte, the esig header byte, and the length arithmetic, and answers invalid on
any mismatch. Internally the verifier is Bouncy Castle; no cryptography is reimplemented (source:
`AereFalconSupport.java`).
For `0x0AE3` and `0x0AE4` the NIST `sm = signature || message` convention applies with the fixed
signature lengths in the table; the remainder of the input after `pk || sig` is the message.
`0x0AE5` reads a 32-byte big-endian output length (values above 65,536 are capped to 65,536; a
nonzero high 28 bytes also caps; input shorter than 32 bytes returns empty output) and returns the
SHAKE256 XOF of the remaining bytes at that length (source:
`precompiles/SHAKE256PrecompiledContract.java`).
### 4.3 Testnet-only precompiles: NOT on mainnet
`0x0AE6` (ML-KEM-768 deterministic encapsulation, FIPS 203, gas 60,000) and `0x0AE7` (Falcon
HashToPoint SHAKE256 rejection sampler, gas 60 + 12/word) exist in `patches/0002` and are testnet
only. A mainnet node must NOT apply patch 0002. The live-chain verification script asserts their
ABSENCE from mainnet as a falsifiable check (source: `README.md`;
`scripts/verify-live-precompiles.mjs`).
### 4.4 Verification against the live chain
`node scripts/verify-live-precompiles.mjs` runs 13 checks (NIST KAT/ACVP vectors from `vectors/`,
every signature check paired with a tampered negative control, plus the two absence checks);
13/13 passed against `rpc.aere.network` at block 10,566,696 [MEASURED 2026-07-20, output recorded
in `README.md`]. The BN254 zero-knowledge verifiers used elsewhere in the Aere stack are classical
and Shor-breakable, and are never described as quantum-safe (source: `README.md`, scope boundary).
`0x100` P-256 verify (RIP-7951) is also live and classical; it is upstream Besu, not part of this
fork (source: `README.md`; format not specified here, see section 9, G7).
---
## 5. Fee rules
### 5.1 EIP-1559 with an Aere floor
London is active from block 0 (source: `genesis.json`). Upstream Besu's `LondonFeeMarket` carries a
default base-fee floor of 7 wei, which is why the pre-fork steady state of this mostly-empty chain
was ~7 wei (source: `patches/0004-aere-basefee-floor.patch`, visible in the diff context,
`DEFAULT_BASEFEE_FLOOR = Wei.of(7L)`).
From block **10,141,734** chain 2800 clamps the computed EIP-1559 base fee to
`max(computed, 1_000_000_000)` wei (1 Gwei). The clamp sits on all three return paths of
`computeBaseFee`, which Besu uses for BOTH production and validation, and is gated by the two
system properties (`aere.basefee.floor.forkBlock`, `aere.basefee.floor.value`); with them unset the
patch is a byte-for-byte no-op (source: `patches/0004-aere-basefee-floor.patch`). A block's
`baseFeePerGas` is the real consensused header value; there is no display shim on the public
endpoints since the floor went live.
### 5.2 What a follower must and must not enforce
- Besu's QBFT ruleset does not recompute the base fee at import (section 2.5), so a Besu-derived
follower needs the patch only to COMPUTE what the network computes (and to propose correctly if
it ever proposed); it will import the chain either way. That is a finding about a missing
upstream rule, not a licence to skip the patch (source: `RUN-A-NODE.md`, "Which Besu").
- A client that DOES validate `baseFeePerGas` at import (the second Aere client, Nethermind-based,
does) MUST implement the floor as a VALIDATION rule with one historical exception window, or it
cannot sync: see section 8, D3. Inside the window the header's own `baseFeePerGas` is accepted as
given; outside it the floor is enforced normally, so a NEW violation still stops the client
(source: `aere-client2/src/Nethermind/Nethermind.Core/AereFloorLapse.cs` in the public Aere
repositories; the file is not part of this bundle).
- Why the window cannot be a calculation rule: inside it the base fee is NOT a function of the
parent header. Producers with and without the floor alternated by round winner (block 12,978,617
is 875,000,000 wei, its child 12,978,618 is 1,000,000,000), so no deterministic recomputation can
reproduce the sequence (source: `AereFloorLapse.cs` javadoc).
- `--min-gas-price` is a local mempool policy, not consensus; the Foundation nodes run it at zero
(source: `RUN-A-NODE.md`).
---
## 6. Sync requirements: what a third-party client must implement to follow the chain
This section is the checklist. Each item names where its details live.
### 6.1 Identity and history
1. Ship `genesis.json` byte for byte (sha256 in section 1.1); verify your block 0 equals
`0xd86d57a8...c5d7f1a`. Ship the configured QBFT transition 2138451 unchanged (section 1.4).
2. Implement the EVM fork schedule of section 1.3, including the Aere futureEips semantics:
Osaka-based ruleset, the five precompiles of section 4, and the EIP-2935 per-block system write,
all from block 9,189,161. A build missing any of these diverges on state root at exactly that
height [MEASURED 2026-08-15, `IMPORT-PROOF-STARE-2026-08-15.md`].
3. Implement the base-fee floor from 10,141,734 (section 5), with the D3 window if you validate
base fees.
4. Implement QBFT header validation with the Aere extraData codec exactly as in sections 2.3 and
2.4 (strict leave, canonical round trip, optional sixth element), the constant difficulty 1, and
the hash pre-image that excludes round, ECDSA seals and the Falcon certificate.
5. Implement the anchor rules of section 3 from height 13,014,000, with the registries of
section 3.5 and the historical windows of section 8 (D4, D5). Without the windows a from-genesis
sync stops at 13,267,824 and cannot proceed; without the parent-height registry alignment it
stops at 13,014,000.
### 6.2 Sync mode
Full sync from block 0 is the mode that verifies the published history, and it is what the anchor
requires: an armed node refuses any lighter mode at startup (section 3.2). Snap sync against a QBFT
chain trusts a peer for the state and verifies far less (source: `RUN-A-NODE.md`). The Foundation
fleet runs BONSAI storage; storage format is a local choice, not consensus.
### 6.3 Peering
Discovery needs a bootnode enode. Bootnodes are deliberately not published in this bundle; a
current enode is provided by mail (office@aere.network, subject "bootnode") until a dedicated
public bootnode exists (source: `RUN-A-NODE.md`). Both discovery UDP and p2p TCP must be open in
both directions. The bootnodes are full nodes outside the validator set, Foundation-operated, and
their liveness at any moment is only provable by your own `net_peerCount` (source: `RUN-A-NODE.md`).
### 6.4 The genesis-set threshold problem, and the published answer
A node syncing from block 0 starts INTO the genesis validator set (N=3, quorum 2). The anchor
threshold guard (section 3.7) refuses to arm K=3 or K=6 against that set, so a from-genesis
follower with the published schedule cannot start at all without one more setting. The published
answer is `aere.pq.anchor.minSealsCeiling=1`, which lowers the EFFECTIVE seal-count floor to
`quorum(3) - 1 = 1` WITHOUT editing the schedule (source: `RUN-A-NODE.md`, "Why the seal-threshold
ceiling").
State the consequence honestly, because it is a real weakening of one check on the follower: with
the ceiling at 1, the follower's seal-COUNT requirement is 1 at every height, not 3 or 6. What is
NOT weakened: every anchor must still carry a digest that binds its certificate under the block
hash, indices must still be strictly increasing, every carried seal is still Falcon-verified
against the height-correct registry, and eligibility in the parent's validator set is still
enforced. Real anchor blocks carry 3 to 9 seals, so all of history imports cleanly. The seal-count
floor is a proposer-liveness property relative to the current set, not a history-verification
property; a validator, which starts into the full set, does not set the ceiling (source:
`RUN-A-NODE.md`). An implementation that instead makes its threshold guard aware of the validator
set it will have at the evaluated HEIGHT (rather than at startup) can enforce the full schedule
end to end; the published Besu-based package does not do this today [NOT MEASURED: no such
implementation exists to compare against].
### 6.5 Facts a syncing client will observe, so they are expectations rather than surprises
- Blocks are almost all empty; the chain advances ~2 blocks/second after the rate change.
- Between anchor heights, headers carry no Falcon element (extraData 634 bytes at N=9); at anchor
heights they carry element 5 and a digest in vanityData (2,516+ bytes) [MEASURED 2026-08-09 on
the live chain; recorded in the repository operating notes; sizes are descriptive, not
normative].
- Stored extraData for the same block can differ between peers (section 2.3); only the hash
pre-image is canonical.
- The full from-genesis import of exactly this bundle's patches reached the live tip (height
14,088,211) and followed it, with zero anchor rejections and zero state mismatches, after the two
historical windows and the H boundary alignment were carried; the consensus test suites stood at
641 tests, 0 failures, with negative controls run in all four directions on the windows
[MEASURED 2026-08-16, run and controls recorded in `IMPORT-PROOF-STARE-2026-08-15.md`].
- The EIP-4788 (Cancun) beacon-roots system call targets a contract with no code on chain 2800, so
it is a per-block no-op that Besu reports at ERROR level in its log; state is unaffected. A
client must not treat the empty beacon-roots contract as an error in its own sync
[MEASURED 2026-08-07, finding D-153 in the repository's findings register; not restated in any
bundle file, see section 9, G8].
---
## 7. Formal models: what is proven, where, and the limits
The machine-checked models live in `../aere-research/formal-consensus/` (SMT, z3; runner
`run_consensus_verification.py`) and, for TLA+/TLC, in the repository directory `formal-tla/`
(not part of this bundle). Method rule throughout: no result counts as a proof unless its negative
control was RUN and produced the predicted violation (SAT model or TLC trace) (source:
`../aere-research/formal-consensus/RULEAZA-TOT.md`).
Suite status [MEASURED 2026-08-24, `python run_consensus_verification.py`, Windows, z3 4.16.0]:
35 models, 35 pass (14 consensus + 11 contract + 10 application), 282 PROVED + 149 CEX-FOUND,
0 FAILED, ~39 s wall (source: `RULEAZA-TOT.md`).
Consensus properties proven, with tool and scale (source: `RULEAZA-TOT.md` and the repository
document `strategie/VERIFICARE-FORMALA-2026-08-24.md`):
| Property | Tool / N | Model |
|---|---|---|
| Agreement (no two different blocks finalize at one height) | SMT parametric N=4..16 plus UNBOUNDED quorum-intersection arithmetic (2q > N for all N); TLC at N=4 | `qbft_safety_smt.py`, `QBFT.tla` |
| Quorum threshold is exactly ceil(2N/3) (N=9 gives 6) | SMT unbounded | `qbft_safety_smt.py` (L1), `pqfinality_smt.py` (Q0) |
| Equivocation: <= f Byzantine double-signers cannot break agreement | SMT N=4..16; TLC | `qbft_safety_smt.py` (L2), `QBFT.tla` |
| Round-change locking: a view change cannot finalize a different value | SMT N=4..13; TLC N=4 | `qbft_locking_smt.py`, `qbft_safety_smt.py` (L3) |
| Liveness post-GST: within f+1 rounds an honest proposer commits | SMT N=4..7 | `qbft_liveness_smt.py` |
| Network partitions: no split-brain in any split; the minority side cannot commit; progress returns on heal | SMT parametric N=9 + unbounded; TLC N=4 EXHAUSTIVE (6 configs, 0 states left in queue) | `partition_safety_smt.py`, `QBFTPartition.tla` |
| PQ anchor certificate acceptance is fail-closed (quorum, root, size, domain, rotation gates each individually load-bearing) | SMT N=9 | `pqfinality_smt.py`, `anchor_blocking_quorum_smt.py` |
| A round change on an anchor height cannot yield two different valid certificates; uniqueness comes from binding to the FINALIZED block, not from K (K=3 alone admits two, shown SAT) | SMT N=9 | `anchor_viewchange_smt.py` |
| Emergency ceiling only lowers K | SMT | `pqanchor_ceiling_monotonicity_smt.py` |
| Registry rotation covers every height exactly once; index binding | SMT | `registry_rotation_coverage_smt.py`, `registry_index_binding_smt.py` |
The honest boundary, stated as prominently as the results (source: `RULEAZA-TOT.md`, "Granita
cinstita"; `VERIFICARE-FORMALA-2026-08-24.md`):
- Model checking is not a complete proof. TLC exhausts only the stated finite configuration
(partitions at N=4, single height). The full `QBFT.tla` model does NOT terminate in local
budgets; what exists there is a partial explicit-state witness (9.35 million states, no
violation, N=4 round 0), not a verification.
- Parametric SMT proves the property for THAT N (9), plus unbounded arithmetic for the
quorum-intersection core. No mechanized induction over all executions at arbitrary N
(Coq/Isabelle/TLAPS) exists.
- The models verify the DESIGN's combinatorics, not the Java bytecode or the EVM. The design-to-
binary gap has historically been caught only by importing real blocks, never by a form check.
- Cryptographic primitives (keccak injectivity, Falcon verification soundness) are modeled as
trusted, not re-proved.
- Liveness results assume partial synchrony (post-GST); asynchronous liveness is impossible (FLP)
and is not claimed.
- Nothing in the formal results implies "post-quantum consensus" per block; the anchor is additive
(section 3.1).
---
## 8. Known divergences, historical windows, and corrections. Normative.
Every entry here is something a naive implementation gets wrong. Each carries its dates and its
ground. D3, D4 and D5 are MANDATORY for a from-genesis sync; the rest are mandatory knowledge.
**D1. The sub-second transition: configured 2138451, measured 2,137,652.** Ship the configured
value; report the measured one. Full statement in section 1.4. Both numbers appear in public Aere
material historically; the wrong lesson is to pick one and "fix" the other (source:
`RUN-A-NODE.md`; measurement dated there).
**D2. Genesis `qbft.startBlock` folklore.** Any Aere document giving 2,138,451 as "when the chain
accelerated" is quoting configuration, not measurement; the acceleration is at 2,137,652
[MEASURED, section 1.4]. The genesis file the fleet runs is identical on all nine validators
(sha256 `361709dcce...`) and is not to be touched.
**D3. Base-fee floor lapse, blocks 12,978,617 to 13,087,959 inclusive.** On 2026-08-09 the
validators lost the floor system properties for about sixteen hours (a configuration rewrite
dropped them); producers with and without the floor alternated. Inside this range a validating
client accepts the header's `baseFeePerGas` as given; outside it, the floor holds from 10,141,734.
Bounds were established by exhaustive scan after a binary search returned a wrong answer
(12,978,949; the violation predicate is intermittent, hence non-monotone), and the true lower bound
was found by the second client refusing to sync past 12,978,617. The 500 blocks before the window
and the 2,000 blocks after it contain zero violations (source:
`aere-client2/src/Nethermind/Nethermind.Core/AereFloorLapse.cs`, public Aere repositories;
measurements dated 2026-08-09..11 in that file's javadoc). Besu-derived followers import this range
regardless (section 5.2); clients that validate base fees need the window.
**D4. Anchor disarmed window, blocks 13,267,824 to 13,268,944 inclusive: 36 anchor heights,
relaxation EVERYTHING.** On 2026-08-10 all nine validators restarted within minutes; no node held
seals, the next anchor height could not be proposed, and the chain stopped for nine minutes. The
fleet-wide emergency measure switched the anchor rules off; the canonical headers produced
meanwhile carry the client version string in vanityData (no digest) although a certificate is
attached, and their certificate indices are in arrival order rather than sorted. Inside this window
a validating client accepts the header as written, EXCEPT that the certificate must still decode
and must still carry distinct non-negative indices (distinctness is the anti-inflation property and
is never relaxed). Bounds are fixed in code so the window cannot widen at runtime, and producers
never consult it, so it is closed by construction. Bounds were measured by decoding every anchor
height from H to 14,077,000 one by one (25,252 heights), not searched (source:
`anchor/consensus/common/.../PqAnchorLapse.java`, first window;
`IMPORT-PROOF-STARE-2026-08-15.md`).
**D5. Anchor threshold-lowered window, blocks 13,268,976 to 13,890,544 inclusive: 19,425 anchor
heights, relaxation THRESHOLD_ONLY, floor 1.** After the D4 incident the rules were restored with
the emergency seal ceiling lowered (raising the threshold back in one step had stopped the chain a
second time). Every header in this range binds its digest and carries sorted indices; 2,926 of the
19,425 anchor heights carry fewer seals than the schedule asks, the fewest being 1. Inside the
window the required count is `min(schedule, 1)`; nothing else is relaxed, and every carried seal is
still verified. The measured floor is 1, never 0: an empty certificate is NOT accepted where one
was required (source: `PqAnchorLapse.java`, second window). This window is also why the schedule
step `13034000:3` reads "enforced since 2026-08-14" in prose: the step was configured earlier, but
the effective minimum was held at 1 by the ceiling until the window's end (source: `anchor/README.md`;
`PqAnchorLapse.java`).
**D6. Correction of 2026-08-19 (repository finding D-235): `aere.falcon.forkBlock=14050000` armed a
RETIRED rule and changed no enforcement.** The fleet was configured on 2026-08-15 with that value,
intended as a per-block blocking Falcon quorum, and public material said so. In the shipped code
the rule it arms (`FalconSealValidationRule`) retires at the anchor activation height 13,014,000 in
favour of the anchor rules (`PqAnchorConfig.legacyFalconRuleRetirementBlock`), so at 14,050,000 it
was already inert. The chain itself confirms it: blocks between anchors carry no Falcon seals,
which a per-block rule would have rejected. The earlier public statement that from 14,050,000 every
block required a 2f+1 Falcon quorum to finalize is WITHDRAWN; nothing changed at that height
(source: `anchor/README.md`, correction paragraph; `RUN-A-NODE.md`, correction paragraph;
code: `FalconSealValidationRule.java`, `PqAnchorConfig.java`). A follower leaves
`aere.falcon.forkBlock` unset; set or unset, it gates nothing above H.
**D7. Genesis `alloc` comment strings are wrong and published anyway.** The dash characters are
mangled (a historical encoding accident) and the 1,400,000,000 AERE account is labelled "Mining
Reserve" although chain 2800 has no mining and never has (it is the staking and emissions reserve).
No client reads these strings; they stay because the file is published byte for byte as the fleet
holds it (source: `RUN-A-NODE.md`).
**D8. The H boundary registry alignment (one block, exactly).** The seals rule resolves the
registry at the PARENT's height; the registry schedule binds inclusively from H; therefore at the
first anchor the lookup at H-1 must be answered by the registry bound at exactly H, and the
boundary is exactly one block wide (two or more blocks below the schedule changes nothing). The
first public build lacked this and rejected the first real anchor with "certificate carries index
0, which the registry does not bind"; the fix carries a dedicated test with a run negative control
(source: `IMPORT-PROOF-STARE-2026-08-15.md`; the test `PqParentHeightAlignmentTest` in `anchor/`).
**D9. K schedule steps and their real dates.** `13014000:0` (warm-up; the first anchor's
certificate is legitimately empty), `13034000:3` (f+1 of nine; effectively in force from
2026-08-14, see D5), `14961456:6` (2f+1, in force from 2026-08-21) (source: `RUN-A-NODE.md`,
`anchor/README.md`). "Certificate of f+1 validators" and "quorum certificate" are different
claims; before 14,961,456 only the former is true, and calling K=3 a quorum is a counting error
anyone will catch (source: `anchor/README.md`, "Terminology, used precisely").
**D10. Anchors described as "universal since block 13,889,296".** Both `RUN-A-NODE.md` and
`anchor/README.md` carry this phrase for the certificate. Its precise operational definition (what
exactly changed at that height, as distinct from the D5 window ending at 13,890,544 and the
on-chain registry pin at 13,889,290) is not stated in any bundle file [NOT MEASURED; flagged in
section 9, G5]. Implementations need no rule keyed to 13,889,296; the windows of D4/D5 plus the
schedule are sufficient to sync [MEASURED 2026-08-16, the full import of
`IMPORT-PROOF-STARE-2026-08-15.md` used exactly those].
**D11. The Cellframe counterexample governs how the anchor may be described.** "No public chain has
a block hash covering a post-quantum validator certificate" is FALSE as stated: Cellframe's ESBoCS
signs block candidates with keys resolving to Dilithium/Falcon/SPHINCS+ and hashes the block with
signatures attached. The defensible sentence is: no public chain has a post-quantum validator
certificate under the block hash that is PROVEN AND INDEPENDENTLY VERIFIABLE, and this bundle is
the "proven and verifiable" half of that sentence (source: `anchor/README.md`, "One claim we
retracted, on purpose").
**D12. Validator-set size statements are dated.** Seven validators until 2026-08-09, nine since
(source: `RUN-A-NODE.md`). Registry rotation heights (13,014,000 seven keys; 13,600,000 nine keys)
are the authoritative in-protocol trace of the expansion (source: `registries/README.md`).
---
**D8. The COMMIT seal message under an armed anchor (found 2026-09-02, D-311).** A verifier of the
COMMIT enforcement that checks the seal over the ECDSA committed-seal hash refuses EVERY commit as
soon as the anchor is armed, because the emitter signs the anchor form (section 2.6). The shipped
client before 2026-09-02 had exactly this divergence between its emitter and its verifier; it was
found on the public testnet 28001 at the first enforcement height (the chain stopped at block
999), fixed by deriving both sides from one function, proven with unit and plumbing tests, a
rehearsal with the anchor armed, and the testnet itself, and the fleet was moved to the fixed
build on 2026-09-02 (image `d311-20260902`, version `26.4.0-aere.1-d203201-wt95fac2d7`). A
third-party client implementing COMMIT enforcement MUST verify over the anchor form whenever
`height + 1 >= aere.pq.anchorBlock` (source: `testnet-public/D-311-DOVADA-2026-09-02.md`,
`deploy/validators/d311-2026-09-02/SURSA.md`).
## 9. What this specification cannot state from the published sources
Honest list, for completion in a later revision. An implementer hitting one of these should treat
the gap as real rather than assume.
- **G1. Bootnode enodes.** Deliberately unpublished; obtainable by mail (section 6.3). A
third-party implementation cannot discover peers from this bundle alone.
- **G2. The upstream QBFT hashing and commit-seal plumbing.** `BftBlockHashing`,
`BftBlockHeaderFunctions`, `BlockHeaderValidator` and `HeaderValidationMode` are upstream files
not carried in the overlay; the hash pre-image statement in section 2.3 is supported by the codec
comments, by the encoding types, and by byte-exact reproduction of live block hashes on two real
headers, but the upstream call sites themselves must be read at commit `d2032017` to be cited
line by line. Similarly, which validation MODE each entry path (gossip NewBlock, full-sync
import, header download) applies has been proven on the import path only (source: the NEMASURAT
list in the repository document `consensus-pqc/CE-DEVINE-INVALID-LA-ANCORA-2026-08-07.md`).
- **G3. The exact byte values of the QBFT vote add/drop constants.** Defined in upstream `Vote`
(referenced by the codec); not restated here rather than quoted from memory.
- **G4. Per-block gas accounting beyond the precompiles.** The chain follows the Osaka gas
schedule of upstream Besu at `d2032017`; this bundle does not enumerate it, and neither does this
document.
- **G5. The precise definition of "universal since 13,889,296"** (D10), and the interface of the
on-chain registry anchor contract at `0x5214F0e53899Dad805ef3AdF0624F5e49d2a5EB5` (section 3.5):
the follower configuration names it, but its ABI, storage layout and exact role at
block 13,889,290 are not specified in any bundle file.
- **G6. The validator (seal-production) configuration.** `aere.falcon.key`,
`aere.falcon.attachBlock`, `aere.falcon.attach*`, `aere.falcon.validatorCount` drive seal
production only and are deliberately outside this follower specification; their live values on
the fleet are not published (source: `RUN-A-NODE.md` names them; nothing publishes values).
- **G7. The `0x100` P-256 (RIP-7951) input/output format.** Live and classical, upstream Besu;
not specified in this bundle.
- **G8. The EIP-4788 no-code observation** (section 6.5) rests on a dated repository finding
(D-153, 2026-08-07), not on a bundle file; a bundle-resident statement (or a fixture proving the
no-op) does not exist yet.
- **G9. Whether a certificate reused by an adversary for its one permitted fork block passes
eligibility in every corner case** was argued from code and never executed as a run (source:
`CE-DEVINE-INVALID-LA-ANCORA-2026-08-07.md`, NEMASURAT item 8).
- **G11. When, and whether, the four message-layer enforcements of section 2.6 are armed on
2800.** At publication they are not; the heights are a founder decision. A reader must measure
the fleet's `aere.pq.*Pq.forkBlock` properties (or observe refusals in node logs) rather than
assume either state.
- **G10. Anything about the chain after 2026-09-02.** Every measured statement carries its date;
the live schedule, set size, and head move. The registries directory and the follower
configuration are the values current at publication.
---
*End of specification. Corrections to this document are made by editing it in the repository and
republishing the bundle; the underlying files it cites are never edited to match the document.*