forked from cerc-io/plugeth
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:
parent
380fb4e249
commit
c537ace249
@ -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,
|
||||
},
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -28,6 +29,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 +92,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 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
|
||||
// contracts specified in EIP-2537. These are exported for testing purposes.
|
||||
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
|
||||
@ -105,6 +122,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
|
||||
}
|
||||
|
||||
var (
|
||||
PrecompiledAddressesCancun []common.Address
|
||||
PrecompiledAddressesBerlin []common.Address
|
||||
PrecompiledAddressesIstanbul []common.Address
|
||||
PrecompiledAddressesByzantium []common.Address
|
||||
@ -124,11 +142,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 +1071,67 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
|
||||
// Encode the G2 point to 256 bytes
|
||||
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
|
||||
}
|
||||
|
@ -65,6 +65,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}): &kzgPointEvaluation{},
|
||||
}
|
||||
|
||||
// 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 TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", 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 BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) }
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
9
core/vm/testdata/precompiles/pointEvaluation.json
vendored
Normal file
9
core/vm/testdata/precompiles/pointEvaluation.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"Input": "013c03613f6fc558fb7e61e75602241ed9a2f04e36d8670aadd286e71b5ca9cc420000000000000000000000000000000000000000000000000000000000000031e5a2356cbc2ef6a733eae8d54bf48719ae3d990017ca787c419c7d369f8e3c83fac17c3f237fc51f90e2c660eb202a438bc2025baded5cd193c1a018c5885bc9281ba704d5566082e851235c7be763b2a99adff965e0a121ee972ebc472d02944a74f5c6243e14052e105124b70bf65faf85ad3a494325e269fad097842cba",
|
||||
"Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001",
|
||||
"Name": "pointEvaluation1",
|
||||
"Gas": 50000,
|
||||
"NoBenchmark": false
|
||||
}
|
||||
]
|
@ -160,12 +160,13 @@ const (
|
||||
RefundQuotient uint64 = 2
|
||||
RefundQuotientEIP3529 uint64 = 5
|
||||
|
||||
BlobTxHashVersion = 0x01 // Version byte of the commitment hash
|
||||
BlobTxMaxDataGasPerBlock = 1 << 19 // Maximum consumable data gas for data blobs per block
|
||||
BlobTxTargetDataGasPerBlock = 1 << 18 // Target consumable data gas for data blobs per block (for 1559-like pricing)
|
||||
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
|
||||
BlobTxHashVersion = 0x01 // Version byte of the commitment hash
|
||||
BlobTxMaxDataGasPerBlock = 1 << 19 // Maximum consumable data gas for data blobs per block
|
||||
BlobTxTargetDataGasPerBlock = 1 << 18 // Target consumable data gas for data blobs per block (for 1559-like pricing)
|
||||
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
|
||||
BlobTxPointEvaluationPrecompileGas = 50000 // Gas price for the point evaluation precompile.
|
||||
)
|
||||
|
||||
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
|
||||
|
Loading…
Reference in New Issue
Block a user