Skip to content

Commit 9a0729c

Browse files
fjlsivaratrisrinivas
authored andcommitted
core/types: cleanup tx signer logic (ethereum#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.
1 parent 7ff98c0 commit 9a0729c

File tree

3 files changed

+98
-199
lines changed

3 files changed

+98
-199
lines changed

cmd/evm/internal/t8ntool/tx_iterator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Tran
102102
if tx.protected {
103103
signed, err = types.SignTx(tx.tx, signer, tx.key)
104104
} else {
105-
signed, err = types.SignTx(tx.tx, types.FrontierSigner{}, tx.key)
105+
signed, err = types.SignTx(tx.tx, types.HomesteadSigner{}, tx.key)
106106
}
107107
if err != nil {
108108
return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err))

core/types/transaction_signing.go

+95-196
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import (
2020
"crypto/ecdsa"
2121
"errors"
2222
"fmt"
23+
"maps"
2324
"math/big"
2425

2526
"github.com/ethereum/go-ethereum/common"
2627
"github.com/ethereum/go-ethereum/crypto"
2728
"github.com/ethereum/go-ethereum/params"
29+
"github.com/ethereum/go-ethereum/params/forks"
2830
)
2931

3032
var ErrInvalidChainId = errors.New("invalid chain id for signer")
@@ -178,245 +180,141 @@ type Signer interface {
178180
Equal(Signer) bool
179181
}
180182

