core: 4844 opcode and precompile (#27356)

* core: crypto: implement BLOBHASH and pointEval precompile

* core: crypto: fixed nitpicks, moved precompile return value

* core/vm: fix review comments
This commit is contained in:
Marius van der Wijden 2023-06-05 15:43:25 +02:00 committed by GitHub
parent 380fb4e249
commit c537ace249
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 199 additions and 13 deletions

View File

@ -128,6 +128,7 @@ func runCmd(ctx *cli.Context) error {
receiver = common.BytesToAddress([]byte("receiver")) receiver = common.BytesToAddress([]byte("receiver"))
genesisConfig *core.Genesis genesisConfig *core.Genesis
preimages = ctx.Bool(DumpFlag.Name) preimages = ctx.Bool(DumpFlag.Name)
blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests
) )
if ctx.Bool(MachineFlag.Name) { if ctx.Bool(MachineFlag.Name) {
tracer = logger.NewJSONLogger(logconfig, os.Stdout) tracer = logger.NewJSONLogger(logconfig, os.Stdout)
@ -217,6 +218,7 @@ func runCmd(ctx *cli.Context) error {
Time: genesisConfig.Timestamp, Time: genesisConfig.Timestamp,
Coinbase: genesisConfig.Coinbase, Coinbase: genesisConfig.Coinbase,
BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), BlockNumber: new(big.Int).SetUint64(genesisConfig.Number),
BlobHashes: blobHashes,
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: tracer, Tracer: tracer,
}, },

View File

@ -74,6 +74,7 @@ func NewEVMTxContext(msg *Message) vm.TxContext {
return vm.TxContext{ return vm.TxContext{
Origin: msg.From, Origin: msg.From,
GasPrice: new(big.Int).Set(msg.GasPrice), GasPrice: new(big.Int).Set(msg.GasPrice),
BlobHashes: msg.BlobHashes,
} }
} }

View File

