diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 52d7c3fa30f98..e61661a7afb22 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -128,6 +128,7 @@ func runCmd(ctx *cli.Context) error { receiver = common.BytesToAddress([]byte("receiver")) genesisConfig *core.Genesis preimages = ctx.Bool(DumpFlag.Name) + blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests ) if ctx.Bool(MachineFlag.Name) { tracer = logger.NewJSONLogger(logconfig, os.Stdout) @@ -217,6 +218,7 @@ func runCmd(ctx *cli.Context) error { Time: genesisConfig.Timestamp, Coinbase: genesisConfig.Coinbase, BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), + BlobHashes: blobHashes, EVMConfig: vm.Config{ Tracer: tracer, }, diff --git a/core/evm.go b/core/evm.go index bd4f2b0e55f1c..b7ff7790295be 100644 --- a/core/evm.go +++ b/core/evm.go @@ -72,8 +72,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common // NewEVMTxContext creates a new transaction context for a single transaction. func NewEVMTxContext(msg *Message) vm.TxContext { return vm.TxContext{ - Origin: msg.From, - GasPrice: new(big.Int).Set(msg.GasPrice), + Origin: msg.From, + GasPrice: new(big.Int).Set(msg.GasPrice), + BlobHashes: msg.BlobHashes, } } diff --git a/core/state_transition.go b/core/state_transition.go index 72f975775c1b6..022238c8d640b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -135,6 +135,7 @@ type Message struct { GasTipCap *big.Int Data []byte AccessList types.AccessList + BlobHashes []common.Hash // 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. @@ -155,6 +156,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In Data: tx.Data(), AccessList: tx.AccessList(), SkipAccountChecks: false, + BlobHashes: tx.BlobHashes(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index aa4a3f13df5ec..c4c42fb5e6fe6 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/bn256" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "golang.org/x/crypto/ripemd160" ) @@ -90,6 +91,21 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } +// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum +// contracts used in the Berlin 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}): &pointEvaluation{}, +} + // PrecompiledContractsBLS contains the set of pre-compiled Ethereum // contracts specified in EIP-2537. These are exported for testing purposes. var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ @@ -105,6 +121,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ } var ( + PrecompiledAddressesCancun []common.Address PrecompiledAddressesBerlin []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address @@ -124,11 +141,16 @@ func init() { for k := range PrecompiledContractsBerlin { PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) } + for k := range PrecompiledContractsCancun { + PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k) + } } // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { + case rules.IsCancun: + return PrecompiledAddressesCancun case rules.IsBerlin: return PrecompiledAddressesBerlin case rules.IsIstanbul: @@ -1048,3 +1070,66 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { // Encode the G2 point to 256 bytes return g.EncodePoint(r), nil } + +// pointEvaluation implements the EIP-4844 point evaluation precompile. +type pointEvaluation struct{} + +// RequiredGas estimates the gas required for running the point evaluation precompile. +func (b *pointEvaluation) RequiredGas(input []byte) uint64 { + return params.PointEvaluationPrecompileGas +} + +const ( + pointBlobVerifyInputLength = 192 // Max input length for the point evaluation precompile. + blobVerifyKZGVersion uint8 = 0x01 // Version byte for the point evaluation precompile. +) + +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 *pointEvaluation) Run(input []byte) ([]byte, error) { + if len(input) != pointBlobVerifyInputLength { + return nil, errBlobVerifyInvalidInputLength + } + // versioned hash: first 32 bytes + var versionedHash common.Hash + copy(versionedHash[:], input[:32]) + + var ( + point kzg4844.Point + claim kzg4844.Claim + ) + // Evaluation point: next 32 bytes + copy(point[:], input[32:64]) + // Expected output: next 32 bytes + copy(claim[:], input[64:96]) + + // input kzg point: next 48 bytes + var commitment kzg4844.Commitment + copy(commitment[:], input[96:144]) + if KZGToVersionedHash(commitment) != versionedHash { + return nil, errBlobVerifyMismatchedVersion + } + + // Proof: next 48 bytes + var proof kzg4844.Proof + copy(proof[:], input[144:pointBlobVerifyInputLength]) + + if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil { + return nil, errors.Join(errBlobVerifyKZGProof, err) + } + + return common.CopyBytes(kzg4844.PrecompileReturnValue[:]), nil +} + +// KZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844 +func KZGToVersionedHash(kzg kzg4844.Commitment) common.Hash { + h := sha256.Sum256(kzg[:]) + h[0] = blobVerifyKZGVersion + + return h +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index b22d999e6cd9a..36dcd4076064d 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/kzg4844" ) // precompiledTest defines the input/output pairs for precompiled contract tests. @@ -65,6 +66,7 @@ var allPrecompiles = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{16}): &bls12381Pairing{}, common.BytesToAddress([]byte{17}): &bls12381MapG1{}, common.BytesToAddress([]byte{18}): &bls12381MapG2{}, + common.BytesToAddress([]byte{20}): &pointEvaluation{}, } // EIP-152 test vectors @@ -311,6 +313,10 @@ func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp", func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "10", t) } func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", t) } func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "12", t) } +func TestPrecompiledPointEvaluation(t *testing.T) { + kzg4844.UseCKZG(false) // We need to call this to compute the PrecompileReturnValue + testJson("pointEvaluation", "14", t) +} func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "0a", b) } func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) } diff --git a/core/vm/eips.go b/core/vm/eips.go index 29ff27c552683..ff1f132cb3551 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -235,9 +235,32 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by 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 func enable3860(jt *JumpTable) { jt[CREATE].dynamicGas = gasCreateEip3860 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), + } +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 01017572d178c..36336d8cbd4cf 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -43,6 +43,8 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { + case evm.chainRules.IsCancun: + precompiles = PrecompiledContractsCancun case evm.chainRules.IsBerlin: precompiles = PrecompiledContractsBerlin case evm.chainRules.IsIstanbul: @@ -81,8 +83,9 @@ type BlockContext struct { // All fields can change between transactions. type TxContext struct { // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE + BlobHashes []common.Hash // Provides information for BLOBHASH } // EVM is the Ethereum Virtual Machine base object and provides diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 2a66f8163e4f6..6fa0737e82422 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -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) + } + } +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 5b2082bc9e90f..873337850e6f3 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -56,6 +56,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // If jump table was not initialised we set the default one. var table *JumpTable switch { + case evm.chainRules.IsCancun: + table = &cancunInstructionSet case evm.chainRules.IsShanghai: table = &shanghaiInstructionSet case evm.chainRules.IsMerge: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a45287de80dfe..41a89ec6b6790 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -56,6 +56,7 @@ var ( londonInstructionSet = newLondonInstructionSet() mergeInstructionSet = newMergeInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() + cancunInstructionSet = newCancunInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -79,6 +80,12 @@ func validate(jt JumpTable) JumpTable { return jt } +func newCancunInstructionSet() JumpTable { + instructionSet := newShanghaiInstructionSet() + enable4844(&instructionSet) // BLOBHASH opcode + return validate(instructionSet) +} + func newShanghaiInstructionSet() JumpTable { instructionSet := newMergeInstructionSet() enable3855(&instructionSet) // PUSH0 instruction diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 910491c60078f..ed074674b2715 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -100,6 +100,7 @@ const ( CHAINID OpCode = 0x46 SELFBALANCE OpCode = 0x47 BASEFEE OpCode = 0x48 + BLOBHASH OpCode = 0x49 ) // 0x50 range - 'storage' and execution. @@ -288,6 +289,7 @@ var opCodeToString = map[OpCode]string{ CHAINID: "CHAINID", SELFBALANCE: "SELFBALANCE", BASEFEE: "BASEFEE", + BLOBHASH: "BLOBHASH", // 0x50 range - 'storage' and execution. POP: "POP", @@ -445,6 +447,7 @@ var stringToOp = map[string]OpCode{ "CALLDATACOPY": CALLDATACOPY, "CHAINID": CHAINID, "BASEFEE": BASEFEE, + "BLOBHASH": BLOBHASH, "DELEGATECALL": DELEGATECALL, "STATICCALL": STATICCALL, "CODESIZE": CODESIZE, diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index dcb0974284548..ffc631a90c95a 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -23,8 +23,9 @@ import ( func NewEnv(cfg *Config) *vm.EVM { txContext := vm.TxContext{ - Origin: cfg.Origin, - GasPrice: cfg.GasPrice, + Origin: cfg.Origin, + GasPrice: cfg.GasPrice, + BlobHashes: cfg.BlobHashes, } blockContext := vm.BlockContext{ CanTransfer: core.CanTransfer, diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 951a472e424ec..a3e75c67212c3 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -44,6 +44,7 @@ type Config struct { Debug bool EVMConfig vm.Config BaseFee *big.Int + BlobHashes []common.Hash State *state.StateDB GetHashFn func(n uint64) common.Hash diff --git a/core/vm/testdata/precompiles/pointEvaluation.json b/core/vm/testdata/precompiles/pointEvaluation.json new file mode 100644 index 0000000000000..2fee5baf2424d --- /dev/null +++ b/core/vm/testdata/precompiles/pointEvaluation.json @@ -0,0 +1,9 @@ +[ + { + "Input": "013c03613f6fc558fb7e61e75602241ed9a2f04e36d8670aadd286e71b5ca9cc420000000000000000000000000000000000000000000000000000000000000031e5a2356cbc2ef6a733eae8d54bf48719ae3d990017ca787c419c7d369f8e3c83fac17c3f237fc51f90e2c660eb202a438bc2025baded5cd193c1a018c5885bc9281ba704d5566082e851235c7be763b2a99adff965e0a121ee972ebc472d02944a74f5c6243e14052e105124b70bf65faf85ad3a494325e269fad097842cba", + "Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + "Name": "pointEvaluation1", + "Gas": 50000, + "NoBenchmark": false + } + ] \ No newline at end of file diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 3bd814159db3a..8798c712495ef 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -41,6 +41,8 @@ type Point [32]byte // Claim is a claimed evaluation value in a specific point. type Claim [32]byte +var PrecompileReturnValue [64]byte + // useCKZG controls whether the cryptography should use the Go or C backend. var useCKZG atomic.Bool @@ -61,6 +63,8 @@ func UseCKZG(use bool) error { } else { gokzgIniter.Do(gokzgInit) } + // Compute the precompile return value using go-kzg + PrecompileReturnValue = goComputePrecompileReturnValue() return nil } diff --git a/crypto/kzg4844/kzg4844_gokzg.go b/crypto/kzg4844/kzg4844_gokzg.go index 3f03bb52738e8..2011f212fe87e 100644 --- a/crypto/kzg4844/kzg4844_gokzg.go +++ b/crypto/kzg4844/kzg4844_gokzg.go @@ -17,6 +17,7 @@ package kzg4844 import ( + "encoding/binary" "encoding/json" "sync" @@ -96,3 +97,12 @@ func gokzgVerifyBlobProof(blob Blob, commitment Commitment, proof Proof) error { return context.VerifyBlobKZGProof((gokzg4844.Blob)(blob), (gokzg4844.KZGCommitment)(commitment), (gokzg4844.KZGProof)(proof)) } + +// goComputePrecompileReturnValue computes the precompile return value which is +// FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded 32 byte big endian values. +func goComputePrecompileReturnValue() [64]byte { + var precompileReturnValue [64]byte + binary.BigEndian.PutUint64(precompileReturnValue[24:], gokzg4844.ScalarsPerBlob) + copy(precompileReturnValue[32:], gokzg4844.BlsModulus[:]) + return precompileReturnValue +} diff --git a/params/protocol_params.go b/params/protocol_params.go index 5ddc13c63b3e8..b6a80c0c376a0 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -165,6 +165,7 @@ const ( BlobTxDataGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size) BlobTxMinDataGasprice = 1 // Minimum gas price for data blobs BlobTxDataGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for data gas price + PointEvaluationPrecompileGas = 50000 // Gas price for the point evaluation precompile. ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations