aere-node/anchor/README.md

314 lines
22 KiB
Markdown

# Post-quantum certificate anchor for QBFT
This is the code that puts a post-quantum validator certificate under the block hash in Hyperledger
Besu's QBFT consensus, as an overlay on a named upstream commit.
It is published so that the claim can be checked rather than believed. Everything below that is not
demonstrable from these files is marked as not demonstrable from these files.
---
## Scope boundary, stated first and not in a footnote
**Consensus on Aere Network chain 2800 is classical secp256k1 ECDSA QBFT with a post-quantum
checkpoint enforced every 32 blocks.** This code is what adds the checkpoint, in two steps that
should not be confused:
1. **The binding.** From block 13,014,000 the block hash of every anchored header (every 32nd
block) commits to a Falcon-512 certificate signed by the validators. That is a binding, not a
replacement: a rewrite of anchored history must also forge the post-quantum signatures.
2. **The enforced minimum.** Since 2026-08-14 a node does not finalize an anchor block unless
its certificate holds at least three valid Falcon-512 seals (f+1 of nine, the guarantee that at
least one honest validator signed; eight or nine are carried in practice). Raised at block
14,961,456 (August 21, 2026): the enforced minimum is now **six of nine, a full 2f+1 quorum**,
the same count of validators QBFT itself requires to agree on the block. The schedule step is
`aere.pq.anchorMinSeals=13014000:0,13034000:3,14961456:6`, uniform on all nine validators; the
startup guard refuses any threshold above N - f = 7, the highest count reachable with the fault
budget down. Blocks between anchors carry no Falcon seals and are finalized by ECDSA alone, so
the post-quantum guarantee is a checkpoint about every 16 seconds, not a per-block quorum.
3. **The hybrid certificate.** Since block 17,047,600 (2026-09-04) the certificate is the v2, scheme-tagged
form `RLP[2, [[scheme, index, signature], ...]]` and carries Falcon-512 (lattice) AND SLH-DSA-SHA2-128s
(hash-based) seals; the enforced minimum applies to EACH scheme on its own, at least six of nine valid
seals of each, and every SLH-DSA seal must sit at an index that also carries a valid Falcon seal. Properties:
`aere.pq.anchorV2Block=17047600`, `aere.pq.hybrid.attachBlock=17047568`,
`aere.pq.schemeSchedule=17047568:falcon-512+slh-dsa-sha2-128s`, `aere.pq.hybridRegistry` (the epoch-2 key
manifest, also published under `/pq/manifests/`). The fleet schedule carries one dated step,
`17102384:0,17102416:6`: on 2026-09-04 a run of validator restarts emptied every node's seal store, the
chain stalled at anchor 17,102,384 for eleven minutes, and it was released by lowering the minimum to
zero at exactly that height and restoring six at the next anchor. The step stays in the schedule because
every node must agree on that history.
4. **The anchor interval.** `aere.pq.anchorIntervalSchedule=17225968:128`, set on all nine validators and
on every reading node on 2026-09-05: from block 17,225,968 an anchor falls on every 128th block instead
of every 32nd. Each hybrid certificate adds about 53 KB to its header; quartering the frequency divides
that cost by four without touching the rule, the thresholds or the schemes. Until that height the
interval is 32, and the checkpoint above becomes one about every 64 seconds after it.
Correction 2026-08-19: the fleet was also configured on 2026-08-15 with `aere.falcon.forkBlock`
at 14,050,000, intended as a per-block blocking Falcon quorum. In the shipped code that rule
(`FalconSealValidationRule`) is retired at `aere.pq.anchorBlock` (13,014,000) in favour of the
anchor rules (`PqAnchorConfig.legacyFalconRuleRetirementBlock`), so at 14,050,000 it was already
inert, and the chain itself confirms it: blocks between anchors carry no Falcon seals, which a
per-block quorum rule would have rejected. The earlier statement in this repository that from
14,050,000 every block required a 2f+1 Falcon quorum to finalize is withdrawn.
"Hybrid" is the honest word in two senses and no more: the checkpoint sense above and, since
2026-09-04, the two-scheme certificate. "Post-quantum consensus" without qualification still
overstates: per-block finality and validator identity are classical elliptic-curve cryptography. The
four QBFT message types carry a post-quantum seal of their author on chain 2800 since blocks
16,620,000 (PROPOSAL), 16,660,000 (ROUND-CHANGE) and 16,690,000 (PREPARE), COMMIT seals since the
anchor itself; and a message without a valid seal is REFUSED from blocks 17,250,000 (COMMIT),
17,400,000 (PROPOSAL), 17,550,000 (ROUND-CHANGE) and 17,700,000 (PREPARE). Those four enforcement
heights were set on all nine validators on 2026-09-05 and take effect as the chain reaches them;
until each height passes, that message type is still accepted without a seal. What a quantum
adversary cannot do at any height is rewrite history past the last anchor without also forging
Falcon-512 and SLH-DSA-SHA2-128s signatures. None of this has been audited by a third party.
The threat it addresses is not "harvest now, decrypt later". A signature is public; there is nothing
to harvest. The threat is **retroactive rewriting**: validator keys recovered later can be used to
re-sign old blocks, and a chain whose history is authenticated only by ECDSA cannot distinguish the
rewrite from the original. Binding a post-quantum certificate into the hash of every anchored header
means a rewrite must also forge the post-quantum signatures.
The threat is not ours to claim. It is the long-range attack, and the peer-reviewed treatment is
Azouvi, Danezis and Nikolaenko, "Winkle: Foiling Long-Range Attacks in Proof-of-Stake Systems"
(IACR 2019/1440; AFT 2020, pp. 189-201). Two things should be said plainly about that citation,
because we checked it rather than repeated it. **Winkle does not mention quantum adversaries at
all**: it treats the case where a validator's old signing keys reach an adversary, by any
means. The quantum framing is
ours. And **Winkle's own defence is not ours**: it adds a secondary layer of client-based
validation, where clients sign a hash of the previously sequenced block. A second published defence
for the same threat, Azouvi and Vukolic, "Pikachu: Securing PoS Blockchains from
Long-Range Attacks by Checkpointing into Bitcoin PoW using Taproot" (arXiv 2208.05408, 2022),
checkpoints into Bitcoin's proof of work.
### Where this sits among the known defences
The literature calls this attack **posterior corruption**, also known as the founders' attack, the
long-range attack, history revision, or costless simulation. Tas, Tse, Gai, Kannan, Maddah-Ali and
Yu survey the mitigations in "Bitcoin-Enhanced Proof-of-Stake Security: Possibilities and
Impossibilities" (arXiv 2207.08392) and count four: social consensus by a trusted committee;
key-evolving signatures, where validators forget their old keys; verifiable delay functions; and
timestamping onto an existing proof-of-work chain.
That paper also proves an impossibility, and it deserves to be stated correctly rather than
paraphrased into something scarier or softer than it is. **Slashable safety**, meaning safety plus the
ability to punish the attacker, is not achievable by a proof-of-stake chain without an external
trusted source. Plain safety against posterior corruption is a weaker property, and key-evolving
signatures are shown to provide it under an honest majority. So the impossibility is not "you cannot
defend yourself"; it is "you cannot make the attacker pay, on your own."
**This anchor does not claim slashable safety and does not compete with checkpointing on that
ground.** What it addresses is the case where key-evolving signatures stop working. Their guarantee
rests on a validator's old key being *gone* once deleted. Against an adversary with a
cryptographically relevant quantum computer, an old secp256k1 key does not need to have been kept or
stolen. QBFT headers carry validator addresses rather than public keys, but every seal a validator
ever wrote is an ECDSA signature from which its public key can be recovered, and those seals are in
the chain forever. From the public key, a quantum adversary derives the private one. Deleting the
private key buys nothing, because the chain reconstructs it. The certificate under the block hash is what still has to be forged, and forging it requires
a secret the adversary cannot derive.
Put plainly: this is the post-quantum analogue of what key-evolving signatures give classically, for
the setting in which their central assumption no longer holds. It is narrower than checkpointing and
it borrows nothing from another chain, and both of those are true at the same time.
---
## The problem this solves, which is not obvious
In QBFT, `extraData` is an RLP list. The block hash is **not** computed over the stored bytes. It is
computed over a re-encoding of the decoded list, with the seal fields removed, so that every node
agrees on a hash before the seals exist.
That has a consequence that is easy to miss: **anything the decoder does not know about is dropped
before hashing.** Append a certificate as a new element and it survives in storage, travels between
nodes, and is entirely absent from the hash. Two nodes can hold different certificates for the same
block and both consider it valid. The certificate would be decoration.
Four obvious designs were tried and all four died on that:
| Attempt | Why it died |
|---|---|
| new RLP element after the seals | dropped by the re-encode, never reaches keccak |
| extend the seal list | changes the seal encoding, so every existing node rejects the header |
| a second `extraData`-like field | not in the header schema; a header with it is not a header |
| commit in the state root | the state root is computed before the certificate exists |
The design that works uses a field that is **already under keccak**: `vanityData`, element 0 of the
list, 32 bytes, historically arbitrary. At an anchor height it carries the digest of the certificate
instead of the usual vanity string. The certificate itself still rides outside the hash, but it is
now pinned: change one byte of it and the digest no longer matches, and the header is rejected.
`PqAnchor.java` and `PqAnchorDigestRule.java` are where that lives. Read those two first.
---
## What is in here
Updated 2026-08-29. The first publication of this directory, on 2026-08-11, was staged by hand and
was never refreshed: by 29 August, 63 of the 78 files here had changed in the tree we actually run
and 50 files had never been published at all. That is fixed at the root rather than by one copy —
`aerenew/publish-bundle/stage-anchor.cjs` in the operator repository derives this directory from the authoritative overlay, refuses
to run if the two files this README tells you to read first are missing, deletes what we removed
rather than letting the published set grow monotonically, and stops on the secret-scanner's verdict
instead of around it. The per-file SHA-256 list is in `MANIFEST-sha256.txt`.
**Three of the files in the overlay are not here, and it is the same three every time**: the
negative-control harnesses. They plant a defect on purpose to prove a guard can fail, so they are a
recipe for disabling a guard rather than a description of one. Everything they prove is stated in
"What is proven, and by what" below, and every guard they exercise is here.
### What is armed on chain 2800, and what is only present
This matters more than the file list, so it is stated before it.
| Layer | In this directory | Armed on chain 2800 |
|---|---|---|
| Anchor certificate under the block hash | yes | **yes**, since block 13,014,000 |
| Enforced minimum seals at an anchor height | yes | **yes**, 6 of 9 since 14,961,456 |
| Legacy per-block Falcon rule (`aere.falcon.forkBlock`) | yes | **no** — retired at the anchor block |
| Post-quantum seal on PREPARE, emission | yes | **yes**, since block 16,690,000 |
| Post-quantum seal on PREPARE, enforcement | yes | **armed for block 17,700,000** (set 2026-09-05) |
| Post-quantum seal on the PROPOSAL, emission | yes | **yes**, since block 16,620,000 |
| Post-quantum seal on the PROPOSAL, enforcement | yes | **armed for block 17,400,000** (set 2026-09-05) |
| Post-quantum seal on ROUND-CHANGE, emission / enforcement | yes | **yes**, since block 16,660,000 / **armed for block 17,550,000** |
| Post-quantum seal on COMMIT, emission / enforcement | yes | **yes**, since the anchor / **armed for block 17,250,000** |
| Hybrid Falcon + SLH-DSA certificate (v2) | yes | **yes**, since block 17,047,600 (2026-09-04) |
| Anchor interval 128 (`aere.pq.anchorIntervalSchedule`) | yes | **armed for block 17,225,968** (set 2026-09-05) |
Everything not yet in force is **disarmed by absence or by height, never by a flag**: the properties that arm them
are unset, and unset means never. Each refuses loudly on a value it cannot parse rather than booting
a node that believes itself armed — a node that disarms itself because of a mistyped character looks
exactly like a correctly configured one, right up to the day it matters. The tests for that
behaviour are in this directory and they are the ones to read if you doubt the claim.
The PREPARE layer is newer than the anchor and stronger where it applies: an armed node that refuses
unsealed PREPAREs never reaches the prepared state, so it never sends COMMIT at all. That also means
it has no safety net during an activation, which is why it was armed LAST and highest: its
enforcement height on chain 2800 is 17,700,000, set on 2026-09-05 after the COMMIT, PROPOSAL and
ROUND-CHANGE heights, so that the three other layers are in force before it.
The PROPOSAL layer (added 2026-08-30) closes the remaining hot-path gap, and its claim is stated
precisely because the imprecise version oversells: an adversary who breaks ECDSA cannot finalize
anything while the PREPARE layer is armed, since "prepared" needs a full quorum of PREPAREs. What
forged proposals could still do is OPEN rounds and waste them. With proposal enforcement armed, a
proposal without a valid Falcon seal from its own proposer does not open a round. The seal signs
its own domain over (chainId, height, round, digest), so an honest proposal seal cannot be replayed
as a vote nor a vote seal as a proposal - both directions are tested. The ROUND-CHANGE message
carries its own seal since 2026-08-31 (its embedded justifications are coupled to PREPARE
enforcement). What remains classical: node-level devp2p authentication and validator identity.
### The files
- `consensus/common/.../bft/` — the anchor itself: configuration, the digest, the seal cache and
store, the producer that attaches seals, the Falcon registry that maps a validator to a key.
- `consensus/qbft/.../headervalidationrules/` — the validation rules: the digest must match, the
seals must verify, and the rules must actually be wired into the validator chain.
- `app/.../controller/` — where the rules are built and where the node refuses to start on a
configuration that would produce headers its own fleet rejects.
- tests — including the negative controls. A test that cannot fail is not a test, and several of
these exist specifically to prove the guards can fail.
`BASE.txt` names the upstream commit. Applying these files to any other tree overwrites whatever
upstream added since, silently. That is stated there in more detail because it is a real hazard.
**One build file changes, and it is one line.** `consensus/common/build.gradle` gains
`implementation 'org.bouncycastle:bcprov-jdk18on'`. Falcon verification happens inside the consensus
module, so the module needs the library on its own compile path. No version is stated, because
upstream pins it: `platform/build.gradle` declares `bcprov-jdk18on:1.83`, and four upstream modules
already take the dependency in exactly this versionless form. So the line adds a compile-path entry
and no new artifact, and it does not move any version. That is the entire build change, and it is
called out here rather than left to be found in the diff.
Falcon signature verification calls Bouncy Castle's implementation. SLH-DSA-SHA2-128s signing and
verification use the `slhdsa/` package in `consensus/common`: Bouncy Castle 1.80's own SLH-DSA
implementation, copied into the tree with one change, the SHA-256 and SHA-512 digests come from the
JDK (`MessageDigest`, hardware SHA extensions) instead of Bouncy Castle's pure-Java digests, because
the pure-Java path made one anchor signature cost 1.4 to 2.7 seconds on the consensus thread
(measured 2026-09-04). Signatures are byte-identical to the original in deterministic mode, which
`SlhDsaFastEngineTest` pins with cross-verification in both directions. The package is regenerated
from the published Bouncy Castle sources by a script, not edited by hand, and carries the Bouncy
Castle licence next to it (`consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/LICENSE-BouncyCastle.txt`). What is ours is the framing, the
registry that maps a validator to its keys, the digest, the scheme schedule and the validation rules.
---
## What is proven, and by what
- **The certificate is byte-identical across nodes.** Measured on a test network, six nodes.
- **A stripped certificate is rejected.** 12 attempts, 12 rejections.
- **The guards compile and pass together**, and the negative control was run: with the guards
removed the proofs go red. A guard that has never failed cannot be trusted, so each was made to
fail on purpose.
- **Arming does not halt the chain across a validator-set change.** On a test network of ten
processes, 1,110 blocks were produced across the arming height while four validator-set votes were
driven through it, and the block rate did not change. The negative control for that run was
separate and blunt: three nodes restarted without their Falcon key produced zero blocks in ninety
seconds while every node was alive; with the keys restored, eighty-nine.
- **The wiring is tested.** An earlier version of this code registered a rule that could not be seen
from outside, because Besu's `BlockHeaderValidator.Builder` wraps detached rules in a lambda. The
rule was present and untested for that reason alone. `QbftAnchorRuleWiringTest` exists because of
that, and its negative control is measured: comment out the registration line and it goes red.
## What is not proven here
- **This overlay has not been audited by a third party.** No external security review of this code
exists. If you are reading it as an auditor, you are the first.
- **A rehearsal with a deliberately un-upgraded node has not been run.** Every rehearsal so far
upgraded every node.
- **Nothing here demonstrates what is configured on any live network.** These files show what the
code does when armed. They are not evidence about any running fleet, and should not be read as any.
- **The message-layer ENFORCEMENT has not yet refused anything on chain 2800.** Emission runs on the
live fleet; enforcement is armed by height (see the table) and, at the time of writing, none of the
four heights has passed. The refusal path has been exercised on the public testnet 28001, including
against a second, independent client implementation; test-network evidence is evidence about a test
network.
- **The hybrid certificate is live but young.** Hybrid certificates have been produced on chain 2800
since 2026-09-04 with real validator keys, and the public verifier reproduces them from the
internet. What is not shown here is any third-party review of the SLH-DSA engine copy or of the
scheme layer.
## One claim we retracted, on purpose
An earlier version of our public material said that no public chain has a block hash covering a
post-quantum validator certificate. That does not survive a hostile reading. Cellframe's ESBoCS
signs blocks with keys that resolve to Dilithium, Falcon or SPHINCS+, and hashes the block with the
signatures attached. The capability exists in their code today.
The defensible statement is narrower: no public chain has a post-quantum validator certificate under
the block hash **that is proven and independently verifiable**. This repository is our half of that
sentence. Someone else has to do the verifying, which is why it is here.
---
## Terminology, used precisely
**A certificate at an anchor height is signed by a quorum of six of nine validators, since block
14,961,456 (August 21, 2026). It was f+1 before that, and this paragraph said so.** The distinction
is worth keeping in view rather than deleting, because it is the difference between two genuinely
different claims. f+1 signatures guarantee only that at least one honest validator signed. A quorum
of 2f+1 is the same count QBFT itself requires to agree on the block, so from 14,961,456 the
post-quantum certificate at an anchor height is backed by as many validators as the block itself.
Two things follow, and both should be said rather than left to be discovered. The word "quorum"
applies **only at anchor heights** - about every 32nd block; the blocks between them carry no Falcon
seals at all and are finalized by ECDSA alone, so "a post-quantum quorum on every block" would be
false. And the higher threshold costs liveness margin: at K=6 of nine, three late validators are
enough to stall an anchor block, where K=3 tolerated six. That trade was made deliberately, and it
is the reason the threshold is a configured schedule rather than a constant.
Anyone counting will notice which of the two regimes a given height falls in, and they should. The
schedule is in the header of this document and on every node.
The seal threshold is a **floor, not a cap**: nodes attach as many verified seals as arrive in time,
which is at least the threshold and often more. A separate cap bounds how many are written, because
each seal costs bytes in every header forever.
---
## Licence
Apache 2.0, matching upstream Hyperledger Besu. See `../LICENSE` and `../NOTICE`. Files that modify
upstream carry the change notice required by section 4(b); files that are new are ours. The `slhdsa/`
package in `consensus/common` is derived from Bouncy Castle and stays under the Bouncy Castle licence
(MIT-style); its notice is kept next to the code in `LICENSE-BouncyCastle.txt`.