@ -135,6 +135,7 @@ type Message struct {
GasTipCap *big.Int GasTipCap *big.Int
Data []byte Data []byte
AccessList types.AccessList AccessList types.AccessList
BlobHashes []common.Hash
// When SkipAccountChecks is true, the message nonce is not checked against the // When SkipAccountChecks is true, the message nonce is not checked against the
// account nonce in state. It also disables checking that the sender is an EOA. // account nonce in state. It also disables checking that the sender is an EOA.
@ -155,6 +156,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
Data: tx.Data(), Data: tx.Data(),
AccessList: tx.AccessList(), AccessList: tx.AccessList(),
SkipAccountChecks: false, SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(),
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {

View File

@ -20,6 +20,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -28,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/blake2b"
"github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/bls12381"
"github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/crypto/bn256"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
@ -90,6 +92,21 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{9}): &blake2F{}, common.BytesToAddress([]byte{9}): &blake2F{},
} }
// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum
// contracts used in the Cancun release.
var PrecompiledContractsCancun = 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{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{20}): &kzgPointEvaluation{},
}
// PrecompiledContractsBLS contains the set of pre-compiled Ethereum // PrecompiledContractsBLS contains the set of pre-compiled Ethereum
// contracts specified in EIP-2537. These are exported for testing purposes. // contracts specified in EIP-2537. These are exported for testing purposes.
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
@ -105,6 +122,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
} }
var ( var (
PrecompiledAddressesCancun []common.Address
PrecompiledAddressesBerlin []common.Address PrecompiledAddressesBerlin []common.Address
PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesIstanbul []common.Address
PrecompiledAddressesByzantium []common.Address PrecompiledAddressesByzantium []common.Address
@ -124,11 +142,16 @@ func init() {
for k := range PrecompiledContractsBerlin { for k := range PrecompiledContractsBerlin {
PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k)
} }
for k := range PrecompiledContractsCancun {
PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k)
}
} }
// ActivePrecompiles returns the precompiles enabled with the current configuration. // ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules params.Rules) []common.Address { func ActivePrecompiles(rules params.Rules) []common.Address {
switch { switch {
case rules.IsCancun:
return PrecompiledAddressesCancun
case rules.IsBerlin: case rules.IsBerlin:
return PrecompiledAddressesBerlin return PrecompiledAddressesBerlin
case rules.IsIstanbul: case rules.IsIstanbul:
@ -1048,3 +1071,67 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
// Encode the G2 point to 256 bytes // Encode the G2 point to 256 bytes
return g.EncodePoint(r), nil return g.EncodePoint(r), nil
} }
// kzgPointEvaluation implements the EIP-4844 point evaluation precompile.
type kzgPointEvaluation struct{}
// RequiredGas estimates the gas required for running the point evaluation precompile.
func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 {
return params.BlobTxPointEvaluationPrecompileGas
}
const (
blobVerifyInputLength = 192 // Max input length for the point evaluation precompile.
blobCommitmentVersionKZG uint8 = 0x01 // Version byte for the point evaluation precompile.
blobPrecompileReturnValue = "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
)
var (
errBlobVerifyInvalidInputLength = errors.New("invalid input length")
errBlobVerifyMismatchedVersion = errors.New("mismatched versioned hash")
errBlobVerifyKZGProof = errors.New("error verifying kzg proof")
)
// Run executes the point evaluation precompile.
func (b *kzgPointEvaluation) Run(input []byte) ([]byte, error) {
if len(input) != blobVerifyInputLength {
return nil, errBlobVerifyInvalidInputLength
}
// versioned hash: first 32 bytes
var versionedHash common.Hash
copy(versionedHash[:], input[:])
var (
point kzg4844.Point
claim kzg4844.Claim
)
// Evaluation point: next 32 bytes
copy(point[:], input[32:])
// Expected output: next 32 bytes
copy(claim[:], input[64:])
// input kzg point: next 48 bytes
var commitment kzg4844.Commitment
copy(commitment[:], input[96:])
if kZGToVersionedHash(commitment) != versionedHash {
return nil, errBlobVerifyMismatchedVersion
}
// Proof: next 48 bytes
var proof kzg4844.Proof
copy(proof[:], input[144:])
if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil {
return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err)
}
return common.Hex2Bytes(blobPrecompileReturnValue), nil
}
// kZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844
func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash {
h := sha256.Sum256(kzg[:])
h[0] = blobCommitmentVersionKZG
return h
}

View File

@ -65,6 +65,7 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{16}): &bls12381Pairing{}, common.BytesToAddress([]byte{16}): &bls12381Pairing{},
common.BytesToAddress([]byte{17}): &bls12381MapG1{}, common.BytesToAddress([]byte{17}): &bls12381MapG1{},
common.BytesToAddress([]byte{18}): &bls12381MapG2{}, common.BytesToAddress([]byte{18}): &bls12381MapG2{},
common.BytesToAddress([]byte{20}): &kzgPointEvaluation{},
} }
// EIP-152 test vectors // EIP-152 test vectors
@ -311,6 +312,7 @@ func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp",
func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "10", t) } func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "10", t) }
func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", t) } func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", t) }
func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "12", t) } func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "12", t) }
func TestPrecompiledPointEvaluation(t *testing.T) { testJson("pointEvaluation", "14", t) }
func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "0a", b) } func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "0a", b) }
func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) } func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) }

View File

