aere-node/README.md

280 lines
14 KiB
Markdown

# aere-node
Post-quantum signature verification precompiles for Hyperledger Besu, as they run on Aere Network
mainnet (chain 2800).
This repository is meant to be used, not read. It contains the precompile sources, a patch that
applies to a named upstream Besu commit, the NIST known-answer vectors, and a script that checks the
live chain without asking you to trust anything here.
---
## Scope boundary, stated first and not in a footnote
**These precompiles verify post-quantum signatures inside the EVM. They are not what makes
consensus post-quantum, and nothing in the precompile patches touches consensus.**
Consensus on chain 2800 is **hybrid post-quantum in a precise and limited sense**, by a separate
mechanism: the QBFT header anchor in `anchor/` and `patches/0003`. Since 2026-08-14 every
32nd block (an anchor block) must carry, under its block hash, a certificate of at least three
valid Falcon-512 validator seals (f+1 of nine; eight or nine are carried in practice), and a node
does not finalize an anchor block without it. Classical secp256k1 ECDSA QBFT still finalizes every
block; validator identity and QBFT message transport are classical. Correction 2026-08-19: the
per-block Falcon quorum rule armed at block 14,050,000 (`aere.falcon.forkBlock`) is, in the shipped
code, retired in favour of the anchor rules from block 13,014,000 and changed no enforcement, so
the earlier statement that from that height every block required a 2f+1 Falcon quorum to finalize
is withdrawn. None of this has been audited by a third party; the scope is stated in
`anchor/README.md` and the proofs are stated as measured, not promised.
What this repository's precompiles add is the *verification* capability exposed to the EVM: a
contract or an account abstraction wallet on chain 2800 can verify a Falcon, ML-DSA or SLH-DSA
signature natively, at precompile cost, since block 9,189,161.
Two further boundaries in the same spirit:
- The on-chain zero-knowledge verifiers used elsewhere in the Aere stack are classical BN254 pairing
verifiers. They are Shor-breakable. They are not quantum-safe and are never described as such.
- `0x0AE6` and `0x0AE7` are **testnet-only**. They are not on mainnet. The verification script in
this repository asserts their absence from mainnet as a falsifiable check, so that a reader can
catch us if that ever stops being true.
---
## The precompiles
Activated on mainnet chain 2800 at block **9,189,161**.
| Address | Algorithm | Standard | Input framing | Output |
|---|---|---|---|---|
| `0x0AE1` | Falcon-512 verify | NIST Falcon round 3 | `pk \|\| sm` | 32-byte word, 1 accept / 0 reject |
| `0x0AE2` | Falcon-1024 verify | NIST Falcon round 3 | `pk \|\| sm` | 32-byte word, 1 accept / 0 reject |
| `0x0AE3` | ML-DSA-44 verify | FIPS 204 | `pk \|\| sm` | 32-byte word, 1 accept / 0 reject |
| `0x0AE4` | SLH-DSA-SHA2-128s verify | FIPS 205 | `pk \|\| sm` | 32-byte word, 1 accept / 0 reject |
| `0x0AE5` | SHAKE256 XOF | FIPS 202 | 32-byte big-endian `outLen` word, then data | `outLen` bytes |
`sm` is the NIST signed-message convention: `signature || message`.
Not part of this fork but relevant and also live: `0x100` P-256 verify (RIP-7951), which is
classical.
**Testnet only, not on mainnet:**
| Address | Algorithm | Standard | Status |
|---|---|---|---|
| `0x0AE6` | ML-KEM-768 deterministic encapsulation | FIPS 203 | Built, 25/25 NIST ACVP KAT on an isolated single-validator QBFT testnet |
| `0x0AE7` | Falcon HashToPoint (SHAKE256 rejection sampler) | NIST Falcon round 3 | Built, 12/12 KAT on the same isolated testnet |
Mainnet activation of either is a governance decision that has not been taken.
The Java sources for all seven are in `precompiles/`, and the same sources are what the patches in
`patches/` install.
---
## The patch
Upstream Hyperledger Besu is roughly a million lines. Our contribution is a few hundred. Publishing a
full fork tree would bury the contribution in upstream code and leave you unable to tell our lines
from Besu's without diffing it yourself. A patch against a named upstream commit inverts that: the
diff *is* the contribution, you fetch the other 99 percent from Hyperledger directly, and when
upstream moves we rebase rather than merge a divergent tree forever.
Both patches target upstream commit `d2032017bb3b8cb215a97303980a1e4a643f7180`, authored
2026-04-17. That is the same commit named in the artifact running on the validators,
`besu-evm-26.7-develop-d203201.jar`.
| Patch | Adds | Applies to |
|---|---|---|
| `patches/0001-aere-pqc-precompiles-mainnet.patch` | `0x0AE1` to `0x0AE5`, the five live on mainnet | pristine upstream `d2032017` |
| `patches/0002-aere-pqc-precompiles-testnet.patch` | `0x0AE6` and `0x0AE7`, testnet only | the tree after 0001 |
| `patches/0003-aere-pq-anchor.patch` | the post-quantum certificate anchor for QBFT | pristine upstream `d2032017` |
Patch 0003 is a different kind of thing from the first two and is described in `anchor/README.md`.
It changes twenty upstream files and adds fifty-five, and it changes one build file by one line.
Verified 2026-08-11 on a pristine `d2032017` checkout: `git apply --check` and `git apply` both
returned 0, the resulting files are byte-identical to the sources in `anchor/`, and
`:consensus:common:test` plus `:consensus:qbft:test` returned 605 tests with 0 failures, the same
count class by class as the same tree without it.
Patches 0001 and 0002 between them touch eight files: two upstream files modified (`Address.java` for the address
constants, `MainnetPrecompiledContracts.java` for the registry wiring) and six new precompile
classes plus one shared Falcon helper. **Those two require no build file changes.** Bouncy Castle
`bcprov-jdk18on` 1.83 is already on the `evm` module's compile classpath as an `api` dependency of
`crypto:algorithms`, and `jakarta.validation-api` is a global subproject dependency.
Patch 0001 also repoints `populateForFutureEIPs` from `populateForCancun` to `populateForOsaka`, so
the fork carries the full Osaka precompile set. That is a real behaviour change beyond the five
precompiles and is called out here rather than left to be discovered in the diff.
### Apply it yourself
```
git clone --filter=blob:none https://github.com/hyperledger/besu.git
cd besu
git checkout d2032017bb3b8cb215a97303980a1e4a643f7180
git apply --check /path/to/aere-node/patches/0001-aere-pqc-precompiles-mainnet.patch
git apply /path/to/aere-node/patches/0001-aere-pqc-precompiles-mainnet.patch
```
Add `0002` the same way if you want the testnet pair as well. Both are `git format-patch` output, so
`git am` works too and carries the commit message.
**Verified, 2026-07-20.** Both patches were applied to a freshly fetched, byte-clean checkout of
`d2032017` in a scratch directory. `git apply --check` and `git apply` each returned exit 0 for both
patches, and the resulting files are byte-identical to the sources in `precompiles/`.
> A note on how that verification earned its place. An earlier draft of patch 0001 also returned
> exit 0 from `git apply --check`, and it was still wrong: it registered a helper class,
> `AereFalconSupport`, that it never added, so it applied cleanly and then failed to compile. A patch
> that applies is not a patch that builds. That is why the next section exists.
---
## Build
```
./gradlew :evm:compileJava # the module this patch touches
./gradlew installDist # full node distribution, in build/install/besu/
```
Requires JDK 21. Commit `d2032017` sits on the development line after the 26.4.0 release, which is
the newest heading in that commit's `CHANGELOG.md`. The artifact running on the validators is named
`besu-evm-26.7-develop-d203201.jar`; treat the commit hash, not the version string, as the identifier
that matters.
Note that upstream has since moved from `hyperledger/besu` to `besu-eth/besu`. GitHub redirects the
old URL, so the clone command above still resolves, and it was the exact command used for the
verification below.
**Verified, 2026-07-20.** `./gradlew --no-daemon :evm:compileJava` was run on the pristine
`d2032017` checkout with both patches applied, on JDK 21.0.11. It exited 0. Ten class files were
emitted for the seven precompiles and the Falcon helper, including the nested `InternalVerifier`
classes of `MLDSA44PrecompiledContract` and `SLHDSA128sPrecompiledContract`. The only compiler notes
were pre-existing upstream deprecation warnings in `datatypes/.../Log.java`, unrelated to this patch.
`./gradlew installDist` produces the full node distribution but was **not run here**, so this
repository does not claim it. A reader who wants the whole node builds it themselves with the command
above; the module this patch actually touches is `:evm`, and that is what was compiled.
### One rough edge, stated rather than hidden
`./gradlew build` will stop at Besu's formatter gate. `:evm:spotlessJavaCheck` and
`:datatypes:spotlessJavaCheck` both exit 1 on our files. The violations are entirely cosmetic and
fall into three groups: Javadoc rewrapped at a different column, the seven `AERE_*` address constants
in `Address.java` exceeding the 100-column limit on one line, and Besu's license-header rule wanting
`Copyright contributors to Besu` where our files say `Copyright contributors to the Aere Network`.
Nothing there changes behaviour, and `:evm:compileJava` passes as reported above. We have not
reformatted, for two reasons. Running `spotlessApply` would rewrite our copyright attribution to
Besu's, which would be wrong on files we wrote. And the sources in this repository are byte-identical
to the sources that built the artifact running on the validators; reformatting them for a cleaner
`gradlew build` would trade that property away for cosmetics.
To build past it:
```
./gradlew installDist -x spotlessJavaCheck
```
Or run `./gradlew spotlessApply` first if you would rather have upstream formatting and do not mind
the header rewrite.
Registration happens in `MainnetPrecompiledContracts.populateForFutureEIPs`, which is why the
precompiles are gated by fork activation rather than present from genesis.
---
## Check the result against the live chain
You do not need our source, our binary or our word for this part. The five precompiles are
addressable on public mainnet RPC. Point NIST's own published test vectors at them and read what
comes back.
```
node scripts/verify-live-precompiles.mjs
```
Requires Node 18 or later. It sends only `eth_call`, `eth_chainId`, `eth_blockNumber` and
`eth_getCode`. It sends no transaction, signs nothing and holds no key. Override the endpoint with
`AERE_RPC=https://rpc2.aere.network` for a second, independently served view of the chain.
Thirteen checks run, and every signature check is paired with a negative control. A precompile that
simply returned "valid" for all input would pass the positive check and fail the tampered one.
**Output of the run staged with this README (MEASURED, 2026-07-20):**
```
RPC: https://rpc.aere.network
chainId: 0xaf0 (2800)
block: 0xa13c28 (10566696)
PASS 0x0AE5 SHAKE256(abc) outLen=32 vs FIPS 202
PASS 0x0AE5 SHAKE256(empty) outLen=32 vs FIPS 202
PASS 0x0AE5 SHAKE256(abc) outLen=64 (true XOF squeeze)
PASS 0x0AE1 Falcon-512 official NIST KAT vector 0 ACCEPTS
PASS 0x0AE1 Falcon-512 tampered signed-message REJECTS (negative control)
PASS 0x0AE2 Falcon-1024 official NIST KAT vector 0 ACCEPTS
PASS 0x0AE2 Falcon-1024 tampered signed-message REJECTS (negative control)
PASS 0x0AE3 ML-DSA-44 ACVP tc108 expectedPass=true
PASS 0x0AE3 ML-DSA-44 ACVP tc106 expectedPass=false
PASS 0x0AE4 SLH-DSA-128s ACVP tc422 expectedPass=true
PASS 0x0AE4 SLH-DSA-128s ACVP tc421 expectedPass=false
PASS 0x0AE6 ML-KEM-768 is NOT live on mainnet 2800 (docs say testnet-only)
PASS 0x0AE7 Falcon HashToPoint is NOT live on mainnet 2800 (docs say testnet-only)
13/13 checks passed
```
The script prints expected and actual bytes for every check, which the summary above elides. The
vectors it uses are in `vectors/`: official NIST Falcon KAT vector 0 for both parameter sets, and
NIST ACVP fixtures for ML-DSA-44 and SLH-DSA-SHA2-128s.
Chaining the three sections gives the property this repository is for. The patch applies to an
upstream commit you fetch from Hyperledger. The result compiles. The compiled precompiles answer the
NIST vectors the same way the addresses on chain 2800 answer them.
---
## Correction, 2026-07-20
An earlier version of this README stated that the Java sources for the five precompiles live on
mainnet were unrecoverable, and it built its whole structure around that gap. **That was wrong**, and
the correction is kept here rather than quietly edited away.
All five exist, complete, on the infrastructure host, alongside five full Besu source trees and a
backup tarball. The earlier search looked in `/root` and at shallow depths under `/opt`, and never
opened the directory named after the thing it was looking for.
They are the real sources, not a lookalike. Three independent checks support that: they declare
exactly the live addresses `0x0AE1` through `0x0AE5`; the tree sits on upstream Besu commit
`d2032017`, the same commit named in the shipped artifact `besu-evm-26.7-develop-d203201.jar`; and
the source timestamps precede the jar build, as they must.
The lesson is worth more than the scare. A negative result about your own infrastructure is only as
good as the paths that were searched, and here "I did not find it" was reported as "it does not
exist". Every search of that kind on this project now has to state where it looked.
---
## What is deliberately not here
No validator keys, no node keys, no enode URLs, no `static-nodes.json`, no operational configuration,
no server addresses. Aere Network publishes its code and not its keys, which is the same line Linux
draws. A secret scanner gates every file in this directory before it is pushed anywhere.
Genesis and chain configuration for joining chain 2800 as a full node are a separate concern from
this repository and are documented with the network's node operator material.
---
## License
Apache 2.0, matching upstream Hyperledger Besu. See `LICENSE` and `NOTICE`.
Until 2026-08-11 the `LICENSE` file in this repository carried the MIT licence, while every source
file header, and this section, said Apache 2.0. The file was wrong and has been replaced with the
Apache 2.0 text as published by the Apache Software Foundation. Nothing about the terms these files
were offered under changed: the headers governed then and govern now. It is recorded here rather
than corrected silently, because a licence is the one thing in a repository that a reader has to be
able to trust without checking.