The Besu fork with native post-quantum signature precompiles running on Aere Network mainnet (chain 2800).
Go to file
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
anchor Add the post-quantum certificate anchor for QBFT 2026-08-12 01:43:58 +03:00
patches Licence hygiene, authoritative patches, and one spelling of the brand 2026-08-11 23:47:17 +03:00
precompiles Licence hygiene, authoritative patches, and one spelling of the brand 2026-08-11 23:47:17 +03:00
scripts Initial public release 2026-07-20 10:25:45 +03:00
vectors Initial public release 2026-07-20 10:25:45 +03:00
LICENSE Replace the MIT LICENSE file with Apache 2.0, and add NOTICE 2026-08-11 23:35:21 +03:00
NOTICE Licence hygiene, authoritative patches, and one spelling of the brand 2026-08-11 23:47:17 +03:00
README.md Licence hygiene, authoritative patches, and one spelling of the brand 2026-08-11 23:47:17 +03:00

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. Consensus on chain 2800 is classical secp256k1 ECDSA QBFT.

Aere Network does not have post-quantum consensus. Block sealing, validator identity and the QBFT vote messages are all classical elliptic-curve cryptography today, and a cryptographically relevant quantum computer would break them exactly as it would break any other ECDSA chain. What is live and post-quantum 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, today.

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

Between them they 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. No build file changes are required. 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.