Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 102 additions & 29 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,18 @@ package vm
import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/crypto/blake2b"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/blake2b"
"github.com/ethereum/go-ethereum/crypto/bn256"
"github.com/harmony-one/harmony/internal/params"
"golang.org/x/crypto/ripemd160"

//Needed for SHA3-256 FIPS202
"encoding/hex"

"golang.org/x/crypto/sha3"
)

Expand Down Expand Up @@ -62,7 +58,8 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},

common.BytesToAddress([]byte{6}): &bn256AddByzantium{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{},
common.BytesToAddress([]byte{8}): &bn256PairingByzantium{},
Expand All @@ -75,7 +72,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
Expand All @@ -89,7 +86,7 @@ var PrecompiledContractsVRF = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
Expand All @@ -104,7 +101,7 @@ var PrecompiledContractsSHA3FIPS = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
Expand All @@ -123,7 +120,28 @@ var PrecompiledContractsStaking = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},

common.BytesToAddress([]byte{251}): &epoch{},
// marked nil to ensure no overwrite
common.BytesToAddress([]byte{252}): nil, // used by WriteCapablePrecompiledContractsStaking
common.BytesToAddress([]byte{253}): &sha3fip{},
common.BytesToAddress([]byte{254}): &ecrecoverPublicKey{},
common.BytesToAddress([]byte{255}): &vrf{},
}

// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
// contracts used in the Berlin release.
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
Expand Down Expand Up @@ -246,13 +264,18 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
}

// bigModExp implements a native big integer exponential modular operation.
type bigModExp struct{}
type bigModExp struct {
eip2565 bool
}

var (
big1 = big.NewInt(1)
big3 = big.NewInt(3)
big4 = big.NewInt(4)
big7 = big.NewInt(7)
big8 = big.NewInt(8)
big16 = big.NewInt(16)
big20 = big.NewInt(20)
big32 = big.NewInt(32)
big64 = big.NewInt(64)
big96 = big.NewInt(96)
Expand All @@ -262,6 +285,36 @@ var (
big199680 = big.NewInt(199680)
)

// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198
//
// def mult_complexity(x):
//
// if x <= 64: return x ** 2
// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
// else: return x ** 2 // 16 + 480 * x - 199680
//
// where is x is max(length_of_MODULUS, length_of_BASE)
func modexpMultComplexity(x *big.Int) *big.Int {
switch {
case x.Cmp(big64) <= 0:
x.Mul(x, x) // x ** 2
case x.Cmp(big1024) <= 0:
// (x ** 2 // 4 ) + ( 96 * x - 3072)
x = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(x, x), big4),
new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072),
)
default:
// (x ** 2 // 16) + (480 * x - 199680)
x = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(x, x), big16),
new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680),
)
}
return x
}

// RequiredGas returns the gas required to execute the pre-compiled contract.
// RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bigModExp) RequiredGas(input []byte) uint64 {
var (
Expand Down Expand Up @@ -293,28 +346,48 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
adjExpLen := new(big.Int)
if expLen.Cmp(big32) > 0 {
adjExpLen.Sub(expLen, big32)
adjExpLen.Mul(big8, adjExpLen)
adjExpLen.Lsh(adjExpLen, 3)
}
adjExpLen.Add(adjExpLen, big.NewInt(int64(msb)))

// Calculate the gas cost of the operation
gas := new(big.Int).Set(math.BigMax(modLen, baseLen))
switch {
case gas.Cmp(big64) <= 0:
gas := new(big.Int)
if modLen.Cmp(baseLen) < 0 {
gas.Set(baseLen)
} else {
gas.Set(modLen)
}
if c.eip2565 {
// EIP-2565 has three changes
// 1. Different multComplexity (inlined here)
// in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565):
//
// def mult_complexity(x):
// ceiling(x/8)^2
//
//where is x is max(length_of_MODULUS, length_of_BASE)
gas.Add(gas, big7)
gas.Rsh(gas, 3)
gas.Mul(gas, gas)
case gas.Cmp(big1024) <= 0:
gas = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(gas, gas), big4),
new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072),
)
default:
gas = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(gas, gas), big16),
new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680),
)

