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 0b8fa9d7f63..31f49713488 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java @@ -90,6 +90,9 @@ public class Address extends DelegatingBytes { /** 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); + /** The constant ZERO. */ public static final Address ZERO = Address.fromHexString("0x0"); @@ -214,10 +217,11 @@ public static Address fromHexStringStrict(final String str) { * @return the address */ public static Address precompiled(final int value) { - // Keep it simple while we don't need precompiled above 127. - checkArgument(value < Byte.MAX_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 - 1] = (byte) value; + address[SIZE - 2] = (byte) (value >>> 8); // High byte + address[SIZE - 1] = (byte) (value & 0xFF); // Low byte return new Address(Bytes.wrap(address)); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/OsakaGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/OsakaGasCalculator.java index adc9f445fcd..daa6a1f5d4e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/OsakaGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/OsakaGasCalculator.java @@ -14,11 +14,12 @@ */ package org.hyperledger.besu.evm.gascalculator; -import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2; +import static org.hyperledger.besu.datatypes.Address.P256_VERIFY; import static org.hyperledger.besu.evm.internal.Words.clampedAdd; import static org.hyperledger.besu.evm.internal.Words.clampedMultiply; import static org.hyperledger.besu.evm.internal.Words.clampedToInt; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.precompile.BigIntegerModularExponentiationPrecompiledContract; @@ -37,7 +38,7 @@ public class OsakaGasCalculator extends PragueGasCalculator { /** Instantiates a new Osaka Gas Calculator. */ public OsakaGasCalculator() { - this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]); + this(P256_VERIFY.getInt(16)); } /** @@ -49,6 +50,25 @@ protected OsakaGasCalculator(final int maxPrecompile) { super(maxPrecompile); } + @Override + public boolean isPrecompile(final Address address) { + final byte[] addressBytes = address.toArrayUnsafe(); + + // First 18 bytes must be zero: + for (int i = 0; i < 18; i++) { + if (addressBytes[i] != 0) { + return false; + } + } + + // Interpret last two bytes as big-endian unsigned short. + final int precompileValue = + (Byte.toUnsignedInt(addressBytes[18]) << 8) | Byte.toUnsignedInt(addressBytes[19]); + + // values in range [1, 0x01FF] inclusive to include L1 and L2 precompiles: + return precompileValue > 0 && precompileValue <= 0x01FF; + } + @Override public long modExpGasCost(final Bytes input) { final long baseLength = BigIntegerModularExponentiationPrecompiledContract.baseLength(input); 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 6c1f984bb3f..9c20b25f09a 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 @@ -14,6 +14,8 @@ */ 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; @@ -197,8 +199,8 @@ static void populateForOsaka( // EIP-7823 - Set upper bounds for MODEXP registry.put( Address.MODEXP, BigIntegerModularExponentiationPrecompiledContract.osaka(gasCalculator)); - // RIP-7212 - secp256r1 P256VERIFY - registry.put(Address.precompiled(100), new P256VerifyPrecompiledContract(gasCalculator)); + // EIP-7951 - secp256r1 P256VERIFY + registry.put(P256_VERIFY, new P256VerifyPrecompiledContract(gasCalculator)); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/P256VerifyPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/P256VerifyPrecompiledContract.java index 336c8630ec7..25caf97b980 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/P256VerifyPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/P256VerifyPrecompiledContract.java @@ -27,21 +27,22 @@ import com.github.benmanes.caffeine.cache.Caffeine; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Draft implementation of RIP-7212 / EIP-7951. - * - *

TODO: malleable signatures, point at infinity check, modular comparison, caching - * implementation - */ +/** Implementation of EIP-7951. */ public class P256VerifyPrecompiledContract extends AbstractPrecompiledContract { private static final Logger LOG = LoggerFactory.getLogger(P256VerifyPrecompiledContract.class); private static final String PRECOMPILE_NAME = "P256VERIFY"; private static final Bytes32 VALID = Bytes32.leftPad(Bytes.of(1), (byte) 0); private static final Bytes INVALID = Bytes.EMPTY; + private static final X9ECParameters R1_PARAMS = SECNamedCurves.getByName("secp256r1"); + private static final BigInteger N = R1_PARAMS.getN(); + private static final BigInteger P = R1_PARAMS.getCurve().getField().getCharacteristic(); + private final GasCalculator gasCalculator; private final SignatureAlgorithm signatureAlgorithm; @@ -49,7 +50,7 @@ public class P256VerifyPrecompiledContract extends AbstractPrecompiledContract { Caffeine.newBuilder().maximumSize(1000).build(); /** - * Instantiates a new Abstract precompiled contract. + * Instantiates a new P256Verify precompiled contract. * * @param gasCalculator the gas calculator */ @@ -57,6 +58,12 @@ public P256VerifyPrecompiledContract(final GasCalculator gasCalculator) { this(gasCalculator, new SECP256R1()); } + /** + * Instantiates a new P256Verify precompiled contract. + * + * @param gasCalculator the gas calculator + * @param signatureAlgorithm the signature algorithm + */ public P256VerifyPrecompiledContract( final GasCalculator gasCalculator, final SignatureAlgorithm signatureAlgorithm) { super(PRECOMPILE_NAME, gasCalculator); @@ -78,7 +85,7 @@ public PrecompileContractResult computePrecompile( input.size()); return PrecompileContractResult.success(INVALID); } - PrecompileInputResultTuple res; + PrecompileInputResultTuple res = null; Integer cacheKey = null; if (enableResultCaching) { cacheKey = getCacheKey(input); @@ -106,25 +113,55 @@ public PrecompileContractResult computePrecompile( final Bytes rBytes = input.slice(32, 32); final Bytes sBytes = input.slice(64, 32); final Bytes pubKeyBytes = input.slice(96, 64); + final BigInteger qx = pubKeyBytes.slice(0, 32).toUnsignedBigInteger(); + final BigInteger qy = pubKeyBytes.slice(32, 32).toUnsignedBigInteger(); try { // Convert r and s to BigIntegers (unsigned) final BigInteger r = rBytes.toUnsignedBigInteger(); final BigInteger s = sBytes.toUnsignedBigInteger(); - // Create the signature; recID is not used in verification - use 0 - final SECPSignature signature = signatureAlgorithm.createSignature(r, s, (byte) 0); + // Check r, s in (0, n) + if (r.signum() <= 0 || r.compareTo(N) >= 0 || s.signum() <= 0 || s.compareTo(N) >= 0) { + LOG.trace("Invalid r or s: must satisfy 0 < r,s < n"); + res = + new PrecompileInputResultTuple( + enableResultCaching ? input.copy() : input, + PrecompileContractResult.success(INVALID)); + } + + // Check qx, qy in [0, p) + if (qx.signum() < 0 || qx.compareTo(P) >= 0 || qy.signum() < 0 || qy.compareTo(P) >= 0) { + LOG.trace("Invalid qx or qy: must satisfy 0 <= qx,qy < p"); + res = + new PrecompileInputResultTuple( + enableResultCaching ? input.copy() : input, + PrecompileContractResult.success(INVALID)); + } - // Construct public key from 64-byte uncompressed format (x || y) - final SECPPublicKey publicKey = signatureAlgorithm.createPublicKey(pubKeyBytes); + // Check point not at infinity (qx, qy ≠ 0,0), and non-trivial infinity encoding + if ((qx.signum() == 0 && qy.signum() == 0)) { + LOG.trace("Invalid public key: point at infinity"); + res = + new PrecompileInputResultTuple( + enableResultCaching ? input.copy() : input, + PrecompileContractResult.success(INVALID)); + } - // Perform verification TODO: implement bouncycastle malleable - final boolean isValid = signatureAlgorithm.verifyMalleable(messageHash, signature, publicKey); + if (res == null) { + // Create the signature; recID is not used in verification - use 0 + final SECPSignature signature = signatureAlgorithm.createSignature(r, s, (byte) 0); + final SECPPublicKey publicKey = signatureAlgorithm.createPublicKey(pubKeyBytes); + + final boolean isValid = + signatureAlgorithm.verifyMalleable(messageHash, signature, publicKey); + + res = + new PrecompileInputResultTuple( + enableResultCaching ? input.copy() : input, + PrecompileContractResult.success(isValid ? VALID : INVALID)); + } - res = - new PrecompileInputResultTuple( - enableResultCaching ? input.copy() : input, - PrecompileContractResult.success(isValid ? VALID : INVALID)); if (enableResultCaching) { p256VerifyCache.put(cacheKey, res); }