最近兩週我在研究BTC生態和各種銘文項目的時候,發現很少有文章能夠清楚地把原理和技術細節介紹的清楚:比如銘文在鑄造的時候,交易是如何發起的,UTXO裡面的sats到底是怎麼被追蹤的,銘刻的內容到底是放在腳本什麼地方,以及BRC20在轉帳的時候為何需要兩次操作?我發現不了解這些技術細節,就很難搞懂BRC20,BRC420,atomicals, stamps, 符文Runes這些各種協議的區別,本文將深入BTC區塊鏈的基礎知識,試著回答上述問題。
BTC的區塊結構
區塊鏈本質是一種多用戶記帳技術,用電腦科學術語來說,是一種分散式資料庫,每一段時間內的記錄(帳目)組成一個區塊,然後根據時間先後順序進行帳本擴展。
我們用excel做了表格來說明區塊鏈的工作原理。一份excel文件代表了一個區塊鏈,其中每一個單獨表格表示一個個區塊,區塊按照時間順序從560331,560332.一直到最新的560336. 560336會在區塊內打包最近的交易。區塊內部主體部分就是我們在會計領域最常見的複式記帳法,一邊地址記做借出(debit)就是inputs from,另一邊地址記做貸入(credit)就是outputs to。 Value對應相應地址的BTC數量。 Inputs的幣的數量會大於Outputs幣的數量,差額就是用戶層面的轉帳費,也是礦工(記帳人)的取得的手續費。區塊頭部會取得上一個區塊高度,上一個區塊的雜湊值,本區塊的建立時間(時間戳記),和隨機數。那麼做為去中心化的記帳技術,到底是誰來搶到下一個區塊的記帳權呢?靠的就是這個隨機數和與之對應的雜湊值。擁有算力的礦工透過對目前區塊的隨機數進行哈希計算,最先得到符合條件哈希值的礦工擁有下一個區塊的記帳權並且贏得區塊獎勵和轉帳費。最後是腳本區域,可以用來做一些擴充應用,例如腳本op_return可以當做附言欄。需要注意的是,在實際的區塊中,腳本區是附著在input和output資訊中的,而不是真的另外單獨一個區域。例如附著在input的腳本是解鎖腳本(ScriptSig),需要錢包位址進行私鑰簽章授權允許轉出,而附著在output的腳本是鎖定腳本(ScriptPubKey),用來設定收到該BTC的解鎖條件(一般情況條件就是「有對應私鑰的人才能消費」)。
上面兩張圖是原始的input和output的資料結構表,在執行層面,腳本表現為交易資訊的附帶參數,其中解鎖腳本(ScriptSig)因為需要私鑰授權,也被稱為「見證資料」(witness data)。
隔離見證和Taproot
儘管比特幣網路已經運行了超過10年,沒有發生過任何顯著的事件,但曾多次出現交易成本飆升到不再可行的高點。因此,比特幣的開發人員一直在討論如何最好地擴展網絡,以處理未來不斷增長的交易量。
2017年,這場辯論達到高潮,比特幣開發社群分裂成兩派,一派是支持使用軟分叉實施名為SegWit的功能,另一派是支持直接區塊擴容的「大區塊」派。
我們在上文提到了解鎖腳本需要用到私鑰授權產生“見證數據”,那麼是不是可以把這個見證數據從區塊中分離,從而變相增加每個區塊可容納的交易數呢?隔離見證(Segregated Witness)在2017年8月啟動正式啟動。它的實作方式正是將所有的交易資料分成兩部分,一部分是交易的基本資訊(Transaction Data),另一部分是交易的簽章資訊(Witness Data),並把簽章資訊保存在一個新的資料結構中,是被稱為「隔離見證(witness)」的新區塊中,並與原始交易分開傳輸。
在技術上,SegWit的實作意味著交易不再需要包含見證資料(不會佔用比特幣原本為區塊安排的1MB 空間)。取而代之的是,在一個區塊的末尾,為見證數據創建了一個額外獨立的空間。它支援任意的數據轉賬,並有一個折扣的”區塊重量(block weight)”,巧妙地將大量的數據保持在比特幣的區塊大小限制內,以避免硬分叉的需要。這樣,比特幣交易的交易資料大小提高了上限,同時降低了簽名資料的交易費用。在SegWit升級之前,比特幣的容量上限是1MB,而SegWit之後,雖然單純交易的容量上限仍舊是1M,但隔離見證空間的大小達到了4MB。
Taproot 於2021年11月實施,由3 項不同的比特幣改進提案(BIP) 組成,其中包括:Taproot、Tapscript 及其名為「Schnorr 簽署」的全新數位簽章方案。 Taproot 旨在為比特幣用戶帶來許多好處,例如提升交易隱私和降低交易費用。也將讓比特幣執行更多複雜的交易,從而拓寬應用場景(新增加了一些操作碼opcodes)。
這些更新是Ordinals NFT的關鍵推動因素,它將NFT資料儲存在Taproot 腳本路徑的花費腳本(spent script)中(見證資料空間)。這次升級使得結構化和儲存任意的見證資料變得更加容易,為”ord” 標準奠定了基礎。隨著資料要求的放寬,假設一個交易可以用其交易和見證資料填滿整個區塊– 達到4MB的區塊大小(見證資料空間)限制– 大大擴展了可以放在鏈上的媒體類型。
也許有人會問,既然在腳本中放入一些字串,那對這些字串沒有限制條件嗎?萬一真的執行這些腳本呢?如果隨便放內容,那會不會出現錯誤代碼拒絕出塊呢?這就要提到OP_FALSE指令。 OP_FALSE(在比特幣腳本中也表示為「0」)確保腳本語言中的執行路徑永遠不會進入OP_IF分支,並保持未執行狀態。它充當腳本中的佔位符或空操作(No Operation),類似於高級語言中的“註釋”,來保證後續的程式碼不會執行。
UTXO轉帳模型
以上都是從電腦資料結構方面來研究BTC的基本原理,我們再從金融模型方面來討論UTXO模型。
UTXO是Unspent Transaction Outputs 的縮寫,中文翻譯是“沒有花掉的交易輸出”,實際上可以理解為在一次轉賬時剩餘沒有轉出的資金。那比特幣為啥要用這麼一個概念呢?這就要從記帳方法的帳戶交易模型和帳戶餘額模型說起了。
因為我們在中心化的體系待的太久,已經非常習慣帳戶餘額模型的記帳方式。當用戶A給用戶B轉100塊時,銀行會先檢查A的銀行帳戶上是否有100元,如果有就從A的帳戶裡扣除100元再在B的帳戶上加上100元,這樣一筆轉帳就完成了。
然而,比特幣的記帳演算法裡沒有餘額這個概念。在區塊鏈的分散式帳本上記錄的只有一筆筆的交易,並不會直接記錄一個帳戶當前餘額是多少(記錄餘額一般需要專門的伺服器節點來記錄,那就中心化了)。假設目前用戶A餘額是1000元,如果用戶A給用戶B轉100元,這筆轉帳會被記錄成:
交易1 用戶A給用戶B轉帳100元
交易2 用戶A給用戶A自己轉帳900元(UTXO)
這裡的交易2雖然是一筆交易,但從功能上來說他擔當了帳戶餘額的作用,表示在完成這筆100元轉帳後A的帳戶上還剩餘900元。
那麼問題來了,為啥非要造一個這樣的UTXO呢?因為在BTC區塊鏈上只能記錄交易,所以沒辦法記錄帳戶餘額。如果沒有這個UTXO的話,要計算餘額需要把一個帳戶的所有交易的入帳和出帳全部累加一遍,這是個非常消耗時間和計算資源的事情。而UTXO的出現巧妙的避免了在計算餘額時要回溯所有交易的痛點問題。
UTXO 有個特點,就是跟硬幣一樣,不能掰開用,那麼交易過程中如何湊夠輸入金額,又如何找零的呢?我們可以用硬幣來做類比(實際上每次當你看到UTXO這個單字的時候請自動翻譯成「硬幣」比較好)。
小明給小剛轉帳1比特幣。整個過程是這樣的,小明要收集足夠的input,比如小明的地址對應的以往交易中,找到了一個面值為0.9的UTXO,不夠1比特幣,好在交易中是允許有多個輸入的,所以小明又找到了一個面額0.2的UTXO,這樣在這次轉帳的交易中,就會有兩個輸入。同時輸出也會有兩個,一個是指向小剛地址,面值是1比特幣。另一個指向小明自己的地址,面值是0.1比特幣,這個輸出就是找零了(這個例子忽略了gas)。
換句話說,小明口袋裡面有兩個硬幣,一個面值0.9,另一個面值0.2,此時小明需要支付面值1的硬幣,就需要同時把這兩個硬幣遞給小剛,小剛收到後找零0.1給小明。所以這個記帳模型的本質就是透過「找零」的動作來避免了「計算餘額」。
Ordinal協定的排序系統
Ordinal協議可以說是本輪BTC生態爆發的源頭,是把同質化的BTC分解成最小單位sat,然後對每一個sat標記一個序號。那是怎麼做的呢?
我們知道,BTC的總量是2,100萬枚,一枚BTC最小可以分到一億份(sat),所以BTC的最小單位就是sat,這些BTC也好,最小單位sat也好,都是典型的同質化代幣FT。我們現在試著給這些sats一個序號(ordinal)。
前面在談到區塊資料結構的時候,我們提到交易資訊需要註明input的地址和金額以及output的地址和金額。而每個區塊是包含了兩個部分交易:BTC出塊獎勵和轉帳的手續費。手續費交易必然有input和output,但出塊獎勵因為是憑空產生的BTC,無input地址,所以這個「input from」的欄位是空白的,也叫做「coinbase交易」。 BTC總量的2100萬枚都是來自這個coinbase交易,也是所有區塊中交易列表排列在第一位的。
Ordinal協議規定如下:
- 編號:每一個sat以他們被開採出來的順序編號
- 轉移:依照先進先出規則,從交易的輸入轉移到輸出
第一條規則相對簡單,它決定了編號只能由挖礦獎勵中的coinbase交易產生。例如,若第一個區塊的挖礦獎勵為50個BTC,則第一個區塊會分配出[0;1;2;…;4,999,999,999]範圍的sats;第二個區塊獎勵也是50 BTC 時,則第二個區塊會分配出[5,000,000,000;5,000,000,001;…;9,999,999,999]範圍的sats。
這裡比較難理解的部分在於,由於UTXO其實包含很多聰,那麼這個UTXO中的每一個聰看起來都一樣,要怎麼為他們排序呢?這個其實是第二條規則決定的,舉一個簡單的例子吧:
我先假設BTC的最小分割單位是1,總共出了10個區塊,每個區塊的出塊獎勵是10個BTC,也就是總量是100個。我們直接可以給這100個BTC一個(0-99)的序號。如果沒有任何轉帳情況,那我們只知道第一個區塊的10個BTC編號是(0-9),第二個區塊的10個BTC編號是(10-19),一直到第十個區塊的10個BTC編號是(90-99)。這其中因為沒有任何花費,也就沒有任何output,我們就只能給每10個BTC一個編號範圍。
假設在第二個區塊中加入兩個支出(output),一個是3BTC,一個是「找零」的7 BTC,對應於給別人轉帳了3個BTC,再給自己找零7個BTC。此時在區塊的交易清單中,假設給自己找零的7個BTC排名第一(對應的編號是10-16),給別人的3BTC排第二(對應的編號是17-19)。這就透過對output的的轉移確認了某個UTXO所包含sats的順序集合。
注意是每個sat不是UTXO! 由於UTXO是不可再分的最小交易單元,因此sat只能存在於UTXO中,且UTXO包含了一定範圍的sats,且只能在花費某一UTXO後產生新的輸出中將sats編號進行拆分。
至於用什麼方式來表達這個“編號”,Ordinal支持多種形式,比如上面提到的“整數法”,其他還有十進制小數法,度數法,百分比法,純字母命名法。
sats有了統一的序號之後,就可以考慮銘刻了(inscription)了。我們在上文中提到,可以在見證資料區域4M大小的空間上傳任意資料類型的文件,不管是文本,還是圖片和視頻,上傳之後,文件會自動轉為16進制存放在的taproot腳本區。所以是,1個UTXO,對應1個Taproot腳本區,而這1個UTXO會同時包含很多sats(整體是一個sats序列集合,為了防止粉塵攻擊,限制單個UTXO 中的比特幣數量不可少於546 聰。)。 Ordinal協定為了方便記錄,人為地規定「使用這個序列集合的第一個sat編號來代表綁定關係」(白皮書原話是第一個output的第一個聰的編號),例如包含(17-19 )號sats的UTXO就直接用17號來取代這個集合和銘刻內容綁定。
Ordinal資產的鑄造與轉移
Ordinal NFT很顯然就是把各種檔案上傳到隔離見證區的腳本中並與之綁定一個sats序列集合,從而實現了在BTC鏈上發行NFT資產。但這裡還有一個問題,隔離見證區的腳本就是包含input的解鎖腳本,又包含output的鎖定腳本,那麼內容是放在哪個腳本中呢?正確的答案是兩者都有。這裡不得不提到區塊鏈技術中的commit-reveal機制。
區塊鏈中的Commit-Reveal機制是一種用於確保資訊公平和透明處理的協議。這個機制通常用在需要提交隱藏資訊(如投票或競標),然後在以後的某個時間點揭示這些資訊的場景中。 Commit-Reveal機制分為兩個階段:提交(Commit)階段和揭示(Reveal)階段。
1. 提交(Commit)階段:在這個階段,使用者提交他們的資訊(如投票選擇或競標價格),但這個資訊是加密的。通常,用戶會產生這個資訊的哈希值(即資訊的加密摘要),然後將這個哈希值發送到區塊鏈上。由於雜湊函數的特性,它們可以產生一個獨特的輸出(雜湊值),這個輸出對於原始資訊來說是不可逆的。這意味著無法從哈希值推斷出原始資訊。這個過程確保了資訊在提交時的保密性。
2. 揭示(Reveal)階段:在一個預定的以後時間,使用者必須揭示他們的原始訊息,並證明它與先前提交的哈希值相符。這通常是透過提交原始資訊以及用於生成哈希值的任何附加資料(如隨機數或“鹽”)來完成的。網路接著驗證這個原始資訊的雜湊值是否與先前提交的雜湊值相同。如果匹配,則原始訊息被接受為有效。
我們前面講過,銘刻的內容是需要和UTXO包含的sats序列集合綁定一起,UTXO在區塊中是一個output,所以必須附著在output的鎖定腳本中。但是BTC的全節點需要在本地維護和傳輸全網路所有的UTXO集合。想像一下,要是有1萬個4M的視訊檔案直接上傳到1萬個UTXO的鎖定腳本,那所有的全節點需要有超高的儲存空間和超快的網速,可以說整個鏈直接就崩了。因此,唯一的解決方法是把內容放到input中的解鎖腳本,然後再讓這個內容「指向」到另一個output。
所以說Ordinal資產的鑄造是需要分成兩步驟(錢包是把這兩步驟進行合併處理了,在構造交易時,同時構造commit-reveal這個父子交易,用戶體驗上會感覺只有一個步驟並且節省了gas費)。
在鑄造階段,用戶首先需要上傳某個文件的哈希值到commit交易中(自己A地址給自己B地址轉賬)的UTXO中的鎖定腳本,因為是哈希值,所以不佔用過多全節點的UTXO資料庫空間。其次,用戶再建構一個新的交易(自己B地址給自己A地址轉賬),稱之為reveal交易,此時的input需要使用上一步commit交易中含有文件哈希值的那個UTXO,並且該input的解鎖腳本必須包含原始銘刻檔案。用白皮書中的原話描述,就是「首先,在commit中,創建一個提交到包含銘文內容的腳本的taproot 輸出。 其次,在reveal交易中,使用commit交易產生的輸出,來顯示鏈上的銘文內容。”
在轉移階段,Ordinal NFT和BRC20稍有不同,Ordinal NFT因為是整體轉移,只需要把綁定某個UTXO的NFT直接轉給接收者即可,類似於普通的BTC轉帳。但BRC20因為牽扯到自訂金額轉賬,同樣分為兩步,第一步叫銘刻「交易」(Inscribe “TRANSFER”),第二步叫轉帳「交易」(Transfer “TRANSFER”),第一步的銘刻交易實際上類似於一個Ordinal NFT的鑄造過程,隱含了commit-reveral 父子交易對,第二步轉賬交易類似於一個普通的Ordinal NFT的轉賬,把綁定某個UTXO的BRC20資產直接轉給接收者。有的錢包會把這三個交易(父子孫三代交易)同時構建,從而節省時間和gas。
總結來說,commit交易用來把銘刻內容(原始內容的雜湊值)和序號的sats(UTXO)綁定,reveal交易用來把內容顯示出來(原始內容)。這個父子交易對共同完成了對於NFT的鑄造。
P2TR與一個例子
上面關於鑄造的技術討論還沒完結,因為有人會好奇,reveal交易到底是如何驗證commit交易中的銘文資訊?為啥建構交易的時候需要自己的AB兩個位址互相轉帳呢?打銘文的時候也沒看到需要準備兩個錢包。這裡就需要講到Taproot的重大升級之一P2TR了。
P2TR (Pay-to-Taproot)是由Taproot升級引入的一種新類型的比特幣交易。 P2TR交易透過允許用戶使用單一公鑰或更複雜的腳本(如多重簽名錢包或智慧合約)來花費比特幣,實現了更高的隱私和靈活性。這是透過使用Merkleized Abstract Syntax Trees(MAST)和Schnorr簽名來實現的,這些技術使得可以在單一交易中有效地編碼多種花費條件。
- 創建花費條件
要建立P2TR交易,使用者首先定義一個花費條件,例如單一公鑰或更複雜的腳本,指定了花費比特幣的要求(例如,多重簽名錢包或智慧合約)。
- 產生Taproot輸出
然後,使用者產生一個Taproot輸出,其中包括一個單一公鑰(公鑰代表花費條件)。這個公鑰是從使用者的公鑰和腳本的雜湊的組合中派生出來的,使用一種稱為“tweaking”的過程。這確保了輸出看起來像一個標準的公鑰,使其在區塊鏈上與其他交易難以區分。
- 花費比特幣
當用戶想要花費比特幣時,他們可以使用他們的單一公鑰(如果花費條件被滿足),或透露原始腳本並提供必要的簽名或資料以滿足花費條件。這是透過使用Tapscript來完成的,它允許更有效率和靈活地執行花費條件。
- 驗證交易
礦工和節點隨後透過檢查所提供的Schnorr簽名和資料與花費條件進行驗證交易。如果條件被滿足,交易被視為有效,比特幣可以被花費。
- 增強的隱私和靈活性
因為P2TR交易只在花費比特幣時透露必要的花費條件,所以它們保持了高水準的隱私。此外,使用MAST和Schnorr簽名使得能夠有效地編碼多個花費條件,允許更複雜和靈活的交易,而不會增加交易的整體大小。
以上就是commit-reveal機制在P2TR中的應用方式,我們以一個實際案例來做說明。
使用區塊鏈瀏覽器https://www.blockchain.com/ 我們來研究一個Ordinal 圖片NFT的鑄造過程,包括了之前的commit-reveal兩個階段。
首先,我們看到commit交易的Hash ID是(2ddf90ddf7c929c8038888fc2b7591fb999c3ba3c3c7b49d54d01f8db4af585c)。可以注意到,這筆交易的輸出不包含銘文資料(實際上放的是16機制圖片檔案的雜湊值),網頁中也沒有相關的銘文資訊。這個輸出的(bc1p4mtc…..)位址其實是透過「tweaking」流程產生的暫存位址(代表了腳本解鎖條件的公鑰),和taproot主位址(bc1pg2mp…)共用一個私鑰。此交易中的第二個UTXO屬於返還的「找零」操作。如此就實現了銘文內容與第一個UTXO包含的sats的綁定。
接著,我們查看reveal交易的記錄,其Hash ID是(e7454db518ca3910d2f17f41c7b215d6cba00f29bd186ae77d4fcd7f0ba7c0e1)。在這裡,我們可以看到Ordinals inscription 的資訊。這筆交易的input位址正是前一個交易產生的臨時輸出位址(bc1p4mtc…..),input的解鎖腳本則包含了原始圖片的16進位文件,而輸出的0.00000546BTC(546聰)則是將這個NFT送到自己的taproot主位址(bc1pg2mp…)。基於First in First Out原則以及“綁定的是第一個output的第一個聰的編號”,雖然前後兩個UTXO包含的sats的數量有變化,但是綁定的sat序號不變。所以,我們可以在(sat 1893640468329373)中找到這個銘文所在的聰。
(https://ordinals.com/sat/1893640468329373)
這兩個交易(屬於父子交易)在鑄造時會同時由錢包提交給內存池,所以只需要花費一筆gas,也很大幾率是進入到同一個區塊中被礦工記錄並廣播(以上例子中的兩個交易正是同時存在於區塊790468中。)。礦工和節點隨後透過檢查reveal交易中的input所提供的Schnorr簽名以及16進位圖片的哈希值與commit交易中的output鎖定腳本中的16進位圖片哈希值進行驗證。如果兩者相同,交易被視為有效,這個比特幣的UTXO可以被花費,那麼這兩個交易自然就被永久記錄在BTC的區塊鏈資料庫中,NFT的圖片也自然被保存下來並顯示出來。如果兩個雜湊值不同,兩個交易會被取消,銘刻失敗。
BRC20協定與索引器
對於Ordinal協議,我們銘刻一段文本,它就是文字NFT(對應以太坊上的Loot),銘刻一張圖片,它就是圖片NFT(對應於以太坊上的PFP),銘刻一段音樂,它就是音頻NFT。那如果我們銘刻一段程式碼,而這段程式碼是一段「發行FT同質化代幣」的程式碼呢?
BRC20正是透過利用Ordinal 協定將inscriptions(銘文)設定為JSON 資料格式來部署、鑄造和轉移Token,JSON 包含一些程式碼片段,描述Token 的各種屬性,例如其供應量、最大鑄造單位和唯一程式碼。我們在上一篇文章中已經講過,BRC20代幣的本質是半同質化代幣SFT,也就是說,在某些情況它可以當做NFT交易,某些情況可以當做FT交易,這種對「不同情況」的控制是如何辦到的呢?答案是索引器。
索引器其實是個記帳人,用來把收到的資訊分門別類的記錄在資料庫裡。在Ordinal協定中,索引器透過對input和output的追踪,來決定排序好的sats在不同位址中的變化。在BRC-20協議裡,索引器多了一個功能:記錄銘文中代幣餘額在不同地址的變化。
所以我們可以從記帳人的角度來看到不同的代幣存在形式:BRC20協議代幣其實存在於一個三重資料庫中。第一重Layer1,記帳人是BTC礦工,資料庫類型是“鍊式資料庫”,產生的BTC是FT資產。第二重layer2,記帳人是Ordinal索引器,資料庫類型是“關係型資料庫”,產生的序號的sats是NFT資產。第三重layer3,記帳人是BRC20索引器,資料庫類型是“關係型資料庫”,產生的BRC20資產是FT資產。當我們把BRC20按照「張」來算的時候,站的角度是ordinal索引器(由該索引器記錄),它自然是NFT;當我們把BRC20按照分拆好的「個」來思考的時候(尤其是儲值到中心化交易所之後),站的角度是BRC20索引器(由該索引器記錄或中心化交易所的伺服器記錄),它自然是FT。由此我們可以得到一個結論,半同質化代幣SFT的存在是因為有不同層級的記帳人所導致的。
區塊鏈不就是一個分散式資料庫嘛,所以才有了礦工這個記帳人群體來共同維護這個「鍊式資料庫」(因為只有鍊式資料庫才能做到真正的去中心化)。但兜兜轉轉,我們還是回到了中心化的「關係型資料庫」的老路。這也是為何前段時間Ordinal協議發起人,BRC20協議發起人,unisat錢包為了索引器是否要升級炒的不可開交的本質原因–記賬人意見不一致啦。
但產業經過了十多年的發展,還是累積了不少「去中心化」的經驗,索引器可不可以用「鍊式資料庫」取代關係型資料庫?能不能採用詐欺證明或ZKP來確保安全和去中心化?比特幣生態的DA需求會不會溢出到其他的DA進而促進多鏈生態繁榮與融合?我似乎看到了更多的可能性。
本文由@hicaptainz 原創
參考資料
https://www.aixinzhijie.com/books/261/master_bitcoin/_book/
https://learnblockchain.cn/article/5717
https://zhuanlan.zhihu.com/p/361854961
https://www.odaily.news/post/5187233
https://learnblockchain.cn/article/5376
https://www.panewslab.com/zh/articledetails/1301r1ibp79c.html
https://docs.ordinals.com/inscriptions.html