if adjExpLen.Cmp(big1) > 0 {
gas.Mul(gas, adjExpLen)
}
// 2. Different divisor (`GQUADDIVISOR`) (3)
gas.Div(gas, big3)
if gas.BitLen() > 64 {
return math.MaxUint64
}
// 3. Minimum price of 200 gas
if gas.Uint64() < 200 {
return 200
}
return gas.Uint64()
}
gas = modexpMultComplexity(gas)
if adjExpLen.Cmp(big1) > 0 {
gas.Mul(gas, adjExpLen)
}
gas.Mul(gas, math.BigMax(adjExpLen, big1))
gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv))
gas.Div(gas, big20)

if gas.BitLen() > 64 {
return math.MaxUint64
Expand Down
78 changes: 77 additions & 1 deletion core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package vm

import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"os"
"reflect"
"testing"

Expand All @@ -41,6 +43,27 @@ type precompiledFailureTest struct {
name string
}

// allPrecompiles does not map to the actual set of precompiles, as it also contains
// repriced versions of precompiles at certain slots
var allPrecompiles = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{251}): &epoch{},
// marked nil to ensure no overwrite
common.BytesToAddress([]byte{252}): nil, // used by WriteCapablePrecompiledContractsStaking
common.BytesToAddress([]byte{253}): &sha3fip{},
common.BytesToAddress([]byte{254}): &ecrecoverPublicKey{},
common.BytesToAddress([]byte{255}): &vrf{},
}

// modexpTests are the test and benchmark data for the modexp precompiled contract.
var modexpTests = []precompiledTest{
{
Expand Down Expand Up @@ -399,7 +422,8 @@ var blake2FTests = []precompiledTest{
}

func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
p := PrecompiledContractsSHA3FIPS[common.HexToAddress(addr)]
hex := common.HexToAddress(addr)
p := allPrecompiles[hex]
in := common.Hex2Bytes(test.input)
contract := NewContract(AccountRef(common.HexToAddress("1337")),
nil, new(big.Int), p.RequiredGas(in))
Expand Down Expand Up @@ -537,6 +561,9 @@ func TestPrecompiledModExp(t *testing.T) {
}
}

func TestPrecompiledModExpEip2565(t *testing.T) { testJson("modexp_eip2565", "f5", t) }
func BenchmarkPrecompiledModExpEip2565(b *testing.B) { benchJson("modexp_eip2565", "f5", b) }

// Benchmarks the sample inputs from the ModExp EIP 198.
func BenchmarkPrecompiledModExp(bench *testing.B) {
for _, test := range modexpTests {
Expand Down Expand Up @@ -659,7 +686,56 @@ func TestPrecompiledEcrecover(t *testing.T) {
for _, test := range ecRecoverTests {
testPrecompiled("01", test, t)
}
}

func testJson(name, addr string, t *testing.T) {
tests, err := loadJson(name)
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
testPrecompiled(addr, test, t)
}
}

func testJsonFail(name, addr string, t *testing.T) {
tests, err := loadJsonFail(name)
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
testPrecompiledFailure(addr, test, t)
}
}

func benchJson(name, addr string, b *testing.B) {
tests, err := loadJson(name)
if err != nil {
b.Fatal(err)
}
for _, test := range tests {
benchmarkPrecompiled(addr, test, b)
}
}

func loadJson(name string) ([]precompiledTest, error) {
data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name))
if err != nil {
return nil, err
}
var testcases []precompiledTest
err = json.Unmarshal(data, &testcases)
return testcases, err
}

func loadJsonFail(name string) ([]precompiledFailureTest, error) {
data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name))
if err != nil {
return nil, err
}
var testcases []precompiledFailureTest
err = json.Unmarshal(data, &testcases)
return testcases, err
}

// sha3fip test vectors
Expand Down
Loading