323 lines
21 KiB
Markdown
323 lines
21 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; every 128th from block 17,225,968 to block 20,746,736, see below) must carry, under its block hash, a certificate of valid post-quantum
|
|
validator seals, and a validator does not finalize an anchor block without it (a node started from this package with
|
|
`run-node.sh` lowers that count to one seal, at the cost stated in `SPEC.md` section 6.4; corrected 2026-10-27: this said
|
|
"a node"): at least three Falcon-512
|
|
seals at first (f+1 of nine), six of nine, as many as the QBFT quorum of nine (corrected 2026-10-27: this said the 2f+1 quorum, which at nine validators is five), from block 14,961,456 (August 21,
|
|
2026), and **seven of ten per scheme, as many as the 2f+1 quorum of the ten validators, since block 20,715,632** (2026-09-29, AIP-22 stage 1; below block 24,148,752 the seals proved those validators signed the block, not that it was decided, per the AIP-22 erratum of 2026-10-01, and from that block a certificate that carries the quorum of one round proves the decision); since block 17,047,600 (2026-09-04) the certificate is
|
|
**hybrid**, Falcon-512 AND SLH-DSA-SHA2-128s seals. From block 17,225,968 (schedule set on all nine
|
|
validators on 2026-09-05) the anchor fell on every 128th block instead of every 32nd, with both
|
|
schemes at every anchor; since block 20,746,736 (AIP-22 stage 2) Falcon-512 is required at every
|
|
32nd block again and SLH-DSA-SHA2-128s at every 128th. Since block 20,836,228 (AIP-22 stage 3) the
|
|
header and `qbft_getValidatorsByBlockNumber` name the validators by post-quantum ids. QBFT finalizes every block, whose votes every validator attributes, since block 24,300,000 (AIP-22 stage 4), only through their verified post-quantum seals (corrected 2026-11-04: from block 24,300,000 until 2026-10-31 12:27Z one path of the second client took the author of a proposal that named a height below 24,300,000 from its ECDSA envelope, as the correction of 2026-11-01 to rule 3 of AIP-22 says) (since block 25,080,000, AIP-22 stage 5, headers no longer carry ECDSA commit seals and are admitted on the Falcon-512 seal of their proposer alone; a commit still carries the ECDSA commit seal of its validator as an additional check), and node identity and transport (RLPx) remain classical, as does the ECDSA envelope that every consensus message still carries on the wire, whose form both clients judge and which no longer names its author on the Besu-derived validators; the QBFT
|
|
messages themselves carry post-quantum seals of their authors on the live fleet (emission since
|
|
blocks 16,620,000 to 16,690,000), with refusal of unsealed messages armed for blocks 17,250,000 to
|
|
17,700,000 (set 2026-09-05; see `anchor/README.md`, the arming table). 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` |
|
|
| `patches/0004-aere-basefee-floor.patch` | the 1 Gwei base-fee floor live from block 10,141,734 | the tree after 0001 and 0003 |
|
|
| `patches/0005-aere-eip2935-futureeips.patch` | EIP-2935 in the futureEips milestone, Osaka block format kept | the tree after 0004 |
|
|
| `patches/0006-aere-pq-transactions.patch` | AIP-20, transaction type `0x50` authorized by ML-DSA | the tree after 0005 |
|
|
| `patches/0007-aere-pq-finality.patch` | AIP-21, `aere_getPqFinality` and the on-disk seal window | the tree after 0006 |
|
|
| `patches/0008-aere-pq-aip22.patch` | AIP-22 stages 1 to 3 (post-quantum validator ids from block 20,836,228) and the rest of the fleet distribution of 2026-09-29 | the tree after 0007 |
|
|
| `patches/0009-aere-pq-seal-judge-snap-cap.patch` | the fleet distribution of 2026-10-02: the heard-seal judge and the pending-commit buffer (both act from the AIP-22 stage 4 height, block 24,300,000 on chain 2800, in force since 2026-10-25; announced on 2026-10-22), the snap-request cap | the tree after 0008 |
|
|
| `patches/0010-aere-pq-round-bound-commit-seal.patch` | the fleet distribution of 2026-10-14: AIP-22 stage R, the round-bound post-quantum commit seal and the v3 anchor certificate, inert below the height `aere.pq.commitRoundBlock`, which is block 24,148,752 on chain 2800, in force there since 2026-10-24 (announced on 2026-10-22; not yet set at 2026-10-14), and 6,157,024 on testnet 28001 since 2026-10-14 | the tree after 0009 |
|
|
|
|
`run-node.sh` applies 0001 and 0003 to 0010, in that order; 0002 is for the testnet only. A node
|
|
built without 0008 follows the chain up to block 20,836,227 and refuses block 20,836,228. (Note added 2026-10-09: 0009 is not needed to follow
|
|
the chain today; it keeps the recipe equal to what the fleet runs, see `RUN-A-NODE.md`. Note added
|
|
2026-10-14: the same holds for 0010 while `aere.pq.commitRoundBlock` is unset on chain 2800, which
|
|
`run-node.sh` does not set; when the founder sets that height, a node without 0010 stops at the block
|
|
before it, see `RUN-A-NODE.md`, "Patch 0010". Note added 2026-10-22: that height is block 24,148,752 on
|
|
chain 2800, carried by `run-node.sh` since 2026-10-21; a node built without 0010 stops at block
|
|
24,148,751, derived from the code.) (Note added 2026-10-26: block 24,148,752 was reached on 2026-10-24 and block 24,300,000, the AIP-22 stage 4 height, on 2026-10-25, so both stages are in force on chain 2800, and a node that follows it today is built from 0001 and 0003 to 0010, which is what `run-node.sh` builds.)
|
|
|
|
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.
|
|
|
|
## See also (2026-09-02)
|
|
|
|
- [`validator/`](validator/README.md) (added 2026-10-20): the operator kit for becoming a validator of chain 2800.
|
|
Every key is generated on the operator's machine (`validator-keys.sh`); `validator-candidacy.sh` writes the public
|
|
candidacy file (registry row, enode, proof of possession, signed clock report). Nothing private leaves the machine.
|
|
|
|
- [`DEVNET.md`](DEVNET.md): AERE on your machine in one `docker run`, the validators' own binary as a local chain.
|
|
- Public testnet 28001: `https://testnet-rpc.aere.network`, faucet on `https://aere.network/testnet.html`, an x402 v2
|
|
facilitator at `https://testnet-rpc.aere.network/x402/` settling a test dollar with EIP-3009 (probed every 6 hours,
|
|
result at `https://testnet-rpc.aere.network/sarcina/x402-proba.json`).
|
|
- [`SPEC.md`](SPEC.md) section 2.6: the four post-quantum consensus message layers (emitted on mainnet, enforced on
|
|
the testnet). (*Note added 2026-10-09:* written on 2026-09-02; since then the layers are enforced on chain 2800 as
|
|
well: every validator refuses a commit without a valid seal, and nine of the ten refuse any consensus message without
|
|
one; the tenth, which runs the second client, applies the rule to proposal, prepare and round-change messages from
|
|
AIP-22 stage 4, whose height is not yet set on chain 2800. On the testnet the second client's validator enforces
|
|
commits since block 1,314,403 and all four types since block 3,117,000 (SPEC section 2.6).) (*Added 2026-10-27:* stage 4 is in force on chain 2800 since block 24,300,000, reached on 2026-10-25; from it the second client judges all four message types by their seals.) (*Note added 2026-11-04 (to the addition of 2026-10-27 before it):* its clause on the second client had one exception until 2026-10-31 12:27Z: on one path of the second client (the Nethermind-derived one) the author of a proposal was taken at the height named in its payload, so for a payload height below 24,300,000 it was the signer of the ECDSA envelope; since 2026-10-31 12:27Z that client refuses at decoding a proposal whose block number differs from its payload height (AIP-22, correction of 2026-11-01 to rule 3 of section 4).)
|