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:
commit
48416dfe73
21
LICENSE
Normal file
21
LICENSE
Normal 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
255
README.md
Normal 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`.
|
||||
607
patches/0001-aere-pqc-precompiles-mainnet.patch
Normal file
607
patches/0001-aere-pqc-precompiles-mainnet.patch
Normal 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);
|
||||
+ }
|
||||
+}
|
||||
317
patches/0002-aere-pqc-precompiles-testnet.patch
Normal file
317
patches/0002-aere-pqc-precompiles-testnet.patch
Normal 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 < 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
268
precompiles/Address.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
94
precompiles/AereFalconSupport.java
Normal file
94
precompiles/AereFalconSupport.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
precompiles/Falcon1024PrecompiledContract.java
Normal file
62
precompiles/Falcon1024PrecompiledContract.java
Normal 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));
|
||||
}
|
||||
}
|
||||
62
precompiles/Falcon512PrecompiledContract.java
Normal file
62
precompiles/Falcon512PrecompiledContract.java
Normal 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));
|
||||
}
|
||||
}
|
||||
127
precompiles/HashToPointPrecompiledContract.java
Normal file
127
precompiles/HashToPointPrecompiledContract.java
Normal 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 < 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
precompiles/MLDSA44PrecompiledContract.java
Normal file
84
precompiles/MLDSA44PrecompiledContract.java
Normal 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);
|
||||
}
|
||||
}
|
||||
128
precompiles/MLKEM768PrecompiledContract.java
Normal file
128
precompiles/MLKEM768PrecompiledContract.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
240
precompiles/MainnetPrecompiledContracts.java
Normal file
240
precompiles/MainnetPrecompiledContracts.java
Normal 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));
|
||||
}
|
||||
}
|
||||
93
precompiles/SHAKE256PrecompiledContract.java
Normal file
93
precompiles/SHAKE256PrecompiledContract.java
Normal 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));
|
||||
}
|
||||
}
|
||||
84
precompiles/SLHDSA128sPrecompiledContract.java
Normal file
84
precompiles/SLHDSA128sPrecompiledContract.java
Normal 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);
|
||||
}
|
||||
}
|
||||
169
scripts/verify-live-precompiles.mjs
Normal file
169
scripts/verify-live-precompiles.mjs
Normal 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);
|
||||
});
|
||||
1
vectors/falcon1024_kat0.json
Normal file
1
vectors/falcon1024_kat0.json
Normal file
File diff suppressed because one or more lines are too long
1
vectors/falcon512_kat0.json
Normal file
1
vectors/falcon512_kat0.json
Normal file
File diff suppressed because one or more lines are too long
147
vectors/mldsa44-acvp-tg8.json
Normal file
147
vectors/mldsa44-acvp-tg8.json
Normal file
File diff suppressed because one or more lines are too long
155
vectors/sphincs-sha2-128s-acvp-tg31.json
Normal file
155
vectors/sphincs-sha2-128s-acvp-tg31.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user