@ -14,39 +14,62 @@
* /
* /
package org.hyperledger.besu.evm.precompile ;
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.frame.MessageFrame ;
import org.hyperledger.besu.evm.gascalculator.GasCalculator ;
import org.hyperledger.besu.evm.gascalculator.GasCalculator ;
import org.hyperledger.besu.nativelib.arithmetic.LibArithmetic ;
import java.math.BigInteger ;
import java.math.BigInteger ;
import java.util.Arrays ;
import java.util.Arrays ;
import java.util.Optional ;
import javax.annotation.Nonnull ;
import javax.annotation.Nonnull ;
import com.sun.jna.ptr.IntByReference ;
import org.apache.tuweni.bytes.Bytes ;
import org.apache.tuweni.bytes.Bytes ;
import org.apache.tuweni.bytes.MutableBytes ;
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.
// The big integer modular exponentiation precompiled contract defined in EIP-198.
public class BigIntegerModularExponentiationPrecompiledContract
public class BigIntegerModularExponentiationPrecompiledContract
extends AbstractPrecompiledContract {
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 PARAMETER_LENGTH = 32 ;
private static final int BASE_LENGTH_OFFSET = 0 ;
private static final int BASE_LENGTH_OFFSET = 0 ;
private static final int EXPONENT_LENGTH_OFFSET = 32 ;
private static final int EXPONENT_LENGTH_OFFSET = 32 ;
private static final int MODULUS_LENGTH_OFFSET = 64 ;
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 ) {
public BigIntegerModularExponentiationPrecompiledContract ( final GasCalculator gasCalculator ) {
super ( "BigIntModExp" , gasCalculator ) ;
super ( "BigIntModExp" , gasCalculator ) ;
}
}
public static void disableNative ( ) {
useNative = false ;
}
public static boolean isNative ( ) {
return useNative ;
}
@Override
@Override
public long gasRequirement ( final Bytes input ) {
public long gasRequirement ( final Bytes input ) {
return gasCalculator ( ) . modExpGasCost ( input ) ;
return gasCalculator ( ) . modExpGasCost ( input ) ;
@ -56,24 +79,33 @@ public class BigIntegerModularExponentiationPrecompiledContract
@Override
@Override
public PrecompileContractResult computePrecompile (
public PrecompileContractResult computePrecompile (
final Bytes input , @Nonnull final MessageFrame messageFrame ) {
final Bytes input , @Nonnull final MessageFrame messageFrame ) {
final BigInteger baseLength = baseLength ( input ) ;
if ( useNative ) {
final BigInteger exponentLength = exponentLength ( input ) ;
return computeNative ( input ) ;
final BigInteger modulusLength = modulusLength ( 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
// If baseLength and modulusLength are zero
// we could have a massively overflowing exp because it wouldn't have been filtered out at the
// we could have a massively overflowing exp because it wouldn't have been filtered out at the
// gas cost phase
// gas cost phase
if ( baseLength . equals ( BigInteger . ZERO ) & & modulusLength . equals ( BigInteger . ZERO ) ) {
if ( ( baseLength = = 0 ) & & ( modulusLength = = 0 ) ) {
return PrecompileContractResult . success ( Bytes . EMPTY ) ;
return PrecompileContractResult . success ( Bytes . EMPTY ) ;
}
}
final BigInteger exponentOffset = BASE_OFFSET . add ( baseLength ) ;
final int exponentOffset = BASE_OFFSET + baseLength ;
final BigInteger modulusOffset = exponentOffset . add ( exponentLength ) ;
final int modulusOffset = exponentOffset + exponentLength ;
final BigInteger base = extractParameter ( input , BASE_OFFSET , baseLength . intValue ( ) ) ;
final BigInteger base = extractParameter ( input , BASE_OFFSET , baseLength ) ;
final BigInteger exp = extractParameter ( input , exponentOffset , exponentLength . intValue ( ) ) ;
final BigInteger exp = extractParameter ( input , exponentOffset , exponentLength ) ;
final BigInteger mod = extractParameter ( input , modulusOffset , modulusLength . intValue ( ) ) ;
final BigInteger mod = extractParameter ( input , modulusOffset , modulusLength ) ;
final Bytes modExp ;
final Bytes modExp ;
// Result must be the length of the modulus.
// 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 ) {
if ( mod . compareTo ( BigInteger . ZERO ) = = 0 ) {
modExp = MutableBytes . EMPTY ;
modExp = MutableBytes . EMPTY ;
} else {
} else {
@ -87,30 +119,29 @@ public class BigIntegerModularExponentiationPrecompiledContract
}
}
// Equation to estimate the multiplication complexity.
// Equation to estimate the multiplication complexity.
public static BigInteger multiplicationComplexity ( final BigInteger x ) {
public static long multiplicationComplexity ( final long x ) {
if ( x . compareTo ( BIGINT_64 ) < = 0 ) {
if ( x < = 64 ) {
return square ( x ) ;
return square ( x ) ;
} else if ( x . compareTo ( BIGINT_1024 ) < = 0 ) {
} else if ( x < = 1 024 ) {
return square ( x ) . divide ( BIGINT_4 ) . add ( BIGINT_96 . multiply ( x ) ) . subtract ( BIGINT_3072 ) ;
return ( square ( x ) / 4 ) + ( x * 96 ) - 3072 ;
} else {
} 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 ) {
public static long baseLength ( final Bytes input ) {
return extractParameter ( input , BASE_LENGTH_OFFSET , PARAMETER_LENGTH ) ;
return extractParameterLong ( input , BASE_LENGTH_OFFSET , PARAMETER_LENGTH ) ;
}
}
public static BigInteger exponentLength ( final Bytes input ) {
public static long exponentLength ( final Bytes input ) {
return extractParameter ( input , EXPONENT_LENGTH_OFFSET , PARAMETER_LENGTH ) ;
return extractParameterLong ( input , EXPONENT_LENGTH_OFFSET , PARAMETER_LENGTH ) ;
}
}
public static BigInteger modulusLength ( final Bytes input ) {
public static long modulusLength ( final Bytes input ) {
return extractParameter ( input , MODULUS_LENGTH_OFFSET , PARAMETER_LENGTH ) ;
return extractParameterLong ( input , MODULUS_LENGTH_OFFSET , PARAMETER_LENGTH ) ;
}
}
private static BigInteger extractParameter (
public static BigInteger extractParameter ( final Bytes input , final int offset , final int length ) {
final Bytes input , final int offset , final int length ) {
if ( offset > input . size ( ) | | length = = 0 ) {
if ( offset > input . size ( ) | | length = = 0 ) {
return BigInteger . ZERO ;
return BigInteger . ZERO ;
}
}
@ -118,15 +149,41 @@ public class BigIntegerModularExponentiationPrecompiledContract
return new BigInteger ( 1 , raw ) ;
return new BigInteger ( 1 , raw ) ;
}
}
public static BigInteger extractParameter (
public static long extractParameterLong ( final Bytes input , final int offset , final int length ) {
final Bytes input , final BigInteger offset , final int length ) {
if ( offset > = input . size ( ) | | length = = 0 ) {
if ( BigInteger . valueOf ( input . size ( ) ) . compareTo ( offset ) < = 0 ) {
return 0 ;
return BigInteger . ZERO ;
}
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 clampedToLong ( num ) ;
}
}
return extractParameter ( input , offset . intValue ( ) , length ) ;
private static long square ( final long n ) {
return clampedMultiply ( n , n ) ;
}
}
private static BigInteger square ( final BigInteger n ) {
public PrecompileContractResult computeNative ( final @Nonnull Bytes input ) {
return n . multiply ( n ) ;
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 ) ) ;
}
}
}
}
}