解讀造成 tBTC 緊急關閉的系統設計缺陷

為了將 Bitcoin 相對龐大的市值注入到 Ethereum DeFi 世界中,許多團隊嘗試去做兩邊跨鏈的橋接,發行針對比特幣做錨定的代幣,例如 imBTC, wBTC, tBTC 等專案,而相對去中心化的 tBTC 在 5/15 上線3天後緊急關閉,原因是合約存在漏洞,官方於 5/20 發布了詳盡的說明,這邊做一點翻譯跟總結,讓大家快速了解到底發生了什麼事情

Image for post

TL;DR
tBTC 驗證 bitcoin 鏈上交易的設計有瑕疵,導致某些情況下合法的 redeemption 永遠無法被確認,造成錯誤賠償,可被有心使用者作為攻擊手段

對細節有興趣的朋友,可以繼續往下看,會需要一些基本 bitcoin script 知識

How tBTC works

首先很快速的描述一下 tBTC 大致是怎麼運作的,這邊的細節並不影響理解實際出問題的環節。

每一次抵押鑄幣以及贖回,是由系統中隨機選出來的三個 signer 去協助完成的,這些 signer 可以是任意使用者,他們需要超額抵押 ETH 到合約裡做擔保,超額抵押率為 150%,目的是防止 signer 捲款逃走。

贖回時,申請贖回的人需要多支付一些費用給 signer (目的是獎勵 signer),並且指定贖回的目的地,signer 們確認後需要一起簽出 bitcoin 的交易並且等待 tBTC 系統確認這筆交易已經上鏈,才可以拿回原先抵押的 ETH 以及額外的手續費。

透過這樣的抵押機制,希望可以做到去中心化的跨鏈貨幣轉換。

What went wrong?

tBTC 在 smart contract 裡面驗證經由 singer 們發出去的 bitcoin tx 中,第一個 output script 與 redeemer 當初指定的一致,也就是說,signer 確實有返還比特幣給使用者指定的地址 (or output script)。合約中使用的 output 資料結構如下圖

Image for post
data structure of a bitcoin tx output used in tBTC smart contract

而出問題的部分,是在驗證交易的最後一項檢查

require(
keccak256(_output.slice(8, 3).concat(_output.extractHash())) ==
keccak256(abi.encodePacked(_d.redeemerOutputScript)),
"Tx sends value to wrong pubkeyhash"
);

首先他先將第 8~10 這三個 bytes 拉出來,並且跟_output.extractHash()做合併,目的是得到下圖這樣的結果

Image for post
append length with output script

利用這樣的結構,去跟原先的的redeemerOutputScript做比對,期待兩者一致,可是很不幸的,這樣的做法,只適用於 native segwit script。

Image for post
script structure of different standard type of bitcoin output script

上圖為 bitcoin 標準 output script 各種型態分別的組成結構,extractHash()這個 function 會解析出 output script 中的 hash 部分 (上圖綠色區塊,可能為 public key hash or script hash),對於不同類型的 script,hash 前後都有可能還有 bitcoin OP_CODE,在這邊稱為 hash 的 prefix & postfix (上圖橘色及藍色區塊),下面將上述的結構統整成一張圖,作為比較。

Image for post
bitcoin script structure breakdown

由此可知_output.slice(8, 3).concat(_output.extractHash())這樣的做法,遇到 P2PKH or P2SH 型態的 output script 時,並無法完整還原出正確的 output script,在這樣的情況下,即使 signer 發送出正確的交易,也永遠不可能被驗證成功,而根據 tBTC 的設計,signer 收到贖回申請後,必須在 6 小時之內發送出贖回交易並成功被確認,遲遲未被驗證會視為 signer 離線,因此也會將原本超額抵押的 ETH 轉發給 redeemer 做賠償,但實際上 BTC 是有成功贖回的,等於 redeemer 白白拿走額外的 ETH。

為什麼現在才發現?

坦白說,tBTC 團隊本身需要付滿大的責任,根據官方說法,他們原本只支援 P2PKH 這種地址,不過後來上了一個 PR 讓贖回可以支援多種地址,此 PR 作者在 commit message 裡面寫著

The result passes all current tests, though there are no tests for non-p2wpkh output scripts in the repo yet.

另外根據官方文章的描述,他們沒有在測試鏈上測到贖回的部分 (不確定原因),而他們測試鏈使用的錢包是 P2SH 地址,如果有測到,就應該會發現了。

其實還有更嚴重的問題被挖出

tBTC 團隊透過這次事件進一步發現,即使 output script 組對了,仍有可能會被惡意攻擊,原因出在 redeemer 可能提交了一個不合法的 bitcoin script,目前 tBTC 合約並沒有另外檢查 script 合法性,不合法的 script 想當然是不可能上鏈,同樣地超過 6 小時之後,signer 抵押的 ETH 被清算,雖然攻擊者確實也沒有拿回 BTC,但卻拿到價值更高的 ETH。

影響

由於問題很快就被發現 (因為很容易就撞到),而初始上線期間參與的 signer 主要是同一個人,因此所幸影響範圍並不廣。

總結

測試很重要!
測試很重要!
測試很重要!

Bitcoin 在地址以及 script 的部分有著很高的多樣性,開發時必須非常謹慎,需要先定義出支援哪幾種 address/script,並且在入口層級就先擋掉不支援的型態進入,unit test 的部分要完整測到每一種支援型態的各種 case,才能確保對各種型態 bitcoin script 的支援度。

另外,利用驗證 output script 的方式作為依據有一些風險,這部分必須要相當了解 bitcoin script 運作方式以及應付未來 OP_CODE 的更動,個人是認為,除非有其他考量,否則驗證地址會是比較實際的解法。

tBTC 其實是有審計過的 (ConsenSys Diligence),以結果來看,第一種可能是審計公司對 bitcoin 的細節敏感度不夠高,第二種可能是沒審到。從這次事件可以得到一個警訊,如果是做跨鏈的項目,必須要找兩邊都非常熟悉的審計公司去做完整的審查。

References

Thanks to Yu-Te Lin. 


本文由 AMIS Charles Jhong 提供

原文連結

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料