1
0
Fork 0
forked from forks/go-ethereum

core/types: cleanup tx signer logic (#31434)

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 new signer also supports disabling legacy transactions.
This commit is contained in:
Felix Lange 2025-03-19 16:05:44 +01:00 committed by GitHub
parent b47e4d5b38
commit 80b8d7a13c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 110 additions and 211 deletions

View file

@ -102,7 +102,7 @@ func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Tran
if tx.protected {
signed, err = types.SignTx(tx.tx, signer, tx.key)
} else {
signed, err = types.SignTx(tx.tx, types.FrontierSigner{}, tx.key)
signed, err = types.SignTx(tx.tx, types.HomesteadSigner{}, tx.key)
}
if err != nil {
return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err))

View file

@ -20,11 +20,13 @@ import (
"crypto/ecdsa"
"errors"
"fmt"
"maps"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
)
var ErrInvalidChainId = errors.New("invalid chain id for signer")
@ -178,7 +180,101 @@ type Signer interface {
Equal(Signer) bool
}
type pragueSigner struct{ cancunSigner }
// modernSigner is the signer implementation that handles non-legacy transaction types.
// For legacy transactions, it defers to one of the legacy signers (frontier, homestead, eip155).
type modernSigner struct {
txtypes map[byte]struct{}
chainID *big.Int
legacy Signer
}
func newModernSigner(chainID *big.Int, fork forks.Fork) Signer {
if chainID == nil || chainID.Sign() <= 0 {
panic(fmt.Sprintf("invalid chainID %v", chainID))
}
s := &modernSigner{
chainID: chainID,
txtypes: make(map[byte]struct{}, 4),
}
// configure legacy signer
switch {
case fork >= forks.SpuriousDragon:
s.legacy = NewEIP155Signer(chainID)
case fork >= forks.Homestead:
s.legacy = HomesteadSigner{}
default:
s.legacy = FrontierSigner{}
}
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) 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) Hash(tx *Transaction) common.Hash {
return tx.inner.sigHash(s.chainID)
}
func (s *modernSigner) supportsType(txtype byte) bool {
_, ok := s.txtypes[txtype]
return ok
}
func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
tt := tx.Type()
if !s.supportsType(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.supportsType(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
}
// NewPragueSigner returns a signer that accepts
// - EIP-7702 set code transactions
@ -188,56 +284,9 @@ type pragueSigner struct{ cancunSigner }
// - EIP-155 replay protected transactions, and
// - legacy Homestead transactions.
func NewPragueSigner(chainId *big.Int) Signer {
signer, _ := NewCancunSigner(chainId).(cancunSigner)
return pragueSigner{signer}
return newModernSigner(chainId, forks.Prague)
}
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
// - EIP-4844 blob transactions
// - EIP-1559 dynamic fee transactions
@ -245,178 +294,27 @@ type cancunSigner struct{ londonSigner }
// - EIP-155 replay protected transactions, and
// - legacy Homestead transactions.
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
// - EIP-1559 dynamic fee transactions
// - EIP-2930 access list transactions,
// - EIP-155 replay protected transactions, and
// - legacy Homestead transactions.
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,
// EIP-155 replay protected transactions, and legacy Homestead transactions.
func NewEIP2930Signer(chainId *big.Int) Signer {
return eip2930Signer{NewEIP155Signer(chainId)}
}
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{}
}
return newModernSigner(chainId, forks.Berlin)
}
// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which
// are replay-protected as well as unprotected homestead transactions.
// Deprecated: always use the Signer interface type
type EIP155Signer struct {
chainId, chainIdMul *big.Int
}
@ -478,8 +376,9 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
return tx.inner.sigHash(s.chainId)
}
// HomesteadSigner implements Signer interface using the
// homestead rules.
// HomesteadSigner implements Signer using the homestead rules. The only valid reason to
// use this type is creating legacy transactions which are intentionally not
// replay-protected.
type HomesteadSigner struct{ FrontierSigner }
func (hs HomesteadSigner) ChainID() *big.Int {
@ -505,8 +404,8 @@ func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) {
return recoverPlain(hs.Hash(tx), r, s, v, true)
}
// FrontierSigner implements Signer interface using the
// frontier rules.
// FrontierSigner implements Signer using the frontier rules.
// Deprecated: always use the Signer interface type
type FrontierSigner struct{}
func (fs FrontierSigner) ChainID() *big.Int {

View file

@ -24,8 +24,8 @@ const (
FrontierThawing
Homestead
DAO
TangerineWhistle
SpuriousDragon
TangerineWhistle // a.k.a. the EIP150 fork
SpuriousDragon // a.k.a. the EIP155 fork
Byzantium
Constantinople
Petersburg