這篇文字作者從go-ethereum的源碼出發(fā),講解什么是交易Hash,簽名Hash,理解的比較深入。以下對代碼做了重新排版,便于閱讀。感謝原作者powervip!
0.概述
進(jìn)行一筆以太坊交易時涉及的哈希計算有2個:
a) 用于簽名的哈希值的計算:在使用私鑰計算一筆交易的簽名內(nèi)容時,先要對交易結(jié)構(gòu)的相關(guān)數(shù)據(jù)以及鏈id(chainId)進(jìn)行哈希,然后再使用私鑰對哈希結(jié)果進(jìn)行簽名得到簽名結(jié)果(簽名結(jié)果包含R, S, V 3個字段)。該哈希值不會被寫入?yún)^(qū)塊鏈里。
b) 交易哈希的計算:對整個交易結(jié)構(gòu)的所有數(shù)據(jù)進(jìn)行哈希。該哈希值(即交易id)會被寫入?yún)^(qū)塊鏈里。我們平時在區(qū)塊鏈瀏覽器里通過交易哈希查詢一筆交易時使用的就是這個哈希值。
1.用于簽名的哈希值的計算
交易發(fā)起者使用私鑰對交易進(jìn)行簽名,并不是對整個交易直接簽名。而是先對交易結(jié)構(gòu)的數(shù)據(jù)以及鏈id(chainId)進(jìn)行哈希計算,然后再使用私鑰對哈希結(jié)果(也就是一串32字節(jié)的字符串,轉(zhuǎn)化為16進(jìn)制的字符串就是64位的16進(jìn)制數(shù)字符串)進(jìn)行ECDSA算法簽名得到簽名結(jié)果(R, S, V)。
以太坊上定義了Signer這個簽名接口,用于對交易進(jìn)行簽名。Signer這個簽名接口的具體實(shí)現(xiàn)是3種簽名器:
FrontierSignerHomesteadSignerEIP155Signer
3種簽名器中,FrontierSigner是簽名器最早實(shí)現(xiàn)的版本,HomesteadSigner是后來實(shí)現(xiàn)的版本,EIP155Signer則是最新的版本,并在簽名的哈希值計算中引入了chainId來防止跨鏈實(shí)施攻擊。
在EIP155Signer版本簽名器中,節(jié)點(diǎn)需要對客戶端發(fā)送過來的簽名結(jié)果中的V值進(jìn)行校驗(yàn),校驗(yàn)V值中包含的chainId是否和本節(jié)點(diǎn)的chainId相比較,如果不一致,則認(rèn)為該筆交易不是本條鏈上的交易,從而判斷該交易非法。具體可以參考core/types/transaction_signing.go里如下函數(shù)實(shí)現(xiàn):
func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error)
對于使用EIP155Signer版本簽名器,簽名結(jié)果中的V值的計算方法是:V = chainId * 2 + 35
具體可以參考下面這個函數(shù)的實(shí)現(xiàn):
func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error)
對一筆以太坊交易進(jìn)行簽名的過程如下:
core/types/transaction_signing.go
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)
}
s.Hash(tx)計算出用于簽名的哈希值h,然后再使用私鑰prv對哈希值h進(jìn)行簽名。s的具體實(shí)例是FrontierSigner,HomesteadSigner,EIP155Signer這3種簽名器中的一個。
需要注意的是,即使是對于同一個哈希值h,每次使用同一個私鑰prv對哈希值h進(jìn)行簽名得到的R, S這2個字段不一定會相同。
下面以使用EIP155Signer版本簽名器為例,看看用于簽名的哈希值的計算過程:
func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
return rlpHash([]interface{}{
tx.data.AccountNonce,
tx.data.Price,
tx.data.GasLimit,
tx.data.Recipient,
tx.data.Amount,
tx.data.Payload,
s.chainId, uint(0), uint(0),
})
}
這里可以看到,用于簽名的哈希值的計算,只是把交易的基礎(chǔ)輸入數(shù)據(jù)加上chainId進(jìn)行RLP編碼再獲取哈希值?;A(chǔ)輸入數(shù)據(jù)包括:AccountNonce(發(fā)起者的Nonce),Price(gasPrice),GasLimit,Recipient(接收者),Amount(轉(zhuǎn)賬金額),Payload(智能合約相關(guān)的內(nèi)容,如果是普通轉(zhuǎn)賬交易,該字段為空)
哈希函數(shù)rlpHash()的具體定義見core/types/block.go
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h
}
該函數(shù)先對接口數(shù)據(jù)進(jìn)行RLP編碼,再運(yùn)用Keccak256哈希算法獲得交易哈希。
RLP編碼的規(guī)則網(wǎng)上有不少的文章都有介紹,這里就不詳細(xì)介紹了。RLP編碼的重點(diǎn)是給原始數(shù)據(jù)前面添加若干字節(jié)的前綴,而且這個前綴是和數(shù)據(jù)的長度相關(guān)的。RLP編碼后得到的是一個字節(jié)數(shù)組。
2.交易哈希的計算
對于交易哈希的計算,它是在core/types/transaction.go里的Hash()函數(shù):
func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil {
return hash.(common.Hash)
}
v := rlpHash(tx)
tx.hash.Store(v)
return v
}
我們再看一下Transaction這個結(jié)構(gòu)體的定義:
type Transaction struct {
data txdata
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *[http://big.Int](https://link.zhihu.com/?target=http%3A//big.Int) `json:"gasPrice" gencodec:"required"`
GasLimit uint64 `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *[http://big.Int](https://link.zhihu.com/?target=http%3A//big.Int) `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`
// Signature values
V *[http://big.Int](https://link.zhihu.com/?target=http%3A//big.Int) `json:"v" gencodec:"required"`
R *[http://big.Int](https://link.zhihu.com/?target=http%3A//big.Int) `json:"r" gencodec:"required"`
S *[http://big.Int](https://link.zhihu.com/?target=http%3A//big.Int) `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}
可以看出,Hash()函數(shù)是對整個交易數(shù)據(jù)(包括簽名后的簽名結(jié)果,但不包括hash字段。因?yàn)閔ash字段使用了rlp:"-",表示rlp編碼解碼時忽略該字段。hash,size 和from這3個atomic.Value類型的字段也是不包括的 )進(jìn)行處理,處理過程是先對整個交易數(shù)據(jù)進(jìn)行RLP編碼,再運(yùn)用Keccak256哈希算法獲得交易哈希。對于每一筆交易,其交易哈希都是確定(不需要等待交易打包進(jìn)區(qū)塊鏈,在錢包端簽名交易后交易哈希就已經(jīng)是可以確定的)和唯一的(不同的2筆交易計算出來的交易哈希相同的可能性基本為0)。
在控制臺中,我們通過調(diào)用eth.signTransaction()這個接口就能獲取一筆交易的交易哈希,下面輸出結(jié)果中紅色的部分就是交易哈希:
\> eth.signTransaction({
...... nonce: 4,
...... gasPrice: '0x09184e72a000',
...... gas: '1000000',
...... from:"0x06fe0338213124b2d8f198eafb1914956824e218",
...... to:"0x815261DC4186502eC0D8CCFEf163785e1617b1A8",
...... value: '0x1'
...... })
{
raw: "0xf8a8048609184e72a000830f42409406fe0338213124b2d8f198eafb1914956824ef1801801ba093e4f549a65405ea931e525a7880ef0e802cd7fa275523d31faf1dae49f54268a0b20f0b87d34e866582b7cfb687a78df7ed88cf404f9947647b7a07670b62897fa0a45a909bbbfe4ed27ec9ad98c41bac220e8cd6aac7e860ed23e2185128d6cf13a088642f0cc9f37afd8d0923871773f52ae314bdd060be7625a9c3f4793aac3557",
tx: {
gas: "0xf4240",
gasPrice: "0x9184e72a000",
hash: "0x6387fd1ad72e604151b2c5900ffb23069c3cdeebf2768d1e8277d43b721448fa",
input: "0x",
nonce: "0x4",
r: "0x93e4f549a65405ea931e525a7880ef0e802cd7fa275523d31faf1dae49f54268",
s: "0xb20f0b87d34e866582b7cfb687a78df7ed88cf404f9947647b7a07670b62897f",
to: "0x815261DC4186502eC0D8CCFEf163785e1617b1A8",
v: "0x1b",
value: "0x1"
}
}
eth.SendTransaction()這個接口的返回結(jié)果也是該筆交易的交易哈希:
\>eth.sendTransaction({from:"0x06FE0338213124B2D8f198eafB1914956824e218",to:"0x815261DC4186502eC0D8CCFEf163785e1617b1A8",value:web3.toWei(20,"ether")})
"0x7cf3167b50e10428d368197e0e00b8631076a930d9f7f58500ecafd6bce16a4b"
我們也可以通過交易哈希來查詢一筆交易:
\>eth.getTransaction("0x22a1264249302682475703933c819e32b4c1524938f94ff35b87c19f4e8beeb2")
{
blockHash: "0x3be26d1481eeb783742f2792d0c6fa1c6398ce15bf1ec3c1c5a863fd46014035",
blockNumber: 22027,
from: "0x06fe0338213124b2d8f198eafb1914956824e218",
gas: 21000,
gasPrice: 1000000000,
hash: "0x22a1264249302682475703933c819e32b4c1524938f94ff35b87c19f4e8beeb2",
input: "0x",
nonce: 3,
r: "0x325ead2013e351133a98876e83e2cb39954c3f370bdba397b091a06c3026f30d",
s: "0x2ba924507b42faa0f709fd7c7a5b785d5f26eff391c804598920cfaf850f8239",
to: "0x815261DC4186502eC0D8CCFEf163785e1617b1A8",
transactionIndex: 0,
v: "0x1df",
value: 20000000000000000000
}
也可以通過交易哈希來查詢一筆交易的收據(jù):
\>eth.getTransactionReceipt("0x22a1264249302682475703933c819e32b4c1524938f94ff35b87c19f4e8beeb2")
{
blockHash: "0x3be26d1481eeb783742f2792d0c6fa1c6398ce15bf1ec3c1c5a863fd46014035",
blockNumber: 22027,
contractAddress: null,
cumulativeGasUsed: 21000,
from: "0x06fe0338213124b2d8f198eafb1914956824e218",
gasUsed: 21000,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
to: "0x815261DC4186502eC0D8CCFEf163785e1617b1A8",
transactionHash: "0x22a1264249302682475703933c819e32b4c1524938f94ff35b87c19f4e8beeb2",
transactionIndex: 0
}