比特幣交易淺析
交易是比特幣系統中最重要的一部分,一筆交易通過花費 UTXO 來實現價值的鏈上轉移,並同時產生新的 UTXO ,要想花費這些新產生的 UTXO ,必須解決 UTXO 對應的數學難題,比特幣系統中稱之為鎖定指令碼。讓我們從一筆交易的構成開始,逐步揭開交易的面紗。
1. 交易基本格式
交易基本格式如下圖所示:
交易基本格式可以參考:
https://bitcoin.org/en/developer-reference 。
上圖中的 VarInt 是一種網路傳輸數字編碼格式,用於減少傳輸頻寬,格式如下:
例如: 0x21 被編碼為 0x21 ; 0x203 被編碼為 0xfd0302 ,注意 0xfd 後面跟著的 uint16 和 0xfe 後面跟著的 uint32 都是小端序。
這裡我通過解析兩個現實交易來撥開交易的真實面目。
1.1 Coinbase Transaction
該交易 ID 為 4c45186fa3ef11d414b94e2e74364e2800dd348582322909e6f96f9e25192981
區塊高度 548202
交易原始資料及欄位解釋如下
總結下 coinbase 交易:
• 只有一個輸入和輸出
• 輸入的 hash 值為空,因為 coinbase 本身是塊中第一個交易,用於生成比特幣和收集塊中其他交易的交易費
• coinbase 的簽名指令碼中除了顯示塊高度欄位外,可以填入任意內容
1.2 Ordinary Transaction
該交易 ID 為 006afa95dec0305ef42d501d9b64daee5f5d8ab2dbf629687ea9748476c0ccc8
塊高度 541,779
交易原始資料及欄位解釋如下
2. 交易指令碼驗證
一筆交易輸入通過解鎖對應的上一筆交易中的輸出指令碼來花費輸出中指定的金額。 其中對應的上一筆交易輸出指的是由該筆輸入部分中的 hash 和 n 兩個欄位共同指定的一筆交易輸出部分。
從上面的兩個交易例子中可以看到,指令碼是由操作碼和運算元構成,而指令碼驗證指的是通過依次執行輸入中的解鎖指令碼和對應輸出指令碼來驗證交易合法性,其中指令碼是基於堆疊執行的。下面是上述兩個交易中交易指令碼驗證的基本流程,最終堆疊上返回 true 代表指令碼驗證通過。
2.1 指令碼型別
• Pay-to-Pubkey
<pubkey> OP_CHECKSIG
<signature>
第一行為鎖定指令碼,第二行為解鎖指令碼,創世區塊中的交易使用這種型別,如下為輸出指令碼:
41- 將接下來的 65 位元組 push 到棧上
04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61de
b649f6bc3f4cef38c4f35504e51ec112de5c384f7ba0b8d578a4c702b6bf11d5f
-65 位元組的公鑰
ac-OP_CHECKSIG
操作碼
• Pay-to-PubkeyHash
OPDUPOPHASH160 <pubkeyHash> OPEQUALVERIFY OPCHECKSIG
<signature><pubkey>
上面簽名基本交易格式章節中提到的兩筆交易就是採用了這種型別。
• Pay-to-ScriptHash
OPHASH160<scriptHash> OPEQUAL
<signatures>{redemption script}
這種型別分兩部分進行驗證:
第一部分:
{redemption script} OPHASH160<scriptHash> OPEQUAL
第二部分:
<signatures> 反序列化後的 {redemption script}
詳細解析見下面的 MultiSig 。
• MultiSig
m <pubkey 1> ... <pubkey n>n OP_CHECKMULTISIG
OP_0 <signature 1> ...<signaturem>
第一行輸出指令碼的含義是:在 n 個公鑰中,至少要有 m 個簽名與之對應。
第二行鎖定指令碼提供了 m 個簽名。
讓我們來看一筆真實交易:
交易 ID 為 d8ded48f7874a6ef590655e18963ffdf0e20a160702bd671dcc2ca75d72e12d5
這筆交易有 135 個輸入, 4 個輸出,所有輸入輸出指令碼均為 P2SH 格式。
我們來看第一個輸入,其地址為 pplthtxum75y27cqt3fa2qf3vhtl45h66v5q5lkda8
其解鎖指令碼為:
其中 105 位元組長度的 redemption script 解析如下:
52-OP_2 操作碼
21- 長度: 33 位元組
023c6e535a80f80d65d8c2b54ad4741768f824c42c46ad738f40e823f192b6b7
- 第一個公鑰
21
038f98b3ff700359433cf7ca700b30a235ac0236ea5c40a6d760a42989b05939
- 第二個公鑰
21
02f93bb4288abf14e1c87f618f9dbd4d8847045021eb9586e2f99c7faec43ec97
- 第三個公鑰
53-OP_3
操作碼
這是一個典型的 2-3 多重簽名,提供三個公鑰,要求至少兩個簽名與之匹配。
而該解鎖指令碼的前部分恰好提供了兩個簽名,也就是說 P2SH 指令碼驗證的第二部分,即反序列化後的 redemption script 的解鎖,可以在該輸入的解鎖指令碼中完成。
我們接下來去找該輸入對應的上一筆交易的輸出。
其交易 ID 為: 7f9d4c72c980d338467538cbbf308000be1b098792016035f723f6d5a8ef62d0
交易輸出指令碼資料如下:
a9147ebbacdcdfa8457b005c53d5013165d7fad2fad387
指令碼解析:
a9-OP_HASH160
14- 將接下來的 20 位元組 push 到棧上
7ebbacdcdfa8457b005c53d5013165d7fad2fad3
- 公鑰雜湊值
87-OP_EQUAL
操作碼
P2SH 指令碼驗證的第一部分的鎖定指令碼,其中雜湊值是 redemption 指令碼的雜湊值。
注意
簽名順序要與公鑰順序一致,因為 OP_CHECKMULTISIG 操作碼在校驗多重簽名時會依次從後往前檢查簽名與交易是否匹配,如果不匹配則繼續向前遍歷公鑰,而公鑰只能被查詢與使用一次,後續簽名校驗時將無法使用之前參與校驗的公鑰,這會導致本本可以校驗通過的簽名無法校驗通過。
原因詳見:
https://bitcoin.org/en/developer-reference#opcodes
• Nulldata
OP_RETURN [SMALLDATA]
這是一類比較特殊的指令碼型別,包含該欄位的鎖定指令碼將永遠無法解鎖,也就是說輸出中對應的金額將永久銷燬。
該欄位後面可以新增任意資料,可以使用這部分空間實現區塊鏈的上層協議開發, wormhole 專案使用這部分空間來實現 BCH 鏈上的 ERC20 Token 的生成與轉移。
3. 交易簽名
先直觀的感受一下交易中的簽名。這是一個交易原始資料,其中加粗部分就是簽名:
0100000001416e9b4555180aaa0c417067a46607bc58c96f0131b2f41f7d0fb665eab03a7e000000006a47 304402201c3be71e1794621cbe3a7adec1af25f818f238f5796d47152137eba710f2174a02204f8fe667b696e30012ef4e56ac96afb830bddffee3b15d2e474066ab3aa39bad01 2103bf350d2821375158a608b51e3e898e507fe47f2d2e8c774de4a9a7edecf74edaffffffff01204e0000000000001976a914e81d742e2c3c7acd4c29de090fc2c4d4120b2bf888ac00000000
這段 16 進製表示的交易可以對應下表來解釋,其中紅色部分對應簽名。
簽名是對一筆新建交易序列化前的最後一個步驟,也就是說上表的 ScriptSig 和 Script Length 是在交易序列化前進行填充,而在此之前,其餘欄位均已填充完畢。
簽名的計算公式:
其中, Secp256k1ECDSA 是一種曲線為 Secp256k1 的橢圓曲線數字簽名演算法, Txhash 是交易被簽名部分的雜湊值, Hashtype 是計算雜湊值時採用的雜湊型別, Privatekey 是生成比特幣地址的使用的私鑰。
Hashtype 決定了交易的哪些欄位被簽名,截止今日, BitcoinCash 支援三種 Hashtype 以及兩種 Hashtype 描述符。
hashtype : SIGHASHALL , SIGHASHSINGLE , SIGHASH_NONE
字尾 ALL , SINGLE , NONE 表示該種簽名方式包含的交易輸出個數,也就是說 hashtype 影響的是交易輸出
hashtype descriptor : SIGHASHANYONECANPAY , SIGHASHFORKID
SIGHASHANYONECANPAY 影響了簽名包含的交易輸入個數, SIGHASHFORKID 作為一種防重放機制引入到 bitcoinCash 中。
它們的取值如下:
SIGHASH_ALL = 1,
SIGHASH_NONE = 2,
SIGHASH_SINGLE = 3,
SIGHASH_FORKID = 0x40,
SIGHASH_ANYONECANPAY = 0x80,
各 Hashtype 詳細計算步驟說明如下
SIGHASH_ALL
圖片來源:
https://github.com/minium/Bitcoin-Spec/blob/master/Bitcoin.pdf
計算步驟:上圖 TxNew 中陰影部分為 SIGHASHALL 型別簽名區域,包含版本號,輸入,全部輸出, nLockTime 。清空其餘輸入的 scripSigLen 和 scriptSig 欄位,即其餘輸入 scripSigLen 欄位為 0 , scriptSig 欄位不佔用空間。複製輸入對應的 TxPrev 鎖定指令碼並去除其中的 OPCODESEPARATOR 操作碼,將改動後的指令碼填入 scriptSig 欄位,計算指令碼長度並填入 scriptSigLen 。在修改後的 TxNew 後序列化並在末端新增四位元組小端序 HashType 欄位,此處為 0x01000000 。最後進行 double-sha256 雜湊運算,生成上述簽名公式中的 Txhash 。
SIGHASH_SINGLE
計算步驟:上圖 TxNew 中陰影部分為 SIGHASHSINGLE 型別簽名區域, 包含版本號,輸入,簽名輸入對應的輸出, nLockTime 。(輸入對應的輸出指的是二者在交易輸入區與輸出區的索引是相同的),清空其餘輸入的 scripSigLen , scriptSig 和 nSequence 欄位。輸出數量即圖中 #vout 欄位重置為 Input Index+1 ,對於本例,此時 #vout 等於 1 。按照 #out 數量重新調整輸出大小,待簽名以外輸出的 nValue 置為 -1 , scriptPubkeyLen 置為 0 。複製輸入對應的 TxPrev 鎖定指令碼並去除其中的 OPCODESEPARATOR 操作碼,將改動後的指令碼填入 scriptSig 欄位,計算指令碼長度並填入 scriptSigLen 。在修改後的 TxNew 後序列化並在末端新增四位元組小端序 HashType 欄位,此處為 0x11000000 。最後進行 double-sha256 雜湊運算,生成上述簽名公式中的 Txhash 。
SIGHASH_SINGLE 不對其他輸入的 sequence 欄位簽名,意味著其他參與者可以通過更改 sequence 欄位來更新交易。
SIGHASH_NONE
計算步驟:清空其餘輸入的 scripSigLen , scriptSig 和 nSequence 欄位,清空輸出數量 #vout 。複製輸入對應的 TxPrev 鎖定指令碼並去除其中的 OP_CODESEPARATOR 操作碼,將改動後的指令碼填入 scriptSig 欄位,計算指令碼長度並填入 scriptSigLen 。在修改後的 TxNew 後序列化並在末端新增四位元組小端序 HashType 欄位,此處為 0x10000000 。進行 double-sha256 雜湊運算,生成上述簽名公式中的 Txhash 。
SIGHASH_NONE 意味著只保護交易輸入而不關心交易輸出,它可以用來實現類似空白支票的應用。
SIGHASH_ANYONECANPAY
修飾符必須和上面三種 Hashtype 共同使用, SIGHASH_ANYONECANPAY 表示計算 Txhash 時只包含該輸入,其他輸入被移除。
計算步驟:將輸入數量 #vin 重置為 0 ,移除該輸入外的其他輸入,最後按照上訴三種 Hashtype 對應的步驟執行。
SIGHASHANYONECANPAY 與上述型別組合可以產生特定功能的交易,例如其與 SIGHASHALL 組合可以用來實現眾籌,將輸出設定為眾籌目標,當全部輸入總和超過目標時眾籌成立,否則交易無效。再比如其與 SIGHASH_NONE 組合可以實現一種名叫 “ 吸塵器 ” 的應用,它用來將可能構成 Dust Transaction 的微小金額的 UTXO 捐贈給受贈者。
SIGHASH_FORKID
SIGHASH_FORKID 雜湊方式為 BitcoinCash 提供了重放保護,它將一筆交易按如下 TxCopy 中的順序序列化。
對上述 TxCopy 中的位元組序列進行 double-sha256 雜湊運算,生成簽名公式中的 Txhash
SIGHASHFORKID 與上述三種 Hashtype 以及 SIGHASHANYONECANPAY 的組合影響到 hashPrevouts 等欄位的計算方法。具體細節參考:
https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/doc/abc/replay-protected-sighash.md
這裡提到防重放攻擊,在硬分叉後, BitcoinCash 中構建的合法交易可以直接拷貝到 Bitcoin 中使用並驗證通過,這樣會導致交易發起者 BTC 的丟失。因此引入 SIGHASH_FORKID 利用 Bitcoin 與 BitcoinCash 對交易指令碼驗證中籤名欄位的不同解釋來防止這類重放攻擊。
最終填充到 SicriptSig 中的簽名由下圖生成
圖片來源:
https://github.com/minium/Bitcoin-Spec/blob/master/Bitcoin.pdf
計算步驟:將按照上述 Hashtype 步驟生成的交易雜湊值 Txhash ,通過私鑰簽名生成原始簽名。然後將原始簽名 DER 編碼。最後在 DER 編碼後的簽名末端新增截斷至一位元組的 Hashtype 。
以文首交易簽名為例
304402201c3be71e1794621cbe3a7adec1af25f818f238f5796d47152137eba710f2174a02204f8fe667b696e30012ef4e56ac96afb830bddffee3b15d2e474066ab3aa39bad01
其具體結構解析如下:
DER Header : 0x30
Total Lenth : 0x44
Integer Header : 0x02
R-Length : 0x20
The R coordinate :
01c3be71e1794621cbe3a7adec1af25f818f238f5796d47152137eba710f2174a
Integer Header : 0x02
S-Length : 0x20
The S coordinate :
4f8fe667b696e30012ef4e56ac96afb830bddffee3b15d2e474066ab3aa39bad01
Hashtype
:
0x01
簽名的校驗過程通常通過操作碼 OP_CHECKSIG 執行,具體步驟如下
• 去除簽名最後一個位元組並獲取 Hashtype 。
• 根據 Hashtype 計算待驗證交易的雜湊值 HashNew 。
• 使用公鑰解析待驗證簽名得到被私鑰簽名的雜湊值 HashOld 。
• 校驗 HashNew 是否等於 HashOld ,相等則簽名校驗通過,否則校驗失敗。
詳細過程請參考 wiki 中關於 OPCHECKSIG 的描述: https://en.bitcoin.it/wiki/OPCHECKSIG#ProcedureforHashtypeSIGHASHSINGLE
綜上,簽名型別由生成簽名的 hashtype 決定,簽名型別的區別本質上是被簽名欄位的不同。通過對交易中不同欄位簽名,實現對特定物件的保護,而未簽名欄位則給其餘交易參與者提供了更多靈活性,利用這種特性可以實現合約功能,例如捐贈。
4. 交易標準
判斷一筆交易是否是標準交易,要檢查如下幾個方面:
• 交易大小不大於 100KB
• 交易的版本號應當為 1 或 2
• 交易的解鎖指令碼大小不能大於 1650 位元組
• 解鎖指令碼中的操作碼只允許為 OP_16 及以下的操作碼
• 交易是 final 狀態
• 輸出指令碼型別是標準型別
• 交易為 Non-DustTransaction
• 最多一個 Nulldata 型別輸出
• 多重簽名的公鑰個數只能為 1 , 2 或 3
5. 總結
交易其實是使用解鎖指令碼解鎖輸入與對應的上一筆交易的輸出指令碼,從而花費其 UTXO ,創造新的 UTXO 的過程。
作為交易的核心的指令碼,目前共有五類: P2PK , P2PKH , P2SH , MultiSig , NullData 。此外,交易本身有必須滿足規定的標準才能在比特幣網路中被傳播與確認。