Skip to content

Commit 4590721

Browse files
MariusVanDerWijdendevopsbo3
authored andcommitted
core: 4844 opcode and precompile (ethereum#27356)
* core: crypto: implement BLOBHASH and pointEval precompile * core: crypto: fixed nitpicks, moved precompile return value * core/vm: fix review comments
1 parent f60b50e commit 4590721

File tree

15 files changed

+199
-13
lines changed

15 files changed

+199
-13
lines changed

cmd/evm/runner.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ func runCmd(ctx *cli.Context) error {
128128
receiver = common.BytesToAddress([]byte("receiver"))
129129
genesisConfig *core.Genesis
130130
preimages = ctx.Bool(DumpFlag.Name)
131+
blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests
131132
)
132133
if ctx.Bool(MachineFlag.Name) {
133134
tracer = logger.NewJSONLogger(logconfig, os.Stdout)
@@ -217,6 +218,7 @@ func runCmd(ctx *cli.Context) error {
217218
Time: genesisConfig.Timestamp,
218219
Coinbase: genesisConfig.Coinbase,
219220
BlockNumber: new(big.Int).SetUint64(genesisConfig.Number),
221+
BlobHashes: blobHashes,
220222
EVMConfig: vm.Config{
221223
Tracer: tracer,
222224
},

core/evm.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
7272
// NewEVMTxContext creates a new transaction context for a single transaction.
7373
func NewEVMTxContext(msg *Message) vm.TxContext {
7474
return vm.TxContext{
75-
Origin: msg.From,
76-
GasPrice: new(big.Int).Set(msg.GasPrice),
75+
Origin: msg.From,
76+
GasPrice: new(big.Int).Set(msg.GasPrice),
77+
BlobHashes: msg.BlobHashes,
7778
}
7879
}
7980

core/state_transition.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ type Message struct {
135135
GasTipCap *big.Int
136136
Data []byte
137137
AccessList types.AccessList
138+
BlobHashes []common.Hash
138139

139140
// When SkipAccountChecks is true, the message nonce is not checked against the
140141
// 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
155156
Data: tx.Data(),
156157
AccessList: tx.AccessList(),
157158
SkipAccountChecks: false,
159+
BlobHashes: tx.BlobHashes(),
158160
}
159161
// If baseFee provided, set gasPrice to effectiveGasPrice.
160162
if baseFee != nil {

core/vm/contracts.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"crypto/sha256"
2121
"encoding/binary"
2222
"errors"
23+
"fmt"
2324
"math/big"
2425

2526
"github.com/ethereum/go-ethereum/common"
@@ -28,6 +29,7 @@ import (
2829
"github.com/ethereum/go-ethereum/crypto/blake2b"
2930
"github.com/ethereum/go-ethereum/crypto/bls12381"
3031
"github.com/ethereum/go-ethereum/crypto/bn256"
32+
"github.com/ethereum/go-ethereum/crypto/kzg4844"
3133
"github.com/ethereum/go-ethereum/params"
3234
"golang.org/x/crypto/ripemd160"
3335
)
@@ -90,6 +92,21 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
9092
common.BytesToAddress([]byte{9}): &blake2F{},
9193
}
9294

95+
// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum
96+
// contracts used in the Cancun release.
97+
var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
98+
common.BytesToAddress([]byte{1}): &ecrecover{},
99+
common.BytesToAddress([]byte{2}): &sha256hash{},
100+
common.BytesToAddress([]byte{3}): &ripemd160hash{},
101+
common.BytesToAddress([]byte{4}): &dataCopy{},
102+
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
103+
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
104+
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
105+
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
106+
common.BytesToAddress([]byte{9}): &blake2F{},
107+
common.BytesToAddress([]byte{20}): &kzgPointEvaluation{},
108+
}
109+
93110
// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
94111
// contracts specified in EIP-2537. These are exported for testing purposes.
95112
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
@@ -105,6 +122,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
105122
}
106123

107124
var (
125+
PrecompiledAddressesCancun []common.Address
108126
PrecompiledAddressesBerlin []common.Address
109127
PrecompiledAddressesIstanbul []common.Address
110128
PrecompiledAddressesByzantium []common.Address
@@ -124,11 +142,16 @@ func init() {
124142
for k := range PrecompiledContractsBerlin {
125143
PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k)
126144
}
145+
for k := range PrecompiledContractsCancun {
146+
PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k)
147+
}
127148
}
128149

129150
// ActivePrecompiles returns the precompiles enabled with the current configuration.
130151
func ActivePrecompiles(rules params.Rules) []common.Address {
131152
switch {
153+
case rules.IsCancun:
154+
return PrecompiledAddressesCancun
132155
case rules.IsBerlin:
133156
return PrecompiledAddressesBerlin
134157
case rules.IsIstanbul:
@@ -1048,3 +1071,67 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
10481071
// Encode the G2 point to 256 bytes
10491072
return g.EncodePoint(r), nil
10501073
}
1074+
1075+
// kzgPointEvaluation implements the EIP-4844 point evaluation precompile.
1076+
type kzgPointEvaluation struct{}
1077+
1078+
// RequiredGas estimates the gas required for running the point evaluation precompile.
1079+
func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 {
1080+
return params.BlobTxPointEvaluationPrecompileGas
1081+
}
1082+
1083+
const (
1084+
blobVerifyInputLength = 192 // Max input length for the point evaluation precompile.
1085+
blobCommitmentVersionKZG uint8 = 0x01 // Version byte for the point evaluation precompile.
1086+
blobPrecompileReturnValue = "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
1087+
)
1088+
1089+
var (
1090+
errBlobVerifyInvalidInputLength = errors.New("invalid input length")
1091+
errBlobVerifyMismatchedVersion = errors.New("mismatched versioned hash")
1092+
errBlobVerifyKZGProof = errors.New("error verifying kzg proof")
1093+
)
1094+
1095+
// Run executes the point evaluation precompile.
1096+
func (b *kzgPointEvaluation) Run(input []byte) ([]byte, error) {
1097+
if len(input) != blobVerifyInputLength {
1098+
return nil, errBlobVerifyInvalidInputLength
1099+
}
1100+
// versioned hash: first 32 bytes
1101+
var versionedHash common.Hash
1102+
copy(versionedHash[:], input[:])
1103+
1104+
var (
1105+
point kzg4844.Point
1106+
claim kzg4844.Claim
1107+
)
1108+
// Evaluation point: next 32 bytes
1109+
copy(point[:], input[32:])
1110+
// Expected output: next 32 bytes
1111+
copy(claim[:], input[64:])
1112+
1113+
// input kzg point: next 48 bytes
1114+
var commitment kzg4844.Commitment
1115+
copy(commitment[:], input[96:])
1116+
if kZGToVersionedHash(commitment) != versionedHash {
1117+
return nil, errBlobVerifyMismatchedVersion
1118+
}
1119+
1120+
// Proof: next 48 bytes
1121+
var proof kzg4844.Proof
1122+
copy(proof[:], input[144:])
1123+
1124+
if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil {
1125+
return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err)
1126+
}
1127+
1128+
return common.Hex2Bytes(blobPrecompileReturnValue), nil
1129+
}
1130+
1131+
// kZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844
1132+
func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash {
1133+
h := sha256.Sum256(kzg[:])
1134+
h[0] = blobCommitmentVersionKZG
1135+
1136+
return h
1137+
}

core/vm/contracts_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
6565
common.BytesToAddress([]byte{16}): &bls12381Pairing{},
6666
common.BytesToAddress([]byte{17}): &bls12381MapG1{},
6767
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
68+
common.BytesToAddress([]byte{20}): &kzgPointEvaluation{},
6869
}
6970

7071
// EIP-152 test vectors
@@ -311,6 +312,7 @@ func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp",
311312
func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "10", t) }
312313
func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", t) }
313314
func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "12", t) }
315+
func TestPrecompiledPointEvaluation(t *testing.T) { testJson("pointEvaluation", "14", t) }
314316

315317
func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "0a", b) }
316318
func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) }

core/vm/eips.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,32 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
235235
return nil, nil
236236
}
237237

238-
// ebnable3860 enables "EIP-3860: Limit and meter initcode"
238+
// enable3860 enables "EIP-3860: Limit and meter initcode"
239239
// https://eips.ethereum.org/EIPS/eip-3860
240240
func enable3860(jt *JumpTable) {
241241
jt[CREATE].dynamicGas = gasCreateEip3860
242242
jt[CREATE2].dynamicGas = gasCreate2Eip3860
243243
}
244+
245+
// opBlobHash implements the BLOBHASH opcode
246+
func opBlobHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
247+
index := scope.Stack.peek()
248+
if index.LtUint64(uint64(len(interpreter.evm.TxContext.BlobHashes))) {
249+
blobHash := interpreter.evm.TxContext.BlobHashes[index.Uint64()]
250+
index.SetBytes32(blobHash[:])
251+
} else {
252+
index.Clear()
253+
}
254+
return nil, nil
255+
}
256+
257+
// enable4844 applies EIP-4844 (DATAHASH opcode)
258+
func enable4844(jt *JumpTable) {
259+
// New opcode
260+
jt[BLOBHASH] = &operation{
261+
execute: opBlobHash,
262+
constantGas: GasFastestStep,
263+
minStack: minStack(1, 1),
264+
maxStack: maxStack(1, 1),
265+
}
266+
}

