交易是比特幣系統中最重要的部分。比特幣中其他的一切都旨在確保交易可以建立,傳播,驗證並最終新增到交易(區塊鏈)的全球總賬中。交易是對比特幣系統參與者之間的價值轉移進行編碼的資料結構。每筆交易都是比特幣區塊鏈中的公開條目,即全球複式簿記分類賬。
在本章中,我們將檢查各種形式的交易,它們包含的內容,如何建立它們,如何驗證以及它們如何成為所有交易永久記錄的一部分。當我們在本章中使用術語“錢包”時,我們指的是建構交易的軟體,而不僅僅是金鑰的資料庫。
在 [ch02_bitcoin_overview] 中,我們使用區塊瀏覽器查看了Alice在Bob的咖啡店購買咖啡的交易( Alice’s transaction to Bob’s Cafe )。
區塊瀏覽器顯示一個從Alice的“地址”到Bob的“地址”的交易。這是交易中包含的內容的簡化檢視。事實上,我們將在本章中看到,大部分資訊都是由區塊瀏覽器建構的,實際上並不在交易中。
實際的交易看起來與典型的區塊瀏覽器提供的非常不同。實際上,我們在各種比特幣應用介面中看到的高層次結構 並不實際存在於 比特幣系統中。
我們可以使用Bitcoin Core的命令列介面( getrawtransaction 和 decoderawtransaction )來檢索Alice的“原始”交易,對其進行解碼並檢視它包含的內容。結果如下所示:
{
"version": 1,
"locktime": 0,
"vin": [
{
"txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.01500000,
"scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": 0.08450000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
}
]
}
你可能只注意到有關此次交易的幾個資訊,大多數資訊缺失了!Alice的地址在哪裡?Bob的地址在哪裡? Alice傳送的0.1輸入在哪裡?在比特幣中,沒有硬幣,沒有傳送者,沒有接收者,沒有餘額,沒有帳戶,也沒有地址。所有這些東西都是在更高層次上建構的,以使事情更易於理解。
你可能會注意到很多奇怪的,難以辨認的欄位和十六進位制字串。別擔心,我們將在本章中詳細解釋每個欄位。
比特幣交易的基本建構塊是 交易的輸出 transaction output 。交易輸出是不可分割的比特幣貨幣,記錄在區塊鏈中,被整個網路識別為有效的。比特幣完整節點追蹤所有可用和可花費的輸出,稱為 未花費的交易輸出 unspent transaction outputs 或 UTXO 。所有UTXO的集合被稱為 UTXO set ,目前有數以百萬的UTXO。UTXO集的大小隨著新UTXO的增加而增長,並在UTXO被消耗時縮小。每個交易都表示UTXO集中的更改(狀態轉移)。
當我們說使用者的錢包“收到”比特幣時,意思是錢包檢測到一個可以使用該錢包控制的金鑰來花費的UTXO。因此,使用者的比特幣“餘額”是使用者錢包可以花費的所有UTXO的總和,可以分散在數百個交易和數百個塊中。餘額的概念是由錢包應用建立的。錢包掃描區塊鏈並將錢包可以使用它的金鑰花費的任何UTXO彙總計算使用者的餘額。大多數錢包維護資料庫或使用資料庫服務來儲存它們可以花費的所有UTXO的快照。
一個交易輸出可以有一個任意的(整數)等於satoshis倍數的值作為。正如美元可以分為小數點後兩位數字一樣,比特幣可以被分為小數點後八位,作為satoshis。儘管輸出可以具有任意值,但一旦建立就是不可分割的。這是需要強調的輸出的一個重要特徵:輸出是 不連續的 和 不可分割的 的價值,以整數satoshis為單位。未使用的輸出只能由交易全部花費。
如果UTXO大於交易的期望值,它仍然必須全部使用,並且必須在交易中產生零錢。換句話說,如果你有一個價值20比特幣的UTXO,並且只需要支付1比特幣,那麼你的交易必須消費整個20比特幣的UTXO,併產生兩個輸出:一個支付1比特幣給你想要的收款人,另一個支付19比特幣回到你的錢包。由於交易輸出的不可分割性,大多數比特幣交易將不得不產生零錢。
想象一下,一個購物者購買了1.50美元的飲料,並試圖從她的錢包找到硬幣和鈔票的組合,以支付1.50美元。如果可能,購物者將找到正好的零錢,例如,一美元鈔票和兩個二十五分硬幣(0.25美元),或小面值(六個二十五分硬幣)的組合;或者,直接向店主支付5美元,她會得到3.50美元的找零,放回她的錢包並且可用於未來的交易。
同樣,比特幣交易必須從使用者的UTXO建立,無論使用者有什麼樣的面額。使用者無法將UTXO削減一半,就像不能將美元分成兩半使用一樣。使用者的錢包應用通常會從使用者的可用UTXO中進行選擇,使組合的金額大於或等於期望交易金額。
與現實一樣,比特幣應用可以使用多種策略來滿足支付需求:合併幾個較小的單位,找到正好的零錢,或者使用比交易價值更大的單元並進行找零。所有這些花費UTXO的複雜操作都由使用者的錢包自動完成,對使用者不可見。只有在編寫程式建構來自UTXO的原始交易時才有意義。
交易消耗先前記錄的未使用的交易輸出,並建立可供未來交易使用的新交易輸出。這樣,大量的比特幣價值透過建立UTXO的交易鏈在擁有者之間轉移。
輸出和輸入鏈的例外是稱為 幣基 coinbase 交易的特殊型別的交易,它是每個塊中的第一個交易。這筆交易由“獲勝”的礦工設定,建立全新的比特幣並支付給該礦工作為挖礦獎勵。此特殊的coinbase交易不消費UTXO,相反,它有一種稱為“coinbase”的特殊輸入型別。這就是比特幣在挖礦過程中創造的貨幣數量,正如我們將在 [minig] 中看到的那樣。
Tip
|
先有的什麼?輸入還是輸出?雞還是雞蛋?嚴格地說,輸出是第一位的,因為產生新比特幣的幣基交易沒有輸入,是憑空產生的輸出。 |
每筆比特幣交易都產生輸出,這些輸出記錄在比特幣帳簿上。除了一個例外(參見 [op_return] ),幾乎所有這些輸出都創造了稱為UTXO的可支付的比特幣,由整個網路認可並可供擁有者在未來的交易中花費。
每個完整節點比特幣客戶端都追蹤UTXO。新交易消耗(花費)UTXO集合的一個或多個輸出。
交易輸出由兩部分組成:
-
一些比特幣,最小單位為 聰 satoshis
-
定義了花費這些輸出所需條件的加密謎題
這個謎題也被稱為 鎖定指令碼 locking script ,見證指令碼 witness script ,或者 scriptPubKey。
在 交易指令碼和指令碼語言 中詳細討論了前面提到的鎖定指令碼中使用的交易指令碼語言。
現在,我們來看看Alice的交易( 交易背後 ),看看我們是否可以識別輸出。在JSON編碼中,輸出位於名為 vout 的陣列(列表)中:
"vout": [
{
"value": 0.01500000,
"scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY
OP_CHECKSIG"
},
{
"value": 0.08450000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
}
]
如你所見,該交易包含兩個輸出。每個輸出由一個值和一個加密謎題定義。在Bitcoin Core顯示的編碼中,該值以比特幣為單位,但在交易本身中,它被記錄為以satoshis為單位的整數。每個輸出的第二部分是設定消費條件的加密謎題。Bitcoin Core將其顯示為 scriptPubKey 並展示了該指令碼的人類可讀的表示。
鎖定和解鎖UTXO的主題將在稍後的 建立指令碼 ( 鎖定 + 解鎖 ) 中討論。在 交易指令碼和指令碼語言 中討論了 scriptPubKey 中使用的指令碼語言。但在深入研究這些話題之前,我們需要了解交易輸入和輸出的總體結構。
當交易透過網路傳輸或在應用程式之間交換時,它們是 序列化 的。序列化是將資料結構的內部表示轉換為可以一次傳輸一個位元組的格式(也稱為位元組流)的過程。序列化最常用於對透過網路傳輸或儲存在檔案中的資料結構進行編碼。交易輸出的序列化格式展示在 Transaction output serialization 中。
Size | Field | Description |
---|---|---|
8 位元組 (小端序) |
數量 Amount |
以聰(satoshis = 10-8 bitcoin) 為單位的比特幣價值 |
1——9 位元組 (VarInt) |
鎖定指令碼的大小 Locking-Script Size |
後面的鎖定指令碼的位元組數 |
變數 |
鎖定指令碼 Locking-Script |
定義花費該輸出的條件的指令碼 |
大多數比特幣函式庫和框架在內部不以位元組流的形式儲存交易,因為每次需要訪問單個欄位時都需要進行複雜的解析。為了方便和易讀,比特幣函式庫在資料結構(通常是面向物件的結構)中儲存交易。
從交易的位元組流表示轉換為函式庫的內部表示資料結構的過程稱為 反序列化 deserialization 或 交易解析 transaction parsing 。轉換回位元組流以透過網路進行傳輸,進行雜湊或儲存在磁碟上的過程稱為 序列化 serialization。大多數比特幣函式庫具有用於交易序列化和反序列化的內建函式。
看看你是否可以從序列化的十六進位制形式手動解碼Alice的交易,找到我們以前看到的一些欄位。兩個輸出部分在 Alice’s transaction, serialized and presented in hexadecimal notation 中突出顯示:
0100000001186f9f998a5aa6f048e51dd8419a14d8a0f1a8a2836dd73 4d2804fe65fa35779000000008b483045022100884d142d86652a3f47 ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039 ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813 01410484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade84 16ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc1 7b4a10fa336a8d752adfffffffff0260e31600000000001976a914ab6 8025513c3dbd2f7b92a94e0581f5d50f654e788acd0ef800000000000 1976a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac 00000000
這裡有一些提示:
-
突出顯示的部分有兩個輸出,每個輸出按照 Transaction output serialization 所示進行了序列化。
-
0.015比特幣是1,500,000聰. 十六進位制表示為 16 e3 60 .
-
在序列化的交易中,16 e3 60 以小端序(低位位元組在前)編碼,所以看起來是: 60 e3 16。
-
scriptPubKey 的長度是 25 位元組, 十六進位制表示為 19 。
交易輸入標識(透過參考)將使用哪個UTXO並透過解鎖指令碼提供所有權證明。
為了建立交易,錢包從其控制的UTXO中選擇具有足夠價值的UTXO進行所請求的付款。有時候一個UTXO就足夠了,有時候需要多個UTXO。對於將用於進行此項付款的每個UTXO,錢包將建立一個指向UTXO的輸入,並使用解鎖指令碼將其解鎖。
讓我們更詳細地看看輸入的組成部分。輸入的第一部分是指向UTXO的指標,參考交易的雜湊值和輸出索引,該索引標識該交易中特定的UTXO。第二部分是一個解鎖指令碼,由錢包建構,為了滿足UTXO中設定的花費條件。大多數情況下,解鎖指令碼是證明比特幣所有權的數位簽章和公鑰。但是,並非所有解鎖指令碼都包含簽名。第三部分是序列號,稍後將進行討論。
考慮 交易背後 中的範例,交易的輸出是 vin 陣列:
"vin": [
{
"txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
]
如你所見,列表中只有一個輸入(因為這個UTXO包含足夠的值來完成此次付款)。輸入包含四個元素:
-
交易ID,參考包含正在使用的UTXO的交易
-
輸出索引( vout ),標識使用來自該交易的哪個UTXO(第一個從0開始)
-
scriptSig,滿足UTXO上的條件的指令碼,用於解鎖並花費
-
一個序列號(後面討論)
在Alice的交易中,輸入指向交易ID:
7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18
輸出索引 0(即由該交易建立的第一個UTXO)。解鎖指令碼由Alice的錢包建構,首先檢索參考的UTXO,檢查其鎖定指令碼,然後使用它建構必要的解鎖指令碼以滿足它。
只看輸入內容,你可能已經注意到我們對這個UTXO一無所知,只有對包含它的交易的參考。我們不知道它的價值(satoshi的數量),也不知道設定花費條件的鎖定指令碼。要找到這些資訊,我們必須透過檢索底層交易來檢索參考的UTXO。請注意,因為輸入值沒有明確說明,我們還必須使用參考的UTXO來計算將在此次交易中支付的費用(請參見 交易費用 )。
不僅Alice的錢包需要檢索輸入中參考的UTXO。一旦這個交易被廣播到網路中,每個驗證節點也將需要檢索在交易輸入中參考的UTXO以驗證交易。
這些交易本身似乎不完整,因為它們缺乏上下文。他們在其輸入中參考UTXO,但不檢索該UTXO,我們不知道輸入值或鎖定條件。在編寫比特幣軟體時,只要你想要驗證交易,計算費用或檢查解鎖指令碼,你的程式碼首先必須從區塊鏈中檢索參考的UTXO,以便建構輸入中參考的UTXO隱含但不包括的上下文。例如,要計算支付的費用金額,你必須知道輸入和輸出值的總和。如果不檢索輸入中參考的UTXO,則不知道它們的價值。因此,像單筆交易中計費的看似簡單的操作實際上涉及多個交易的多個步驟和資料。
我們可以使用在檢索Alice的交易時使用的相同的Bitcoin Core命令序列( getrawtransaction 和 decoderawtransaction )。得到前面輸入中參考的UTXO:
"vout": [
{
"value": 0.10000000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG"
}
]
我們看到這個UTXO的值為 0.1 BTC,包含一個鎖定指令碼( scriptPubKey ): "OP_DUP OP_HASH160…".
Tip
|
為了完全理解Alice的交易,我們必須檢索輸入參考的交易。幾乎每個比特幣函式庫和API中都有一個函式,用於檢索以前的交易和未使用的交易輸出。 |
當交易被序列化以便在網路上傳輸時,它們的輸入被編碼為位元組流,如 Transaction input serialization 所示。
Size | Field | Description |
---|---|---|
32 位元組 |
交易的雜湊值 Transaction Hash |
指向包含要花費的UTXO的交易的指標 |
4 位元組 |
輸出的索引 Output Index |
要花費的UTXO的索引,從0開始 |
1——9 位元組 (VarInt) |
解鎖指令碼的大小 Unlocking-Script Size |
後面的解鎖指令碼的位元組長度 |
變數 |
解鎖指令碼 Unlocking-Script |
滿足UTXO鎖定指令碼條件的指令碼 |
4 位元組 |
序列號 Sequence Number |
用於鎖定時間(locktime)或禁用 (0xFFFFFFFF) |
與輸出一樣,看看是否能夠在序列化格式中查詢來自Alice的交易的輸入。首先,解碼的輸入如下:
"vin": [
{
"txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
],
現在,看看我們是否可以在 Alice’s transaction, serialized and presented in hexadecimal notation 中的序列化的十六進位制編碼中識別這些欄位:
0100000001186f9f998a5aa6f048e51dd8419a14d8a0f1a8a2836dd73 4d2804fe65fa35779000000008b483045022100884d142d86652a3f47 ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039 ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813 01410484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade84 16ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc1 7b4a10fa336a8d752adfffffffff0260e31600000000001976a914ab6 8025513c3dbd2f7b92a94e0581f5d50f654e788acd0ef800000000000 1976a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac00000 000
提示:
-
交易ID是以反向位元組順序序列化的,因此它以(十六進位制)18 開頭並以 79 結尾
-
輸出索引是一個4位元組的零,容易識別
-
scriptSig 的長度為139個位元組,十六進位制的 8b
-
序列號設定為 FFFFFFFF,也易於識別
大多數交易包括交易費用,以獎勵比特幣礦工,保證網路安全。費用本身也可以作為一種安全機制,因為攻擊者透過大量交易充斥網路在經濟上是不可行的。[mining] 更詳細地討論了礦工以及礦工收取的費用和獎勵。
本節探討交易費用如何包含在典型的交易中。大多數錢包會自動計算幷包含交易費用。但是,如果你以程式設計方式建構交易或使用命令列介面,則必須手動進行計算幷包含這些費用。
交易費用是將交易納入下一個區塊的激勵措施,也是對每次交易徵收小額費用以抵制系統濫用的防範機制。交易費由礦工收集,該礦工將開採在區塊鏈上記錄交易的區塊。
交易費用是以交易資料的大小(KB)計算的,而不是比特幣交易的價值。總體而言,交易費用是根據比特幣網路內的市場力量設定的。礦工根據許多不同的優先條件(包括費用)處理交易,也可能在某些情況下免費處理交易。交易費用會影響處理優先權,這意味著如果交易費用足夠,交易就可能包含在下一個開採區塊中,而費用不足或不收費的交易可能會延遲,在幾個區塊後以盡力而為的方式處理,或者根本不處理。交易費用不是強制性的,沒有費用的交易最終可以被處理;但是,包括交易費用鼓勵優先處理。
隨著時間的推移,交易費用的計算方式以及它們對交易優先順序的影響已經發生了變化。起初,交易費用在整個網路中是固定不變的。逐漸地,收費結構放鬆,並可能受到基於網路容量和交易量的市場力量的影響。至少從2016年初開始,比特幣的容量限制已經造成了交易之間的競爭,導致了更高的費用,使免費的交易成為了歷史。免費或低費用的交易很少能被開採,有時甚至不會透過網路傳播。
在Bitcoin Core中,收費中繼策略由 minrelaytxfee 選項設定。當前的預設值是每KB資料0.00001比特幣或0.01毫比特幣。因此,預設情況下,低於0.00001比特幣的交易將被視為免費,並且只在記憶體池有空間時才會被中轉;否則,它們將被丟棄。比特幣節點可以透過調整 minrelaytxfee 的值來覆蓋預設的收費中繼策略。
任何建立交易的比特幣服務,包括錢包,交易所,零售應用等,都 必須 實施動態費用。動態費用可以透過第三方費用估算服務或內建費用估算演算法來實現。如果你不確定,請先從第三方服務開始,如果你希望移除第三方依賴關係,設計並實現自己的演算法。
費用估算演算法根據容量和“競爭”交易提供的費用計算適當的費用。這些演算法的從簡單(最後一個區塊的平均費用或中值費用)到複雜(統計分析)。他們估計必要的費用(每位元組多少satoshis),使交易被選中幷包含在一定數量的區塊內的可能性很高。大多數服務為使用者提供選擇高,中,低優先順序費用的選項。高優先順序意味著使用者支付更高的費用,但交易很可能包含在下一個區塊中。中等和低優先順序意味著使用者支付較低的交易費用,但交易可能需要更長時間才能確認。
許多錢包應用使用第三方服務計算費用。一種流行的服務是 http://bitcoinfees.21.co,它提供了一個API和一個可視圖表,顯示了不同優先順序的 satoshi/位元組 費用。
Tip
|
比特幣網路上的固定費用已不再可行。設定固定費用的錢包將產生糟糕的使用者體驗,因為交易通常會“卡住”,不被驗證。不瞭解比特幣交易和費用的使用者會因為“停滯的”交易感到沮喪,他們會認為錢已經遺失了。 |
Fee estimation service bitcoinfees.21.co 中的圖表以10 satoshi/位元組的增量顯示即時的費用估算值,以及每個費用範圍內的預期確認時間(以分鐘和塊數表示)。對於每個費用範圍(例如,61-70 satoshi/位元組),兩個橫條顯示了未確認交易的數量(1405)和過去24小時內的交易總數(102,975)。根據圖表,此時建議的高優先順序費用為 80 satoshi /位元組,可能使交易在下一個區塊中開採(0塊延遲)。交易規模的中位數為226位元組,所以此交易規模的建議費用為 18,080 satoshis(0.00018080 BTC)。
費用估算資料可以透過簡單的HTTP REST API檢索, https://bitcoinfees.21.co/api/v1/fees/recommended. 例如,在命令列中使用 curl 命令:
$ curl https://bitcoinfees.21.co/api/v1/fees/recommended {"fastestFee":80,"halfHourFee":80,"hourFee":60}
API返回一個帶有當前費用估計的JSON物件,包含最快速度確認( fasterFee ),三個塊內確認( halfHourFee )和六個塊內確認( hourFee )的費用,單位是 satoshi/位元組。
交易的資料結構沒有費用欄位。相反,費用隱含表示為輸入總和與輸出總和的差額。從所有輸入中扣除所有輸出後剩餘的金額都是礦工收取的費用:
Fees = Sum(Inputs) – Sum(Outputs)
這是一個有點令人困惑的交易元素,也是需要理解的重要一點,因為如果你正在建構自己的交易,則必須確保你不會花費了很少的輸入卻無意中包含非常高的費用。這意味著你必須考慮所有輸入,必要時建立找零,否則最終會給礦工一個非常高的小費!
例如,如果你使用20比特幣UTXO進行1比特幣支付,則必須將19比特幣零錢輸出回你的錢包。否則,19比特幣將被算作交易費用,並將由礦工在一個區塊中進行交易。雖然你會得到優先處理並讓礦工很高興,但這可能不是你想要的。
Warning
|
如果你忘記在手動建構的交易中新增找零輸出,則你將支付零錢作為交易費用。“不用找了!” 可能不是你想要的。 |
我們再來看看Alice購買咖啡的情況,看看它在實踐中是如何運作的。愛麗絲想花0.015比特幣來買咖啡。為確保此交易得到及時處理,她希望包含交易費用,例如0.001。這意味著交易的總成本將是0.016。她的錢包因此必須提供一些UTXO,加起來0.016比特幣或更多,如有必要,可以建立找零。假設她的錢包有一個0.2比特幣的UTXO。因此,它需要消費這個UTXO,建立一個給Bob 0.015的輸出,和一個0.184比特幣的零錢輸出,返回她自己的錢包,剩下0.001比特幣未分配,作為隱含的交易費用。
現在讓我們看看不同的場景。菲律賓的兒童慈善總監Eugenia已經完成了為兒童購買教科書的籌款活動。她收到了來自世界各地的數千人的小額捐款,共計50比特幣,所以她的錢包充滿了非常多的小額未使用輸出(UTXO)。現在她想從本地出版商處購買數百本教科書,用比特幣支付。
Eugenia的錢包應用試圖建構一個較大的付款交易,因此它必須從可用的小金額UTXO集合中獲取資金。這意味著由此產生的交易將有超過一百個小型UTXO輸入,只有一個輸出支付給書籍出版商。具有許多輸入的交易將大於一千位元組,也許幾千位元組大小。因此,它需要比中等規模交易高得多的費用。
Eugenia的錢包應用程式將透過衡量交易規模並將其乘以每千位元組的費用來計算適當的費用。許多錢包會為較大的交易多付費用,以確保交易得到及時處理。較高的費用並不是因為Eugenia花費更多的錢,而是因為她的交易規模更大更複雜 - 收費與交易的比特幣價值無關。
比特幣交易指令碼語言,稱為 Script ,是一種類似Forth的逆波蘭表示法的基於堆疊的執行語言。如果這聽起來像是胡言亂語,那麼你可能沒有研究過60年代的程式語言,但沒關係 - 我們將在本章中解釋它。放置在UTXO上的鎖定指令碼和解鎖指令碼都是用這種指令碼語言編寫的。當一個交易被驗證時,每個輸入中的解鎖指令碼將與相應的鎖定指令碼一起執行,以檢視它是否滿足花費條件。
指令碼是一種非常簡單的語言,在有限的範圍內設計,可在一系列硬體上執行,可能與嵌入式裝置一樣簡單。它只需要很少的處理,並且不能完成許多現代程式語言能夠做的事情。為了用於驗證可程式設計的金錢,這是一個深思熟慮的安全特性。
今天,大多數透過比特幣網路處理的交易具有“支付給Bob的比特幣地址”的形式,並且基於稱為 Pay-to-Public-Key-Hash(付費到公鑰雜湊) 的指令碼。但是,比特幣交易不限於“支付給Bob的比特幣地址”型別的指令碼。事實上,可以編寫鎖定指令碼來表達各種複雜的條件。為了理解這些更複雜的指令碼,我們必須首先了解交易指令碼和指令碼語言的基礎知識。
在本節中,我們將示範比特幣交易指令碼語言的基本元件,並說明如何使用它來表達簡單的花費條件以及解鎖指令碼如何滿足這些條件。
Tip
|
比特幣交易驗證不是基於靜態模式的,而是透過執行指令碼語言來實現的。這種語言允許表示幾乎無限的各種條件。這就是比特幣如何獲得“可程式設計金錢”力量的。 |
比特幣交易指令碼語言包含許多運算子,但是故意在一個重要方面進行了限制 - 除了條件控制外,沒有迴圈或複雜的流程控制功能。這確保語言不是 圖靈完備 Turing Complete 的,這意味著指令碼具有有限的複雜性和可預測的執行時間。指令碼不是通用語言。這些限制確保了該語言不能用於建立無限迴圈或其他形式的“邏輯炸彈”,這種“邏輯炸彈”可能嵌入交易中,導致對比特幣網路的拒絕服務攻擊。請記住,每筆交易都由比特幣網路上的每個完整節點驗證。有限制的語言會阻止交易驗證機制被當作漏洞。
比特幣交易指令碼語言是無狀態的,在執行指令碼之前沒有狀態,在執行指令碼之後也不儲存狀態。因此,執行指令碼所需的所有資訊都包含在指令碼中。指令碼在任何系統上都能可預測地執行。如果你的系統驗證了指令碼,你可以確定比特幣網路中的其他每個系統都會驗證該指令碼,這意味著有效的交易對每個人都有效,每個人都知道這一點。結果的可預測性是比特幣系統的一個重要好處。
比特幣的交易驗證引擎依靠兩種型別的指令碼來驗證交易:鎖定指令碼和解鎖指令碼。
鎖定指令碼是放置在輸出上的花費條件:它指定將來要花費輸出必須滿足的條件。由於歷史原因,鎖定指令碼被稱為 scriptPubKey ,因為它通常包含公鑰或比特幣地址(公鑰的雜湊)。在本書中,我們將其稱為“鎖定指令碼”,以表示此指令碼技術更廣泛的可能性。在大多數比特幣應用中,我們所稱的鎖定指令碼將作為 scriptPubKey 出現在原始碼中。你還會看到被稱為 witness script 的鎖定指令碼(參見 [segwit])或更一般地稱為 cryptographic puzzle 。這些術語在不同的抽象層次代表著相同的東西。
解鎖指令碼是可以“解決”或滿足鎖定指令碼放置到輸出上的條件,從而花費輸出的指令碼。解鎖指令碼是每個交易輸入的一部分。大多數情況下,它們包含使用者錢包利用私鑰產生的數位簽章。由於歷史原因,解鎖指令碼被稱為 scriptSig ,因為它通常包含數位簽章。在大多數比特幣應用中,原始碼將解鎖指令碼稱為 scriptSig 。你還將看到稱為 witness 的解鎖指令碼(參見[segwit])。在本書中,我們將其稱為“解鎖指令碼”來表示更廣泛的鎖定指令碼,因為並非所有解鎖指令碼都必須包含簽名。
每個比特幣驗證節點透過一起執行鎖定和解鎖指令碼來驗證交易。每個輸入都包含一個解鎖指令碼,並參考先前存在的UTXO。驗證軟體將複製解鎖指令碼,檢索輸入參考的UTXO,並從該UTXO複製鎖定指令碼。然後按順序執行解鎖和鎖定指令碼。如果解鎖指令碼滿足鎖定指令碼條件,則輸入有效(參見 單獨執行解鎖和鎖定指令碼 )。所有輸入都是作為交易整體驗證的一部分獨立驗證的。
請注意,UTXO永久記錄在區塊鏈中,因此不會改變,也不會因為在新交易中花費它的失敗嘗試而受到影響。只有正確滿足輸出條件的有效交易才會導致輸出被視為“已花費”並從未使用的交易輸出集和(UTXO集)中移除。
Combining scriptSig and scriptPubKey to evaluate a transaction script 是最常見型別的比特幣交易(支付到公鑰的雜湊)的解鎖和鎖定指令碼範例,顯示了在指令碼驗證之前將解鎖指令碼和鎖定指令碼連線在一起所產生的組合指令碼。
比特幣的指令碼語言稱為基於堆疊的語言,因為它使用稱為 棧 stack 的資料結構。堆疊是一個非常簡單的資料結構,可以將其視為一疊卡片。一個堆疊允許兩個操作:push和pop。Push會在堆疊頂部新增一個專案。Pop從堆疊中刪除頂部的專案。堆疊上的操作只能作用於堆疊中最頂端的專案。堆疊資料結構也稱為後進先出或“LIFO”佇列。
指令碼語言透過從左向右處理每個專案來執行指令碼。"數字"(資料常量)被push進入堆疊。"操作"從堆疊中pop一個或多個引數,執行操作,並可能將結果push到堆疊。例如,OP_ADD 會從堆疊中彈出兩個專案,做加法,並將結果push到堆疊上。
條件運算子評估一個條件,產生TRUE或FALSE的布林結果。例如,OP_EQUAL pop堆疊中的兩個專案,如果它們相等,則push TRUE(TRUE由數字1表示),如果不相等,則push FALSE(由零表示)。比特幣交易指令碼通常包含一個條件運算子,以便它們可以產生表示有效交易的TRUE結果。
現在讓我們將有關指令碼和堆疊的知識應用於一些簡單的範例。
在 Bitcoin’s script validation doing simple math 中,指令碼 2 3 OP_ADD 5 OP_EQUAL 示範了算術加法運算子 OP_ADD,將兩個數字相加並將結果放在堆疊上,後面跟著條件運算子 OP_EQUAL,它檢查結果總和是否相等到 5 。為簡潔起見,在範例中省略了 OP_ 字首。有關可用指令碼運算子和函式的更多詳細資訊,請參見 [tx_script_ops]。
雖然大多數鎖定指令碼都是指公鑰雜湊(本質上是比特幣地址),因此需要所有權證明來支付資金,指令碼並不一定非常複雜。產生TRUE值的鎖定和解鎖指令碼的任何組合都是有效的。我們用作指令碼語言範例的簡單算術也是一個有效的鎖定指令碼,可用於鎖定交易輸出。
使用算術範例指令碼的一部分作為鎖定指令碼:
3 OP_ADD 5 OP_EQUAL
可以被包含以下解鎖指令碼的交易滿足:
2
驗證軟體將鎖定和解鎖指令碼結合在一起:
2 3 OP_ADD 5 OP_EQUAL
正如我們在 Bitcoin’s script validation doing simple math 中的範例中看到的,執行此指令碼時,結果為 OP_TRUE,交易有效。這不僅是一個有效的交易輸出鎖定指令碼,而且由此產生的UTXO可以被具有任何知道數字2滿足指令碼的人花費。
Tip
|
如果堆疊最上層結果為 TRUE( 標記為 {0x01} ),任何其他非零值,或者指令碼執行後堆疊為空,則交易有效。如果堆疊頂部的值為 FALSE(一個零長度的空值,標記為{}),或者指令碼被運算子顯式終止了,例如 OP_VERIFY,OP_RETURN 或一個條件終止符,如 OP_ENDIF,則交易無效。詳細資訊,請參見 [tx_script_ops]。 |
以下是一個稍微複雜的指令碼,計算 2 + 7 - 3 + 1 。請注意,當指令碼在一行中包含多個運算子時,堆疊允許一個運算子的結果由下一個運算子執行:
2 7 OP_ADD 3 OP_SUB 1 OP_ADD 7 OP_EQUAL
嘗試使用筆和紙驗證前面的指令碼。當指令碼執行結束時,在堆疊中應該保留值 TRUE。
在原始的比特幣客戶端中,解鎖和鎖定指令碼按順序連線並執行。出於安全原因,2010年發生了變化,原因是存在一個漏洞,允許惡意解鎖指令碼將資料推送到堆疊並破壞鎖定指令碼。在當前的實現中,如下所述,指令碼是在兩次執行之間傳輸堆疊的情況下單獨執行的。
首先,使用堆疊執行引擎執行解鎖指令碼。如果解鎖指令碼沒有錯誤地執行(例如,它沒有遺留的“懸掛(dangling)”運算子),則複製主堆疊並執行鎖定指令碼。如果使用從解鎖指令碼複製的堆疊資料執行鎖定指令碼的結果為“TRUE”,則解鎖指令碼已成功解決由鎖定指令碼施加的條件,證明該輸入是用於花費UTXO的有效授權。如果在執行組合指令碼後仍然存在除“TRUE”之外的結果,則輸入無效,因為它未能滿足放置在UTXO上的消費條件。
在比特幣網路上處理的絕大多數交易花費由支付到公鑰雜湊(P2PKH)鎖定的輸出這些輸出包含一個鎖定指令碼。這些輸出包含將它們鎖定到公鑰雜湊(比特幣地址)的指令碼。由P2PKH指令碼鎖定的輸出可以通過出示公鑰,和由相應私鑰建立的數位簽章來解鎖(花費)( 參見 數位簽章 (ECDSA) )。
例如,讓我們再看看Alice對Bob’s Cafe的付款。Alice向咖啡廳的比特幣地址支付了0.015比特幣。該交易輸出將具有以下形式的鎖定指令碼:
OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
Cafe Public Key Hash 等同於咖啡館的比特幣地址,沒有Base58Check編碼。大多數應用程式會以十六進位制編碼顯示 public key hash ,而不是以“1”開頭的大家熟悉的比特幣地址Base58Check格式。
上述鎖定指令碼可以由以下形式的解鎖指令碼滿足:
<Cafe Signature> <Cafe Public Key>
這兩個指令碼組合在一起形成以下的驗證指令碼:
<Cafe Signature> <Cafe Public Key> OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
執行時,只有在解鎖指令碼與鎖定指令碼設定的條件匹配時,此組合指令碼才會輸出TRUE。換句話說,如果解鎖指令碼具有來自咖啡館的私鑰的有效簽名,該公鑰對應於公鑰雜湊集合作為負擔,則結果為TRUE。
圖 #P2PubKHash1 和 #P2PubKHash2 顯示(分兩部分)了逐步執行的組合指令碼,證明這是一個有效的交易。
到目前為止,我們還沒有深入探討“數位簽章”的細節。在本節中,我們將探討數位簽章如何工作,以及如何在不洩露私鑰的情況下提供私鑰的所有權證明。
比特幣中使用的數位簽章演算法是 Elliptic Curve Digital Signature Algorithm 或 ECDSA 。ECDSA是用於基於橢圓曲線私鑰/公鑰對的數位簽章的演算法,如 [elliptic_curve] 中所述。ECDSA由指令碼函式 OP_CHECKSIG,OP_CHECKSIGVERIFY,OP_CHECKMULTISIG 和 OP_CHECKMULTISIGVERIFY 使用。無論何時,你在鎖定指令碼中看到這些指令碼的話,解鎖指令碼都必須包含ECDSA簽名。
數位簽章在比特幣中有三個用途(參見下面的邊欄)。首先,簽名證明私鑰的擁有者,暗示資金的擁有者,已經 授權 支出這些資金。其次,授權證明是 不可否認的 undeniable(nonrepudiation)。第三,簽名證明交易(或交易的特定部分)在簽名後沒有也不能被任何人修改。
請注意,交易的每個輸入都是獨立簽署的。這是至關重要的,因為簽名和輸入都不必屬於同一個“擁有者”或被其使用。事實上,一個名為“CoinJoin”的特定交易方案利用這一事實來建立隱私的多方交易。
Note
|
交易的每個輸入及其可能包含的任何簽名完全獨立於任何其他輸入或簽名。多方可以協作建構交易並各自簽署一個輸入。 |
A digital signature is a mathematical scheme for demonstrating the authenticity of a digital message or documents. A valid digital signature gives a recipient reason to believe that the message was created by a known sender (authentication), that the sender cannot deny having sent the message (nonrepudiation), and that the message was not altered in transit (integrity).
數位簽章是由兩部分組成的數學模式 mathematical scheme。第一部分是使用私鑰(簽名金鑰)從訊息(交易)建立簽名的演算法。第二部分是,允許任何人使用訊息和公鑰驗證簽名的演算法
在比特幣的ECDSA演算法實現中,被簽名的“訊息”是交易,或者更準確地說是交易中特定資料子集的雜湊(參見 簽名雜湊的型別 (SIGHASH) )。簽名金鑰是使用者的私鑰。結果是如下簽名:
\(\(Sig = F_{sig}(F_{hash}(m), dA)\)\)
其中:
-
dA 是簽名私鑰
-
m 是交易(或交易的一部分)
-
Fhash 是雜湊函式
-
Fsig 是簽名演算法
-
Sig 是簽名結果
更多關於ECDSA的細節可以在 ECDSA 數學 中找到。
Fsig 方法產生簽名 Sig ,由兩部分組成: R 和 S:
Sig = (R, S)
現在已經計算了兩個值+ R 和 S +,它們使用稱為 Distinguished Encoding Rules 或 DER 的國際標準編碼方案序列化為位元組流。
讓我們再看一下Alice建立的交易。在交易輸入中有一個解鎖指令碼,其中包含來自Alice錢包的DER編碼簽名:
3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301
該簽名是Alice的錢包產生的 R 和 S 的序列化位元組流,用於證明她擁有授權使用該輸出的私鑰。序列化格式由以下九個元素組成:
-
0x30 —— 標識 DER 序列的開始
-
0x45 —— 序列長度 (69 bytes)
-
0x02 —— 接下來是一個整數
-
0x21 —— 整數的長度 (33 bytes)
-
R —— 00884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb
-
0x02 —— 接下來是另一個整數
-
0x20 —— 另一個整數的長度 (32 bytes)
-
S —— 4b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813
-
一個字尾 (0x01) 標識使用的雜湊型別 (SIGHASH_ALL)
看看你是否可以使用這個列表解碼Alice的序列化(DER編碼)簽名。重要的數字是 R 和 S ;其餘的資料是DER編碼方案的一部分。
要驗證簽名,必須拿到簽名( R 和 S ),序列化交易和公鑰(對應的用於建立簽名的私鑰)。實質上,對簽名的驗證意味著“只有產生此公鑰的私鑰的擁有者才能在此交易上產生此簽名”。
簽名驗證演算法採用訊息(交易或其部分資料的雜湊),簽名者的公鑰和簽名( R 和 S 值),如果簽名對此訊息和公鑰有效,則返回TRUE。
數位簽章是應用於訊息的,對比特幣來說,訊息就是交易。簽名意味著簽名者對具體交易資料的 保證 commitment 。最簡單的形式是,簽名應用於整個交易,從而保證所有輸入,輸出和其他交易欄位。但是,簽名也可以只保證交易中的一部分資料,在許多場景下很有用,我們將在本節中看到。
比特幣的簽名可以使用 SIGHASH 指示交易資料的哪部分包含在由私鑰簽名的雜湊中。SIGHASH 標誌是附加到簽名後面的單個位元組。每個簽名都有一個 SIGHASH 標誌,並且該標誌對於不同輸入是不同的。具有三個簽名輸入的交易可以具有三個不同的帶有 SIGHASH 標誌的簽名,每個簽名簽署(保證)交易的不同部分。
請記住,每個輸入都能在其解鎖指令碼中包含一個簽名。因此,包含多個輸入的交易可能具有不同的帶有 SIGHASH 標誌的簽名,這些標誌會在每個輸入中保證交易的不同部分。還要注意的是,比特幣交易可能包含來自不同“擁有者”的輸入,他們可能在部分建構的(無效的)交易中僅簽署一個輸入,需要其他人合作收集所有必要的簽名才能進行有效交易。許多 SIGHASH 標誌型別只有在你認為多位參與者在比特幣網路之外協作並各自更新部分簽名的交易時才有意義。
有三種 SIGHASH 標誌: ALL, NONE, 和 SINGLE, 如 SIGHASH types and their meanings 所示。
SIGHASH flag | Value | Description |
---|---|---|
ALL |
0x01 |
簽名應用於所有輸入和輸出。 |
NONE |
0x02 |
簽名應用於所有輸入,不包括任何輸出 |
SINGLE |
0x03 |
簽名應用於所有輸入,但僅應用於與簽名輸入具有相同索引編號的一個輸出 |
另外,還有一個修飾符標誌 SIGHASH_ANYONECANPAY,它可以與前面的每個標誌結合使用。當設定了 ANYONECANPAY 時,只有一個輸入被簽名,剩下的(及其序列號)保持開放可以修改。ANYONECANPAY 的值為 0x80,並按位OR應用,產生組合的標誌,如 SIGHASH types with modifiers and their meanings 所示。
SIGHASH flag | Value | Description |
---|---|---|
ALL|ANYONECANPAY |
0x81 |
簽名應用於一個輸入和所有輸出 |
NONE|ANYONECANPAY |
0x82 |
簽名應用於一個輸入,不應用於輸出 |
SINGLE|ANYONECANPAY |
0x83 |
簽名應用於一個輸入和有相同索引號的輸出 |
在簽名和驗證過程中應用 SIGHASH 標誌的方式是建立交易的副本,將內部的某些欄位截斷(設定長度為零並清空)。將產生的交易序列化。將 SIGHASH 標誌新增到序列化交易的末尾,並對結果進行雜湊雜湊。雜湊本身就是被簽名的“訊息”。根據使用哪個 SIGHASH 標誌,交易的不同部分被截斷。結果雜湊取決於交易中資料的不同子集。在雜湊之前最後一步包含了 SIGHASH ,簽名也保證了 SIGHASH 型別,不能被(礦工)改變。
Note
|
所有 SIGHASH 型別都簽署了交易的 nLocktime 欄位(請參見 [transaction_locktime_nlocktime])。另外,SIGHASH 型別本身在簽名之前附加到交易中,在簽名後不能修改。 |
在Alice的交易範例中(請參見 簽名的序列化 (DER) 中的列表),我們看到DER編碼簽名的最後一部分是 01 ,它是 SIGHASH_ALL 標誌。這會鎖定交易資料,所以Alice的簽名會保證所有輸入和輸出的狀態。這是最常見的簽名形式。
讓我們看看其他型別的 SIGHASH 以及它們如何在實踐中使用:
- ALL|ANYONECANPAY
-
這種結構可以用來進行“眾籌”式的交易。試圖籌集資金的人可以建立一個單一輸出的交易。單一輸出向資金籌集人支付“目標”金額。這樣的交易顯然是無效的,因為它沒有輸入。現在,其他人可以透過新增自己的輸入來進行修改這筆交易,作為捐贈。他們用 ALL|ANYONECANPAY 來簽名自己的輸入。除非收集到足夠的投入,達到輸出的價值,否則交易無效。每一筆捐款都是一種“承諾/抵押”,在籌集到目標金額之前,籌款不能收回。
- NONE
-
這種結構可用於建立特定數量的“不記名支票”或“空白支票”。它交付輸入,但允許更改輸出鎖定指令碼。任何人都可以將自己的比特幣地址寫入輸出鎖定指令碼並贖回資金。但是,輸出值本身被簽名鎖定。
- NONE|ANYONECANPAY
-
這種結構可以用來建立一個“集塵器”。錢包裡有微型UTXO的使用者,如果不支付超過灰塵價值的費用,就無法消費這些東西。有了這種簽名,微型UTXO可以捐贈給任何人,聚集並在任何時候花費它們。
有一些關於修改或擴充套件 SIGHASH 系統的建議。其中一個是 Blockstream 的 Glenn Willen 提出的 BitTek Sighash Modes ,是 Elements 專案的一部分。它旨在建立一個靈活的 SIGHASH 型別替代方案,允許“輸入和輸出的任意的,礦工可重寫的位掩碼”,可以表達“更複雜的合約預先承諾方案,例如在分散式資產交換中籤署帶有更改的報價"。
Note
|
你不會在使用者的錢包應用程式中看到+ SIGHASH 標誌選項。除了少數例外,錢包建構P2PKH指令碼並使用 +SIGHASH_ALL 標誌進行簽名。要使用不同的 SIGHASH 標誌,你必須編寫軟體來建立和簽署交易。更重要的是,SIGHASH 標誌可以被特殊用途的比特幣應用程式使用,實現新用途。 |
如前所述,簽名是由一個數學函式 Fsig 建立的,產生由兩個值 R 和 S 組成的簽名。在本節中,我們將更詳細地討論函式 Fsig。
簽名演算法首先產生 ephemeral(臨時)私鑰公鑰對。在涉及簽名私鑰和交易雜湊的轉換之後,此臨時金鑰對用於計算_R_和_S_值。
臨時金鑰對基於隨機數 k ,也就是臨時私鑰。從 k 開始,我們產生相應的臨時公鑰 P(按照_P = k * G_計算,與比特幣公鑰的產生方式相同;參見 [pubkey] )。數位簽章的 R 值就是臨時公鑰 P 的 x 座標。
演算法計算簽名的_S_值,如下:
S = k-1 (Hash(m) + dA * R) mod p
其中:
-
k 是臨時私鑰
-
R 是臨時公鑰的 x 座標
-
dA 是簽名私鑰
-
m 是交易資料
-
p 是橢圓曲線的主要階數
“驗證”是簽名產生函式的反函式,使用 R,S 值和公鑰來計算一個值 P,它是橢圓曲線上的一個點(簽名建立中使用的臨時公鑰):
P = S-1 * Hash(m) * G + S-1 * R * Qa
where:
-
R 和 S 是簽名的值
-
Qa 是Alice的公鑰
-
m 是被簽名的交易資料
-
G 是橢圓曲線的產生點
如果計算點 P 的 x 座標等於 R ,那麼驗證者可以推斷簽名是有效的。
請注意,在驗證簽名時,沒有用到私鑰,也不會被洩露。
Tip
|
ECDSA是一門相當複雜的數學;完整的解釋超出了本書的範圍。許多優秀的線上指南會一步一步地講解它:搜尋“ECDSA解釋”或嘗試這一個:http://bit.ly/2r0HhGB[]。 |
正如我們在 ECDSA 數學 中看到的,簽名產生演算法使用隨機金鑰 k 作為臨時私鑰/公鑰對的基礎。k 的值並不重要,只要它是隨機的。如果使用相同的值 k 在不同的訊息(交易)上產生兩個簽名,那麼則任何人都可以計算簽名私鑰。在簽名演算法中重複使用 k 的相同值會導致私鑰的暴露!
Warning
|
如果在兩個不同交易的簽名演算法中使用相同的 k ,則可以計算私鑰並將其公開給全世界! |
這不僅僅是一種理論上的可能性。我們已經看到這個問題導致私鑰暴露在比特幣的幾種不同的交易簽名演算法中。由於無意中重複使用 k 值,有人資金被盜。重用 k 值的最常見原因是沒有初始化正確的隨機數產生器。
為避免此漏洞,最佳做法是不產生帶有熵的隨機數產生器的 k,而是使用透過交易資料本身作為種子的確定性隨機過程。這確保每筆交易產生不同的 k。k 的確定性初始化的行業標準演算法在 Internet Engineering Task Force 發佈的 RFC 6979 中定義。
如果你正在實施一種演算法來簽署比特幣交易,你必須使用RFC 6979或類似的確定性隨機演算法來確保你為每筆交易產生不同的 k。
我們發現交易在“幕後”看起來與它們在“錢包”,區塊鏈瀏覽器,和其他面向使用者的應用程式中的呈現方式非常不同。交易的結構中似乎沒有來自前幾章的許多簡單和熟悉的概念,比如比特幣地址和餘額。我們看到交易本身不包含比特幣地址,而是透過鎖定和解鎖比特幣的離散值的指令碼進行操作。餘額不存在於此係統的任何位置,但每個錢包應用程式會突出顯示使用者錢包的餘額。
現在我們已經研究了實際包含在比特幣交易中的內容,我們可以研究更高層次的抽象是如何從交易的看似原始的組成部分中獲得的。
讓我們再看看Alice的交易是如何在區塊鏈瀏覽器( Alice’s transaction to Bob’s Cafe )上展示的。
在交易左側,區塊鏈瀏覽器顯示Alice的比特幣地址為“發件人”。事實上,這些資訊並不在交易本身中。當區塊鏈瀏覽器檢索到該交易時,它還檢索到輸入中參考的前一個交易,並從這個之前的交易中提取第一個輸出。該輸出中是一個鎖定指令碼,將UTXO鎖定到Alice的公鑰雜湊(一個P2PKH指令碼)。區塊鏈瀏覽器提取公鑰雜湊並使用Base58Check編碼對其進行編碼,以產生並顯示代表該公鑰的比特幣地址。
同樣,在右側,區塊鏈瀏覽器顯示了兩個輸出;第一個是Bob的比特幣地址,第二個是Alice的比特幣地址(找零)。再次,為了建立這些比特幣地址,區塊鏈瀏覽器從每個輸出中提取鎖定指令碼,將其識別為P2PKH指令碼,並從內部提取公鑰雜湊。最後,區塊鏈瀏覽器使用Base58Check重新編碼該公鑰,以產生並顯示比特幣地址。
如果你點選了Bob的比特幣地址,區塊鏈瀏覽器會顯示 The balance of Bob’s bitcoin address 中的檢視。
區塊鏈瀏覽器顯示Bob的比特幣地址的餘額。但比特幣系統中沒有任何地方存在“餘額”的概念。這裡顯示的值是由區塊鏈瀏覽器建構的,如下所示。
為了建構“總共收到的”金額,區塊鏈瀏覽器首先解碼比特幣地址的Base58Check編碼,以檢索編碼在地址中的Bob的公鑰的160位雜湊。然後,區塊鏈瀏覽器將搜尋交易資料庫,尋找包含Bob公鑰雜湊P2PKH鎖定指令碼的輸出。透過彙總所有輸出的值,區塊鏈瀏覽器可以產生收到的總價值。
建構當前餘額(顯示為“最終餘額 Final Balance”)需要更多的工作。區塊鏈瀏覽器維護了目前未使用的輸出的單獨的資料庫,即UTXO集。為了維護此資料庫,區塊鏈瀏覽器必須即時監控比特幣網路,新增新建立的UTXO,並即時刪除已花費的UTXO,當它們出現在未經確認的交易中時。這是一個複雜的過程,它依賴於追蹤交易的傳播過程,以及與比特幣網路保持一致,以確保遵循正確的鏈條。有時,區塊鏈瀏覽器不同步,並且其UTXO集的視角不完整或不正確。
從UTXO集合中,區塊鏈瀏覽器彙總所有參考Bob的公鑰雜湊值的未使用輸出的值,併產生顯示給使用者的“最終餘額”數字。
為了製作這一張帶兩個“餘額”圖片,區塊鏈瀏覽器必須對幾十,幾百甚至幾十萬的交易進行索引和搜尋。
總之,錢包應用程式,區塊鏈瀏覽器和其他比特幣使用者介面呈現給使用者的資訊通常由更高級別的抽象組成,這些抽象透過搜尋許多不同的交易,檢查其內容並操縱其中包含的資料而派生。為了呈現這種簡單的比特幣交易檢視,類似於從一個發件人到一個收件人的銀行支票,這些應用程式必須抽象許多底層細節。他們主要關注常見型別的交易:P2PKH 和 SIGHASH_ALL 在每個輸入上簽名。因此,雖然比特幣應用程式可以以易於閱讀的方式呈現超過80%的交易,但它們有時會被偏離規範的交易所難倒。包含更複雜的鎖定指令碼,或不同的 SIGHASH 標誌,或許多輸入和輸出的交易,表明了這些抽象的簡單性和缺陷。
每天,在區塊鏈中確認數百個不包含P2PKH輸出的交易。區塊鏈瀏覽器通常會用紅色警告資訊顯示他們無法解碼地址。以下連結包含未完全解碼的最新的“奇怪交易”:https://blockchain.info/strange-transactions[] 。
我們將在下一章中看到的,這些並不一定是奇怪的交易。它們是包含比普通 P2PKH 更複雜的鎖定指令碼的交易。我們將學習如何解碼和理解更複雜的指令碼及其支援的應用程式。