From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: 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. + * + *

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. + * + *

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). + * + *

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). + * + *

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). + * + *

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). + * + *

Input layout: outLen (32 bytes, big-endian, capped at MAX_OUTPUT) || data (arbitrary length). + * Output: exactly {@code outLen} bytes of SHAKE256(data). + * + *

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). + * + *

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); + } +}