1. 交易的签名
  2. 理解收据receipt
  3. 理解区块
  4. 理解交易
  5. blockchain核心
  6. forkId 解读
  7. 布隆过滤器原理
  8. oracle 原理和实现
  9. TxList 解读
  10. 交易池分析
  11. MPT树
  12. 区块同步
  13. geth源码学习——介绍
  14. How Geth starts its server

在阅读交易的签名之前,请先阅读 理解交易 以理解源码中交易的定义和实现,这里不做重复的说明。这篇文章暂时只关注逻辑和实现,密码学函数日后单独讨论。

核心接口 Signer

core/types/transaction_signing.go 中主要定义了签名器 Signer,他是一个接口,封装了签名相关的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Note that this interface is not a stable API and may change at any time to accommodate
// new protocol rules.
type Signer interface {
// Sender returns the sender address of the transaction.
Sender(tx *Transaction) (common.Address, error)

// SignatureValues returns the raw R, S, V values corresponding to the
// given signature.
SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error)
ChainID() *big.Int

// Hash returns 'signature hash', i.e. the transaction hash that is signed by the
// private key. This hash does not uniquely identify the transaction.
Hash(tx *Transaction) common.Hash

// Equal returns true if the given signer is the same as the receiver.
Equal(Signer) bool
}

根据变量名称和英文注释,可以知道签名器主要定义了:

  • 从交易中恢复签名的方法 Sender
  • 获取初始的三个签名参数 s, v, r 的方法 SignatureValues
  • 比较两个签名器是否相同的 Equal 方法。
  • 获取相关参数的 ChainID() 和交易的哈希 Hash

Signer 的实现

由于许多的 EIP 都影响到了交易的签名过程,或者为了方便未来拓展不同的交易类型,Signer 的方法有许多实现,例如 EIP155SignerHomesteadSigner 等都有自己封装的签名器。因此,这里只针对最新的 londonSigner 作说明,其他的大同小异。

首先 londonSigner 是之前的签名器的封装,

1
2
3
4
5
type EIP155Signer struct {
chainId, chainIdMul *big.Int
}
type eip2930Signer struct{ EIP155Signer }
type londonSigner struct{ eip2930Signer }

创建新的 londonSigner 类型也可以看出来封装的特点:

1
2
3
4
5
6
7
8
// 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)}}
}

Sender 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
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{}, ErrInvalidChainId
}
return recoverPlain(s.Hash(tx), R, S, V, true)
}

先获取签名的三个参数,其中 V 的操作是 EIP-155 规定的防止重放的变化。恢复签名的核心参数是交易哈希和签名。注意这是过程中的交易哈希,并不是已经发送交易之后的哈希标识。

获取哈希的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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 prefixedRlpHash(
tx.Type(),
[]interface{}{
s.chainId,
tx.Nonce(),
tx.GasTipCap(),
tx.GasFeeCap(),
tx.Gas(),
tx.To(),
tx.Value(),
tx.Data(),
tx.AccessList(),
})
}

可见,是交易的很多的参数综合生成的。

ecoverPlain 涉及到密码学,暂时不深入。

SignatureValues 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unc (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, ErrInvalidChainId
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
}

其实也主要是检查兼容性,然后分解签名,具体的密码学函数也不做深入,之后可能会专门写关于签名和密码学的内容

创建签名器

源码中设置了三种创建 signer 的方法。

第一个是根据区块高度检查是否有某个分叉,选则不同版本的签名器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MakeSigner returns a Signer based on the given chain config and block number.
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
var signer Signer
switch {
case config.IsLondon(blockNumber):
signer = NewLondonSigner(config.ChainID)
case config.IsBerlin(blockNumber):
signer = NewEIP2930Signer(config.ChainID)
case config.IsEIP155(blockNumber):
signer = NewEIP155Signer(config.ChainID)
case config.IsHomestead(blockNumber):
signer = HomesteadSigner{}
default:
signer = FrontierSigner{}
}
return signer
}

第二个是未知区块高度,根据是否设置分叉选择 “条件最宽松”的签名器,也可以说是兼容性优先。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// LatestSigner returns the 'most permissive' Signer available for the given chain
// configuration. Specifically, this enables support of EIP-155 replay protection and
// EIP-2930 access list transactions when their respective forks are scheduled to occur at
// any block number in the chain config.
//
// Use this in transaction-handling code where the current block number is unknown. If you
// have the current block number available, use MakeSigner instead.
func LatestSigner(config *params.ChainConfig) Signer {
if config.ChainID != nil {
if config.LondonBlock != nil {
return NewLondonSigner(config.ChainID)
}
if config.BerlinBlock != nil {
return NewEIP2930Signer(config.ChainID)
}
if config.EIP155Block != nil {
return NewEIP155Signer(config.ChainID)
}
}
return HomesteadSigner{}
}

第三个是把签名器分成两类,一类是 EIP-155 之前交易未引入 chainid 的签名器,一类则是最具兼容性的最新的 NewLondonSigner

1
2
3
4
5
6
7
8
9
10
11
12
13
// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically,
// this enables support for EIP-155 replay protection and all implemented EIP-2718
// transaction types if chainID is non-nil.
//
// Use this in transaction-handling code where the current block number and fork
// configuration are unknown. If you have a ChainConfig, use LatestSigner instead.
// If you have a ChainConfig and know the current block number, use MakeSigner instead.
func LatestSignerForChainID(chainID *big.Int) Signer {
if chainID == nil {
return HomesteadSigner{}
}
return NewLondonSigner(chainID)
}

交易的签名

源码中设置了两种签名方式:

交易+签名器+私钥

1
2
3
4
5
6
7
8
9
// SignTx signs the transaction using the given signer and private key.
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := s.Hash(tx)
sig, err := crypto.Sign(h[:], prv)
if err != nil {
return nil, err
}
return tx.WithSignature(s, sig)
}

原始交易数据+签名器+私钥

1
2
3
4
5
6
7
8
9
10
// SignNewTx creates a transaction and signs it.
func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) {
tx := NewTx(txdata)
h := s.Hash(tx)
sig, err := crypto.Sign(h[:], prv)
if err != nil {
return nil, err
}
return tx.WithSignature(s, sig)
}

其中的 WithSignature 方法用于封装交易和签名。