签名算法
ETH Tx签名使用的是secp256k1签名算法。具体细节如下:
- 签名算法代码
github.com/ethereum/go-ethereum/crypto
- 私钥类型:secp256k1.PrivKeySecp256k1。
- 私钥对应地址类型: common.Address。
- 签名时使用私钥:ecdsa.PrivateKey。
//从secp256k1.PrivKeySecp256k1转化为ecdsa.PrivateKey。使用crypto包 key.ToECDSA()
- 签名。对输入私钥,哈希签名
sig, err := ethcrypto.Sign(txHash[:], priv) //进一步调用: secp256k1.Sign(digestHash, seckey)
- 验证。输入pubkey,哈希,以及签名验证。
secp256k1.VerifySignature(pubkey, txHash, sig)
各种Signer
secp256k1签名算法是封装的独立接口,对传入的任意hash签名,而不在乎hash代表的意义。
当然我们这里说的hash代表的是tx哈希。
在ETH版本升级硬分叉过程中,tx的哈希也在变化,为了计算不同版本的哈希,出现了各种Signer:不同signer计算出来的tx哈希是不一样的。
看签名代码的实现,Signer的核心作用就是用于计算哈希:
// 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) }
现在ETH中已经有很多Signer了:
// 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}
我们按照ETH升级的时间线,整理下各个Signer哈希的区别。
- Frontier。第一版以太坊
- Homestead。升级高度:1,150,000
- EIP155。EIP155是被包含在Spurious Dragon版本中。升级高度:2,675,000
- EIP2930。EIP2030倍包含在Berlin版本中。升级高度:12,244,000
- London。升级高度:12,965,000
FrontierSigner
这是最原始的哈希计算方式:
// Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { return rlpHash([]interface{}{ tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), }) }
HomesteadSigner
这一版其实和Frontier一样。为啥一摸一样的要搞两份呢?可能当初开发者觉得,即然版本都硬分叉了,Signer也变变没毛病吧,可是又没有什么好变的,那就变个名字吧~(仅属个人臆测)。
// HomesteadSigner implements Signer interface using the // homestead rules. type HomesteadSigner struct{ FrontierSigner }
EIP155Signer
这一版在哈希中加入了chainid,目的是为了防止重返攻击,为啥不用版本名Spurious Dragon,而用EIP155作为Signer名字呢?显然一个字,任性。
// Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (s EIP155Signer) Hash(tx *Transaction) common.Hash { return rlpHash([]interface{}{ tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), s.chainId, uint(0), uint(0), }) }
eip2930Signer
这一版引入了accessList,可以看小弟关于eip2930的介绍,做了gas费读取的调整。
// 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 rlpHash([]interface{}{ tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), s.chainId, uint(0), uint(0), }) case AccessListTxType: return prefixedRlpHash( tx.Type(), []interface{}{ s.chainId, tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList(), }) 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{} } }
londonSigner
这一版升级是EIP1559。相当重要的调整,把gas划分为baseFee和tip,目标是调整网络拥堵。具体也可见小弟关于EIP的介绍。
对应的,在哈希中也引入了GasTipCap(最大小费的gas price)和GasFeeCap(最大gas price)。
// 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(), }) }
回复 agodelo 取消回复