core/vm/evm.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ type (
4343
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
4444
var precompiles map[common.Address]PrecompiledContract
4545
switch {
46+
case evm.chainRules.IsCancun:
47+
precompiles = PrecompiledContractsCancun
4648
case evm.chainRules.IsBerlin:
4749
precompiles = PrecompiledContractsBerlin
4850
case evm.chainRules.IsIstanbul:
@@ -81,8 +83,9 @@ type BlockContext struct {
8183
// All fields can change between transactions.
8284
type TxContext struct {
8385
// Message information
84-
Origin common.Address // Provides information for ORIGIN
85-
GasPrice *big.Int // Provides information for GASPRICE
86+
Origin common.Address // Provides information for ORIGIN
87+
GasPrice *big.Int // Provides information for GASPRICE
88+
BlobHashes []common.Hash // Provides information for BLOBHASH
8689
}
8790

8891
// EVM is the Ethereum Virtual Machine base object and provides

core/vm/instructions_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,45 @@ func TestRandom(t *testing.T) {
746746
}
747747
}
748748
}
749+
750+
func TestBlobHash(t *testing.T) {
751+
type testcase struct {
752+
name string
753+
idx uint64
754+
expect common.Hash
755+
hashes []common.Hash
756+
}
757+
var (
758+
zero = common.Hash{0}
759+
one = common.Hash{1}
760+
two = common.Hash{2}
761+
three = common.Hash{3}
762+
)
763+
for _, tt := range []testcase{
764+
{name: "[{1}]", idx: 0, expect: one, hashes: []common.Hash{one}},
765+
{name: "[1,{2},3]", idx: 2, expect: three, hashes: []common.Hash{one, two, three}},
766+
{name: "out-of-bounds (empty)", idx: 10, expect: zero, hashes: []common.Hash{}},
767+
{name: "out-of-bounds", idx: 25, expect: zero, hashes: []common.Hash{one, two, three}},
768+
{name: "out-of-bounds (nil)", idx: 25, expect: zero, hashes: nil},
769+
} {
770+
var (
771+
env = NewEVM(BlockContext{}, TxContext{BlobHashes: tt.hashes}, nil, params.TestChainConfig, Config{})
772+
stack = newstack()
773+
pc = uint64(0)
774+
evmInterpreter = env.interpreter
775+
)
776+
stack.push(uint256.NewInt(tt.idx))
777+
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
778+
if len(stack.data) != 1 {
779+
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
780+
}
781+
actual := stack.pop()
782+
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes()))
783+
if overflow {
784+
t.Errorf("Testcase %v: invalid overflow", tt.name)
785+
}
786+
if actual.Cmp(expected) != 0 {
787+
t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual)
788+
}
789+
}
790+
}

core/vm/interpreter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
5656
// If jump table was not initialised we set the default one.
5757
var table *JumpTable
5858
switch {
59+
case evm.chainRules.IsCancun:
60+
table = &cancunInstructionSet
5961
case evm.chainRules.IsShanghai:
6062
table = &shanghaiInstructionSet
6163
case evm.chainRules.IsMerge:

core/vm/jump_table.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var (
5656
londonInstructionSet = newLondonInstructionSet()
5757
mergeInstructionSet = newMergeInstructionSet()
5858
shanghaiInstructionSet = newShanghaiInstructionSet()
59+
cancunInstructionSet = newCancunInstructionSet()
5960
)
6061

6162
// JumpTable contains the EVM opcodes supported at a given fork.
@@ -79,6 +80,12 @@ func validate(jt JumpTable) JumpTable {
7980
return jt
8081
}
8182

83+
func newCancunInstructionSet() JumpTable {
84+
instructionSet := newShanghaiInstructionSet()
85+
enable4844(&instructionSet) // BLOBHASH opcode
86+
return validate(instructionSet)
87+
}
88+
8289
func newShanghaiInstructionSet() JumpTable {
8390
instructionSet := newMergeInstructionSet()
8491
enable3855(&instructionSet) // PUSH0 instruction

0 commit comments

Comments
 (0)