區塊鏈技術 -- 比特幣隔離見證地址 與 延展性攻擊
作者:林冠巨集 / 指尖下的幽靈。轉載者,請: 務必標明出處。
GitHub : github.com/af913337456…
提示:閱讀本文需要一定的比特幣技術基礎知識
目錄
- 背景簡介
- 協議
- 隔離見證地址的生成
- 擴容原理
- 交易的驗籤
- 延展性攻擊
- 場景
- 疑問與解答
- 修改S的程式碼
背景簡介
隔離見證源於比特幣的一次升級,這次升級涉及到了共識的層面,它支援多了一種交易模式 --- 隔離見證交易,也因此導致了一次軟分叉。
它出現背景是為了解決下面兩個問題
:
-
因為橢圓曲線簽名演演算法ECDSA的漏洞導致的比特幣“
延展性攻擊
”問題; -
在一定程度上達到比特幣
區塊擴容
的目的。
協議
要注意,“隔離見證地址”
是比特幣 “隔離見證體系”
中的一部分,整個隔離見證體系是由多個部分組成的。完整體系的介紹在比特幣的改進協議(BIP)
裡面,主要由下面的協議檔案共同參與:
-
BIP-141,連結是:github.com/bitcoin/bip… ,141對隔離見證做了詳細的介紹,包含其定義和用途;
-
BIP-143,連結是:github.com/bitcoin/bip… ,143對版本號為0的隔離見證,在交易中的,其簽名,和驗籤的整體流程做了詳細的介紹;
-
BIP-144,連結是:github.com/bitcoin/bip… ,144 對隔離見證在點對點(P2P)網路中,是如何被操作參與的,做了詳細的介紹;
-
BIP-173,連結是:github.com/bitcoin/bip… ,173裡面對隔離見證
地址
做了介紹,包括它是使用什麼的編碼格式,校驗碼是怎樣的等等。
其他的更多關於隔離見證的官方介紹,可以自行檢視完整的所有改進協議,連結是:github.com/bitcoin/bip…
地址的生成
下面我們來認識下隔離見證地址是如何生成的。
比特幣的地址生成步驟,都是從公鑰開始進行的,需要經過多個位元組拼湊
再進行hash演演算法
和編碼
的過程。同樣地,隔離見證地址的生成流程也是類似的,參照BIP-173協議,我們可以總結出它的生成流程:
- 準備好比特幣中,解鎖指令碼中的
16進位制雜湊位元組流
,目前主要有兩種,分別是P2WPKH 和 P2WSH。這兩種指令碼它們最明顯的區別是:P2WPKH 中的雜湊佔了20個位元組,而P2WSH中的雜湊是32個位元組,對應的指令碼結構分別是:
-
P2WPKH
:OP_0 <20-byte雜湊> -
P2WSH
:OP_HASH160 <32-byte 雜湊> OP_EQUAL
-
選擇好不同比特幣網路所對應的“hrp”字串,分別有,主網:“bc”,測試網路:“tb”,私人regtest網路:“bcrt”。這些資訊定義在原始碼中的配置檔案裡面,比如Go版本的路徑是:
github.com\btcsuite\btcd\chaincfg\params.go
。 -
將步驟1的位元組流使用5位一個位元組進行編碼,原本的是8位一個位元組,結果設為 B;
-
將0x00位元組新增到 B前面,結果設為 C;
-
使用“bech32”的生成校驗碼演演算法對hrp 和 C 的位元組流生成校驗碼 D;
-
將D新增到C後面,結果設為 E ;
-
組裝:hrp + "1" + E結合編碼表的對映字串,得到地址。
其中,步驟一雖然不是公鑰直接參與生成,但其中的雜湊資料也是由公鑰經過演變來的。第七步驟的“bech32”
的編碼表字元組合
是:qpzry9x8gf2tvdw0s3jn54khce6mua7l
。因為不同的指令碼中的雜湊結構的位元組數不一樣,所以結果也是不一樣的。下圖是上面生成步驟對應的流程圖:
地址生成的程式碼實現可以直接使用btcd原始碼
中提供的函式,如下圖所示:
擴容原理
在比特幣交易的一般打包流程
裡,是會把每筆交易的簽名資料
也包含進去的,如下圖所示。
同時我們知道,一個區塊所能容納的資料量大小是有限的
,意味著一個區塊所能打包的交易數也是有限的,如果我們能夠想辦法把交易裡面的資料量減少,那麼就能間接地達到了區塊擴容的目的。
而隔離見證的本質含義就是在區塊打包交易的時候,不打包簽名的資料,把簽名資料放到另外一個地方去。如果是這樣,那麼怎樣驗證交易的簽名是否正確
,以保證資料沒被篡改?
隔離見證交易的驗籤
做法是這樣的,在將要發起交易,構造指令碼的輸入Vin時,把輸入的解鎖指令碼資料放置到另外一個欄位處,原始碼中,這個欄位的名稱是:Witness。當交易被髮送到交易節點的時候,節點程式碼中會從這個欄位裡面提取出資料恢復出解鎖指令碼,接著在節點進行驗籤操作,驗籤通過後,簽名的資料就不再被打包進區塊裡面,後續要消費這邊交易的時候,解鎖指令碼也是從Witness欄位中恢復。
而不是隔離見證的交易型別,Witness欄位是沒有資料的,解鎖指令碼的資料被攜帶在簽名裡面,導致要恢復,只能從簽名中恢復,意味著簽名資料必須要被打包在區塊裡面。下面是Vin的和上述相關的結構:
type Vin struct {
// 省略無關欄位
ScriptSig *ScriptSig `json:"scriptSig"`
Witness []string `json:"txinwitness"`
}
複製程式碼
ScriptSig 放置的就是簽名資料
,Witness 放置的就是解鎖指令碼資料
。原始碼中的指令碼恢復操作函式在比特幣操作碼虛擬機器器部分,如下圖所示。
延展性攻擊
上面說到了隔離見證的出現背景之一是為瞭解決“延展性攻擊”,這個攻擊又被稱為“可鍛性攻擊”,英文名稱:transaction malleability attack。
2014年,MT.GOX交易所(門頭溝)發生的85萬個比特幣丟失事件,事後當事人把此次事故,歸罪於比特幣的交易延展性攻擊。下面我們來看看它是怎樣達到攻擊目的的。 在比特幣中,區分一筆交易的憑據是交易的id,即TxId。如果兩筆交易的TxId不一樣,那麼會被認為是兩筆不同的交易。在我們日常使用區塊鏈瀏覽器去檢視交易的時候,也會根據TxId去查詢交易,兩個不同的TxId,就肯定不是同一筆交易。
現在假設這麼一個場景:
A 使用橢圓曲線簽名演演算法ECDSA傳送了一筆交易T去比特幣節點N,此時N會根據T的id校驗T是否已經存在交易池裡面及其相關邏輯,如果發現已經存在且不滿足替換條件,那麼就會返回錯誤資訊給 A。如果不存在,就會被放置到交易池裡面,等待被處理,同時返回了 T 的 id 給A。 此時假設 B 自己編寫了個程式,對比特幣節點交易池進行了監聽,發現了交易 T,便將 T 提取了出來,並獲取了 Vin 結構中的ScriptSig 欄位。 上面場景對應的流程圖如下圖所示。
B 獲取了T 的 ScriptSig 後,要做什麼呢?首先可以肯定的是,B 肯定不能篡改 T 的資料,那麼他要怎樣進行“延展性攻擊”
?
B 接下來這樣做:
B 將ScriptSig 使用ECDSA的相關程式碼恢復出簽名資訊的 R 和 S 整形大數。然後根據橢圓曲線加密演演算法的漏洞修改了 S,再重新生成簽名的 ScriptSig,然後將這筆交易重放。注意這裡!重放的時候,節點N 會重新根據整筆交易的資訊,包含 ScriptSig 生成一個TxId_2,但是因為 ScriptSig 被改過了,所以導致了 TxId不一樣,因為TxId 是使用hash 演演算法生成的,hash 演演算法在不同的輸入情況下,輸出是肯定不一樣的。
B 操作部分的描述留下了兩個疑問:
-
相同交易的發到鏈上,輸入部分(UTXO)不會被檢測嗎?
-
為什麼 ScriptSig 被改過了,還能驗籤成功?
回答:
-
因為比特幣的賬戶模型是基於UTXO的,如果某筆 UTXO 還沒被消費,那麼它可以被嘗試雙花。而當它已經被消費了,此時再消費,就會出錯。在上面的例子中,交易T及其被 B 修改過的複製版本中的UTXO,雖然是一樣的,但是它們都是處於沒被花費的,所以可以被多次引用。即檢測,只檢測是否已被上鍊花費了。
-
之所以能驗籤成功,是因為橢圓曲線的簽名演演算法ECDSA中,對於 S 和 R 可以驗籤,負S 和 R 也可以驗籤成功。但一個負S 卻導致了 TxId的不一樣。 B 的攻擊流程圖如下圖所示:
至此,節點中同樣的交易內容,卻出現了不同的 TxId,交易的手續費和收款人完全相同。意味著,這兩筆交易,具備都有被打包的可能,當其中一筆被打包了,另一筆就不會被打包,因為其中涉及的UTXO已經被花費了。
此時假設 A 是某交易所,而它此時正在幫使用者提現,而B 是這個使用者,A 在對賬的時候,會根據自己傳送交易時候拿到的TxId去核對,但它並不知道此時還有一個 TxId_2 是做了同樣的提現操作。然後此時 TxId_2 被打包了,B 作為攻擊者,發現自己攻擊成功了,就會去向交易所說,自己的提現怎麼還沒成功。而A 會進行核實,發現自己的TxId失敗了,然後就會重新給使用者發起提現操作。 至此,B 收到了兩筆或多筆鏈上的轉賬。
上面的整個過程,就是一次“延展性攻擊”。
修改S的程式碼
在程式碼中的具體實現也是很簡單的,只需要新增一行程式碼就能修改簽名中的S,使得驗籤依然有效。下圖是我實現的一個可行的函式,為了不被濫用,關鍵行數已打碼
。
修改簽名中的S 的程式碼: