core/types: clean up tx signing

This removes the signer type train in favor of defining a single object that
can handle all tx types. Supported types are enabled via a map. Notably, the
Pnew signer also supports disabling legacy transactions.
This commit is contained in:
Felix Lange 2025-03-19 09:59:24 +01:00
parent 57a6c0be24
commit 097b1eec88

View file

@ -20,11 +20,13 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"errors" "errors"
"fmt" "fmt"
"maps"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
) )
var ErrInvalidChainId = errors.New("invalid chain id for signer") var ErrInvalidChainId = errors.New("invalid chain id for signer")
@ -178,7 +180,99 @@ type Signer interface {
Equal(Signer) bool Equal(Signer) bool
} }
type pragueSigner struct{ cancunSigner } type modernSigner struct {
txtypes map[byte]struct{}
chainID *big.Int
legacy Signer
}
func newModernSigner(chainID *big.Int, fork forks.Fork) Signer {
if chainID == nil {
panic("nil chainID")
}
s := &modernSigner{
chainID: chainID,
txtypes: make(map[byte]struct{}, 4),
}
// configure legacy signer
switch {
case fork <= forks.FrontierThawing:
s.legacy = FrontierSigner{}
case fork <= forks.Homestead:
s.legacy = HomesteadSigner{}
default:
s.legacy = NewEIP155Signer(chainID)
}
s.txtypes[LegacyTxType] = struct{}{}
// configure tx types
if fork >= forks.Berlin {
s.txtypes[AccessListTxType] = struct{}{}
}
if fork >= forks.London {
s.txtypes[DynamicFeeTxType] = struct{}{}
}
if fork >= forks.Cancun {
s.txtypes[BlobTxType] = struct{}{}
}
if fork >= forks.Prague {
s.txtypes[SetCodeTxType] = struct{}{}
}
return s
}
func (s *modernSigner) supports(txtype byte) bool {
_, ok := s.txtypes[txtype]
return ok
}
func (s *modernSigner) ChainID() *big.Int {
return s.chainID
}
func (s *modernSigner) Equal(s2 Signer) bool {
other, ok := s2.(*modernSigner)
return ok && s.chainID.Cmp(other.chainID) == 0 && maps.Equal(s.txtypes, other.txtypes) && s.legacy.Equal(other.legacy)
}
func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
tt := tx.Type()
if !s.supports(tt) {
return common.Address{}, ErrTxTypeNotSupported
}
if tt == LegacyTxType {
return s.legacy.Sender(tx)
}
if tx.ChainId().Cmp(s.chainID) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID)
}
// 'modern' txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V, R, S := tx.RawSignatureValues()
V = new(big.Int).Add(V, big.NewInt(27))
return recoverPlain(s.Hash(tx), R, S, V, true)
}
func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
tt := tx.Type()
if !s.supports(tt) {
return nil, nil, nil, ErrTxTypeNotSupported
}
if tt == LegacyTxType {
return s.legacy.SignatureValues(tx, sig)
}
// Check that chain ID of tx matches the signer. We also accept ID zero here,
// because it indicates that the chain ID was not specified in the tx.
if tx.inner.chainID().Sign() != 0 && tx.inner.chainID().Cmp(s.chainID) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.inner.chainID(), s.chainID)
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
}
func (s *modernSigner) Hash(tx *Transaction) common.Hash {
return tx.inner.sigHash(s.chainID)
}
// NewPragueSigner returns a signer that accepts // NewPragueSigner returns a signer that accepts
// - EIP-7702 set code transactions // - EIP-7702 set code transactions
@ -188,56 +282,9 @@ type pragueSigner struct{ cancunSigner }
// - EIP-155 replay protected transactions, and // - EIP-155 replay protected transactions, and
// - legacy Homestead transactions. // - legacy Homestead transactions.
func NewPragueSigner(chainId *big.Int) Signer { func NewPragueSigner(chainId *big.Int) Signer {
signer, _ := NewCancunSigner(chainId).(cancunSigner) return newModernSigner(chainId, forks.Prague)
return pragueSigner{signer}
} }
func (s pragueSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.Type() != SetCodeTxType {
return s.cancunSigner.Sender(tx)
}
V, R, S := tx.RawSignatureValues()
// Set code txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27))
if tx.ChainId().Cmp(s.chainId) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
}
return recoverPlain(s.Hash(tx), R, S, V, true)
}
func (s pragueSigner) Equal(s2 Signer) bool {
x, ok := s2.(pragueSigner)
return ok && x.chainId.Cmp(s.chainId) == 0
}
func (s pragueSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
txdata, ok := tx.inner.(*SetCodeTx)
if !ok {
return s.cancunSigner.SignatureValues(tx, sig)
}
// Check that chain ID of tx matches the signer. We also accept ID zero here,
// because it indicates that the chain ID was not specified in the tx.
if txdata.ChainID.Sign() != 0 && txdata.ChainID.CmpBig(s.chainId) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
}
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s pragueSigner) Hash(tx *Transaction) common.Hash {
if tx.Type() != SetCodeTxType {
return s.cancunSigner.Hash(tx)
}
return tx.inner.sigHash(s.chainId)
}
type cancunSigner struct{ londonSigner }
// NewCancunSigner returns a signer that accepts // NewCancunSigner returns a signer that accepts
// - EIP-4844 blob transactions // - EIP-4844 blob transactions
// - EIP-1559 dynamic fee transactions // - EIP-1559 dynamic fee transactions
@ -245,174 +292,22 @@ type cancunSigner struct{ londonSigner }
// - EIP-155 replay protected transactions, and // - EIP-155 replay protected transactions, and
// - legacy Homestead transactions. // - legacy Homestead transactions.
func NewCancunSigner(chainId *big.Int) Signer { func NewCancunSigner(chainId *big.Int) Signer {
return cancunSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}} return newModernSigner(chainId, forks.Cancun)
} }
func (s cancunSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.Type() != BlobTxType {
return s.londonSigner.Sender(tx)
}
V, R, S := tx.RawSignatureValues()
// Blob txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27))
if tx.ChainId().Cmp(s.chainId) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
}
return recoverPlain(s.Hash(tx), R, S, V, true)
}
func (s cancunSigner) Equal(s2 Signer) bool {
x, ok := s2.(cancunSigner)
return ok && x.chainId.Cmp(s.chainId) == 0
}
func (s cancunSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
txdata, ok := tx.inner.(*BlobTx)
if !ok {
return s.londonSigner.SignatureValues(tx, sig)
}
// Check that chain ID of tx matches the signer. We also accept ID zero here,
// because it indicates that the chain ID was not specified in the tx.
if txdata.ChainID.Sign() != 0 && txdata.ChainID.CmpBig(s.chainId) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
}
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s cancunSigner) Hash(tx *Transaction) common.Hash {
if tx.Type() != BlobTxType {
return s.londonSigner.Hash(tx)
}
return tx.inner.sigHash(s.chainId)
}
type londonSigner struct{ eip2930Signer }
// NewLondonSigner returns a signer that accepts // NewLondonSigner returns a signer that accepts
// - EIP-1559 dynamic fee transactions // - EIP-1559 dynamic fee transactions
// - EIP-2930 access list transactions, // - EIP-2930 access list transactions,
// - EIP-155 replay protected transactions, and // - EIP-155 replay protected transactions, and
// - legacy Homestead transactions. // - legacy Homestead transactions.
func NewLondonSigner(chainId *big.Int) Signer { func NewLondonSigner(chainId *big.Int) Signer {
return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}} return newModernSigner(chainId, forks.London)
} }
func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.Type() != DynamicFeeTxType {
return s.eip2930Signer.Sender(tx)
}
V, R, S := tx.RawSignatureValues()
// DynamicFee txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27))
if tx.ChainId().Cmp(s.chainId) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
}
return recoverPlain(s.Hash(tx), R, S, V, true)
}
func (s londonSigner) Equal(s2 Signer) bool {
x, ok := s2.(londonSigner)
return ok && x.chainId.Cmp(s.chainId) == 0
}
func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
txdata, ok := tx.inner.(*DynamicFeeTx)
if !ok {
return s.eip2930Signer.SignatureValues(tx, sig)
}
// Check that chain ID of tx matches the signer. We also accept ID zero here,
// because it indicates that the chain ID was not specified in the tx.
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
}
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s londonSigner) Hash(tx *Transaction) common.Hash {
if tx.Type() != DynamicFeeTxType {
return s.eip2930Signer.Hash(tx)
}
return tx.inner.sigHash(s.chainId)
}
type eip2930Signer struct{ EIP155Signer }
// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, // NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions,
// EIP-155 replay protected transactions, and legacy Homestead transactions. // EIP-155 replay protected transactions, and legacy Homestead transactions.
func NewEIP2930Signer(chainId *big.Int) Signer { func NewEIP2930Signer(chainId *big.Int) Signer {
return eip2930Signer{NewEIP155Signer(chainId)} return newModernSigner(chainId, forks.Berlin)
}
func (s eip2930Signer) ChainID() *big.Int {
return s.chainId
}
func (s eip2930Signer) Equal(s2 Signer) bool {
x, ok := s2.(eip2930Signer)
return ok && x.chainId.Cmp(s.chainId) == 0
}
func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) {
V, R, S := tx.RawSignatureValues()
switch tx.Type() {
case LegacyTxType:
return s.EIP155Signer.Sender(tx)
case AccessListTxType:
// AL txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27))
default:
return common.Address{}, ErrTxTypeNotSupported
}
if tx.ChainId().Cmp(s.chainId) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
}
return recoverPlain(s.Hash(tx), R, S, V, true)
}
func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
switch txdata := tx.inner.(type) {
case *LegacyTx:
return s.EIP155Signer.SignatureValues(tx, sig)
case *AccessListTx:
// Check that chain ID of tx matches the signer. We also accept ID zero here,
// because it indicates that the chain ID was not specified in the tx.
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
default:
return nil, nil, nil, ErrTxTypeNotSupported
}
return R, S, V, nil
}
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s eip2930Signer) Hash(tx *Transaction) common.Hash {
switch tx.Type() {
case LegacyTxType:
return s.EIP155Signer.Hash(tx)
case AccessListTxType:
return tx.inner.sigHash(s.chainId)
default:
// This _should_ not happen, but in case someone sends in a bad
// json struct via RPC, it's probably more prudent to return an
// empty hash instead of killing the node with a panic
//panic("Unsupported transaction type: %d", tx.typ)
return common.Hash{}
}
} }
// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which // EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which