aere-node/anchor/README.md
Liviu 56a02656aa Add the post-quantum certificate anchor for QBFT
This is the code that puts a post-quantum validator certificate under the block
hash. It is the thing this project exists to do, and it is published so that the
claim can be checked rather than believed.

What it is. In QBFT the block hash is computed over a re-encoding of the decoded
extraData with the seals removed, so anything the decoder does not know about is
dropped before hashing. Appending a certificate as a new element gives you a
certificate that is stored, gossiped, and entirely absent from the hash. The
design that works instead puts a 32-byte digest of the certificate into
vanityData, which is already under keccak. anchor/README.md sets out the four
designs that died before this one and why.

Scope, stated in the README and repeated here because it matters: consensus on
chain 2800 is classical secp256k1 ECDSA. This binds a post-quantum certificate to
the block hash. It does not make consensus post-quantum and is never described as
such.

What is here: the anchor, the validation rules, the wiring, and the tests,
including the negative controls. Applied to upstream d2032017bb, the pinned base
named in anchor/BASE.txt. One build file changes, by one line, and the README says
which and why. No cryptography is implemented here; Falcon verification calls
Bouncy Castle.

What is not here: no keys, no fleet configuration, and nothing about what is armed
on any running network.

Measured before publishing, on upstream d2032017bb with this overlay applied:
consensus:common and consensus:qbft, 605 tests, 0 failures, identical to the same
tree before this work, class by class.

Three things were found while preparing it, and all three are fixed here:
- the code spoke Romanian in 134 comment lines and 43 strings, 37 of them on
  production paths, which is to say in the messages a node prints when it refuses
  to start. An auditor given the code to check the guards could not read the
  guards.
- ten test classes carried internal issue numbers in their names. They now say
  what they test.
- the suite was green partly by ordering luck. One class cleared its system
  properties but not the configuration PqAnchorProducer remembers, so it left the
  anchor armed for whichever class ran next. Renaming the classes changed the
  order and four tests began failing on a guard that was firing correctly. Fixed
  where it leaks, with the negative control measured: remove the line and the
  pair goes red, restore it and it goes green.
2026-08-12 01:43:58 +03:00

143 lines
8.0 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.** Block proposal, validator
identity and the QBFT vote messages are all classical elliptic-curve cryptography. A cryptographically
relevant quantum computer would break them exactly as it would break any other ECDSA chain.
What this code adds is narrower and is the whole point: **the block hash commits to a Falcon
certificate signed by the validators.** That is a binding, not a replacement. It does not make
consensus post-quantum and is never described as such.
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 reasoning follows Azouvi, Danezis,
Nikolaenko, "Winkle" (IACR 2019/1440, AFT 2020).
---
## 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
- `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.
No cryptography is implemented in this overlay. Falcon signature verification calls Bouncy Castle's
implementation; what is ours is the framing, the registry that maps a validator to a key, the digest,
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.
## 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 here is signed by f+1 validators, not by a quorum.** With f Byzantine faults
tolerated, f+1 signatures guarantee that at least one honest validator signed. That is a real
property and it is not the same as a quorum, and we do not call it one. Anyone counting will notice,
and they should.
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.