Speedup modexp (#4780)

Increate the speed of ModExp gas calculations by using primitive types.
Use a native lib for modexp precompile.

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>

* Native modexp
pull/4797/head
Danno Ferrin 2 years ago committed by GitHub
parent 4120501e5f
commit 89ce75a614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      CHANGELOG.md
  2. 9
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  3. 13
      besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NativeLibraryOptions.java
  4. 11
      crypto/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java
  5. 13
      crypto/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java
  6. 2
      docker/graalvm/Dockerfile
  7. 2
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java
  8. 1
      evm/build.gradle
  9. 50
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java
  10. 64
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java
  11. 62
      evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java
  12. 11
      evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java
  13. 137
      evm/src/main/java/org/hyperledger/besu/evm/precompile/BigIntegerModularExponentiationPrecompiledContract.java
  14. 38
      evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java
  15. 72
      gradle/verification-metadata.xml
  16. 11
      gradle/versions.gradle

@ -1,11 +1,13 @@
# Changelog
## 22.10.3
### Additions and Improvements
- Implement Eth/68 sub-protocol [#4715](https://github.com/hyperledger/besu/issues/4715)
### Breaking Changes
- Added `--rpc-max-logs-range` CLI option to allow limiting the number of blocks queried by `eth_getLogs` RPC API. Default value: 1000 [#4597](https://github.com/hyperledger/besu/pull/4597)
- The `graalvm` docker variant no longer meets the performance requirements for Ethereum Mainnet. The `openjdk-11` and `openjdk-latest` variants are recommended in its place.
### Additions and Improvements
- Implement Eth/68 sub-protocol [#4715](https://github.com/hyperledger/besu/issues/4715)
- Increase the speed of modexp gas execution and execution. [#4780](https://github.com/hyperledger/besu/pull/4780)
### Bug Fixes
@ -18,8 +20,8 @@ This is a hotfix release to resolve a race condition that results in segfaults,
- bugfix for async operations on Snashot worldstates [#4767](https://github.com/hyperledger/besu/pull/4767)
### Download Links
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.tar.gz / sha256: TBA
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.zip / sha256: TBA
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.tar.gz / sha256: cdb36141e3cba6379d35016e0a2de2edba579d4786124b5f7257b1e4a68867a2
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.zip / sha256: 4c9208f684762670cb4f2c6ebfb6930e05e339a7c3c586fe8caa9f26462830aa
## 22.10.1

@ -146,6 +146,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.evm.precompile.AbstractAltBnPrecompiledContract;
import org.hyperledger.besu.evm.precompile.BigIntegerModularExponentiationPrecompiledContract;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.metrics.MetricCategoryRegistryImpl;
import org.hyperledger.besu.metrics.MetricsProtocol;
@ -1740,6 +1741,14 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
logger.info("Using the Java implementation of alt bn128");
}
if (unstableNativeLibraryOptions.getNativeModExp()
&& BigIntegerModularExponentiationPrecompiledContract.isNative()) {
logger.info("Using the native implementation of modexp");
} else {
BigIntegerModularExponentiationPrecompiledContract.disableNative();
logger.info("Using the Java implementation of modexp");
}
if (unstableNativeLibraryOptions.getNativeSecp()
&& SignatureAlgorithmFactory.getInstance().isNative()) {
logger.info("Using the native implementation of the signature algorithm");

@ -45,6 +45,15 @@ public class NativeLibraryOptions {
arity = "1")
private final Boolean nativeBlake2bf = Boolean.TRUE;
@CommandLine.Option(
hidden = true,
names = {"--Xmodexp-native-enabled"},
description =
"Per default a native library is used for modexp. "
+ "If the Java implementation should be used instead, this option must be set to false",
arity = "1")
private final Boolean nativeModExp = Boolean.TRUE;
public static NativeLibraryOptions create() {
return new NativeLibraryOptions();
}
@ -60,4 +69,8 @@ public class NativeLibraryOptions {
public Boolean getNativeBlake2bf() {
return nativeBlake2bf;
}
public Boolean getNativeModExp() {
return nativeModExp;
}
}

@ -80,7 +80,16 @@ public class Blake2bfMessageDigest extends BCMessageDigest implements Cloneable
private long rounds; // unsigned integer represented as long
private final long[] v;
private static boolean useNative = LibBlake2bf.ENABLED;
private static boolean useNative;
static {
try {
useNative = LibBlake2bf.ENABLED;
} catch (UnsatisfiedLinkError ule) {
LOG.info("blake2bf native precompile not available: {}", ule.getMessage());
useNative = false;
}
}
Blake2bfDigest() {
if (!useNative) {

@ -16,6 +16,7 @@ package org.hyperledger.besu.crypto;
import org.hyperledger.besu.nativelib.secp256r1.LibSECP256R1;
import org.hyperledger.besu.nativelib.secp256r1.Signature;
import org.hyperledger.besu.nativelib.secp256r1.besuNativeEC.BesuNativeEC;
import java.math.BigInteger;
import java.util.Optional;
@ -25,15 +26,25 @@ import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.crypto.signers.DSAKCalculator;
import org.bouncycastle.crypto.signers.RandomDSAKCalculator;
import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SECP256R1 extends AbstractSECP256 {
private static final Logger LOG = LoggerFactory.getLogger(SECP256R1.class);
public static final String CURVE_NAME = "secp256r1";
private boolean useNative = true;
private boolean useNative;
private final LibSECP256R1 libSECP256R1 = new LibSECP256R1();
public SECP256R1() {
super(CURVE_NAME, SecP256R1Curve.q);
try {
useNative = BesuNativeEC.INSTANCE != null;
} catch (UnsatisfiedLinkError ule) {
LOG.info("secp256r1 native precompile not available: {}", ule.getMessage());
useNative = false;
}
}
@Override

@ -1,5 +1,5 @@
FROM ghcr.io/graalvm/graalvm-ce:ol7-java11
FROM ghcr.io/graalvm/graalvm-ce:ol8-java11
ARG VERSION="dev"
RUN adduser --home /opt/besu besu && \

@ -100,7 +100,7 @@ public class StateTestVersionedTransaction {
@JsonDeserialize(using = StateTestAccessListDeserializer.class) @JsonProperty("accessLists")
final List<List<AccessListEntry>> maybeAccessLists) {
this.nonce = Long.decode(nonce);
this.nonce = Bytes.fromHexStringLenient(nonce).toLong();
this.gasPrice = Optional.ofNullable(gasPrice).map(Wei::fromHexString).orElse(null);
this.maxFeePerGas = Optional.ofNullable(maxFeePerGas).map(Wei::fromHexString).orElse(null);
this.maxPriorityFeePerGas =

@ -41,6 +41,7 @@ dependencies {
compileOnly 'com.fasterxml.jackson.core:jackson-databind'
implementation 'org.apache.tuweni:tuweni-bytes'
implementation 'org.hyperledger.besu:arithmetic'
implementation 'org.hyperledger.besu:bls12-381'
implementation 'net.java.dev.jna:jna'
implementation 'com.github.ben-manes.caffeine:caffeine'

@ -16,11 +16,14 @@ package org.hyperledger.besu.evm.gascalculator;
import static org.hyperledger.besu.datatypes.Address.BLAKE2B_F_COMPRESSION;
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.datatypes.Wei;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.precompile.BigIntegerModularExponentiationPrecompiledContract;
import java.math.BigInteger;
@ -218,38 +221,29 @@ public class BerlinGasCalculator extends IstanbulGasCalculator {
@Override
public long modExpGasCost(final Bytes input) {
final BigInteger baseLength =
BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final BigInteger exponentLength =
final long baseLength = BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final long exponentLength =
BigIntegerModularExponentiationPrecompiledContract.exponentLength(input);
final BigInteger modulusLength =
final long modulusLength =
BigIntegerModularExponentiationPrecompiledContract.modulusLength(input);
final BigInteger exponentOffset =
BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET.add(baseLength);
final int firstExponentBytesCap =
exponentLength.min(ByzantiumGasCalculator.MAX_FIRST_EXPONENT_BYTES).intValue();
final long exponentOffset =
clampedAdd(BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET, baseLength);
final long firstExponentBytesCap =
Math.min(exponentLength, ByzantiumGasCalculator.MAX_FIRST_EXPONENT_BYTES);
final BigInteger firstExpBytes =
BigIntegerModularExponentiationPrecompiledContract.extractParameter(
input, exponentOffset, firstExponentBytesCap);
final BigInteger adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
final BigInteger multiplicationComplexity =
modulusLength
.max(baseLength)
.add(BigInteger.valueOf(7))
.divide(BigInteger.valueOf(8))
.pow(2);
final BigInteger gasRequirement =
multiplicationComplexity
.multiply(adjustedExponentLength.max(BigInteger.ONE))
.divide(BigInteger.valueOf(3));
// Gas price is so large it will not fit in a Gas type, so a
// very very very unlikely high gas price is used instead.
if (gasRequirement.bitLength() > ByzantiumGasCalculator.MAX_GAS_BITS) {
return Long.MAX_VALUE;
} else {
return Math.max(gasRequirement.longValueExact(), 200L);
input, clampedToInt(exponentOffset), clampedToInt(firstExponentBytesCap));
final long adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
long multiplicationComplexity = (Math.max(modulusLength, baseLength) + 7L) / 8L;
multiplicationComplexity =
Words.clampedMultiply(multiplicationComplexity, multiplicationComplexity);
long gasRequirement =
clampedMultiply(multiplicationComplexity, Math.max(adjustedExponentLength, 1L));
if (gasRequirement != Long.MAX_VALUE) {
gasRequirement /= 3;
}
return Math.max(gasRequirement, 200L);
}
}

@ -15,6 +15,10 @@
*/
package org.hyperledger.besu.evm.gascalculator;
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.evm.precompile.BigIntegerModularExponentiationPrecompiledContract;
import java.math.BigInteger;
@ -22,58 +26,44 @@ import java.math.BigInteger;
import org.apache.tuweni.bytes.Bytes;
public class ByzantiumGasCalculator extends SpuriousDragonGasCalculator {
private static final BigInteger GQUADDIVISOR = BigInteger.valueOf(20);
private static final BigInteger WORD_SIZE = BigInteger.valueOf(32);
private static final BigInteger BITS_IN_BYTE = BigInteger.valueOf(8);
private static final int GQUADDIVISOR = 20;
private static final int WORD_SIZE = 32;
private static final int BITS_IN_BYTE = 8;
public static final BigInteger MAX_FIRST_EXPONENT_BYTES = BigInteger.valueOf(32);
public static final int MAX_GAS_BITS = 63;
public static final int MAX_FIRST_EXPONENT_BYTES = 32;
@Override
public long modExpGasCost(final Bytes input) {
final BigInteger baseLength =
BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final BigInteger exponentLength =
final long baseLength = BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final long exponentLength =
BigIntegerModularExponentiationPrecompiledContract.exponentLength(input);
final BigInteger modulusLength =
final long modulusLength =
BigIntegerModularExponentiationPrecompiledContract.modulusLength(input);
final BigInteger exponentOffset =
BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET.add(baseLength);
final int firstExponentBytesCap = exponentLength.min(MAX_FIRST_EXPONENT_BYTES).intValue();
final long exponentOffset =
clampedAdd(BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET, baseLength);
final long firstExponentBytesCap = Math.min(exponentLength, MAX_FIRST_EXPONENT_BYTES);
final BigInteger firstExpBytes =
BigIntegerModularExponentiationPrecompiledContract.extractParameter(
input, exponentOffset, firstExponentBytesCap);
final BigInteger adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
final BigInteger multiplicationComplexity =
input, clampedToInt(exponentOffset), clampedToInt(firstExponentBytesCap));
final long adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
final long multiplicationComplexity =
BigIntegerModularExponentiationPrecompiledContract.multiplicationComplexity(
baseLength.max(modulusLength));
final BigInteger gasRequirement =
multiplicationComplexity
.multiply(adjustedExponentLength.max(BigInteger.ONE))
.divide(GQUADDIVISOR);
// Gas price is so large it will not fit in a Gas type, so a
// very very very unlikely high gas price is used instead.
if (gasRequirement.bitLength() > MAX_GAS_BITS) {
return Long.MAX_VALUE;
} else {
return gasRequirement.longValueExact();
}
Math.max(baseLength, modulusLength));
long numerator = clampedMultiply(multiplicationComplexity, Math.max(adjustedExponentLength, 1));
return (numerator == Long.MAX_VALUE) ? Long.MAX_VALUE : numerator / GQUADDIVISOR;
}
public static BigInteger adjustedExponentLength(
final BigInteger exponentLength, final BigInteger firstExpBytes) {
final BigInteger bitLength = bitLength(firstExpBytes);
if (exponentLength.compareTo(WORD_SIZE) <= 0) {
public static long adjustedExponentLength(
final long exponentLength, final BigInteger firstExpBytes) {
final int bitLength = bitLength(firstExpBytes);
if (exponentLength < WORD_SIZE) {
return bitLength;
} else {
return BITS_IN_BYTE.multiply(exponentLength.subtract(WORD_SIZE)).add(bitLength);
return clampedAdd(clampedMultiply(BITS_IN_BYTE, (exponentLength - WORD_SIZE)), bitLength);
}
}
private static BigInteger bitLength(final BigInteger n) {
return n.compareTo(BigInteger.ZERO) == 0
? BigInteger.ZERO
: BigInteger.valueOf(n.bitLength() - 1L);
private static int bitLength(final BigInteger n) {
return n.compareTo(BigInteger.ZERO) == 0 ? 0 : (n.bitLength() - 1);
}
}

@ -22,9 +22,7 @@ import org.apache.tuweni.bytes.MutableBytes;
import org.apache.tuweni.units.bigints.UInt256;
/** Static utility methods to work with VM words (that is, {@link Bytes32} values). */
public abstract class Words {
private Words() {}
public interface Words {
/**
* Creates a new word containing the provided address.
*
@ -32,7 +30,7 @@ public abstract class Words {
* @return A VM word containing {@code address} (left-padded as according to the VM specification
* (Appendix H. of the Yellow paper)).
*/
public static UInt256 fromAddress(final Address address) {
static UInt256 fromAddress(final Address address) {
return UInt256.fromBytes(Bytes32.leftPad(address));
}
@ -43,18 +41,7 @@ public abstract class Words {
* @return An address build from the right-most 160-bits of the {@code bytes} (as according to the
* VM specification (Appendix H. of the Yellow paper)).
*/
public static Address toAddress(final Bytes32 bytes) {
return Address.wrap(bytes.slice(bytes.size() - Address.SIZE, Address.SIZE));
}
/**
* Extract an address from the provided address.
*
* @param bytes The word to extract the address from.
* @return An address build from the right-most 160-bits of the {@code bytes} (as according to the
* VM specification (Appendix H. of the Yellow paper)).
*/
public static Address toAddress(final Bytes bytes) {
static Address toAddress(final Bytes bytes) {
final int size = bytes.size();
if (size < 20) {
final MutableBytes result = MutableBytes.create(20);
@ -77,7 +64,7 @@ public abstract class Words {
* @param input the input to check.
* @return the number of (32 bytes) words that {@code input} spans.
*/
public static int numWords(final Bytes input) {
static int numWords(final Bytes input) {
// m/n round up == (m + n - 1)/n: http://www.cs.nott.ac.uk/~psarb2/G51MPC/slides/NumberLogic.pdf
return (input.size() + Bytes32.SIZE - 1) / Bytes32.SIZE;
}
@ -89,7 +76,7 @@ public abstract class Words {
* @param uint the unsigned integer
* @return the least of the integer value or Long.MAX_VALUE
*/
public static long clampedToLong(final Bytes uint) {
static long clampedToLong(final Bytes uint) {
if (uint.size() <= 8) {
final long result = uint.toLong();
return result < 0 ? Long.MAX_VALUE : result;
@ -105,6 +92,23 @@ public abstract class Words {
}
}
/**
* The value of the long as though it was representing an unsigned integer, however if the value
* is out of range it will return the number at the end of the range.
*
* @param l the signed integer
* @return The int value, or Integer.MAX_VALUE if too large or Integer.MIN_VALUE if to small.
*/
static int clampedToInt(final long l) {
if (l > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else if (l < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
} else {
return (int) l;
}
}
/**
* Adds a and b, but if an underflow/overflow occurs return the Long max/min value
*
@ -112,7 +116,7 @@ public abstract class Words {
* @param b second value
* @return value of a plus b if no over/underflows or Long.MAX_VALUE/Long.MIN_VALUE otherwise
*/
public static long clampedAdd(final long a, final long b) {
static long clampedAdd(final long a, final long b) {
try {
return Math.addExact(a, b);
} catch (final ArithmeticException ae) {
@ -127,7 +131,7 @@ public abstract class Words {
* @param b second value
* @return value of a times b if no over/underflows or Long.MAX_VALUE/Long.MIN_VALUE otherwise
*/
public static long clampedMultiply(final long a, final long b) {
static long clampedMultiply(final long a, final long b) {
try {
return Math.multiplyExact(a, b);
} catch (final ArithmeticException ae) {
@ -135,6 +139,22 @@ public abstract class Words {
}
}
/**
* Multiplies a and b, but if an underflow/overflow occurs return the Integer max/min value
*
* @param a first value
* @param b second value
* @return value of a times b if no over/underflows or Integer.MAX_VALUE/Integer.MIN_VALUE
* otherwise
*/
static int clampedMultiply(final int a, final int b) {
try {
return Math.multiplyExact(a, b);
} catch (final ArithmeticException ae) {
return ((a ^ b) < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
}
/**
* Returns the lesser of the two values, when compared as an unsigned value
*
@ -142,7 +162,7 @@ public abstract class Words {
* @param b second value
* @return a if, as an unsigned integer, a is less than b; otherwise b.
*/
public static long unsignedMin(final long a, final long b) {
static long unsignedMin(final long a, final long b) {
return Long.compareUnsigned(a, b) < 0 ? a : b;
}
}

@ -35,7 +35,16 @@ public abstract class AbstractAltBnPrecompiledContract extends AbstractPrecompil
private static final Logger LOG = LoggerFactory.getLogger(AbstractAltBnPrecompiledContract.class);
// use the native library implementation, if it is available
static boolean useNative = LibEthPairings.ENABLED;
static boolean useNative;
static {
try {
useNative = LibEthPairings.ENABLED;
} catch (UnsatisfiedLinkError ule) {
LOG.info("altbn128 native precompile not available: {}", ule.getMessage());
useNative = false;
}
}
public static void disableNative() {
useNative = false;

@ -14,39 +14,62 @@
*/
package org.hyperledger.besu.evm.precompile;
import static org.hyperledger.besu.evm.internal.Words.clampedMultiply;
import static org.hyperledger.besu.evm.internal.Words.clampedToInt;
import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.nativelib.arithmetic.LibArithmetic;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Optional;
import javax.annotation.Nonnull;
import com.sun.jna.ptr.IntByReference;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.MutableBytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// The big integer modular exponentiation precompiled contract defined in EIP-198.
public class BigIntegerModularExponentiationPrecompiledContract
extends AbstractPrecompiledContract {
public static final BigInteger BASE_OFFSET = BigInteger.valueOf(96);
private static final Logger LOG =
LoggerFactory.getLogger(BigIntegerModularExponentiationPrecompiledContract.class);
static boolean useNative;
static {
try {
useNative = LibArithmetic.ENABLED;
} catch (UnsatisfiedLinkError ule) {
LOG.info("modexp native precompile not available: {}", ule.getMessage());
useNative = false;
}
}
public static final int BASE_OFFSET = 96;
private static final int PARAMETER_LENGTH = 32;
private static final int BASE_LENGTH_OFFSET = 0;
private static final int EXPONENT_LENGTH_OFFSET = 32;
private static final int MODULUS_LENGTH_OFFSET = 64;
private static final BigInteger BIGINT_4 = BigInteger.valueOf(4);
private static final BigInteger BIGINT_16 = BigInteger.valueOf(16);
private static final BigInteger BIGINT_64 = BigInteger.valueOf(64);
private static final BigInteger BIGINT_96 = BigInteger.valueOf(96);
private static final BigInteger BIGINT_480 = BigInteger.valueOf(480);
private static final BigInteger BIGINT_1024 = BigInteger.valueOf(1_024L);
private static final BigInteger BIGINT_3072 = BigInteger.valueOf(3_072L);
private static final BigInteger BIGINT_199680 = BigInteger.valueOf(199_680L);
public BigIntegerModularExponentiationPrecompiledContract(final GasCalculator gasCalculator) {
super("BigIntModExp", gasCalculator);
}
public static void disableNative() {
useNative = false;
}
public static boolean isNative() {
return useNative;
}
@Override
public long gasRequirement(final Bytes input) {
return gasCalculator().modExpGasCost(input);
@ -56,24 +79,33 @@ public class BigIntegerModularExponentiationPrecompiledContract
@Override
public PrecompileContractResult computePrecompile(
final Bytes input, @Nonnull final MessageFrame messageFrame) {
final BigInteger baseLength = baseLength(input);
final BigInteger exponentLength = exponentLength(input);
final BigInteger modulusLength = modulusLength(input);
if (useNative) {
return computeNative(input);
} else {
return computeDefault(input);
}
}
@Nonnull
public PrecompileContractResult computeDefault(final Bytes input) {
final int baseLength = clampedToInt(baseLength(input));
final int exponentLength = clampedToInt(exponentLength(input));
final int modulusLength = clampedToInt(modulusLength(input));
// If baseLength and modulusLength are zero
// we could have a massively overflowing exp because it wouldn't have been filtered out at the
// gas cost phase
if (baseLength.equals(BigInteger.ZERO) && modulusLength.equals(BigInteger.ZERO)) {
if ((baseLength == 0) && (modulusLength == 0)) {
return PrecompileContractResult.success(Bytes.EMPTY);
}
final BigInteger exponentOffset = BASE_OFFSET.add(baseLength);
final BigInteger modulusOffset = exponentOffset.add(exponentLength);
final BigInteger base = extractParameter(input, BASE_OFFSET, baseLength.intValue());
final BigInteger exp = extractParameter(input, exponentOffset, exponentLength.intValue());
final BigInteger mod = extractParameter(input, modulusOffset, modulusLength.intValue());
final int exponentOffset = BASE_OFFSET + baseLength;
final int modulusOffset = exponentOffset + exponentLength;
final BigInteger base = extractParameter(input, BASE_OFFSET, baseLength);
final BigInteger exp = extractParameter(input, exponentOffset, exponentLength);
final BigInteger mod = extractParameter(input, modulusOffset, modulusLength);
final Bytes modExp;
// Result must be the length of the modulus.
final MutableBytes result = MutableBytes.create(modulusLength.intValue());
final MutableBytes result = MutableBytes.create(modulusLength);
if (mod.compareTo(BigInteger.ZERO) == 0) {
modExp = MutableBytes.EMPTY;
} else {
@ -87,30 +119,29 @@ public class BigIntegerModularExponentiationPrecompiledContract
}
// Equation to estimate the multiplication complexity.
public static BigInteger multiplicationComplexity(final BigInteger x) {
if (x.compareTo(BIGINT_64) <= 0) {
public static long multiplicationComplexity(final long x) {
if (x <= 64) {
return square(x);
} else if (x.compareTo(BIGINT_1024) <= 0) {
return square(x).divide(BIGINT_4).add(BIGINT_96.multiply(x)).subtract(BIGINT_3072);
} else if (x <= 1024) {
return (square(x) / 4) + (x * 96) - 3072;
} else {
return square(x).divide(BIGINT_16).add(BIGINT_480.multiply(x)).subtract(BIGINT_199680);
return (square(x) / 16) + (480 * x) - 199680;
}
}
public static BigInteger baseLength(final Bytes input) {
return extractParameter(input, BASE_LENGTH_OFFSET, PARAMETER_LENGTH);
public static long baseLength(final Bytes input) {
return extractParameterLong(input, BASE_LENGTH_OFFSET, PARAMETER_LENGTH);
}
public static BigInteger exponentLength(final Bytes input) {
return extractParameter(input, EXPONENT_LENGTH_OFFSET, PARAMETER_LENGTH);
public static long exponentLength(final Bytes input) {
return extractParameterLong(input, EXPONENT_LENGTH_OFFSET, PARAMETER_LENGTH);
}
public static BigInteger modulusLength(final Bytes input) {
return extractParameter(input, MODULUS_LENGTH_OFFSET, PARAMETER_LENGTH);
public static long modulusLength(final Bytes input) {
return extractParameterLong(input, MODULUS_LENGTH_OFFSET, PARAMETER_LENGTH);
}
private static BigInteger extractParameter(
final Bytes input, final int offset, final int length) {
public static BigInteger extractParameter(final Bytes input, final int offset, final int length) {
if (offset > input.size() || length == 0) {
return BigInteger.ZERO;
}
@ -118,15 +149,41 @@ public class BigIntegerModularExponentiationPrecompiledContract
return new BigInteger(1, raw);
}
public static BigInteger extractParameter(
final Bytes input, final BigInteger offset, final int length) {
if (BigInteger.valueOf(input.size()).compareTo(offset) <= 0) {
return BigInteger.ZERO;
public static long extractParameterLong(final Bytes input, final int offset, final int length) {
if (offset >= input.size() || length == 0) {
return 0;
}
Bytes num;
if (offset + length <= input.size()) {
num = input.slice(offset, length).trimLeadingZeros();
} else {
// Ethereum's memory is always infinitely full of zeros, but we don't store those zeros, just
// what we write. If we are asked for a range that is outside the written memory create a
// result of the correct size (defaults to zeros) and copy the memory we do have into there.
MutableBytes mut = MutableBytes.create(length);
input.slice(offset).copyTo(mut, 0);
num = mut.trimLeadingZeros();
}
return extractParameter(input, offset.intValue(), length);
return clampedToLong(num);
}
private static long square(final long n) {
return clampedMultiply(n, n);
}
private static BigInteger square(final BigInteger n) {
return n.multiply(n);
public PrecompileContractResult computeNative(final @Nonnull Bytes input) {
final int modulusLength = clampedToInt(modulusLength(input));
final IntByReference o_len = new IntByReference(modulusLength);
final byte[] result = new byte[modulusLength];
final int errorNo =
LibArithmetic.modexp_precompiled(input.toArrayUnsafe(), input.size(), result, o_len);
if (errorNo == 0) {
return PrecompileContractResult.success(Bytes.wrap(result, 0, o_len.getValue()));
} else {
LOG.trace("Error executing precompiled contract {}: {}", getName(), errorNo);
return PrecompileContractResult.halt(
null, Optional.of(ExceptionalHaltReason.PRECOMPILE_ERROR));
}
}
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.evm.precompile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator;
@ -143,6 +144,36 @@ public class MODEXPPrecompiledContractTest {
"5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500",
285900L,
87381L
},
{
"00000000000000000000000000000000000000000000000000000000000000ff2a1e5300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
null,
Long.MAX_VALUE,
Long.MAX_VALUE
},
{
"0000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010001",
null,
Long.MAX_VALUE,
Long.MAX_VALUE
},
{
"00000000000000000000000000000000000000000000000000000000000000e300000000000000000000000000000000000000000000000000",
null,
1580L,
280L
},
{
"00000000008000000000000000000000000000000000000000000000000000000000000400000000000000000000000a",
null,
Long.MAX_VALUE,
Long.MAX_VALUE
},
{
"0x00000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000",
null,
28928590731427686L,
Long.MAX_VALUE,
}
};
}
@ -160,10 +191,13 @@ public class MODEXPPrecompiledContractTest {
@Test
public void testPrecompiledContract() {
assumeThat(precompiledResult).isNotNull();
final Bytes input = Bytes.fromHexString(this.input);
final Bytes expected = Bytes.fromHexString(precompiledResult);
assertThat(byzantiumContract.compute(input, messageFrame)).isEqualTo(expected);
assertThat(berlinContract.compute(input, messageFrame)).isEqualTo(expected);
assertThat(byzantiumContract.computePrecompile(input, messageFrame).getOutput())
.isEqualTo(expected);
assertThat(berlinContract.computePrecompile(input, messageFrame).getOutput())
.isEqualTo(expected);
}
@Test

@ -3874,6 +3874,11 @@
<sha256 value="c363f28eade2e7486a0961d43b571421456e8d79363b58ae6a763f0ad338c0b4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.eclipse.platform" name="org.eclipse.swt" version="3.122.0">
<artifact name="org.eclipse.swt-3.122.0.pom">
<sha256 value="256e0626d5d690afc08479e01b9901aaa4cecd029d91b8f2c41f814e7a97aa83" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.eclipse.platform" name="org.eclipse.text" version="3.12.0">
<artifact name="org.eclipse.text-3.12.0.jar">
<sha256 value="457c1f8af07e870acce65130a2d9aa1e0fd95c92a7667bbd2db5bf7f9f19dd31" origin="Generated by Gradle"/>
@ -3953,48 +3958,59 @@
<sha256 value="6d535f94efb663bdb682c9f27a50335394688009642ba7a9677504bc1be4129b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.hyperledger.besu" name="blake2bf" version="0.6.1">
<artifact name="blake2bf-0.6.1.jar">
<sha256 value="809715f55a410fe6da444eacecd26f7065414c3ed40988b956c20b3a8e11abe2" origin="Generated by Gradle"/>
<component group="org.hyperledger.besu" name="arithmetic" version="0.7.1">
<artifact name="arithmetic-0.7.1.jar">
<sha256 value="38d5039419b5fc7c74da78be86cac2e11ce35bbfa9cd047f18da65b84ae2c2b4" origin="Generated by Gradle"/>
</artifact>
<artifact name="arithmetic-0.7.1.module">
<sha256 value="d508c4971ddaacdb75b7bec400fb292bbc2cec934ddad17106965dade7fffe4d" origin="Generated by Gradle"/>
</artifact>
<artifact name="arithmetic-0.7.1.pom">
<sha256 value="125ed16e300f225a36c5b64c5d24b6d01d7dd607a16ba906aac563d9f1584183" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.hyperledger.besu" name="blake2bf" version="0.7.1">
<artifact name="blake2bf-0.7.1.jar">
<sha256 value="e06429773d69d6fe5006ef7d7c45f11c2df56dc69f00a15e29519f17cc53754e" origin="Generated by Gradle"/>
</artifact>
<artifact name="blake2bf-0.6.1.module">
<sha256 value="e1e33b9398e192aa886ee4daee145e7c13359011e115fb7e3f4cb208546d1c23" origin="Generated by Gradle"/>
<artifact name="blake2bf-0.7.1.module">
<sha256 value="001b26fa58ac344c50e343866a76d0e26b36580de16043cc89c8c40fb22f5d7b" origin="Generated by Gradle"/>
</artifact>
<artifact name="blake2bf-0.6.1.pom">
<sha256 value="c59f7a81e169b9099d5da38c346b69b93e84a5ace7ab3a9d17d042929ed42614" origin="Generated by Gradle"/>
<artifact name="blake2bf-0.7.1.pom">
<sha256 value="58d501993a559e26cfab9151abcd52636386bd26029c84541c7f5f52e063f580" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.hyperledger.besu" name="bls12-381" version="0.6.1">
<artifact name="bls12-381-0.6.1.jar">
<sha256 value="b2a6d139a7d13e453272aa68ad448989c4651d69c0e24cd1632f76a3cdfad0ee" origin="Generated by Gradle"/>
<component group="org.hyperledger.besu" name="bls12-381" version="0.7.1">
<artifact name="bls12-381-0.7.1.jar">
<sha256 value="bf116b267489302805cf0b89f07cde139f1ab41089336cf0779dd269463b2acb" origin="Generated by Gradle"/>
</artifact>
<artifact name="bls12-381-0.6.1.module">
<sha256 value="281ab4d15d0e5a39dd63520c84c39c14e6a8d2eb75abe3da3e6d6fd01e3dfda2" origin="Generated by Gradle"/>
<artifact name="bls12-381-0.7.1.module">
<sha256 value="2d5677e9d4c9894c6fea7826c2a3ce50cf13d37a6874b03d4198587576aeed02" origin="Generated by Gradle"/>
</artifact>
<artifact name="bls12-381-0.6.1.pom">
<sha256 value="8cecb17d0ed5de8ab1059b8aee8da2928272e5601185f1a19a42b080b5724026" origin="Generated by Gradle"/>
<artifact name="bls12-381-0.7.1.pom">
<sha256 value="eb939df4f6ad29183c8f216e193a8aa83690b3e6314ed9f5627c9f9e8b9dd762" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.hyperledger.besu" name="secp256k1" version="0.6.1">
<artifact name="secp256k1-0.6.1.jar">
<sha256 value="c12e0ed581a727240bd16c67e137ee30ca1cda2a55238e58e745378a6159478e" origin="Generated by Gradle"/>
<component group="org.hyperledger.besu" name="secp256k1" version="0.7.1">
<artifact name="secp256k1-0.7.1.jar">
<sha256 value="80e5650d89099976c7932b4dfdfba0e09ecdd1650cdf0d27f45b9ade8e5ee71f" origin="Generated by Gradle"/>
</artifact>
<artifact name="secp256k1-0.6.1.module">
<sha256 value="93fa3b93096072e3934b5e0f9d863405c57158461c5d303dbc1978090613c865" origin="Generated by Gradle"/>
<artifact name="secp256k1-0.7.1.module">
<sha256 value="bdb425e3cee659f63eedbe1828d7cb4957a4f7ae2feca2364724fb5cb2dea8a1" origin="Generated by Gradle"/>
</artifact>
<artifact name="secp256k1-0.6.1.pom">
<sha256 value="f322921a5ef98b9012c6d4f83611bd8e5f56f3e26b26d3750179f1f47b153a72" origin="Generated by Gradle"/>
<artifact name="secp256k1-0.7.1.pom">
<sha256 value="f1685ec3024a025df9a87c36936fc607915f2178586aa8e5cea2f572920836c1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.hyperledger.besu" name="secp256r1" version="0.6.1">
<artifact name="secp256r1-0.6.1.jar">
<sha256 value="f0af26c128616a65616df3419bafeb14675079f3bbda12dfea829e37ab831e10" origin="Generated by Gradle"/>
<component group="org.hyperledger.besu" name="secp256r1" version="0.7.1">
<artifact name="secp256r1-0.7.1.jar">
<sha256 value="520f2d2b8d97ab070af317a740fb5be3c052b04c177e96779e46b0a8502aa35a" origin="Generated by Gradle"/>
</artifact>
<artifact name="secp256r1-0.6.1.module">
<sha256 value="399ea9bc9cfa7bd9ef95e8b8c3fff9839be61e3a3b4fe2fe4daa6d392aa7538c" origin="Generated by Gradle"/>
<artifact name="secp256r1-0.7.1.module">
<sha256 value="bb5179249597b5f685bcac1937af2d8f747797aff4d0bfe871513994aa66fc09" origin="Generated by Gradle"/>
</artifact>
<artifact name="secp256r1-0.6.1.pom">
<sha256 value="81017f26ffedf193875f64f6850d16b09e730dd43bf236360fbc97bc44dee722" origin="Generated by Gradle"/>
<artifact name="secp256r1-0.7.1.pom">
<sha256 value="b1bd15999f2f6d8a9ed8ac269d625b463055e7c60f6c6dbe5456d950a63c0a2a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.immutables" name="immutables" version="2.9.0">

@ -153,10 +153,13 @@ dependencyManagement {
dependency 'org.fusesource.jansi:jansi:2.4.0'
dependency 'org.hyperledger.besu:bls12-381:0.6.1'
dependency 'org.hyperledger.besu:secp256k1:0.6.1'
dependency 'org.hyperledger.besu:secp256r1:0.6.1'
dependency 'org.hyperledger.besu:blake2bf:0.6.1'
dependencySet(group: 'org.hyperledger.besu', version: '0.7.1') {
entry 'arithmetic'
entry 'bls12-381'
entry 'secp256k1'
entry 'secp256r1'
entry 'blake2bf'
}
dependency 'org.immutables:value-annotations:2.9.0'
dependency 'org.immutables:value:2.9.0'

Loading…
Cancel
Save