你能清楚從本篇文章理解 BitVM 的基礎思路、比特幣腳本和隔離見證,本文由極客Web3 Nickqiao & Faust & Shew Wang 所著,顧問為 Bitlayer 研究團隊。
(前情提要:深度|新一代比特幣Layer2跨鏈橋的設計思路:BitVM+OP-DLC )
(背景補充:比特幣「又實現」圖靈完備了?BitVM 論文講了什麼? )
摘要
近期 Delphi Digital 發布了題為《The Dawn of Bitcoin Programmability: Paving the Way for Rollups 》的比特幣二層相關技術研報,系統的梳理了和比特幣 Rollup 有關的核心概念,如 BitVM 全家桶、OP_CAT 和 Covenant 限制條款、比特幣生態 DA 層、橋以及 Bitlayer、Citrea、Yona、Bob 等四大採用 BitVM 的比特幣二層。
該研報雖然大致展示了比特幣二層技術的大致圖景,但整體比較泛泛而缺乏細節描述,讓人似懂非懂。極客 web3 在 Delphi 研報基礎上進行了展開式的深入挖掘,嘗試讓更多人系統的理解 BitVM 等技術。
我們將與 Bitlayer 研究團隊及 BitVM 中文社群共同開展一個名為「走近 BTC」的系列專欄,長期圍繞 BitVM、OP_CAT 和比特幣跨鏈橋等重點話題進行科普,致力於為更多人祛魅比特幣二層相關技術,幫助更多愛好者鋪路。
正文
幾個月前,ZeroSync 負責人 Robin Linus 發布了名為《BitVM: Compute Anything on Bitcoin》的文章,正式提出了 BitVM 的概念,推動了比特幣二層技術的進展。可以說這是比特幣生態最具革命性的創新之一,引爆了整個比特幣二層生態,吸引如 Bitlayer、Citrea、BOB 等明星專案的參與,為整個市場帶來了活力。
之後,更多研究人員參與改進了 BitVM,先後推出了 BitVM1、BitVM2、BitVMX、BitSNARK 等不同的迭代版本。其大致情況如下圖所示:
Robin Linus 於去年最早提出的 BitVM 實作白皮書,就是基於虛構邏輯閘電路的 BitVM 實作方案,稱為 BitVM0;
Robin Linus 在後面幾次演講和訪談中,又非正式的介紹了基於虛構 CPU 的 BitVM 方案(稱為 BitVM1),類似於 Optimism 的詐欺證明系統 Cannon,可以用比特幣指令碼在鏈下模擬出一個通用 CPU 的效果。
Robin Linus 也提出了 BitVM2,一個 Permissionless 的單步非互動式詐欺證明協議。
Rootstock Labs 和 Fairgate Labs 的成員發布了 BitVMX 白皮書,與 BitVM1 類似,他們希望透過比特幣指令碼模擬出通用 CPU 的效果(在鏈下)。
目前 BitVM 相關開發者生態的建設日漸明朗,週邊工具的迭代完善也已肉眼可見,相比於去年,如今的 BitVM 生態已經從最初的 「空中樓閣」 變得 「依稀可見」,這也吸引了越來越多的開發者和 VC 爭相湧入比特幣生態。
但對大多數人而言,要理解 BitVM 和比特幣二層相關的技術名詞絕非易事,因為你要先對其周邊的基礎知識有系統性的理解,尤其是比特幣指令碼和 Taproot 等背景知識。目前網路上已有的參考資料不是篇幅太長廢話連篇,就是解釋的不夠透徹讓人似懂非懂。我們致力於解決上述問題,力求盡可能清晰的語言,幫助更多人理解比特幣二層的周邊知識,對 BitVM 體系建立系統性認知。
MATT 與承諾:BitVM 的基礎思想
首先我們要強調,BitVM 的基礎概念是 MATT,意義是 Merkleize All The Things,主要指透過 Merkle Tree 這種樹狀的資料儲存結構來展示複雜的程式執行過程,設法讓比特幣 Native 的驗證詐欺證明。
MATT 雖然可以表達出一段複雜程式及其資料處理痕跡,但不會直接在 BTC 鏈上發布這些資料,因為這些資料的整體規模非常龐大。採用 MATT 的方案只在鏈下的 Merkle 樹中儲存資料,只把 Merkle 樹最頂端的摘要(Merkle Root)釋出到鏈上。這棵 Merkle 樹主要包含三大核心內容:
智慧合約指令碼程式碼
合約所需的資料
合約執行中留下的痕跡(智慧合約在 EVM 等虛擬機器器中執行時對記憶體、CPU 暫存器產生的變更記錄)
MATT方案下,只有尺寸極小的Merkle Root儲存在鏈上,Merkle Tree包含的完整資料集儲存在鏈下,這用到了一種稱為「承諾」的想法。這裡解釋下什麼是「承諾」(Commitment)。
承諾類似於一種簡潔化的宣告,我們可以把它理解為一大批資料壓縮後所得到的「指紋」。一般而言,在鏈上發布「承諾」的人會聲稱,某些存放在鏈下的資料是準確無誤的,這些鏈下資料要對應一個簡潔化的宣告,這個宣告就是「承諾」。
在某些時候,資料的 hash 可以作為對資料本身的 「承諾」,其他的承諾方案還有 KZG 承諾或 Merkle Tree 等。在 Layer2 慣用的詐欺證明協議中,資料發布者會在鏈下發布完整資料集,在鏈上發布資料集的承諾。如果有人發現鏈下的資料集中存在無效資料,就會針對鏈上的資料承諾進行挑戰。
透過承諾(Commitment),二層能夠壓縮大量資料處理,只在比特幣鏈上發布其「承諾」。當然,也要確保釋出在鏈下的完整資料集可以被外界觀測到。
目前幾大 BitVM 方案如 BitVM0、BitVM1、BitVM2 和 BitVMX,基本上都採用了類似的抽象結構:
1. 程式分解與承諾:先將複雜的程式分解成大量的、較基礎的操作碼(編譯),然後把這些操作碼在具體執行時產生的痕跡記錄下來(說白了就是一段程式跑在 CPU 和記憶體中時,整個的狀態變化記錄,稱為 Trace)。之後,我們對包括 Trace 和操作碼在內的所有資料進行整理,組織成一個資料集,然後產生該資料集的承諾。
具體的承諾方案可以有多種形式,如:Merkle 樹、PIOPs(各種 ZK 演演算法)、雜湊函式
2. 資產質押和預簽名:資料發布者和驗證者需要透過預簽名的形式,把一定金額的資產鎖定在鏈上,並且會有限制條件。這些條件會針對未來可能發生的情況而針對性的觸發,如果資料發布者作惡,驗證者可以提交證明把資料發布者的資產拿走
3. 資料和承諾發布:資料發布者在鏈上發布承諾,鏈下發布完整的資料集,驗證者檢索資料集並檢查是否有任何錯誤。鏈下資料集中的每個部分都與鏈上的承諾有關。
4. 挑戰與懲罰:一旦驗證者發現資料發布者提供的資料有錯誤,它會把這部分資料拿到鏈上去直接驗證(要先把這部分資料切的特別細),這就是詐欺證明的邏輯。如果驗證結果顯示,資料發布者的確在鏈下提供了無效資料,它的資產就會被挑戰他的驗證者拿走。
總結下就是,資料發布者 Alice 在鏈下公開二層交易執行過程中產生的所有痕跡,把對應的承諾釋出到鏈上。如果你要證明某部分資料有誤,先向比特幣節點證明這部分資料和鏈上的承諾相關聯,也就是證明這些資料是 Alice 本人對外公開的,然後讓比特幣節點確定這部分資料有錯誤。
現在我們大致理解了 BitVM 的整體思路,所有的 BitVM 變體基本上都脫離不了上述正規化。那麼接下來,讓我們開始學習並理解上述流程中用到的一些重要技術,先從最基礎的比特幣指令碼和 Taproot 以及預簽名開始。
什麼是 Bitcoin Script 指令碼
比特幣相關的知識要比以太坊的更難理解,就連最基礎的轉帳行為都涉及到一系列概念,包括 UTXO(未花費的交易輸出)、鎖定指令碼(也稱為 ScriptPubKey)和解鎖指令碼(也稱為 ScriptSig)。我們先對這幾個主要概念進行講解。
(一段比特幣指令碼程式碼的範例由比高階語言更底層的操作碼組成 )
以太坊的資產表達方式,更像支付寶或微信,每次轉帳只是對不同帳戶的餘額做加減法,這種方法是以帳戶為核心,資產餘額只是帳戶名下的一個數位;比特幣的資產表達形式更像黃金,每塊黃金(UTXO)都會標記出主人,轉帳其實就是把舊的 UTXO 銷毀,把新的 UTXO 產生(主人會變更)。
比特幣 UTXO 包含兩個關鍵欄位:
金額,以「聰(satoshi)」為單位(一億聰為一 BTC);
鎖定指令碼,也稱為 「指令碼公鑰(ScriptPubKey)」,會定義 UTXO 的解鎖條件。
要注意的是,比特幣 UTXO 的所有權是透過鎖定指令碼來表達的,如果你要把自己的 UTXO 轉讓給 Sam,可以發起交易銷毀自己的某個 UTXO,把新生成的 UTXO 的解鎖條件寫為 「只有 Sam 可解鎖」。
之後,Sam 如果要使用這些比特幣,需要提交一個解鎖指令碼(ScriptSig),在這個解鎖指令碼中 Sam 要出示自己的數位簽名,證明自己是 Sam 本人。如果解鎖指令碼和前述鎖定指令碼相匹配,Sam 就可以解鎖並把這些比特幣再轉給別人。
(解鎖指令碼要和鎖定指令碼相符才行)
從表現形式的角度來看,比特幣鏈上的每筆交易都對應著多個 Input 和 Output,每個 Input 中要宣告自己想解鎖的某個 UTXO,並提交解鎖指令碼,解鎖並銷毀該 UTXO;Output 中會展示新產生的 UTXO 訊息,對外公示鎖定指令碼的內容。
例如,在一筆交易的 Input 中,你證明自己是 Sam,把別人給你的多個 UTXO 解鎖,統一銷毀,再生成多個新的 UTXO 並宣告讓 xxx 在未來去解鎖。
具體而言,在交易的 Input 資料中,你要宣告自己要解鎖哪些 UTXO,並指出這些 UTXO 資料的「儲存位置」。這裡要注意,比特幣和以太坊截然不同,以太坊提供了合約帳戶和 EOA 帳戶兩種帳戶來儲存資料, 資產餘額作為數位,記錄在合約帳戶或 EOA 帳戶名下,統一放置在名為「世界狀態」的資料庫中,轉帳時直接從「世界狀態」對特定帳戶進行修改,以便於定位到資料的儲存位置;
比特幣沒有世界狀態的設計,資產資料分散儲存在過往的區塊中(就是未解鎖的 UTXO 資料,在每筆交易的 OutPut 中單獨存放)。
如果你想解鎖某個 UTXO,要說明該 UTXO 資訊存在於過去哪筆交易的 Output 中,出示這筆交易的 ID(就是其 hash),讓比特幣節點去歷史記錄中尋找。如果要查詢某個地址的比特幣餘額,則需要從頭遍歷所有區塊,找出和 xx 地址關聯的未解鎖 UTXO。
平時用比特幣錢包時,可以快速檢查某個地址擁有的比特幣餘額,很多時候是因為錢包服務本身通過掃描區塊,對所有地址建立了索引,方便我們快速查詢。
(當你產生交易宣告把自己的UTXO送給別人時,要根據這些UTXO所屬的交易hash/ID來標記出該UTXO在比特幣歷史記錄中的位置)
有趣的是,比特幣交易的結果是在鏈下計算完成的,使用者在本地裝置上產生交易時,就要直接把 Input 和 Output 全部建立好,相當於把交易的輸出結果計算完了。交易在廣播到比特幣網路中,被節點驗證後才上鏈。這種「鏈下計算 — 鏈上驗證」的模式與以太坊是完全不同的,在以太坊上,你只需要提供交易輸入引數,交易結果由以太坊節點計算並輸出。
此外,UTXO 的鎖定指令碼(Locking Script)是可以自訂的,你可以把 UTXO 設定為 「某個比特幣地址的主人可解鎖」,該地址的主人需要提供數位簽章和公鑰(P2PKH)。而在 Pay-to-Script-Hash(P2SH)交易型別中,你可以在 UTXO 鎖定指令碼中加入一個 Script Hash,誰能提交這個 Hash 對應的指令碼原像,並滿足該指令碼原像中預設的條件,就可以解鎖 UTXO。 BitVM 所依賴的 Taproot 指令碼,用到了類似 P2SH 的特性。
比特幣指令碼怎麼觸發
這裡我們先以 P2PKH 為案例介紹比特幣指令碼的觸發方式,只有理解了其觸發方式才能理解更為複雜的 Taproot 和 BitVM。 P2PKH 全名為 「Pay to Public Key Hash」,在這種方案下,UTXO 的鎖定指令碼中會設定一個公鑰 hash,解鎖時需要提交對應該 hash 的公鑰,這和常規的比特幣轉帳思路基本一致。
此時,比特幣節點要確定解鎖指令碼中的公鑰,和鎖定指令碼中指定的公鑰 hash 能對上號,也就是說,要確定解鎖人提交的 「鑰匙」 和 UTXO 預設的 「鎖」 彼此匹配。
進一步說,P2PKH 方案下,比特幣節點收到交易後,會將使用者給出的解鎖指令碼 ScriptSig,與要解鎖的 UTXO 的鎖定指令碼 ScriptPubkey 拼接到一起,放在 BTC 指令碼的執行環境內執行。下圖給出執行前的拼接結果:
可能讀者並不瞭解 BTC 的指令碼執行環境,此處我們進行簡單介紹。首先,BTC 指令碼包含兩種元素:
資料和操作碼。這些資料和操作碼會依照從左到右的順序,依序壓入棧內依照指定邏輯來執行,得到最終結果(關於什麼是棧此處不展開詳述讀者可以自行 Chatgpt)。
以上圖為例,左側是某人上傳的解鎖指令碼 ScriptSig,包含他的數位簽章和公鑰,而右側的鎖定指令碼 ScriptPubkey 中,包含 UTXO 建立者產生該 UTXO 時設定的一段操作碼和資料(此處我們不需要了解每個操作碼的意義,理解個大概即可)。
上圖右側的鎖定指令碼中的 DUP、HASH160、EQUALVERIFY 等操作碼,負責把左側的解鎖指令碼中攜帶的 Public key 取hash,和鎖定指令碼中預設的 Public key hash 做對比,若兩者相等,說明解鎖指令碼中上傳的公鑰,和鎖定指令碼中預設的公鑰hash相匹配,這就通過了第一道驗證。
但是,有個問題,UTXO 鎖定指令碼的內容其實是在鏈上公開的,任何人都能觀測到其中包含的公鑰hash,誰都可以上傳對應的公鑰,謊稱自己是那個被 「欽定」的人。所以在驗證完公鑰和公鑰 hash 後,也要驗證交易發起人是否真是該公鑰的實際控制者,這就要對數位簽章進行核驗。鎖定指令碼中的 CHECKSIG 操作碼,就是負責驗證數位簽章的。
總結一下,P2PKH 方案下,交易發起人提交的解鎖指令碼中,包含公鑰和數位簽名,該公鑰要和鎖定指令碼中指定的公鑰hash匹配,且交易的數位簽名正確,滿足這些條件才能順利解鎖 UTXO。
(這張圖是動態的:P2PKH方案下比特幣解鎖指令碼示意圖
資料來源: https://learnmeabitcoin.com/technical/script )
當然,比特幣網路中支援多種交易型別,不只有 Pay to public key/public key hash,還有 P2SH(Pay to Script hash)等,一切取決於 UTXO 建立時自訂的鎖定指令碼被設定成什麼樣。
這裡要注意的是,P2SH 方案下,鎖定指令碼中可以預設一個 Script Hash,而解鎖指令碼需要把 Script Hash 對應的指令碼內容完整提交上來。比特幣節點可以執行這段指令碼,如果這段指令碼定義了多簽驗證的邏輯,就可以在比特幣鏈上實現多簽錢包的效果。
當然,P2SH 方案下,UTXO 創作者要讓未來解鎖 UTXO 的人事先知道 Script Hash 對應的指令碼內容,只要雙方都知道這段 Script 的內容,那麼我們就可以實現比多簽更複雜的業務邏輯。
這裡要說明一點,比特幣鏈上(區塊)並不直接記錄哪些 UTXO 和哪些位址關聯,它只記錄 UTXO 可以被哪個公鑰hash / 哪個指令碼hash解鎖,但我們根據公鑰 hash / 指令碼 hash 可以快速算出對應的位址(皮夾介面顯示的那一段像亂碼的東西)。
我們之所以能在區塊瀏覽器和錢包介面看到 xx 地址下有 xx 數額的比特幣,是因為區塊瀏覽器和錢包專案方幫你解析了這些資料,會掃描所有區塊並根據鎖定指令碼中宣告的公鑰 hash / 指令碼 hash,計算出對應的 「地址」,然後顯示 xx 地址名下有多少比特幣。
隔離見證與 Witness
當我們瞭解 P2SH 的想法後,便和 BitVM 所依賴的 Taproot 更近一步了。但在此之前,我們要先了解一個重要的概念:Witness 和隔離見證。
複盤前面講到的解鎖指令碼和鎖定指令碼,以及 UTXO 解鎖流程,會發現一個問題:交易的數位簽名包含在解鎖指令碼中,生成簽名時不能把解鎖指令碼覆蓋進去(生成簽名用到的引數不能包含簽章本身),所以數位簽章只能覆蓋解鎖指令碼以外的部分,也就是隻能與交易資料的主幹部分建立關聯,不能完整的覆蓋交易資料。
這樣一來,就算交易的解鎖指令碼被中間人稍做手腳,也不會影響到驗簽結果。比如說,比特幣節點或礦池可以在交易的解鎖指令碼中,塞入其他資料,在不影響驗簽和交易結果的前提下,使得交易資料發生細微變化,最後算出的交易 hash / 交易 ID 也會改變。這被稱為交易延展性問題。
這帶來的壞處是,如果你打算連續發起多筆交易,並且有次序上的依賴關係(比如,交易 3 引用了交易 2 的輸出,交易 2 引用了交易 1 的輸出),那麼排後面的交易必然要引用前面交易的 ID(hash),礦池或比特幣節點等任意中間人可以微調解鎖指令碼中的內容,使交易上鏈後的 hash 與你預期的不一致,那麼你預先建立好的多筆有次序關聯的交易會失效。
實際上,在 DLC 橋和 BitVM2 的方案中,會批量建構有先後次序關聯性的交易,所以前面提到的場景並不少見。
簡單來說,交易延展性問題是因為,交易的 ID/hash 在計算時,會把解鎖指令碼的資料包含進去,而比特幣節點等中間人可以微調解鎖指令碼中的內容, 導致交易 ID 與使用者預期的不符合。其實這是比特幣在早期設計時考慮不周留下的歷史包袱。
後來推出的隔離見證 / SegWit 升級,其實就是把交易 ID 和解鎖指令碼徹底解耦,計算交易 hash 時不需要把解鎖指令碼資料包含進去。遵循 SegWit 升級的 UTXO 鎖定指令碼,會預設在首位設定一個叫做「OP_0」的操作碼,充當標記;而對應的解鎖指令碼,從 SigScript 更名為了 Witness(見證)。
遵循隔離見證規則後,交易延展性問題會被妥善解決,你不需要擔心傳送給比特幣節點的交易資料被微調。當然我們不需要想的太複雜,P2WSH 的功能和前面談到的 P2SH 並無本質差異,你可以在 UTXO 鎖定指令碼中預設一個指令碼hash,等解鎖指令碼的提交者把 hash 對應的指令碼內容提交到鏈上並執行。
但如果你要實現的指令碼內容特別龐大,包含特別多的程式碼,透過常規的方法無法把完整的指令碼提交到比特幣鏈上(每個區塊都有大小限制)。那怎麼辦?這就需要藉助 Taproot,針對上鏈的指令碼內容進行精簡化處理,而 BitVM 正是基於 Taproot 所建構的複雜方案。
📍相關報導📍
一覽比特幣生態新趨勢:Ordinal、Atomical、bitVM、閃電網路