@ -235,9 +235,32 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
return nil, nil return nil, nil
} }
// ebnable3860 enables "EIP-3860: Limit and meter initcode" // enable3860 enables "EIP-3860: Limit and meter initcode"
// https://eips.ethereum.org/EIPS/eip-3860 // https://eips.ethereum.org/EIPS/eip-3860
func enable3860(jt *JumpTable) { func enable3860(jt *JumpTable) {
jt[CREATE].dynamicGas = gasCreateEip3860 jt[CREATE].dynamicGas = gasCreateEip3860
jt[CREATE2].dynamicGas = gasCreate2Eip3860 jt[CREATE2].dynamicGas = gasCreate2Eip3860
} }
// opBlobHash implements the BLOBHASH opcode
func opBlobHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
index := scope.Stack.peek()
if index.LtUint64(uint64(len(interpreter.evm.TxContext.BlobHashes))) {
blobHash := interpreter.evm.TxContext.BlobHashes[index.Uint64()]
index.SetBytes32(blobHash[:])
} else {
index.Clear()
}
return nil, nil
}
// enable4844 applies EIP-4844 (DATAHASH opcode)
func enable4844(jt *JumpTable) {
// New opcode
jt[BLOBHASH] = &operation{
execute: opBlobHash,
constantGas: GasFastestStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
}

View File

@ -43,6 +43,8 @@ type (
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract var precompiles map[common.Address]PrecompiledContract
switch { switch {
case evm.chainRules.IsCancun:
precompiles = PrecompiledContractsCancun
case evm.chainRules.IsBerlin: case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin precompiles = PrecompiledContractsBerlin
case evm.chainRules.IsIstanbul: case evm.chainRules.IsIstanbul:
@ -83,6 +85,7 @@ type TxContext struct {
// Message information // Message information
Origin common.Address // Provides information for ORIGIN Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE GasPrice *big.Int // Provides information for GASPRICE
BlobHashes []common.Hash // Provides information for BLOBHASH
} }
// EVM is the Ethereum Virtual Machine base object and provides // EVM is the Ethereum Virtual Machine base object and provides

View File

@ -746,3 +746,45 @@ func TestRandom(t *testing.T) {
} }
} }
} }
func TestBlobHash(t *testing.T) {
type testcase struct {
name string
idx uint64
expect common.Hash
hashes []common.Hash
}
var (
zero = common.Hash{0}
one = common.Hash{1}
two = common.Hash{2}
three = common.Hash{3}
)
for _, tt := range []testcase{
{name: "[{1}]", idx: 0, expect: one, hashes: []common.Hash{one}},
{name: "[1,{2},3]", idx: 2, expect: three, hashes: []common.Hash{one, two, three}},
{name: "out-of-bounds (empty)", idx: 10, expect: zero, hashes: []common.Hash{}},
{name: "out-of-bounds", idx: 25, expect: zero, hashes: []common.Hash{one, two, three}},
{name: "out-of-bounds (nil)", idx: 25, expect: zero, hashes: nil},
} {
var (
env = NewEVM(BlockContext{}, TxContext{BlobHashes: tt.hashes}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
stack.push(uint256.NewInt(tt.idx))
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes()))
if overflow {
t.Errorf("Testcase %v: invalid overflow", tt.name)
}
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual)
}
}
}

View File

@ -56,6 +56,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
// If jump table was not initialised we set the default one. // If jump table was not initialised we set the default one.
var table *JumpTable var table *JumpTable
switch { switch {
case evm.chainRules.IsCancun:
table = &cancunInstructionSet
case evm.chainRules.IsShanghai: case evm.chainRules.IsShanghai:
table = &shanghaiInstructionSet table = &shanghaiInstructionSet
case evm.chainRules.IsMerge: case evm.chainRules.IsMerge:

View File

@ -56,6 +56,7 @@ var (
londonInstructionSet = newLondonInstructionSet() londonInstructionSet = newLondonInstructionSet()
mergeInstructionSet = newMergeInstructionSet() mergeInstructionSet = newMergeInstructionSet()
shanghaiInstructionSet = newShanghaiInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet()
cancunInstructionSet = newCancunInstructionSet()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
@ -79,6 +80,12 @@ func validate(jt JumpTable) JumpTable {
return jt return jt
} }
func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // BLOBHASH opcode
return validate(instructionSet)
}
func newShanghaiInstructionSet() JumpTable { func newShanghaiInstructionSet() JumpTable {
instructionSet := newMergeInstructionSet() instructionSet := newMergeInstructionSet()
enable3855(&instructionSet) // PUSH0 instruction enable3855(&instructionSet) // PUSH0 instruction

View File

@ -100,6 +100,7 @@ const (
CHAINID OpCode = 0x46 CHAINID OpCode = 0x46
SELFBALANCE OpCode = 0x47 SELFBALANCE OpCode = 0x47
BASEFEE OpCode = 0x48 BASEFEE OpCode = 0x48
BLOBHASH OpCode = 0x49
) )
// 0x50 range - 'storage' and execution. // 0x50 range - 'storage' and execution.
@ -288,6 +289,7 @@ var opCodeToString = map[OpCode]string{
CHAINID: "CHAINID", CHAINID: "CHAINID",
SELFBALANCE: "SELFBALANCE", SELFBALANCE: "SELFBALANCE",
BASEFEE: "BASEFEE", BASEFEE: "BASEFEE",
BLOBHASH: "BLOBHASH",
// 0x50 range - 'storage' and execution. // 0x50 range - 'storage' and execution.
POP: "POP", POP: "POP",
@ -445,6 +447,7 @@ var stringToOp = map[string]OpCode{
"CALLDATACOPY": CALLDATACOPY, "CALLDATACOPY": CALLDATACOPY,
"CHAINID": CHAINID, "CHAINID": CHAINID,
"BASEFEE": BASEFEE, "BASEFEE": BASEFEE,
"BLOBHASH": BLOBHASH,
"DELEGATECALL": DELEGATECALL, "DELEGATECALL": DELEGATECALL,
"STATICCALL": STATICCALL, "STATICCALL": STATICCALL,
"CODESIZE": CODESIZE, "CODESIZE": CODESIZE,

View File

@ -25,6 +25,7 @@ func NewEnv(cfg *Config) *vm.EVM {
txContext := vm.TxContext{ txContext := vm.TxContext{
Origin: cfg.Origin, Origin: cfg.Origin,
GasPrice: cfg.GasPrice, GasPrice: cfg.GasPrice,
BlobHashes: cfg.BlobHashes,
} }
blockContext := vm.BlockContext{ blockContext := vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,

View File

@ -44,6 +44,7 @@ type Config struct {
Debug bool Debug bool
EVMConfig vm.Config EVMConfig vm.Config
BaseFee *big.Int BaseFee *big.Int
BlobHashes []common.Hash
State *state.StateDB State *state.StateDB
GetHashFn func(n uint64) common.Hash GetHashFn func(n uint64) common.Hash

View File

@ -0,0 +1,9 @@
[
{
"Input": "013c03613f6fc558fb7e61e75602241ed9a2f04e36d8670aadd286e71b5ca9cc420000000000000000000000000000000000000000000000000000000000000031e5a2356cbc2ef6a733eae8d54bf48719ae3d990017ca787c419c7d369f8e3c83fac17c3f237fc51f90e2c660eb202a438bc2025baded5cd193c1a018c5885bc9281ba704d5566082e851235c7be763b2a99adff965e0a121ee972ebc472d02944a74f5c6243e14052e105124b70bf65faf85ad3a494325e269fad097842cba",
"Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001",
"Name": "pointEvaluation1",
"Gas": 50000,
"NoBenchmark": false
}
]

View File

@ -166,6 +166,7 @@ const (
BlobTxDataGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size) BlobTxDataGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size)
BlobTxMinDataGasprice = 1 // Minimum gas price for data blobs BlobTxMinDataGasprice = 1 // Minimum gas price for data blobs
BlobTxDataGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for data gas price BlobTxDataGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for data gas price
BlobTxPointEvaluationPrecompileGas = 50000 // Gas price for the point evaluation precompile.
) )
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations