作者:Nickqiao & Faust & Shew Wang,極客web3

顧問:Bitlayer研究團隊

摘要:

近期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等不同的迭代版本。其大致情況如下所示:

  1. Robin Linus於去年最先提出的BitVM實現白皮書,就是基於虛構邏輯門電路的BitVM實現方案,被稱爲BitVM0;

  2. Robin Linus在後面幾次演講和採訪中,又非正式的介紹了基於虛構CPU的BitVM方案(稱爲BitVM1),類似於Optimism的欺詐證明系統Cannon,可以用比特幣腳本在鏈下模擬出一個通用CPU的效果。

  3. Robin Linus還提出了BitVM2,一個Permissionless的單步非交互式欺詐證明協議。

  4. 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取哈希,和鎖定腳本中預設的Public key hash做對比,若兩者相等,說明解鎖腳本中上傳的公鑰,和鎖定腳本中預設的公鑰哈希相匹配,這就通過了第一道驗證。

但是,有個問題,UTXO鎖定腳本的內容其實是在鏈上公開的,任何人都能觀測到其中包含的公鑰哈希,誰都可以上傳對應的公鑰,謊稱自己是那個被“欽定”的人。所以在驗證完公鑰和公鑰hash後,還要驗證交易發起人是否真是該公鑰的實際控制者,這就要對數字簽名進行覈驗。鎖定腳本中的CHECKSIG操作碼,就是負責驗證數字簽名的。

總結一下,P2PKH方案下,交易發起人提交的解鎖腳本中,包含公鑰和數字簽名,該公鑰要和鎖定腳本中指定的公鑰哈希匹配,且交易的數字簽名正確,滿足這些條件才能順利解鎖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可以快速算出對應的地址(錢包界面顯示的那一段像亂碼的東西)。

我們之所以能在區塊瀏覽器和錢包界面看到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對應的腳本內容提交到鏈上並執行。

但如果你要實現的腳本內容特別龐大,包含特別多的代碼,通過常規的方法無法把完整的腳本提交到比特幣鏈上(每個區塊有大小限制)。那怎麼辦?這就需要藉助Taproot,針對上鍊的腳本內容進行精簡化處理,而BitVM正是基於Taproot構建出的複雜方案。