
右邊的txdata才是實(shí)際的交易數(shù)據(jù),它在core/types/transaction.go里是這樣聲明的:
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit uint64 `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}
第一個(gè)字段AccountNonce,直譯就是賬戶(hù)隨機(jī)數(shù)。它是以太坊中很小但也很重要的一個(gè)細(xì)節(jié)。以太坊為每個(gè)賬戶(hù)和交易都創(chuàng)建了一個(gè)Nonce,當(dāng)從賬戶(hù)發(fā)起交易的時(shí)候,當(dāng)前賬戶(hù)的Nonce值就被作為交易的Nonce。這里,如果是普通賬戶(hù)那么Nonce就是它發(fā)出的交易數(shù),如果是合約賬戶(hù)就是從它的創(chuàng)建合約數(shù)。
為什么要使用這個(gè)Nonce呢?其主要目的就是為了防止重復(fù)攻擊(Replay Attack)。因?yàn)榻灰锥际切枰灻模俣](méi)有Nonce,那么只要交易數(shù)據(jù)和發(fā)起人是確定的,簽名就一定是相同的,這樣攻擊者就能在收到一個(gè)交易數(shù)據(jù)后,重新生成一個(gè)完全相同的交易并再次提交,比如A給B發(fā)了個(gè)交易,因?yàn)榻灰资怯泻灻?,B雖然不能改動(dòng)這個(gè)交易數(shù)據(jù),但只要反復(fù)提交一模一樣的交易數(shù)據(jù),就能把A賬戶(hù)的所有資金都轉(zhuǎn)到B手里。
當(dāng)使用賬戶(hù)Nonce之后,每次發(fā)起一個(gè)交易,A賬戶(hù)的Nonce值就會(huì)增加,當(dāng)B重新提交時(shí),因?yàn)镹once對(duì)不上了,交易就會(huì)被拒絕。這樣就可以防止重復(fù)攻擊。當(dāng)然,事情還沒(méi)有完,因?yàn)檫€能跨鏈實(shí)施攻擊,直到EIP-155引入了chainID,才實(shí)現(xiàn)了不同鏈之間的交易數(shù)據(jù)不兼容。事實(shí)上,Nonce并不能真正防止重復(fù)攻擊,比如A向B買(mǎi)東西,發(fā)起交易T1給B,緊接著又提交另一個(gè)交易T2,T2的Gas價(jià)格更高、優(yōu)先級(jí)更高將被優(yōu)先處理,如果恰好T2處理完成后剩余資金已經(jīng)不足以支付T1,那么T1就會(huì)被拒絕。這時(shí)如果B已經(jīng)把東西給了A,那A也就攻擊成功了。所以說(shuō),就算交易被處理了也還要再等待一定時(shí)間,確保生成足夠深度的區(qū)塊,才能保證交易的不可逆。
Price指的是單位Gas的價(jià)格,所謂Gas就是交易的消耗,Price就是單位Gas要消耗多少以太幣(Ether),Gas * Price就是處理交易需要消耗多少以太幣,它就相當(dāng)于比特幣中的交易手續(xù)費(fèi)。
GasLimit限定了本次交易允許消耗資源的最高上限,換句話(huà)說(shuō),以太坊中的交易不可能無(wú)限制地消耗資源,這也是以太坊的安全策略之一,防止攻擊者惡意占用資源。
Recipient是交易接收者,它是common.Address指針類(lèi)型,代表一個(gè)地址。這個(gè)值也可以是空的,這時(shí)在交易執(zhí)行時(shí),會(huì)通過(guò)智能合約創(chuàng)建一個(gè)地址來(lái)完成交易。
Amount是交易額。這個(gè)簡(jiǎn)單,不用解釋。
Payload比較重要,它是一個(gè)字節(jié)數(shù)組,可以用來(lái)作為創(chuàng)建合約的指令數(shù)組,這時(shí)每個(gè)字節(jié)都是一個(gè)單獨(dú)的指令;也可以作為數(shù)據(jù)數(shù)組,由合約指令來(lái)進(jìn)行操作。合約由以太坊虛擬機(jī)(Ethereum Virtual Machine,EVM)創(chuàng)建并執(zhí)行。
V、R、S是交易的簽名數(shù)據(jù)。以太坊當(dāng)中,交易經(jīng)過(guò)數(shù)字簽名之后,生成的signature是一個(gè)長(zhǎng)度65的字節(jié)數(shù)組,它被截成三段,前32字節(jié)被放進(jìn)R,再32字節(jié)放進(jìn)S,最后1個(gè)字節(jié)放進(jìn)V。那么為什么要被截成3段呢?以太坊用的是ECDSA算法,R和S就是ECSDA簽名輸出,V則是Recovery ID??聪旅娴膉avascript代碼:
var sig = secp256k1.sign(msgHash, privateKey)
var ret = {}
ret.r = sig.signature.slice(0, 32)
ret.s = sig.signature.slice(32, 64)
ret.v = sig.recovery + 27
在早前的版本中,根據(jù)R的奇偶性取值27或28。在EIP-155之后,為了防范Replay Attack,V被調(diào)整為CHAIN_ID * 2 + 35/36,確保不同的鏈中V值不相同。來(lái)看一下core/types/transaction_signing.go末尾定義的deriveChainId函數(shù):
func deriveChainId(v *big.Int) *big.Int {
if v.BitLen() <= 64 {
v := v.Uint64()
if v == 27 || v == 28 {
return new(big.Int)
}
return new(big.Int).SetUint64((v - 35) / 2)
}
v = new(big.Int).Sub(v, big.NewInt(35))
return v.Div(v, big.NewInt(2))
}
OK,下面仔細(xì)研究一下以太坊交易是怎么簽名的。在core/types/transaction_signing.go當(dāng)中,定義了Signer這個(gè)簽名接口,以及幾個(gè)實(shí)現(xiàn)簽名的類(lèi),其UML類(lèi)圖如下:
如何確定用哪個(gè)Signer實(shí)施簽名操作?這是MakeSigner函數(shù)的任務(wù):
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
var signer Signer
switch {
case config.IsEIP155(blockNumber):
signer = NewEIP155Signer(config.ChainID)
case config.IsHomestead(blockNumber):
signer = HomesteadSigner{}
default:
signer = FrontierSigner{}
}
return signer
}
Signer接口定義了4個(gè)函數(shù),其作用分別如下:
- Sender返回交易發(fā)起方,也是付款方的地址。
- SignatureValues根據(jù)給定簽名返回原始的R、S、V值。
- Hash返回一個(gè)交易的哈希值,用于簽名操作。
- Equal用于判斷兩個(gè)Signer是否相同。
我們知道,以太坊發(fā)布分成為四個(gè)階段,分別是Frontier、Homestead、Metropolis和Serenity。所以在幾個(gè)不同的簽名類(lèi)中,F(xiàn)rontierSigner是最早出來(lái)的,然后是HomesteadSigner,之后EIP155推出時(shí)才有EIP155Signer。我們依次來(lái)看一下。
type FrontierSigner struct{}
func (s FrontierSigner) Equal(s2 Signer) bool {
_, ok := s2.(FrontierSigner)
return ok
}
所以實(shí)際上FrontierSigner就是一個(gè)空類(lèi),是最基礎(chǔ)的實(shí)現(xiàn)。來(lái)看它的另外兩個(gè)函數(shù):
func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
}
r = new(big.Int).SetBytes(sig[:32])
s = new(big.Int).SetBytes(sig[32:64])
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
return r, s, v, nil
}
func (fs FrontierSigner) 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, })
}
都比較簡(jiǎn)單直觀。其中Hash函數(shù)采用了RLP編碼過(guò)程。最后來(lái)看Sender函數(shù)的實(shí)現(xiàn):
func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) {
//注意最后一個(gè)homestead參數(shù),這里是false
return recoverPlain(fs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, false)
}
func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) {
if Vb.BitLen() > 8 {
return common.Address{}, ErrInvalidSig
}
V := byte(Vb.Uint64() - 27)
if !crypto.ValidateSignatureValues(V, R, S, homestead) {
return common.Address{}, ErrInvalidSig
}
//合成sig
r, s := R.Bytes(), S.Bytes()
sig := make([]byte, 65)
copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s)
sig[64] = V
//恢復(fù)公鑰
pub, err := crypto.Ecrecover(sighash[:], sig)
if err != nil {
return common.Address{}, err
}
if len(pub) == 0 || pub[0] != 4 {
return common.Address{}, errors.New("invalid public key")
}
var addr common.Address
copy(addr[:], crypto.Keccak256(pub[1:])[12:])
return addr, nil
}
這里用到的加密算法,將來(lái)如果有機(jī)會(huì)再深入剖析。接著看HomesteadSigner,它的相關(guān)代碼是這樣的:
type HomesteadSigner struct{ FrontierSigner }
func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
return hs.FrontierSigner.SignatureValues(tx, sig)
}
func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) {
return recoverPlain(hs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, true)
}
很簡(jiǎn)單是不是?跟FrontierSigner的區(qū)別就是在調(diào)用recoverPlain的時(shí)候,改動(dòng)了末尾最后一個(gè)參數(shù),內(nèi)部實(shí)現(xiàn)上的差別就是多了一步驗(yàn)證,這里不再多述。
最后看EIP155Signer。代碼不多,不再分拆了,詳看注釋?zhuān)?/p>
type EIP155Signer struct {
chainId, chainIdMul *big.Int //EIP155對(duì)不同的鏈?zhǔn)亲隽藚^(qū)分的
}
func (s EIP155Signer) Equal(s2 Signer) bool {
eip155, ok := s2.(EIP155Signer)
return ok && eip155.chainId.Cmp(s.chainId) == 0 //不同的鏈,不相等
}
var big8 = big.NewInt(8)
func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) {
if !tx.Protected() { //如果還是早前的交易,直接調(diào)用Homestead版的方法
return HomesteadSigner{}.Sender(tx)
}
if tx.ChainId().Cmp(s.chainId) != 0 { //鏈號(hào)不對(duì),報(bào)錯(cuò)
return common.Address{}, ErrInvalidChainId
}
V := new(big.Int).Sub(tx.data.V, s.chainIdMul) //chainIdMul = 2 * chainId
V.Sub(V, big8) //35 - 8 = 27。EIP155就在這里有所差別
return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true)
}
func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig)
if err != nil {
return nil, nil, nil, err
}
if s.chainId.Sign() != 0 {
V = big.NewInt(int64(sig[64] + 35))
V.Add(V, s.chainIdMul) // 2 * chainId + 35/36
}
return R, S, V, nil
}
func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
//注意這里的區(qū)別,Hash的時(shí)候多增加了一個(gè)chainId
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), })
}
全文完。
將來(lái)的你,一定會(huì)感謝今天拼命的自己。