181-
type pragueSigner struct{ cancunSigner }
182-
183-
// NewPragueSigner returns a signer that accepts
184-
// - EIP-7702 set code transactions
185-
// - EIP-4844 blob transactions
186-
// - EIP-1559 dynamic fee transactions
187-
// - EIP-2930 access list transactions,
188-
// - EIP-155 replay protected transactions, and
189-
// - legacy Homestead transactions.
190-
func NewPragueSigner(chainId *big.Int) Signer {
191-
signer, _ := NewCancunSigner(chainId).(cancunSigner)
192-
return pragueSigner{signer}
183+
// modernSigner is the signer implementation that handles non-legacy transaction types.
184+
// For legacy transactions, it defers to one of the legacy signers (frontier, homestead, eip155).
185+
type modernSigner struct {
186+
txtypes map[byte]struct{}
187+
chainID *big.Int
188+
legacy Signer
193189
}
194190

195-
func (s pragueSigner) Sender(tx *Transaction) (common.Address, error) {
196-
if tx.Type() != SetCodeTxType {
197-
return s.cancunSigner.Sender(tx)
191+
func newModernSigner(chainID *big.Int, fork forks.Fork) Signer {
192+
if chainID == nil || chainID.Sign() <= 0 {
193+
panic(fmt.Sprintf("invalid chainID %v", chainID))
198194
}
199-
V, R, S := tx.RawSignatureValues()
200-
201-
// Set code txs are defined to use 0 and 1 as their recovery
202-
// id, add 27 to become equivalent to unprotected Homestead signatures.
203-
V = new(big.Int).Add(V, big.NewInt(27))
204-
if tx.ChainId().Cmp(s.chainId) != 0 {
205-
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
195+
s := &modernSigner{
196+
chainID: chainID,
197+
txtypes: make(map[byte]struct{}, 4),
206198
}
207-
return recoverPlain(s.Hash(tx), R, S, V, true)
199+
// configure legacy signer
200+
switch {
201+
case fork >= forks.SpuriousDragon:
202+
s.legacy = NewEIP155Signer(chainID)
203+
case fork >= forks.Homestead:
204+
s.legacy = HomesteadSigner{}
205+
default:
206+
s.legacy = FrontierSigner{}
207+
}
208+
s.txtypes[LegacyTxType] = struct{}{}
209+
// configure tx types
210+
if fork >= forks.Berlin {
211+
s.txtypes[AccessListTxType] = struct{}{}
212+
}
213+
if fork >= forks.London {
214+
s.txtypes[DynamicFeeTxType] = struct{}{}
215+
}
216+
if fork >= forks.Cancun {
217+
s.txtypes[BlobTxType] = struct{}{}
218+
}
219+
if fork >= forks.Prague {
220+
s.txtypes[SetCodeTxType] = struct{}{}
221+
}
222+
return s
208223
}
209224

210-
func (s pragueSigner) Equal(s2 Signer) bool {
211-
x, ok := s2.(pragueSigner)
212-
return ok && x.chainId.Cmp(s.chainId) == 0
225+
func (s *modernSigner) ChainID() *big.Int {
226+
return s.chainID
213227
}
214228

215-
func (s pragueSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
216-
txdata, ok := tx.inner.(*SetCodeTx)
217-
if !ok {
218-
return s.cancunSigner.SignatureValues(tx, sig)
219-
}
220-
// Check that chain ID of tx matches the signer. We also accept ID zero here,
221-
// because it indicates that the chain ID was not specified in the tx.
222-
if txdata.ChainID.Sign() != 0 && txdata.ChainID.CmpBig(s.chainId) != 0 {
223-
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
224-
}
225-
R, S, _ = decodeSignature(sig)
226-
V = big.NewInt(int64(sig[64]))
227-
return R, S, V, nil
229+
func (s *modernSigner) Equal(s2 Signer) bool {
230+
other, ok := s2.(*modernSigner)
231+
return ok && s.chainID.Cmp(other.chainID) == 0 && maps.Equal(s.txtypes, other.txtypes) && s.legacy.Equal(other.legacy)
228232
}
229233

230-
// Hash returns the hash to be signed by the sender.
231-
// It does not uniquely identify the transaction.
232-
func (s pragueSigner) Hash(tx *Transaction) common.Hash {
233-
if tx.Type() != SetCodeTxType {
234-
return s.cancunSigner.Hash(tx)
235-
}
236-
return tx.inner.sigHash(s.chainId)
234+
func (s *modernSigner) Hash(tx *Transaction) common.Hash {
235+
return tx.inner.sigHash(s.chainID)
237236
}
238237

239-
type cancunSigner struct{ londonSigner }
240-
241-
// NewCancunSigner returns a signer that accepts
242-
// - EIP-4844 blob transactions
243-
// - EIP-1559 dynamic fee transactions
244-
// - EIP-2930 access list transactions,
245-
// - EIP-155 replay protected transactions, and
246-
// - legacy Homestead transactions.
247-
func NewCancunSigner(chainId *big.Int) Signer {
248-
return cancunSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}}
238+
func (s *modernSigner) supportsType(txtype byte) bool {
239+
_, ok := s.txtypes[txtype]
240+
return ok
249241
}
250242

251-
func (s cancunSigner) Sender(tx *Transaction) (common.Address, error) {
252-
if tx.Type() != BlobTxType {
253-
return s.londonSigner.Sender(tx)
243+
func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
244+
tt := tx.Type()
245+
if !s.supportsType(tt) {
246+
return common.Address{}, ErrTxTypeNotSupported
254247
}
255-
V, R, S := tx.RawSignatureValues()
256-
// Blob txs are defined to use 0 and 1 as their recovery
248+
if tt == LegacyTxType {
249+
return s.legacy.Sender(tx)
250+
}
251+
if tx.ChainId().Cmp(s.chainID) != 0 {
252+
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID)
253+
}
254+
// 'modern' txs are defined to use 0 and 1 as their recovery
257255
// id, add 27 to become equivalent to unprotected Homestead signatures.
256+
V, R, S := tx.RawSignatureValues()
258257
V = new(big.Int).Add(V, big.NewInt(27))
259-
if tx.ChainId().Cmp(s.chainId) != 0 {
260-
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
261-
}
262258
return recoverPlain(s.Hash(tx), R, S, V, true)
263259
}
264260

265-
func (s cancunSigner) Equal(s2 Signer) bool {
266-
x, ok := s2.(cancunSigner)
267-
return ok && x.chainId.Cmp(s.chainId) == 0
268-
}
269-
270-
func (s cancunSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
271-
txdata, ok := tx.inner.(*BlobTx)
272-
if !ok {
273-
return s.londonSigner.SignatureValues(tx, sig)
261+
func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
262+
tt := tx.Type()
263+
if !s.supportsType(tt) {
264+
return nil, nil, nil, ErrTxTypeNotSupported
265+
}
266+
if tt == LegacyTxType {
267+
return s.legacy.SignatureValues(tx, sig)
274268
}
275269
// Check that chain ID of tx matches the signer. We also accept ID zero here,
276270
// because it indicates that the chain ID was not specified in the tx.
277-
if txdata.ChainID.Sign() != 0 && txdata.ChainID.CmpBig(s.chainId) != 0 {
278-
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
271+
if tx.inner.chainID().Sign() != 0 && tx.inner.chainID().Cmp(s.chainID) != 0 {
272+
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.inner.chainID(), s.chainID)
279273
}
280274
R, S, _ = decodeSignature(sig)
281275
V = big.NewInt(int64(sig[64]))
282276
return R, S, V, nil
283277
}
284278

285-
// Hash returns the hash to be signed by the sender.
286-
// It does not uniquely identify the transaction.
287-
func (s cancunSigner) Hash(tx *Transaction) common.Hash {
288-
if tx.Type() != BlobTxType {
289-
return s.londonSigner.Hash(tx)
290-
}
291-
return tx.inner.sigHash(s.chainId)
279+
// NewPragueSigner returns a signer that accepts
280+
// - EIP-7702 set code transactions
281+
// - EIP-4844 blob transactions
282+
// - EIP-1559 dynamic fee transactions
283+
// - EIP-2930 access list transactions,
284+
// - EIP-155 replay protected transactions, and
285+
// - legacy Homestead transactions.
286+
func NewPragueSigner(chainId *big.Int) Signer {
287+
return newModernSigner(chainId, forks.Prague)
292288
}
293289

294-
type londonSigner struct{ eip2930Signer }
290+
// NewCancunSigner returns a signer that accepts
291+
// - EIP-4844 blob transactions
292+
// - EIP-1559 dynamic fee transactions
293+
// - EIP-2930 access list transactions,
294+
// - EIP-155 replay protected transactions, and
295+
// - legacy Homestead transactions.
296+
func NewCancunSigner(chainId *big.Int) Signer {
297+
return newModernSigner(chainId, forks.Cancun)
298+
}
295299

296300
// NewLondonSigner returns a signer that accepts
297301
// - EIP-1559 dynamic fee transactions
298302
// - EIP-2930 access list transactions,
299303
// - EIP-155 replay protected transactions, and
300304
// - legacy Homestead transactions.
301305
func NewLondonSigner(chainId *big.Int) Signer {
302-
return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}
306+
return newModernSigner(chainId, forks.London)
303307
}
304308

305-
func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
306-
if tx.Type() != DynamicFeeTxType {
307-
return s.eip2930Signer.Sender(tx)
308-
}
309-
V, R, S := tx.RawSignatureValues()
310-
// DynamicFee txs are defined to use 0 and 1 as their recovery
311-
// id, add 27 to become equivalent to unprotected Homestead signatures.
312-
V = new(big.Int).Add(V, big.NewInt(27))
313-
if tx.ChainId().Cmp(s.chainId) != 0 {
314-
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
315-
}
316-
return recoverPlain(s.Hash(tx), R, S, V, true)
317-
}
318-
319-
func (s londonSigner) Equal(s2 Signer) bool {
320-
x, ok := s2.(londonSigner)
321-
return ok && x.chainId.Cmp(s.chainId) == 0
322-
}
323-
324-
func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
325-
txdata, ok := tx.inner.(*DynamicFeeTx)
326-
if !ok {
327-
return s.eip2930Signer.SignatureValues(tx, sig)
328-
}
329-
// Check that chain ID of tx matches the signer. We also accept ID zero here,
330-
// because it indicates that the chain ID was not specified in the tx.
331-
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
332-
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
333-
}
334-
R, S, _ = decodeSignature(sig)
335-
V = big.NewInt(int64(sig[64]))
336-
return R, S, V, nil
337-
}
338-
339-
// Hash returns the hash to be signed by the sender.
340-
// It does not uniquely identify the transaction.
341-
func (s londonSigner) Hash(tx *Transaction) common.Hash {
342-
if tx.Type() != DynamicFeeTxType {
343-
return s.eip2930Signer.Hash(tx)
344-
}
345-
return tx.inner.sigHash(s.chainId)
346-
}
347-
348-
type eip2930Signer struct{ EIP155Signer }
349-
350309
// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions,
351310
// EIP-155 replay protected transactions, and legacy Homestead transactions.
352311
func NewEIP2930Signer(chainId *big.Int) Signer {
353-
return eip2930Signer{NewEIP155Signer(chainId)}
354-
}
355-
356-
func (s eip2930Signer) ChainID() *big.Int {
357-
return s.chainId
358-
}
359-
360-
func (s eip2930Signer) Equal(s2 Signer) bool {
361-
x, ok := s2.(eip2930Signer)
362-
return ok && x.chainId.Cmp(s.chainId) == 0
363-
}
364-
365-
func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) {
366-
V, R, S := tx.RawSignatureValues()
367-
switch tx.Type() {
368-
case LegacyTxType:
369-
return s.EIP155Signer.Sender(tx)
370-
case AccessListTxType:
371-
// AL txs are defined to use 0 and 1 as their recovery
372-
// id, add 27 to become equivalent to unprotected Homestead signatures.
373-
V = new(big.Int).Add(V, big.NewInt(27))
374-
default:
375-
return common.Address{}, ErrTxTypeNotSupported
376-
}
377-
if tx.ChainId().Cmp(s.chainId) != 0 {
378-
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
379-
}
380-
return recoverPlain(s.Hash(tx), R, S, V, true)
381-
}
382-
383-
func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
384-
switch txdata := tx.inner.(type) {
385-
case *LegacyTx:
386-
return s.EIP155Signer.SignatureValues(tx, sig)
387-
case *AccessListTx:
388-
// Check that chain ID of tx matches the signer. We also accept ID zero here,
389-
// because it indicates that the chain ID was not specified in the tx.
390-
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
391-
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
392-
}
393-
R, S, _ = decodeSignature(sig)
394-
V = big.NewInt(int64(sig[64]))
395-
default:
396-
return nil, nil, nil, ErrTxTypeNotSupported
397-
}
398-
return R, S, V, nil
399-
}
400-
401-
// Hash returns the hash to be signed by the sender.
402-
// It does not uniquely identify the transaction.
403-
func (s eip2930Signer) Hash(tx *Transaction) common.Hash {
404-
switch tx.Type() {
405-
case LegacyTxType:
406-
return s.EIP155Signer.Hash(tx)
407-
case AccessListTxType:
408-
return tx.inner.sigHash(s.chainId)
409-
default:
410-
// This _should_ not happen, but in case someone sends in a bad
411-
// json struct via RPC, it's probably more prudent to return an
412-
// empty hash instead of killing the node with a panic
413-
//panic("Unsupported transaction type: %d", tx.typ)
414-
return common.Hash{}
415-
}
312+
return newModernSigner(chainId, forks.Berlin)
416313
}
417314

418315
// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which
419316
// are replay-protected as well as unprotected homestead transactions.
317+
// Deprecated: always use the Signer interface type
420318
type EIP155Signer struct {
421319
chainId, chainIdMul *big.Int
422320
}
@@ -478,8 +376,9 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
478376
return tx.inner.sigHash(s.chainId)
479377
}
480378

481-
// HomesteadSigner implements Signer interface using the
482-
// homestead rules.
379+
// HomesteadSigner implements Signer using the homestead rules. The only valid reason to
380+
// use this type is creating legacy transactions which are intentionally not
381+
// replay-protected.
483382
type HomesteadSigner struct{ FrontierSigner }
484383

485384
func (hs HomesteadSigner) ChainID() *big.Int {
@@ -505,8 +404,8 @@ func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) {
505404
return recoverPlain(hs.Hash(tx), r, s, v, true)
506405
}
507406

508-
// FrontierSigner implements Signer interface using the
509-
// frontier rules.
407+
// FrontierSigner implements Signer using the frontier rules.
408+
// Deprecated: always use the Signer interface type
510409
type FrontierSigner struct{}
511410

512411
func (fs FrontierSigner) ChainID() *big.Int {

params/forks/forks.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ const (
2424
FrontierThawing
2525
Homestead
2626
DAO
27-
TangerineWhistle
28-
SpuriousDragon
27+
TangerineWhistle // a.k.a. the EIP150 fork
28+
SpuriousDragon // a.k.a. the EIP155 fork
2929
Byzantium
3030
Constantinople
3131
Petersburg

0 commit comments

Comments
 (0)