Initial public release

Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
This commit is contained in:
Aere Network 2026-07-20 10:25:45 +03:00
commit 48416dfe73
19 changed files with 2915 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 AERE Network
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

255
README.md Normal file
View File

@ -0,0 +1,255 @@
# 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`.

View File

@ -0,0 +1,607 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aere Network <node@aere.network>
Date: Mon, 20 Jul 2026 10:05:53 +0300
Subject: [PATCH] Aere Network: post-quantum signature verification precompiles
(mainnet 0x0ae1-0x0ae5)
Adds five native precompiled contracts exposing post-quantum signature
verification to the EVM, and registers them in populateForFutureEIPs so they
activate by fork timestamp rather than at genesis.
0x0ae1 Falcon-512 verify NIST Falcon round 3
0x0ae2 Falcon-1024 verify NIST Falcon round 3
0x0ae3 ML-DSA-44 verify FIPS 204
0x0ae4 SLH-DSA-SHA2-128s verify FIPS 205
0x0ae5 SHAKE256 XOF FIPS 202
Live on Aere Network chain 2800 from block 9,189,161.
Scope: these precompiles verify post-quantum signatures inside the EVM. They
do not change consensus. Block sealing and validator identity on chain 2800
remain classical secp256k1 ECDSA QBFT.
populateForFutureEIPs is additionally repointed from populateForCancun to
populateForOsaka, so the fork carries the full Osaka precompile set.
No build file change is required: bcprov-jdk18on is already exposed to the evm
module as an api dependency of crypto:algorithms, and jakarta.validation-api is
a global subproject dependency.
---
.../hyperledger/besu/datatypes/Address.java | 15 +++
.../evm/precompile/AereFalconSupport.java | 94 +++++++++++++++++++
.../Falcon1024PrecompiledContract.java | 62 ++++++++++++
.../Falcon512PrecompiledContract.java | 62 ++++++++++++
.../MLDSA44PrecompiledContract.java | 84 +++++++++++++++++
.../MainnetPrecompiledContracts.java | 11 ++-
.../SHAKE256PrecompiledContract.java | 93 ++++++++++++++++++
.../SLHDSA128sPrecompiledContract.java | 84 +++++++++++++++++
8 files changed, 504 insertions(+), 1 deletion(-)
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/AereFalconSupport.java
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon1024PrecompiledContract.java
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon512PrecompiledContract.java
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/MLDSA44PrecompiledContract.java
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/SHAKE256PrecompiledContract.java
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/SLHDSA128sPrecompiledContract.java
diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java
index 950cd59..0f486df 100644
--- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java
+++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java
@@ -91,6 +91,21 @@ public class Address extends BytesHolder {
/** Precompile address for P256_VERIFY. */
public static final Address P256_VERIFY = Address.precompiled(0x0100);
+ /** AERE PQC precompile: Falcon-512 verify. */
+ public static final Address AERE_FALCON512 = Address.fromHexString("0x0000000000000000000000000000000000000ae1");
+
+ /** AERE PQC precompile: Falcon-1024 verify. */
+ public static final Address AERE_FALCON1024 = Address.fromHexString("0x0000000000000000000000000000000000000ae2");
+
+ /** AERE PQC precompile: ML-DSA-44 (FIPS 204) verify. */
+ public static final Address AERE_MLDSA44 = Address.fromHexString("0x0000000000000000000000000000000000000ae3");
+
+ /** AERE PQC precompile: SLH-DSA-SHA2-128s (FIPS 205) verify. */
+ public static final Address AERE_SLHDSA128S = Address.fromHexString("0x0000000000000000000000000000000000000ae4");
+
+ /** AERE PQC precompile: SHAKE256 XOF (FIPS 202). */
+ public static final Address AERE_SHAKE256 = Address.fromHexString("0x0000000000000000000000000000000000000ae5");
+
/** The constant ZERO. */
public static final Address ZERO = Address.fromHexString("0x0");
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AereFalconSupport.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AereFalconSupport.java
new file mode 100644
index 0000000..1dbb2b8
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AereFalconSupport.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import java.util.Arrays;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
+import org.bouncycastle.pqc.crypto.falcon.FalconPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.falcon.FalconSigner;
+
+/**
+ * Shared logic for the AERE Falcon-512 / Falcon-1024 verification precompiles.
+ *
+ * <p>Transcodes the NIST round-3 reference "signed message" (sm) blob into the encoding Bouncy
+ * Castle's {@link FalconSigner#verifySignature(byte[], byte[])} expects and drives the audited
+ * verifier. No cryptography is reimplemented here.
+ *
+ * <p>NIST sm layout: {@code sigLen(2, big-endian) || nonce(40) || message || esig} where
+ * {@code esig = (0x20+logn) || compressedSig} and {@code sigLen == esig.length}. The Falcon public
+ * key is {@code (0x00+logn) || packed_h}; Bouncy Castle wants only {@code packed_h} (header
+ * stripped), and the signature it wants is {@code (0x30+logn) || nonce(40) || compressedSig}.
+ */
+final class AereFalconSupport {
+
+ static final int NONCE_LEN = 40;
+
+ private AereFalconSupport() {}
+
+ static Bytes resultWord(final boolean valid) {
+ return valid ? Bytes32.leftPad(Bytes.of((byte) 1)) : Bytes32.ZERO;
+ }
+
+ /**
+ * Verify a NIST signed-message blob against a Falcon public key.
+ *
+ * @param params Bouncy Castle Falcon parameter set (falcon_512 / falcon_1024)
+ * @param logn 9 for Falcon-512, 10 for Falcon-1024
+ * @param pkFull the 897- (512) or 1793-byte (1024) public key including the leading header byte
+ * @param sm the NIST signed-message blob
+ * @return true iff the signature is valid for the embedded message under pkFull
+ */
+ static boolean verify(
+ final FalconParameters params, final int logn, final byte[] pkFull, final byte[] sm) {
+ try {
+ if (pkFull.length < 2 || sm.length < 2 + NONCE_LEN + 2) {
+ return false;
+ }
+ if ((pkFull[0] & 0xff) != logn) {
+ return false;
+ }
+ final int sigLen = ((sm[0] & 0xff) << 8) | (sm[1] & 0xff);
+ if (sigLen < 2 || 2 + NONCE_LEN + sigLen > sm.length) {
+ return false;
+ }
+ final int msgLen = sm.length - 2 - NONCE_LEN - sigLen;
+ if (msgLen < 0) {
+ return false;
+ }
+ final byte[] esig = Arrays.copyOfRange(sm, sm.length - sigLen, sm.length);
+ if ((esig[0] & 0xff) != (0x20 + logn)) {
+ return false;
+ }
+ final byte[] H = Arrays.copyOfRange(pkFull, 1, pkFull.length);
+ final byte[] nonce = Arrays.copyOfRange(sm, 2, 2 + NONCE_LEN);
+ final byte[] message = Arrays.copyOfRange(sm, 2 + NONCE_LEN, 2 + NONCE_LEN + msgLen);
+
+ final byte[] bcSig = new byte[1 + NONCE_LEN + (esig.length - 1)];
+ bcSig[0] = (byte) (0x30 + logn);
+ System.arraycopy(nonce, 0, bcSig, 1, NONCE_LEN);
+ System.arraycopy(esig, 1, bcSig, 1 + NONCE_LEN, esig.length - 1);
+
+ final FalconPublicKeyParameters pub = new FalconPublicKeyParameters(params, H);
+ final FalconSigner signer = new FalconSigner();
+ signer.init(false, pub);
+ return signer.verifySignature(message, bcSig);
+ } catch (final Throwable t) {
+ return false;
+ }
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon1024PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon1024PrecompiledContract.java
new file mode 100644
index 0000000..80aca57
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon1024PrecompiledContract.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+import jakarta.validation.constraints.NotNull;
+import org.apache.tuweni.bytes.Bytes;
+import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
+
+/**
+ * AERE PQC precompile: Falcon-1024 signature verification (NIST round-3 reference encoding).
+ *
+ * <p>Input: {@code pk(1793) || sm(rest)} where pk is the reference public key (header 0x0A) and sm
+ * is the reference signed-message blob. Output: 32-byte word, {@code ...01} valid else {@code ...00}.
+ */
+public class Falcon1024PrecompiledContract extends AbstractPrecompiledContract {
+
+ static final int PK_LEN = 1793;
+ private static final int LOGN = 10;
+ private static final long GAS = 75_000L;
+
+ /**
+ * Instantiates a new Falcon-1024 precompiled contract.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ Falcon1024PrecompiledContract(final GasCalculator gasCalculator) {
+ super("AereFalcon1024", gasCalculator);
+ }
+
+ @Override
+ public long gasRequirement(final Bytes input) {
+ return GAS;
+ }
+
+ @NotNull
+ @Override
+ public PrecompileContractResult computePrecompile(
+ final Bytes input, @NotNull final MessageFrame messageFrame) {
+ boolean valid = false;
+ if (input.size() > PK_LEN) {
+ final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
+ final byte[] sm = input.slice(PK_LEN).toArrayUnsafe();
+ valid = AereFalconSupport.verify(FalconParameters.falcon_1024, LOGN, pk, sm);
+ }
+ return PrecompileContractResult.success(AereFalconSupport.resultWord(valid));
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon512PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon512PrecompiledContract.java
new file mode 100644
index 0000000..5df074c
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/Falcon512PrecompiledContract.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+import jakarta.validation.constraints.NotNull;
+import org.apache.tuweni.bytes.Bytes;
+import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
+
+/**
+ * AERE PQC precompile: Falcon-512 signature verification (NIST round-3 reference encoding).
+ *
+ * <p>Input: {@code pk(897) || sm(rest)} where pk is the reference public key (header 0x09) and sm is
+ * the reference signed-message blob. Output: 32-byte word, {@code ...01} if valid else {@code ...00}.
+ */
+public class Falcon512PrecompiledContract extends AbstractPrecompiledContract {
+
+ static final int PK_LEN = 897;
+ private static final int LOGN = 9;
+ private static final long GAS = 40_000L;
+
+ /**
+ * Instantiates a new Falcon-512 precompiled contract.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ Falcon512PrecompiledContract(final GasCalculator gasCalculator) {
+ super("AereFalcon512", gasCalculator);
+ }
+
+ @Override
+ public long gasRequirement(final Bytes input) {
+ return GAS;
+ }
+
+ @NotNull
+ @Override
+ public PrecompileContractResult computePrecompile(
+ final Bytes input, @NotNull final MessageFrame messageFrame) {
+ boolean valid = false;
+ if (input.size() > PK_LEN) {
+ final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
+ final byte[] sm = input.slice(PK_LEN).toArrayUnsafe();
+ valid = AereFalconSupport.verify(FalconParameters.falcon_512, LOGN, pk, sm);
+ }
+ return PrecompileContractResult.success(AereFalconSupport.resultWord(valid));
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MLDSA44PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MLDSA44PrecompiledContract.java
new file mode 100644
index 0000000..f25be64
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MLDSA44PrecompiledContract.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+import jakarta.validation.constraints.NotNull;
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.bouncycastle.pqc.crypto.mldsa.MLDSAParameters;
+import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.mldsa.MLDSASigner;
+
+/**
+ * AERE PQC precompile: ML-DSA-44 (FIPS 204) signature verification via the INTERNAL interface
+ * (ML-DSA.Verify_internal, Algorithm 8 — no context/domain-separation prefix).
+ *
+ * <p>Input: {@code pk(1312) || sig(2420) || message(rest)}. Output: 32-byte word, {@code ...01}
+ * valid else {@code ...00}. The internal (rather than pure) verifier is reached by subclassing
+ * Bouncy Castle's {@link MLDSASigner} and calling its {@code protected internalVerifySignature}.
+ */
+public class MLDSA44PrecompiledContract extends AbstractPrecompiledContract {
+
+ static final int PK_LEN = 1312;
+ static final int SIG_LEN = 2420;
+ private static final long GAS = 55_000L;
+
+ /** Subclass exposing Bouncy Castle's protected internal (Verify_internal) verifier. */
+ private static final class InternalVerifier extends MLDSASigner {
+ boolean verifyInternal(final byte[] message, final byte[] signature) {
+ return internalVerifySignature(message, signature);
+ }
+ }
+
+ /**
+ * Instantiates a new ML-DSA-44 precompiled contract.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ MLDSA44PrecompiledContract(final GasCalculator gasCalculator) {
+ super("AereMLDSA44", gasCalculator);
+ }
+
+ @Override
+ public long gasRequirement(final Bytes input) {
+ return GAS;
+ }
+
+ @NotNull
+ @Override
+ public PrecompileContractResult computePrecompile(
+ final Bytes input, @NotNull final MessageFrame messageFrame) {
+ boolean valid = false;
+ if (input.size() >= PK_LEN + SIG_LEN) {
+ try {
+ final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
+ final byte[] sig = input.slice(PK_LEN, SIG_LEN).toArrayUnsafe();
+ final byte[] message = input.slice(PK_LEN + SIG_LEN).toArrayUnsafe();
+ final MLDSAPublicKeyParameters pub =
+ new MLDSAPublicKeyParameters(MLDSAParameters.ml_dsa_44, pk);
+ final InternalVerifier verifier = new InternalVerifier();
+ verifier.init(false, pub);
+ valid = verifier.verifyInternal(message, sig);
+ } catch (final Throwable t) {
+ valid = false;
+ }
+ }
+ return PrecompileContractResult.success(
+ valid ? Bytes32.leftPad(Bytes.of((byte) 1)) : Bytes32.ZERO);
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java
index 28d84ab..43d9fb7 100644
--- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java
@@ -226,6 +226,15 @@ public interface MainnetPrecompiledContracts {
*/
static void populateForFutureEIPs(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
- populateForCancun(registry, gasCalculator);
+ // AERE "AerePQC" fork: full Osaka precompile set plus native post-quantum verifiers.
+ // Activated on a running chain via genesis config "futureEipsTime"; no re-genesis needed.
+ populateForOsaka(registry, gasCalculator);
+
+ // Native post-quantum precompiles (audited Bouncy Castle BCPQC verifiers).
+ registry.put(Address.AERE_FALCON512, new Falcon512PrecompiledContract(gasCalculator));
+ registry.put(Address.AERE_FALCON1024, new Falcon1024PrecompiledContract(gasCalculator));
+ registry.put(Address.AERE_MLDSA44, new MLDSA44PrecompiledContract(gasCalculator));
+ registry.put(Address.AERE_SLHDSA128S, new SLHDSA128sPrecompiledContract(gasCalculator));
+ registry.put(Address.AERE_SHAKE256, new SHAKE256PrecompiledContract(gasCalculator));
}
}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/SHAKE256PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/SHAKE256PrecompiledContract.java
new file mode 100644
index 0000000..0c20e6a
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/SHAKE256PrecompiledContract.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+import jakarta.validation.constraints.NotNull;
+import org.apache.tuweni.bytes.Bytes;
+import org.bouncycastle.crypto.digests.SHAKEDigest;
+
+/**
+ * AERE PQC precompile: SHAKE256 extendable-output function (FIPS 202).
+ *
+ * <p>Input layout: outLen (32 bytes, big-endian, capped at MAX_OUTPUT) || data (arbitrary length).
+ * Output: exactly {@code outLen} bytes of SHAKE256(data).
+ *
+ * <p>SHAKE256 is the hashing bottleneck inside Falcon, ML-DSA and SLH-DSA; exposing it natively lets
+ * on-chain PQC flows offload the hot path to audited Bouncy Castle rather than hand-rolled Solidity.
+ */
+public class SHAKE256PrecompiledContract extends AbstractPrecompiledContract {
+
+ /** Upper bound on requested output length to keep gas/allocation bounded. */
+ static final int MAX_OUTPUT = 1 << 16; // 65536 bytes
+
+ private static final int BASE_GAS = 60;
+ private static final int GAS_PER_WORD = 12;
+
+ /**
+ * Instantiates a new SHAKE256 precompiled contract.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ SHAKE256PrecompiledContract(final GasCalculator gasCalculator) {
+ super("AereSHAKE256", gasCalculator);
+ }
+
+ private static int outputLength(final Bytes input) {
+ if (input.size() < 32) {
+ return 0;
+ }
+ // Big-endian 32-byte length; only the low 4 bytes are honoured, then capped.
+ long v = input.slice(28, 4).toLong() & 0xFFFFFFFFL;
+ // If any of the high 28 bytes are non-zero the value is enormous; cap regardless.
+ if (!input.slice(0, 28).isZero()) {
+ return MAX_OUTPUT;
+ }
+ if (v > MAX_OUTPUT) {
+ return MAX_OUTPUT;
+ }
+ return (int) v;
+ }
+
+ @Override
+ public long gasRequirement(final Bytes input) {
+ int outLen = outputLength(input);
+ int dataLen = input.size() < 32 ? 0 : input.size() - 32;
+ long words = ((long) dataLen + 31) / 32 + ((long) outLen + 31) / 32;
+ return BASE_GAS + GAS_PER_WORD * words;
+ }
+
+ @NotNull
+ @Override
+ public PrecompileContractResult computePrecompile(
+ final Bytes input, @NotNull final MessageFrame messageFrame) {
+ if (input.size() < 32) {
+ return PrecompileContractResult.success(Bytes.EMPTY);
+ }
+ final int outLen = outputLength(input);
+ final byte[] data = input.slice(32).toArrayUnsafe();
+ final SHAKEDigest digest = new SHAKEDigest(256);
+ if (data.length > 0) {
+ digest.update(data, 0, data.length);
+ }
+ final byte[] out = new byte[outLen];
+ if (outLen > 0) {
+ digest.doFinal(out, 0, outLen);
+ }
+ return PrecompileContractResult.success(Bytes.wrap(out));
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/SLHDSA128sPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/SLHDSA128sPrecompiledContract.java
new file mode 100644
index 0000000..2666602
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/SLHDSA128sPrecompiledContract.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+import jakarta.validation.constraints.NotNull;
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAParameters;
+import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.slhdsa.SLHDSASigner;
+
+/**
+ * AERE PQC precompile: SLH-DSA-SHA2-128s (SPHINCS+, FIPS 205) signature verification via the
+ * INTERNAL interface (slh_verify_internal, Algorithm 20 — message hashed directly, no prefix).
+ *
+ * <p>Input: {@code pk(32) || sig(7856) || message(rest)}. Output: 32-byte word, {@code ...01} valid
+ * else {@code ...00}. The internal verifier is reached by subclassing Bouncy Castle's
+ * {@link SLHDSASigner} and calling its {@code protected internalVerifySignature}.
+ */
+public class SLHDSA128sPrecompiledContract extends AbstractPrecompiledContract {
+
+ static final int PK_LEN = 32;
+ static final int SIG_LEN = 7856;
+ private static final long GAS = 350_000L;
+
+ /** Subclass exposing Bouncy Castle's protected internal (slh_verify_internal) verifier. */
+ private static final class InternalVerifier extends SLHDSASigner {
+ boolean verifyInternal(final byte[] message, final byte[] signature) {
+ return internalVerifySignature(message, signature);
+ }
+ }
+
+ /**
+ * Instantiates a new SLH-DSA-128s precompiled contract.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ SLHDSA128sPrecompiledContract(final GasCalculator gasCalculator) {
+ super("AereSLHDSA128s", gasCalculator);
+ }
+
+ @Override
+ public long gasRequirement(final Bytes input) {
+ return GAS;
+ }
+
+ @NotNull
+ @Override
+ public PrecompileContractResult computePrecompile(
+ final Bytes input, @NotNull final MessageFrame messageFrame) {
+ boolean valid = false;
+ if (input.size() >= PK_LEN + SIG_LEN) {
+ try {
+ final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
+ final byte[] sig = input.slice(PK_LEN, SIG_LEN).toArrayUnsafe();
+ final byte[] message = input.slice(PK_LEN + SIG_LEN).toArrayUnsafe();
+ final SLHDSAPublicKeyParameters pub =
+ new SLHDSAPublicKeyParameters(SLHDSAParameters.sha2_128s, pk);
+ final InternalVerifier verifier = new InternalVerifier();
+ verifier.init(false, pub);
+ valid = verifier.verifyInternal(message, sig);
+ } catch (final Throwable t) {
+ valid = false;
+ }
+ }
+ return PrecompileContractResult.success(
+ valid ? Bytes32.leftPad(Bytes.of((byte) 1)) : Bytes32.ZERO);
+ }
+}

View File

@ -0,0 +1,317 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aere Network <node@aere.network>
Date: Mon, 20 Jul 2026 10:06:11 +0300
Subject: [PATCH] Aere Network: ML-KEM-768 and Falcon HashToPoint precompiles
(testnet only, 0x0ae6-0x0ae7)
0x0ae6 ML-KEM-768 deterministic encapsulation FIPS 203
0x0ae7 Falcon HashToPoint (SHAKE256 sampler) NIST Falcon round 3
NOT ACTIVE ON MAINNET. These two are built and known-answer tested on an
isolated single-validator QBFT testnet only. Mainnet activation is a governance
decision that has not been taken. Applies on top of patch 0001.
---
.../hyperledger/besu/datatypes/Address.java | 6 +
.../HashToPointPrecompiledContract.java | 127 +++++++++++++++++
.../MLKEM768PrecompiledContract.java | 128 ++++++++++++++++++
.../MainnetPrecompiledContracts.java | 2 +
4 files changed, 263 insertions(+)
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/HashToPointPrecompiledContract.java
create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/precompile/MLKEM768PrecompiledContract.java
diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java
index 0f486df..0ddc8ce 100644
--- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java
+++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java
@@ -106,6 +106,12 @@ public class Address extends BytesHolder {
/** AERE PQC precompile: SHAKE256 XOF (FIPS 202). */
public static final Address AERE_SHAKE256 = Address.fromHexString("0x0000000000000000000000000000000000000ae5");
+ /** AERE PQC precompile: ML-KEM-768 (FIPS 203) deterministic encapsulation. */
+ public static final Address AERE_MLKEM768 = Address.fromHexString("0x0000000000000000000000000000000000000ae6");
+
+ /** AERE PQC precompile: Falcon HashToPoint (FIPS 206) SHAKE256 rejection sampler. */
+ public static final Address AERE_HASHTOPOINT = Address.fromHexString("0x0000000000000000000000000000000000000ae7");
+
/** The constant ZERO. */
public static final Address ZERO = Address.fromHexString("0x0");
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/HashToPointPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/HashToPointPrecompiledContract.java
new file mode 100644
index 0000000..49efff4
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/HashToPointPrecompiledContract.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+import jakarta.validation.constraints.NotNull;
+import org.apache.tuweni.bytes.Bytes;
+import org.bouncycastle.crypto.digests.SHAKEDigest;
+
+/**
+ * AERE PQC precompile: Falcon HashToPoint (FIPS 206 / NIST Falcon round-3) at 0x0AE7.
+ *
+ * <p>HashToPoint is the SHAKE256-driven map from a (nonce, message) pair to a challenge polynomial
+ * {@code c} in Z_q[x]/(x^n+1), q = 12289. It is the single most expensive step of an on-chain
+ * Falcon verification: a hand-rolled Solidity Falcon-512 verify spends the bulk of its ~10.5M gas
+ * inside the in-EVM Keccak-f[1600] permutations that drive this rejection sampler. Exposing it
+ * natively lets a Solidity Falcon verifier replace that whole loop with one ~500-gas staticcall,
+ * collapsing per-auth Falcon cost.
+ *
+ * <p>Input layout: {@code logn(1) || nonce(40) || message(rest)} where {@code logn} is 9
+ * (Falcon-512, n=512) or 10 (Falcon-1024, n=1024). Output: {@code n} coefficients, each a
+ * big-endian uint16 in [0, q), i.e. {@code 2*n} bytes. Malformed input (length &lt; 41, or logn not
+ * in {9,10}) returns EMPTY (0x).
+ *
+ * <p>Algorithm (matches the reference {@code hash_to_point_vartime} exactly): absorb
+ * {@code nonce || message} into a SHAKE256 sponge, then repeatedly squeeze two bytes, interpret
+ * them as a big-endian 16-bit value {@code w}, and keep {@code w mod q} whenever {@code w < 5q =
+ * 61445}, until n coefficients are collected. Uses the audited Bouncy Castle SHAKE256 XOF.
+ */
+public class HashToPointPrecompiledContract extends AbstractPrecompiledContract {
+
+ private static final int Q = 12289;
+ private static final int REJECT_BOUND = 5 * Q; // 61445
+ private static final int NONCE_LEN = 40;
+ private static final int MIN_INPUT = 1 + NONCE_LEN; // logn byte + 40-byte nonce
+
+ private static final int BASE_GAS = 60;
+ private static final int GAS_PER_WORD = 12;
+
+ /**
+ * Instantiates a new HashToPoint precompiled contract.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ HashToPointPrecompiledContract(final GasCalculator gasCalculator) {
+ super("AereHashToPoint", gasCalculator);
+ }
+
+ /** Ring degree n from the logn selector byte, or 0 if the selector is invalid. */
+ private static int degree(final Bytes input) {
+ if (input.size() < MIN_INPUT) {
+ return 0;
+ }
+ final int logn = input.get(0) & 0xff;
+ if (logn == 9) {
+ return 512;
+ }
+ if (logn == 10) {
+ return 1024;
+ }
+ return 0;
+ }
+
+ @Override
+ public long gasRequirement(final Bytes input) {
+ final int n = degree(input);
+ if (n == 0) {
+ // Malformed: charge only for the bytes actually presented for hashing.
+ final long words = ((long) input.size() + 31) / 32;
+ return BASE_GAS + GAS_PER_WORD * words;
+ }
+ // Absorbed bytes (everything after the logn selector) + expected squeeze. The sampler keeps a
+ // sample with probability 61445/65536, so it squeezes ~2*n / 0.9375 bytes on average; charge a
+ // conservative fixed 70/64 (~1.094x) expansion so gas is a pure function of the input.
+ final long absorbBytes = input.size() - 1L;
+ final long squeezeBytes = (2L * n * 70L) / 64L;
+ final long words = (absorbBytes + 31) / 32 + (squeezeBytes + 31) / 32;
+ return BASE_GAS + GAS_PER_WORD * words;
+ }
+
+ @NotNull
+ @Override
+ public PrecompileContractResult computePrecompile(
+ final Bytes input, @NotNull final MessageFrame messageFrame) {
+ final int n = degree(input);
+ if (n == 0) {
+ return PrecompileContractResult.success(Bytes.EMPTY);
+ }
+ try {
+ // Absorb nonce || message (everything after the 1-byte logn selector).
+ final byte[] absorbed = input.slice(1).toArrayUnsafe();
+ final SHAKEDigest shake = new SHAKEDigest(256);
+ shake.update(absorbed, 0, absorbed.length);
+
+ final byte[] out = new byte[2 * n];
+ final byte[] two = new byte[2];
+ int filled = 0;
+ while (filled < n) {
+ shake.doOutput(two, 0, 2); // incremental squeeze, keeps the sponge in squeezing phase
+ final int w = ((two[0] & 0xff) << 8) | (two[1] & 0xff);
+ if (w < REJECT_BOUND) {
+ final int coeff = w % Q;
+ out[2 * filled] = (byte) (coeff >>> 8);
+ out[2 * filled + 1] = (byte) (coeff & 0xff);
+ filled++;
+ }
+ }
+ return PrecompileContractResult.success(Bytes.wrap(out));
+ } catch (final Throwable t) {
+ return PrecompileContractResult.success(Bytes.EMPTY);
+ }
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MLKEM768PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MLKEM768PrecompiledContract.java
new file mode 100644
index 0000000..cf455de
--- /dev/null
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MLKEM768PrecompiledContract.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright contributors to the AERE Network.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.evm.precompile;
+
+import org.hyperledger.besu.crypto.SecureRandomProvider;
+import org.hyperledger.besu.evm.frame.MessageFrame;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+import java.security.SecureRandom;
+
+import jakarta.validation.constraints.NotNull;
+import org.apache.tuweni.bytes.Bytes;
+import org.bouncycastle.pqc.crypto.mlkem.MLKEMGenerator;
+import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
+import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
+
+/**
+ * AERE PQC precompile: ML-KEM-768 (FIPS 203) DETERMINISTIC encapsulation at 0x0AE6.
+ *
+ * <p>This is AERE's first post-quantum CONFIDENTIALITY primitive on-chain. Every other native PQC
+ * precompile (0x0AE1-0x0AE5) is a signature or hash and gives post-quantum AUTHENTICITY only. This
+ * precompile makes a Module-Lattice KEM key-agreement transcript verifiable on-chain: given an
+ * encapsulation key {@code ek} and the 32-byte encapsulation randomness {@code m} ("coins"), it
+ * recomputes the ciphertext {@code c} and shared secret {@code K} that ML-KEM.Encaps(ek, m)
+ * produces. A verifier compares the recomputed {@code (c, K)} against a claimed transcript; equality
+ * proves the KEM step was performed honestly with the stated coins. This serves UMBRA's PQXDH
+ * handshake settlement and the AERE PQC key-registry.
+ *
+ * <p>Input layout: {@code ek(1184) || m(32)} = 1216 bytes exactly.
+ * Output layout: {@code c(1088) || K(32)} = 1120 bytes, or EMPTY (0x) on any malformed input.
+ *
+ * <p>Determinism: FIPS-203 Encaps normally draws {@code m} from a CSPRNG, which cannot run inside a
+ * consensus-critical precompile. We take {@code m} from calldata and drive Bouncy Castle's
+ * ML-KEM.Encaps_internal (K-PKE.Encrypt with explicit coins), so every node computes the identical
+ * {@code (c, K)}. No cryptography is reimplemented here; the audited Bouncy Castle BCPQC ML-KEM
+ * implementation on the classpath does the work.
+ */
+public class MLKEM768PrecompiledContract extends AbstractPrecompiledContract {
+
+ /** ML-KEM-768 encapsulation-key (public key) length, FIPS 203. */
+ static final int EK_LEN = 1184;
+
+ /** Encapsulation randomness ("coins" m) length. */
+ static final int M_LEN = 32;
+
+ /** Expected total calldata length. */
+ static final int INPUT_LEN = EK_LEN + M_LEN; // 1216
+
+ /** ML-KEM-768 ciphertext length. */
+ static final int CT_LEN = 1088;
+
+ /** ML-KEM shared-secret length. */
+ static final int SS_LEN = 32;
+
+ /**
+ * Fixed gas. ML-KEM-768 encapsulation is dominated by one A*r matrix-vector product in the NTT
+ * domain (k=3) plus SHA3/SHAKE hashing; measured on the AERE Besu scratch fork it sits between
+ * ML-DSA-44 verify (55k) and Falcon-1024 verify (75k). Priced fixed like the other lattice
+ * precompiles.
+ */
+ private static final long GAS = 60_000L;
+
+ // The generator constructor requires a SecureRandom, but the DETERMINISTIC encapsulation path
+ // (internalGenerateEncapsulated with caller-supplied coins m) never draws from it: the output
+ // depends only on (ek, m). Uses Besu's approved provider rather than constructing one directly.
+ private static final SecureRandom RNG = SecureRandomProvider.publicSecureRandom();
+
+ /**
+ * Instantiates a new ML-KEM-768 precompiled contract.
+ *
+ * @param gasCalculator the gas calculator
+ */
+ MLKEM768PrecompiledContract(final GasCalculator gasCalculator) {
+ super("AereMLKEM768", gasCalculator);
+ }
+
+ @Override
+ public long gasRequirement(final Bytes input) {
+ return GAS;
+ }
+
+ @NotNull
+ @Override
+ public PrecompileContractResult computePrecompile(
+ final Bytes input, @NotNull final MessageFrame messageFrame) {
+ if (input.size() != INPUT_LEN) {
+ return PrecompileContractResult.success(Bytes.EMPTY);
+ }
+ try {
+ final byte[] ek = input.slice(0, EK_LEN).toArrayUnsafe();
+ final byte[] m = input.slice(EK_LEN, M_LEN).toArrayUnsafe();
+
+ final MLKEMPublicKeyParameters pub =
+ new MLKEMPublicKeyParameters(MLKEMParameters.ml_kem_768, ek);
+
+ // Deterministic Encaps: feed the caller-supplied coins m as the encapsulation randomness.
+ final MLKEMGenerator gen = new MLKEMGenerator(RNG);
+ final org.bouncycastle.crypto.SecretWithEncapsulation enc =
+ gen.internalGenerateEncapsulated(pub, m);
+
+ final byte[] ss = enc.getSecret();
+ final byte[] ct = enc.getEncapsulation();
+ if (ct.length != CT_LEN || ss.length != SS_LEN) {
+ return PrecompileContractResult.success(Bytes.EMPTY);
+ }
+
+ final byte[] out = new byte[CT_LEN + SS_LEN];
+ System.arraycopy(ct, 0, out, 0, CT_LEN);
+ System.arraycopy(ss, 0, out, CT_LEN, SS_LEN);
+ return PrecompileContractResult.success(Bytes.wrap(out));
+ } catch (final Throwable t) {
+ // Consensus rule for the non-signature PQC precompiles: malformed input -> EMPTY, never fault.
+ return PrecompileContractResult.success(Bytes.EMPTY);
+ }
+ }
+}
diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java
index 43d9fb7..38baa14 100644
--- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java
+++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java
@@ -236,5 +236,7 @@ public interface MainnetPrecompiledContracts {
registry.put(Address.AERE_MLDSA44, new MLDSA44PrecompiledContract(gasCalculator));
registry.put(Address.AERE_SLHDSA128S, new SLHDSA128sPrecompiledContract(gasCalculator));
registry.put(Address.AERE_SHAKE256, new SHAKE256PrecompiledContract(gasCalculator));
+ registry.put(Address.AERE_MLKEM768, new MLKEM768PrecompiledContract(gasCalculator));
+ registry.put(Address.AERE_HASHTOPOINT, new HashToPointPrecompiledContract(gasCalculator));
}
}

268
precompiles/Address.java Normal file
View File

@ -0,0 +1,268 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.datatypes;
import static com.google.common.base.Preconditions.checkArgument;
import static org.hyperledger.besu.crypto.Hash.keccak256;
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.util.concurrent.ExecutionException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
/** A 160-bits account address. */
public class Address extends BytesHolder {
/** The constant SIZE. */
public static final int SIZE = 20;
/** Specific addresses of the "precompiled" contracts. */
public static final Address ECREC = Address.precompiled(0x01);
/** The constant SHA256. */
public static final Address SHA256 = Address.precompiled(0x02);
/** The constant RIPEMD160. */
public static final Address RIPEMD160 = Address.precompiled(0x03);
/** The constant ID. */
public static final Address ID = Address.precompiled(0x04);
/** The constant MODEXP. */
public static final Address MODEXP = Address.precompiled(0x05);
/** The constant ALTBN128_ADD. */
public static final Address ALTBN128_ADD = Address.precompiled(0x06);
/** The constant ALTBN128_MUL. */
public static final Address ALTBN128_MUL = Address.precompiled(0x07);
/** The constant ALTBN128_PAIRING. */
public static final Address ALTBN128_PAIRING = Address.precompiled(0x08);
/** The constant BLAKE2B_F_COMPRESSION. */
public static final Address BLAKE2B_F_COMPRESSION = Address.precompiled(0x09);
/** The constant KZG_POINT_EVAL aka POINT_EVALUATION_PRECOMPILE_ADDRESS. */
public static final Address KZG_POINT_EVAL = Address.precompiled(0xA);
/** The constant BLS12_G1ADD. */
public static final Address BLS12_G1ADD = Address.precompiled(0xB);
/** The constant BLS12_G1MULTIEXP. */
public static final Address BLS12_G1MULTIEXP = Address.precompiled(0xC);
/** The constant BLS12_G2ADD. */
public static final Address BLS12_G2ADD = Address.precompiled(0xD);
/** The constant BLS12_G2MULTIEXP. */
public static final Address BLS12_G2MULTIEXP = Address.precompiled(0xE);
/** The constant BLS12_PAIRING. */
public static final Address BLS12_PAIRING = Address.precompiled(0xF);
/** The constant BLS12_MAP_FP_TO_G1. */
public static final Address BLS12_MAP_FP_TO_G1 = Address.precompiled(0x10);
/** The constant BLS12_MAP_FP2_TO_G2. */
public static final Address BLS12_MAP_FP2_TO_G2 = Address.precompiled(0x11);
/** Precompile address for P256_VERIFY. */
public static final Address P256_VERIFY = Address.precompiled(0x0100);
/** AERE PQC precompile: Falcon-512 verify. */
public static final Address AERE_FALCON512 = Address.fromHexString("0x0000000000000000000000000000000000000ae1");
/** AERE PQC precompile: Falcon-1024 verify. */
public static final Address AERE_FALCON1024 = Address.fromHexString("0x0000000000000000000000000000000000000ae2");
/** AERE PQC precompile: ML-DSA-44 (FIPS 204) verify. */
public static final Address AERE_MLDSA44 = Address.fromHexString("0x0000000000000000000000000000000000000ae3");
/** AERE PQC precompile: SLH-DSA-SHA2-128s (FIPS 205) verify. */
public static final Address AERE_SLHDSA128S = Address.fromHexString("0x0000000000000000000000000000000000000ae4");
/** AERE PQC precompile: SHAKE256 XOF (FIPS 202). */
public static final Address AERE_SHAKE256 = Address.fromHexString("0x0000000000000000000000000000000000000ae5");
/** The constant ZERO. */
public static final Address ZERO = Address.fromHexString("0x0");
static LoadingCache<Address, Hash> hashCache =
CacheBuilder.newBuilder()
.maximumSize(4000)
// .weakKeys() // unless we "intern" all addresses we cannot use weak or soft keys.
.build(
new CacheLoader<>() {
@Override
public Hash load(final Address key) {
return Hash.hash(key.getBytes());
}
});
/**
* Instantiates a new Address.
*
* @param bytes the bytes
*/
protected Address(final Bytes bytes) {
super(bytes);
}
/**
* Wrap address.
*
* @param value the value
* @return the address
*/
public static Address wrap(final Bytes value) {
checkArgument(
value.size() == SIZE,
"An account address must be %s bytes long, got %s",
SIZE,
value.size());
return new Address(value);
}
/**
* Creates an address from the given RLP-encoded input.
*
* @param input The input to read from
* @return the input's corresponding address
*/
public static Address readFrom(final RLPInput input) {
final Bytes bytes = input.readBytes();
if (bytes.size() != SIZE) {
throw new RLPException(
String.format("Address unexpected size of %s (needs %s)", bytes.size(), SIZE));
}
return Address.wrap(bytes);
}
/**
* Extracts an address from a ECDSARECOVER result hash.
*
* @param hash A hash that has been obtained through hashing the return of the <code>
* ECDSARECOVER </code> function from Appendix F (Signing Transactions) of the Ethereum
* Yellow Paper.
* @return The ethereum address from the provided hash.
*/
public static Address extract(final Bytes32 hash) {
return wrap(hash.slice(12, 20));
}
/**
* Extract address.
*
* @param publicKey the public key
* @return the address
*/
public static Address extract(final SECPPublicKey publicKey) {
return Address.extract(keccak256(publicKey.getEncodedBytes()));
}
/**
* Parse a hexadecimal string representing an account address.
*
* @param str A hexadecimal string (with or without the leading '0x') representing a valid account
* address.
* @return The parsed address: {@code null} if the provided string is {@code null}.
* @throws IllegalArgumentException if the string is either not hexadecimal, or not the valid
* representation of an address.
*/
@JsonCreator
public static Address fromHexString(final String str) {
if (str == null) return null;
return wrap(Bytes.fromHexStringLenient(str, SIZE));
}
/**
* Parse a hexadecimal string representing an account address.
*
* @param str A hexadecimal string representing a valid account address (strictly 20 bytes).
* @return The parsed address.
* @throws IllegalArgumentException if the provided string is {@code null}.
* @throws IllegalArgumentException if the string is either not hexadecimal, or not the valid
* representation of a 20 byte address.
*/
public static Address fromHexStringStrict(final String str) {
checkArgument(str != null);
final Bytes value = Bytes.fromHexString(str);
checkArgument(
value.size() == SIZE,
"An account address must be %s bytes long, got %s",
SIZE,
value.size());
return new Address(value);
}
/**
* Precompiled address.
*
* @param value the value
* @return the address
*/
public static Address precompiled(final int value) {
// Allow values up to 0x01FF (511) to encompass layer2 precompile address space
checkArgument(value < 0x01FF, "Precompiled value must be <= 0x01FF");
final byte[] address = new byte[SIZE];
address[SIZE - 2] = (byte) (value >>> 8); // High byte
address[SIZE - 1] = (byte) (value & 0xFF); // Low byte
return new Address(Bytes.wrap(address));
}
/**
* Address of the created contract.
*
* <p>This implement equation (86) in Section 7 of the Yellow Paper (rev. a91c29c).
*
* @param senderAddress the address of the transaction sender.
* @param nonce the nonce of this transaction.
* @return The generated address of the created contract.
*/
public static Address contractAddress(final Address senderAddress, final long nonce) {
return Address.extract(
keccak256(
RLP.encode(
out -> {
out.startList();
out.writeBytes(senderAddress.getBytes());
out.writeLongScalar(nonce);
out.endList();
})));
}
/**
* Returns the hash of the address. Backed by a cache for performance reasons.
*
* @return the hash of the address.
*/
public Hash addressHash() {
try {
return hashCache.get(this);
} catch (ExecutionException e) {
return Hash.hash(getBytes());
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import java.util.Arrays;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
import org.bouncycastle.pqc.crypto.falcon.FalconPublicKeyParameters;
import org.bouncycastle.pqc.crypto.falcon.FalconSigner;
/**
* Shared logic for the AERE Falcon-512 / Falcon-1024 verification precompiles.
*
* <p>Transcodes the NIST round-3 reference "signed message" (sm) blob into the encoding Bouncy
* Castle's {@link FalconSigner#verifySignature(byte[], byte[])} expects and drives the audited
* verifier. No cryptography is reimplemented here.
*
* <p>NIST sm layout: {@code sigLen(2, big-endian) || nonce(40) || message || esig} where
* {@code esig = (0x20+logn) || compressedSig} and {@code sigLen == esig.length}. The Falcon public
* key is {@code (0x00+logn) || packed_h}; Bouncy Castle wants only {@code packed_h} (header
* stripped), and the signature it wants is {@code (0x30+logn) || nonce(40) || compressedSig}.
*/
final class AereFalconSupport {
static final int NONCE_LEN = 40;
private AereFalconSupport() {}
static Bytes resultWord(final boolean valid) {
return valid ? Bytes32.leftPad(Bytes.of((byte) 1)) : Bytes32.ZERO;
}
/**
* Verify a NIST signed-message blob against a Falcon public key.
*
* @param params Bouncy Castle Falcon parameter set (falcon_512 / falcon_1024)
* @param logn 9 for Falcon-512, 10 for Falcon-1024
* @param pkFull the 897- (512) or 1793-byte (1024) public key including the leading header byte
* @param sm the NIST signed-message blob
* @return true iff the signature is valid for the embedded message under pkFull
*/
static boolean verify(
final FalconParameters params, final int logn, final byte[] pkFull, final byte[] sm) {
try {
if (pkFull.length < 2 || sm.length < 2 + NONCE_LEN + 2) {
return false;
}
if ((pkFull[0] & 0xff) != logn) {
return false;
}
final int sigLen = ((sm[0] & 0xff) << 8) | (sm[1] & 0xff);
if (sigLen < 2 || 2 + NONCE_LEN + sigLen > sm.length) {
return false;
}
final int msgLen = sm.length - 2 - NONCE_LEN - sigLen;
if (msgLen < 0) {
return false;
}
final byte[] esig = Arrays.copyOfRange(sm, sm.length - sigLen, sm.length);
if ((esig[0] & 0xff) != (0x20 + logn)) {
return false;
}
final byte[] H = Arrays.copyOfRange(pkFull, 1, pkFull.length);
final byte[] nonce = Arrays.copyOfRange(sm, 2, 2 + NONCE_LEN);
final byte[] message = Arrays.copyOfRange(sm, 2 + NONCE_LEN, 2 + NONCE_LEN + msgLen);
final byte[] bcSig = new byte[1 + NONCE_LEN + (esig.length - 1)];
bcSig[0] = (byte) (0x30 + logn);
System.arraycopy(nonce, 0, bcSig, 1, NONCE_LEN);
System.arraycopy(esig, 1, bcSig, 1 + NONCE_LEN, esig.length - 1);
final FalconPublicKeyParameters pub = new FalconPublicKeyParameters(params, H);
final FalconSigner signer = new FalconSigner();
signer.init(false, pub);
return signer.verifySignature(message, bcSig);
} catch (final Throwable t) {
return false;
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import jakarta.validation.constraints.NotNull;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
/**
* AERE PQC precompile: Falcon-1024 signature verification (NIST round-3 reference encoding).
*
* <p>Input: {@code pk(1793) || sm(rest)} where pk is the reference public key (header 0x0A) and sm
* is the reference signed-message blob. Output: 32-byte word, {@code ...01} valid else {@code ...00}.
*/
public class Falcon1024PrecompiledContract extends AbstractPrecompiledContract {
static final int PK_LEN = 1793;
private static final int LOGN = 10;
private static final long GAS = 75_000L;
/**
* Instantiates a new Falcon-1024 precompiled contract.
*
* @param gasCalculator the gas calculator
*/
Falcon1024PrecompiledContract(final GasCalculator gasCalculator) {
super("AereFalcon1024", gasCalculator);
}
@Override
public long gasRequirement(final Bytes input) {
return GAS;
}
@NotNull
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @NotNull final MessageFrame messageFrame) {
boolean valid = false;
if (input.size() > PK_LEN) {
final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
final byte[] sm = input.slice(PK_LEN).toArrayUnsafe();
valid = AereFalconSupport.verify(FalconParameters.falcon_1024, LOGN, pk, sm);
}
return PrecompileContractResult.success(AereFalconSupport.resultWord(valid));
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import jakarta.validation.constraints.NotNull;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
/**
* AERE PQC precompile: Falcon-512 signature verification (NIST round-3 reference encoding).
*
* <p>Input: {@code pk(897) || sm(rest)} where pk is the reference public key (header 0x09) and sm is
* the reference signed-message blob. Output: 32-byte word, {@code ...01} if valid else {@code ...00}.
*/
public class Falcon512PrecompiledContract extends AbstractPrecompiledContract {
static final int PK_LEN = 897;
private static final int LOGN = 9;
private static final long GAS = 40_000L;
/**
* Instantiates a new Falcon-512 precompiled contract.
*
* @param gasCalculator the gas calculator
*/
Falcon512PrecompiledContract(final GasCalculator gasCalculator) {
super("AereFalcon512", gasCalculator);
}
@Override
public long gasRequirement(final Bytes input) {
return GAS;
}
@NotNull
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @NotNull final MessageFrame messageFrame) {
boolean valid = false;
if (input.size() > PK_LEN) {
final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
final byte[] sm = input.slice(PK_LEN).toArrayUnsafe();
valid = AereFalconSupport.verify(FalconParameters.falcon_512, LOGN, pk, sm);
}
return PrecompileContractResult.success(AereFalconSupport.resultWord(valid));
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import jakarta.validation.constraints.NotNull;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.crypto.digests.SHAKEDigest;
/**
* AERE PQC precompile: Falcon HashToPoint (FIPS 206 / NIST Falcon round-3) at 0x0AE7.
*
* <p>HashToPoint is the SHAKE256-driven map from a (nonce, message) pair to a challenge polynomial
* {@code c} in Z_q[x]/(x^n+1), q = 12289. It is the single most expensive step of an on-chain
* Falcon verification: a hand-rolled Solidity Falcon-512 verify spends the bulk of its ~10.5M gas
* inside the in-EVM Keccak-f[1600] permutations that drive this rejection sampler. Exposing it
* natively lets a Solidity Falcon verifier replace that whole loop with one ~500-gas staticcall,
* collapsing per-auth Falcon cost.
*
* <p>Input layout: {@code logn(1) || nonce(40) || message(rest)} where {@code logn} is 9
* (Falcon-512, n=512) or 10 (Falcon-1024, n=1024). Output: {@code n} coefficients, each a
* big-endian uint16 in [0, q), i.e. {@code 2*n} bytes. Malformed input (length &lt; 41, or logn not
* in {9,10}) returns EMPTY (0x).
*
* <p>Algorithm (matches the reference {@code hash_to_point_vartime} exactly): absorb
* {@code nonce || message} into a SHAKE256 sponge, then repeatedly squeeze two bytes, interpret
* them as a big-endian 16-bit value {@code w}, and keep {@code w mod q} whenever {@code w < 5q =
* 61445}, until n coefficients are collected. Uses the audited Bouncy Castle SHAKE256 XOF.
*/
public class HashToPointPrecompiledContract extends AbstractPrecompiledContract {
private static final int Q = 12289;
private static final int REJECT_BOUND = 5 * Q; // 61445
private static final int NONCE_LEN = 40;
private static final int MIN_INPUT = 1 + NONCE_LEN; // logn byte + 40-byte nonce
private static final int BASE_GAS = 60;
private static final int GAS_PER_WORD = 12;
/**
* Instantiates a new HashToPoint precompiled contract.
*
* @param gasCalculator the gas calculator
*/
HashToPointPrecompiledContract(final GasCalculator gasCalculator) {
super("AereHashToPoint", gasCalculator);
}
/** Ring degree n from the logn selector byte, or 0 if the selector is invalid. */
private static int degree(final Bytes input) {
if (input.size() < MIN_INPUT) {
return 0;
}
final int logn = input.get(0) & 0xff;
if (logn == 9) {
return 512;
}
if (logn == 10) {
return 1024;
}
return 0;
}
@Override
public long gasRequirement(final Bytes input) {
final int n = degree(input);
if (n == 0) {
// Malformed: charge only for the bytes actually presented for hashing.
final long words = ((long) input.size() + 31) / 32;
return BASE_GAS + GAS_PER_WORD * words;
}
// Absorbed bytes (everything after the logn selector) + expected squeeze. The sampler keeps a
// sample with probability 61445/65536, so it squeezes ~2*n / 0.9375 bytes on average; charge a
// conservative fixed 70/64 (~1.094x) expansion so gas is a pure function of the input.
final long absorbBytes = input.size() - 1L;
final long squeezeBytes = (2L * n * 70L) / 64L;
final long words = (absorbBytes + 31) / 32 + (squeezeBytes + 31) / 32;
return BASE_GAS + GAS_PER_WORD * words;
}
@NotNull
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @NotNull final MessageFrame messageFrame) {
final int n = degree(input);
if (n == 0) {
return PrecompileContractResult.success(Bytes.EMPTY);
}
try {
// Absorb nonce || message (everything after the 1-byte logn selector).
final byte[] absorbed = input.slice(1).toArrayUnsafe();
final SHAKEDigest shake = new SHAKEDigest(256);
shake.update(absorbed, 0, absorbed.length);
final byte[] out = new byte[2 * n];
final byte[] two = new byte[2];
int filled = 0;
while (filled < n) {
shake.doOutput(two, 0, 2); // incremental squeeze, keeps the sponge in squeezing phase
final int w = ((two[0] & 0xff) << 8) | (two[1] & 0xff);
if (w < REJECT_BOUND) {
final int coeff = w % Q;
out[2 * filled] = (byte) (coeff >>> 8);
out[2 * filled + 1] = (byte) (coeff & 0xff);
filled++;
}
}
return PrecompileContractResult.success(Bytes.wrap(out));
} catch (final Throwable t) {
return PrecompileContractResult.success(Bytes.EMPTY);
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import jakarta.validation.constraints.NotNull;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.pqc.crypto.mldsa.MLDSAParameters;
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
import org.bouncycastle.pqc.crypto.mldsa.MLDSASigner;
/**
* AERE PQC precompile: ML-DSA-44 (FIPS 204) signature verification via the INTERNAL interface
* (ML-DSA.Verify_internal, Algorithm 8 no context/domain-separation prefix).
*
* <p>Input: {@code pk(1312) || sig(2420) || message(rest)}. Output: 32-byte word, {@code ...01}
* valid else {@code ...00}. The internal (rather than pure) verifier is reached by subclassing
* Bouncy Castle's {@link MLDSASigner} and calling its {@code protected internalVerifySignature}.
*/
public class MLDSA44PrecompiledContract extends AbstractPrecompiledContract {
static final int PK_LEN = 1312;
static final int SIG_LEN = 2420;
private static final long GAS = 55_000L;
/** Subclass exposing Bouncy Castle's protected internal (Verify_internal) verifier. */
private static final class InternalVerifier extends MLDSASigner {
boolean verifyInternal(final byte[] message, final byte[] signature) {
return internalVerifySignature(message, signature);
}
}
/**
* Instantiates a new ML-DSA-44 precompiled contract.
*
* @param gasCalculator the gas calculator
*/
MLDSA44PrecompiledContract(final GasCalculator gasCalculator) {
super("AereMLDSA44", gasCalculator);
}
@Override
public long gasRequirement(final Bytes input) {
return GAS;
}
@NotNull
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @NotNull final MessageFrame messageFrame) {
boolean valid = false;
if (input.size() >= PK_LEN + SIG_LEN) {
try {
final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
final byte[] sig = input.slice(PK_LEN, SIG_LEN).toArrayUnsafe();
final byte[] message = input.slice(PK_LEN + SIG_LEN).toArrayUnsafe();
final MLDSAPublicKeyParameters pub =
new MLDSAPublicKeyParameters(MLDSAParameters.ml_dsa_44, pk);
final InternalVerifier verifier = new InternalVerifier();
verifier.init(false, pub);
valid = verifier.verifyInternal(message, sig);
} catch (final Throwable t) {
valid = false;
}
}
return PrecompileContractResult.success(
valid ? Bytes32.leftPad(Bytes.of((byte) 1)) : Bytes32.ZERO);
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import org.hyperledger.besu.crypto.SecureRandomProvider;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import java.security.SecureRandom;
import jakarta.validation.constraints.NotNull;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMGenerator;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
/**
* AERE PQC precompile: ML-KEM-768 (FIPS 203) DETERMINISTIC encapsulation at 0x0AE6.
*
* <p>This is AERE's first post-quantum CONFIDENTIALITY primitive on-chain. Every other native PQC
* precompile (0x0AE1-0x0AE5) is a signature or hash and gives post-quantum AUTHENTICITY only. This
* precompile makes a Module-Lattice KEM key-agreement transcript verifiable on-chain: given an
* encapsulation key {@code ek} and the 32-byte encapsulation randomness {@code m} ("coins"), it
* recomputes the ciphertext {@code c} and shared secret {@code K} that ML-KEM.Encaps(ek, m)
* produces. A verifier compares the recomputed {@code (c, K)} against a claimed transcript; equality
* proves the KEM step was performed honestly with the stated coins. This serves UMBRA's PQXDH
* handshake settlement and the AERE PQC key-registry.
*
* <p>Input layout: {@code ek(1184) || m(32)} = 1216 bytes exactly.
* Output layout: {@code c(1088) || K(32)} = 1120 bytes, or EMPTY (0x) on any malformed input.
*
* <p>Determinism: FIPS-203 Encaps normally draws {@code m} from a CSPRNG, which cannot run inside a
* consensus-critical precompile. We take {@code m} from calldata and drive Bouncy Castle's
* ML-KEM.Encaps_internal (K-PKE.Encrypt with explicit coins), so every node computes the identical
* {@code (c, K)}. No cryptography is reimplemented here; the audited Bouncy Castle BCPQC ML-KEM
* implementation on the classpath does the work.
*/
public class MLKEM768PrecompiledContract extends AbstractPrecompiledContract {
/** ML-KEM-768 encapsulation-key (public key) length, FIPS 203. */
static final int EK_LEN = 1184;
/** Encapsulation randomness ("coins" m) length. */
static final int M_LEN = 32;
/** Expected total calldata length. */
static final int INPUT_LEN = EK_LEN + M_LEN; // 1216
/** ML-KEM-768 ciphertext length. */
static final int CT_LEN = 1088;
/** ML-KEM shared-secret length. */
static final int SS_LEN = 32;
/**
* Fixed gas. ML-KEM-768 encapsulation is dominated by one A*r matrix-vector product in the NTT
* domain (k=3) plus SHA3/SHAKE hashing; measured on the AERE Besu scratch fork it sits between
* ML-DSA-44 verify (55k) and Falcon-1024 verify (75k). Priced fixed like the other lattice
* precompiles.
*/
private static final long GAS = 60_000L;
// The generator constructor requires a SecureRandom, but the DETERMINISTIC encapsulation path
// (internalGenerateEncapsulated with caller-supplied coins m) never draws from it: the output
// depends only on (ek, m). Uses Besu's approved provider rather than constructing one directly.
private static final SecureRandom RNG = SecureRandomProvider.publicSecureRandom();
/**
* Instantiates a new ML-KEM-768 precompiled contract.
*
* @param gasCalculator the gas calculator
*/
MLKEM768PrecompiledContract(final GasCalculator gasCalculator) {
super("AereMLKEM768", gasCalculator);
}
@Override
public long gasRequirement(final Bytes input) {
return GAS;
}
@NotNull
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @NotNull final MessageFrame messageFrame) {
if (input.size() != INPUT_LEN) {
return PrecompileContractResult.success(Bytes.EMPTY);
}
try {
final byte[] ek = input.slice(0, EK_LEN).toArrayUnsafe();
final byte[] m = input.slice(EK_LEN, M_LEN).toArrayUnsafe();
final MLKEMPublicKeyParameters pub =
new MLKEMPublicKeyParameters(MLKEMParameters.ml_kem_768, ek);
// Deterministic Encaps: feed the caller-supplied coins m as the encapsulation randomness.
final MLKEMGenerator gen = new MLKEMGenerator(RNG);
final org.bouncycastle.crypto.SecretWithEncapsulation enc =
gen.internalGenerateEncapsulated(pub, m);
final byte[] ss = enc.getSecret();
final byte[] ct = enc.getEncapsulation();
if (ct.length != CT_LEN || ss.length != SS_LEN) {
return PrecompileContractResult.success(Bytes.EMPTY);
}
final byte[] out = new byte[CT_LEN + SS_LEN];
System.arraycopy(ct, 0, out, 0, CT_LEN);
System.arraycopy(ss, 0, out, CT_LEN, SS_LEN);
return PrecompileContractResult.success(Bytes.wrap(out));
} catch (final Throwable t) {
// Consensus rule for the non-signature PQC precompiles: malformed input -> EMPTY, never fault.
return PrecompileContractResult.success(Bytes.EMPTY);
}
}
}

View File

@ -0,0 +1,240 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import static org.hyperledger.besu.datatypes.Address.P256_VERIFY;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
/** Provides the various precompiled contracts used on mainnet hard forks. */
public interface MainnetPrecompiledContracts {
/**
* Frontier precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry frontier(final GasCalculator gasCalculator) {
PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry();
populateForFrontier(precompileContractRegistry, gasCalculator);
return precompileContractRegistry;
}
/**
* Populate registry for frontier.
*
* @param registry the registry
* @param gasCalculator the gas calculator
*/
static void populateForFrontier(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
registry.put(Address.ECREC, new ECRECPrecompiledContract(gasCalculator));
registry.put(Address.SHA256, new SHA256PrecompiledContract(gasCalculator));
registry.put(Address.RIPEMD160, new RIPEMD160PrecompiledContract(gasCalculator));
registry.put(Address.ID, new IDPrecompiledContract(gasCalculator));
}
/**
* Homestead precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry homestead(final GasCalculator gasCalculator) {
return frontier(gasCalculator);
}
/**
* Byzantium precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry byzantium(final GasCalculator gasCalculator) {
PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry();
populateForByzantium(precompileContractRegistry, gasCalculator);
return precompileContractRegistry;
}
/**
* Populate registry for byzantium.
*
* @param registry the registry
* @param gasCalculator the gas calculator
*/
static void populateForByzantium(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
populateForFrontier(registry, gasCalculator);
registry.put(
Address.MODEXP,
new BigIntegerModularExponentiationPrecompiledContract(gasCalculator, Long.MAX_VALUE));
registry.put(Address.ALTBN128_ADD, new AltBN128AddPrecompiledContract(gasCalculator, 500L));
registry.put(Address.ALTBN128_MUL, new AltBN128MulPrecompiledContract(gasCalculator, 40_000L));
registry.put(
Address.ALTBN128_PAIRING,
new AltBN128PairingPrecompiledContract(gasCalculator, 80_000L, 100_000L));
}
/**
* Istanbul precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry istanbul(final GasCalculator gasCalculator) {
PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry();
populateForIstanbul(precompileContractRegistry, gasCalculator);
return precompileContractRegistry;
}
/**
* Populate registry for istanbul.
*
* @param registry the registry
* @param gasCalculator the gas calculator
*/
static void populateForIstanbul(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
populateForByzantium(registry, gasCalculator);
registry.put(Address.ALTBN128_ADD, new AltBN128AddPrecompiledContract(gasCalculator, 150L));
registry.put(Address.ALTBN128_MUL, new AltBN128MulPrecompiledContract(gasCalculator, 6_000L));
registry.put(
Address.ALTBN128_PAIRING,
new AltBN128PairingPrecompiledContract(gasCalculator, 34_000L, 45_000L));
registry.put(Address.BLAKE2B_F_COMPRESSION, new BLAKE2BFPrecompileContract(gasCalculator));
}
/**
* Cancun precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry cancun(final GasCalculator gasCalculator) {
PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry();
populateForCancun(precompileContractRegistry, gasCalculator);
return precompileContractRegistry;
}
/**
* Populate registry for Cancun.
*
* @param registry the registry
* @param gasCalculator the gas calculator
*/
static void populateForCancun(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
populateForIstanbul(registry, gasCalculator);
// EIP-4844 - shard blob transactions
registry.put(Address.KZG_POINT_EVAL, new KZGPointEvalPrecompiledContract());
}
/**
* Prague precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry prague(final GasCalculator gasCalculator) {
PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry();
populateForPrague(precompileContractRegistry, gasCalculator);
return precompileContractRegistry;
}
/**
* Populate registry for Prague.
*
* @param registry the registry
* @param gasCalculator the gas calculator
*/
static void populateForPrague(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
populateForCancun(registry, gasCalculator);
// EIP-2537 - BLS12-381 curve operations
registry.put(Address.BLS12_G1ADD, new BLS12G1AddPrecompiledContract());
registry.put(Address.BLS12_G1MULTIEXP, new BLS12G1MultiExpPrecompiledContract());
registry.put(Address.BLS12_G2ADD, new BLS12G2AddPrecompiledContract());
registry.put(Address.BLS12_G2MULTIEXP, new BLS12G2MultiExpPrecompiledContract());
registry.put(Address.BLS12_PAIRING, new BLS12PairingPrecompiledContract());
registry.put(Address.BLS12_MAP_FP_TO_G1, new BLS12MapFpToG1PrecompiledContract());
registry.put(Address.BLS12_MAP_FP2_TO_G2, new BLS12MapFp2ToG2PrecompiledContract());
}
/**
* Osaka precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry osaka(final GasCalculator gasCalculator) {
PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry();
populateForOsaka(precompileContractRegistry, gasCalculator);
return precompileContractRegistry;
}
/**
* Populate registry for Osaka.
*
* @param registry the registry
* @param gasCalculator the gas calculator
*/
static void populateForOsaka(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
populateForPrague(registry, gasCalculator);
// EIP-7823 - Set upper bounds for MODEXP
registry.put(
Address.MODEXP,
new BigIntegerModularExponentiationPrecompiledContract(gasCalculator, 1024L));
// EIP-7951 - secp256r1 P256VERIFY
registry.put(P256_VERIFY, new P256VerifyPrecompiledContract(gasCalculator));
}
/**
* FutureEIPs precompile contract registry.
*
* @param gasCalculator the gas calculator
* @return the precompile contract registry
*/
static PrecompileContractRegistry futureEIPs(final GasCalculator gasCalculator) {
PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry();
populateForFutureEIPs(precompileContractRegistry, gasCalculator);
return precompileContractRegistry;
}
/**
* Populate registry for Future EIPs.
*
* @param registry the registry
* @param gasCalculator the gas calculator
*/
static void populateForFutureEIPs(
final PrecompileContractRegistry registry, final GasCalculator gasCalculator) {
// AERE "AerePQC" fork: full Osaka precompile set plus native post-quantum verifiers.
// Activated on a running chain via genesis config "futureEipsTime"; no re-genesis needed.
populateForOsaka(registry, gasCalculator);
// Native post-quantum precompiles (audited Bouncy Castle BCPQC verifiers).
registry.put(Address.AERE_FALCON512, new Falcon512PrecompiledContract(gasCalculator));
registry.put(Address.AERE_FALCON1024, new Falcon1024PrecompiledContract(gasCalculator));
registry.put(Address.AERE_MLDSA44, new MLDSA44PrecompiledContract(gasCalculator));
registry.put(Address.AERE_SLHDSA128S, new SLHDSA128sPrecompiledContract(gasCalculator));
registry.put(Address.AERE_SHAKE256, new SHAKE256PrecompiledContract(gasCalculator));
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import jakarta.validation.constraints.NotNull;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.crypto.digests.SHAKEDigest;
/**
* AERE PQC precompile: SHAKE256 extendable-output function (FIPS 202).
*
* <p>Input layout: outLen (32 bytes, big-endian, capped at MAX_OUTPUT) || data (arbitrary length).
* Output: exactly {@code outLen} bytes of SHAKE256(data).
*
* <p>SHAKE256 is the hashing bottleneck inside Falcon, ML-DSA and SLH-DSA; exposing it natively lets
* on-chain PQC flows offload the hot path to audited Bouncy Castle rather than hand-rolled Solidity.
*/
public class SHAKE256PrecompiledContract extends AbstractPrecompiledContract {
/** Upper bound on requested output length to keep gas/allocation bounded. */
static final int MAX_OUTPUT = 1 << 16; // 65536 bytes
private static final int BASE_GAS = 60;
private static final int GAS_PER_WORD = 12;
/**
* Instantiates a new SHAKE256 precompiled contract.
*
* @param gasCalculator the gas calculator
*/
SHAKE256PrecompiledContract(final GasCalculator gasCalculator) {
super("AereSHAKE256", gasCalculator);
}
private static int outputLength(final Bytes input) {
if (input.size() < 32) {
return 0;
}
// Big-endian 32-byte length; only the low 4 bytes are honoured, then capped.
long v = input.slice(28, 4).toLong() & 0xFFFFFFFFL;
// If any of the high 28 bytes are non-zero the value is enormous; cap regardless.
if (!input.slice(0, 28).isZero()) {
return MAX_OUTPUT;
}
if (v > MAX_OUTPUT) {
return MAX_OUTPUT;
}
return (int) v;
}
@Override
public long gasRequirement(final Bytes input) {
int outLen = outputLength(input);
int dataLen = input.size() < 32 ? 0 : input.size() - 32;
long words = ((long) dataLen + 31) / 32 + ((long) outLen + 31) / 32;
return BASE_GAS + GAS_PER_WORD * words;
}
@NotNull
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @NotNull final MessageFrame messageFrame) {
if (input.size() < 32) {
return PrecompileContractResult.success(Bytes.EMPTY);
}
final int outLen = outputLength(input);
final byte[] data = input.slice(32).toArrayUnsafe();
final SHAKEDigest digest = new SHAKEDigest(256);
if (data.length > 0) {
digest.update(data, 0, data.length);
}
final byte[] out = new byte[outLen];
if (outLen > 0) {
digest.doFinal(out, 0, outLen);
}
return PrecompileContractResult.success(Bytes.wrap(out));
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright contributors to the AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import jakarta.validation.constraints.NotNull;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAParameters;
import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAPublicKeyParameters;
import org.bouncycastle.pqc.crypto.slhdsa.SLHDSASigner;
/**
* AERE PQC precompile: SLH-DSA-SHA2-128s (SPHINCS+, FIPS 205) signature verification via the
* INTERNAL interface (slh_verify_internal, Algorithm 20 message hashed directly, no prefix).
*
* <p>Input: {@code pk(32) || sig(7856) || message(rest)}. Output: 32-byte word, {@code ...01} valid
* else {@code ...00}. The internal verifier is reached by subclassing Bouncy Castle's
* {@link SLHDSASigner} and calling its {@code protected internalVerifySignature}.
*/
public class SLHDSA128sPrecompiledContract extends AbstractPrecompiledContract {
static final int PK_LEN = 32;
static final int SIG_LEN = 7856;
private static final long GAS = 350_000L;
/** Subclass exposing Bouncy Castle's protected internal (slh_verify_internal) verifier. */
private static final class InternalVerifier extends SLHDSASigner {
boolean verifyInternal(final byte[] message, final byte[] signature) {
return internalVerifySignature(message, signature);
}
}
/**
* Instantiates a new SLH-DSA-128s precompiled contract.
*
* @param gasCalculator the gas calculator
*/
SLHDSA128sPrecompiledContract(final GasCalculator gasCalculator) {
super("AereSLHDSA128s", gasCalculator);
}
@Override
public long gasRequirement(final Bytes input) {
return GAS;
}
@NotNull
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @NotNull final MessageFrame messageFrame) {
boolean valid = false;
if (input.size() >= PK_LEN + SIG_LEN) {
try {
final byte[] pk = input.slice(0, PK_LEN).toArrayUnsafe();
final byte[] sig = input.slice(PK_LEN, SIG_LEN).toArrayUnsafe();
final byte[] message = input.slice(PK_LEN + SIG_LEN).toArrayUnsafe();
final SLHDSAPublicKeyParameters pub =
new SLHDSAPublicKeyParameters(SLHDSAParameters.sha2_128s, pk);
final InternalVerifier verifier = new InternalVerifier();
verifier.init(false, pub);
valid = verifier.verifyInternal(message, sig);
} catch (final Throwable t) {
valid = false;
}
}
return PrecompileContractResult.success(
valid ? Bytes32.leftPad(Bytes.of((byte) 1)) : Bytes32.ZERO);
}
}

View File

@ -0,0 +1,169 @@
#!/usr/bin/env node
// ---------------------------------------------------------------------------
// verify-live-pqc-precompiles.mjs
//
// READ-ONLY verification that the Aere PQC precompiles are genuinely live on
// mainnet chain 2800 and produce correct results against known answer vectors.
//
// Sends ONLY eth_call / eth_chainId / eth_blockNumber. Sends NO transactions,
// signs nothing, touches no validator, holds no key.
//
// node scripts/verify-live-precompiles.mjs
//
// Exit 0 only if every live-band check passes AND the testnet-only band
// (0x0AE6 ML-KEM-768, 0x0AE7 Falcon HashToPoint) is confirmed NOT live on
// mainnet, which is itself a published claim that should be falsifiable.
//
// Ground truth this script does NOT assert: consensus on chain 2800 is
// classical secp256k1 ECDSA QBFT. These precompiles are an application and
// account layer capability only.
// ---------------------------------------------------------------------------
import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const RPC = process.env.AERE_RPC || "https://rpc.aere.network";
const HERE = dirname(fileURLToPath(import.meta.url));
const VECTORS = join(HERE, "..", "vectors");
const PRECOMPILES = {
FALCON512: "0x0000000000000000000000000000000000000ae1",
FALCON1024: "0x0000000000000000000000000000000000000ae2",
MLDSA44: "0x0000000000000000000000000000000000000ae3",
SLHDSA128S: "0x0000000000000000000000000000000000000ae4",
SHAKE256: "0x0000000000000000000000000000000000000ae5",
MLKEM768: "0x0000000000000000000000000000000000000ae6", // testnet-only
HASHTOPOINT: "0x0000000000000000000000000000000000000ae7", // testnet-only
};
const strip = (h) => (h || "").replace(/^0x/, "");
const hex = (h) => "0x" + strip(h);
const word = (n) => n.toString(16).padStart(64, "0");
let id = 0;
async function rpc(method, params) {
const r = await fetch(RPC, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method, params }),
});
const j = await r.json();
if (j.error) throw new Error(`${method}: ${JSON.stringify(j.error)}`);
return j.result;
}
const call = (to, data) => rpc("eth_call", [{ to, data: hex(data) }, "latest"]);
const results = [];
function record(name, expected, got, pass, note) {
results.push({ name, expected, got, pass, note });
console.log(
`${pass ? "PASS" : "FAIL"} ${name}\n expected: ${expected}\n got: ${got}` +
(note ? `\n note: ${note}` : "")
);
}
const TRUE_WORD = "0x" + word(1);
const FALSE_WORD = "0x" + word(0);
async function main() {
const chainId = await rpc("eth_chainId", []);
const block = await rpc("eth_blockNumber", []);
console.log(`RPC: ${RPC}`);
console.log(`chainId: ${chainId} (${parseInt(chainId, 16)})`);
console.log(`block: ${block} (${parseInt(block, 16)})`);
console.log("");
if (parseInt(chainId, 16) !== 2800) throw new Error("not chain 2800");
// -- 0x0AE5 SHAKE256 -----------------------------------------------------
// Shipped framing: 32-byte big-endian outLen word, then the data to absorb.
for (const [label, msg] of [["abc", Buffer.from("abc")], ["empty", Buffer.alloc(0)]]) {
const want = "0x" + createHash("shake256", { outputLength: 32 }).update(msg).digest("hex");
const got = await call(PRECOMPILES.SHAKE256, word(32) + msg.toString("hex"));
record(`0x0AE5 SHAKE256(${label}) outLen=32 vs FIPS 202`, want, got, got === want);
}
{
// XOF property: the first 32 bytes of a 64-byte squeeze must equal the 32-byte squeeze.
const want = "0x" + createHash("shake256", { outputLength: 64 }).update("abc").digest("hex");
const got = await call(PRECOMPILES.SHAKE256, word(64) + Buffer.from("abc").toString("hex"));
record("0x0AE5 SHAKE256(abc) outLen=64 (true XOF squeeze)", want, got, got === want);
}
// -- 0x0AE1 / 0x0AE2 Falcon ---------------------------------------------
// Shipped framing for the verify precompiles: pk || sm (NIST signed-message).
const falcon = [
["0x0AE1 Falcon-512", PRECOMPILES.FALCON512, "falcon512_kat0.json"],
["0x0AE2 Falcon-1024", PRECOMPILES.FALCON1024, "falcon1024_kat0.json"],
];
for (const [label, addr, file] of falcon) {
const k = JSON.parse(readFileSync(join(VECTORS, file), "utf8"));
const good = await call(addr, strip(k.pk) + strip(k.sm));
record(`${label} official NIST KAT vector 0 ACCEPTS`, TRUE_WORD, good, good === TRUE_WORD);
// Negative control: flip one byte of the signed message. A precompile that
// returns 0x01 for everything would pass the line above and fail here.
const b = Buffer.from(strip(k.sm), "hex");
b[Math.floor(b.length / 2)] ^= 0x01;
const bad = await call(addr, strip(k.pk) + b.toString("hex"));
record(
`${label} tampered signed-message REJECTS (negative control)`,
FALSE_WORD,
bad,
bad === FALSE_WORD
);
}
// -- 0x0AE3 ML-DSA-44 / 0x0AE4 SLH-DSA-128s against NIST ACVP ------------
// ACVP fixtures carry pk / message / signature; NIST sm = signature || message.
const acvp = [
["0x0AE3 ML-DSA-44", PRECOMPILES.MLDSA44, "mldsa44-acvp-tg8.json"],
["0x0AE4 SLH-DSA-128s", PRECOMPILES.SLHDSA128S, "sphincs-sha2-128s-acvp-tg31.json"],
];
for (const [label, addr, file] of acvp) {
const k = JSON.parse(readFileSync(join(VECTORS, file), "utf8"));
const pos = k.tests.find((t) => t.expectedPass === true);
const neg = k.tests.find((t) => t.expectedPass === false);
for (const [t, want] of [[pos, TRUE_WORD], [neg, FALSE_WORD]]) {
const sm = strip(t.signature) + strip(t.message);
const got = await call(addr, strip(t.pk) + sm);
record(
`${label} ACVP tc${t.tcId} expectedPass=${t.expectedPass}`,
want,
got,
got === want,
t.reason || ""
);
}
}
// -- 0x0AE6 / 0x0AE7 must NOT be live on mainnet -------------------------
// Docs say these are testnet-only. An empty (codeless) address returns 0x.
for (const [label, addr] of [
["0x0AE6 ML-KEM-768", PRECOMPILES.MLKEM768],
["0x0AE7 Falcon HashToPoint", PRECOMPILES.HASHTOPOINT],
]) {
const code = await rpc("eth_getCode", [addr, "latest"]);
const out = await call(addr, word(32) + Buffer.from("abc").toString("hex"));
const notLive = out === "0x" && code === "0x";
record(
`${label} is NOT live on mainnet 2800 (docs say testnet-only)`,
"0x (empty: no precompile, no code)",
`eth_call=${out} eth_getCode=${code}`,
notLive
);
}
const failed = results.filter((r) => !r.pass);
console.log(`\n${results.length - failed.length}/${results.length} checks passed`);
if (failed.length) {
console.log("FAILED:");
for (const f of failed) console.log(` - ${f.name}`);
}
process.exit(failed.length ? 1 : 0);
}
main().catch((e) => {
console.error("ERROR:", e.message);
process.exit(2);
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long