符文Runes協議技術詳解

最近符文Runes協議是比特幣生態最火的項目,於是我利用晚上的時間,把Runes協議使用Go語言實現了一遍,項目地址:https://github.com/bxelab/runestone,另外也基於這個Runestone庫編寫對應的一個命令行客戶端在這裏,基於對Runes協議的深入理解,發現網上很多項目對符文的用法是不對的,於是我這裏再寫一篇技術文章,詳細介紹一下。大家如果要進行符文的開發,可以參考。

1. 數據結構

1.1 Etching 蝕刻

Etching(蝕刻)是Rune銘文協議中用於創建新符文的結構。它包含以下可選字段:

  • divisibility:表示符文的可分割性。相當於ERC20中的decimal字段,小數位數。
  • premine:預挖礦的數量。不設就表示不預挖
  • rune:符文的名稱,以修改後的基數-26整數編碼。
  • spacers:表示在符文名稱字符之間顯示的間隔符。
  • symbol:符文的貨幣符號。一個UTF8字符
  • terms:包含鑄造條款,如數量、上限、開始和結束的區塊高度。
    這你需要進一步說明一下terms,其定義如下:
 
type Terms struct {  
    Amount *uint128.Uint128  //Mint一次能夠鑄造的數量
    Cap    *uint128.Uint128  //能夠Mint多少次
    Height [2]*uint64   //允許Mint的開始高度和結束高度(絕對值)
    Offset [2]*uint64   //允許Mint的開始高度和結束高度(相當於發行符文的高度而言的相對值)
}

因爲允許預挖,而允許後續Mint,所以這個符文的髮型總量就是:
預挖+Amount*Cap
符文的數採用uint128,並不是以太坊的uint256也不是比特幣的uint64,所以這個符文數量也可以是很大很大的。

1.2 RuneId 符文ID

RuneId是標識符文的唯一標識符,由區塊高度和交易索引編碼而成,並以BLOCK:TX的文本形式表示。

1.3 Edict 法令

Edict(法令)是用於轉移符文的結構,包含以下字段:

  • id:涉及的符文ID。
  • amount:轉移的符文數量。
  • output:指定的輸出索引。

1.4 Runestone符文石

Rune協議的消息稱爲Runestones,它們包含以下字段:

  • edicts:一個Edict(法令)的集合,用於轉移Rune。
  • etching:一個可選的Etching(蝕刻),用於創建Rune。
  • mint:一個可選的RuneId,表示要鑄造的Rune的ID。
  • pointer:一個可選的u32,指向未被Edict分配的Rune應轉移至的輸出。

1.5 Cenotaph 墓碑

Cenotaph(墓碑)是當Runestones(銘文石)不符合協議規則時產生的結構。它代表無效的銘文操作,可能導致輸入的符文被銷燬。也就是說如果我們定義了一個符文,但是這個符文又不滿足協議規範,那麼這個符文就會被標記爲墓碑。

2. 編碼

2.1 Rune符文名稱的規則

  1. 字符集:Rune名稱由大寫字母A到Z組成,不使用其他字符。

  2. 基數-26編碼:每個字母代表一個基數-26的數值,其中A=0, B=1, ..., Y=24, Z=25。這意味着每個字母都對應一個從0到25的整數。

  3. 組合名稱:當Rune名稱由多個字母組成時,每個字母的數值是連續的,並且它們表示的是一個累積的數值。例如,名稱"AB"不是A和B的數值相加(即不是26),而是按照字母順序直接表示爲27。

  4. 遞增編碼:隨着Rune名稱中字母數量的增加,其對應的數值會遞增。例如,"AA"對應的數值是26,"AB"是27,依此類推。

  5. 特殊保留名稱:一些特定的Rune名稱被協議保留,這些名稱通常較長,並且不會被普通用戶使用。在文檔中提到,名稱"AAAAAAAAAAAAAAAAAAA"及更長的名稱是保留的。

  6. 名稱解鎖:Rune名稱的解鎖是分階段進行的。從Rune協議激活的區塊(高度840,000)開始,隨着時間的推移,不同長度的Rune名稱逐漸解鎖,允許用戶創建新的符文。

  7. 名稱承諾:爲了防止搶先交易,如果一個非保留的Rune名稱正在被蝕刻(Etching),蝕刻交易必須包含對該名稱的有效承諾。這個承諾通過在滿足一定確認數的輸入見證TapScript中包含Rune名稱的數據推送來實現(參考3.2)。

  8. 名稱分配:如果用戶在蝕刻時沒有提供Rune名稱,協議將按照特定的算法分配一個保留的Rune名稱。這個算法基於蝕刻交易的區塊高度和交易索引來計算一個唯一的Rune名稱。

通過這些規則,Rune銘文協議確保了Rune名稱的唯一性和預測性,同時也提供了一種機制來防止名稱被提前搶注或濫用。這種命名規則不僅爲Rune銘文協議提供了靈活性,還保證了系統的安全性和穩定性。

3. 定義新符文的規則

3.1 對符文石進行編碼

我們先定義了一個Etching對象,然後基於該對象構建Runestone,並使用Runestone可對其進行編碼,從而得到蝕刻需要的二進制數據。示例代碼如下:

runeName := "STUDYZY.GMAIL.COM"  
symbol := ''  
myRune, err := runestone.SpacedRuneFromString(runeName)  
if err != nil {  
    fmt.Println(err)  
    return  
}  
amt := uint128.From64(666666)  
ca := uint128.From64(21000000)  
etching := &runestone.Etching{   //定義Etching
    Rune:    &myRune.Rune,  
    Spacers: &myRune.Spacers,  
    Symbol:  &symbol,  
    Terms: &runestone.Terms{  
       Amount: &amt,  
       Cap:    &ca,  
    },  
}  
r := runestone.Runestone{Etching: etching}  // 構建Runestone
data, err := r.Encipher() //編碼爲二進制
 

3.2 OP_RETURN實現Etching蝕刻內容的上鍊

在Rune銘文協議中,使用比特幣腳本的OP_RETURN操作碼是實現Etching蝕刻內容上鍊的關鍵步驟。OP_RETURN允許我們將特定的數據,即上面編碼成二進制的符文的蝕刻信息,嵌入到比特幣區塊鏈的交易中。這些數據被永久記錄在區塊鏈上,不可篡改,爲每個符文提供了一個獨一無二的“印記”。
在交易的輸出中使用OP_RETURN操作碼,後跟要MAGIC_NUMBER:OP_13,然後再跟蝕刻的符文信息。這些信息通常包括符文的名稱、屬性和其他相關數據。這筆輸出不要求必須是第0號輸出。
以下是構造OP_RETURN腳本的代碼:

 
//build op_return script  
builder := txscript.NewScriptBuilder()  
// Push OP_RETURN  
builder.AddOp(txscript.OP_RETURN)  
// Push MAGIC_NUMBER  
builder.AddOp(MAGIC_NUMBER)  
for len(payload) > 0 {  
    chunkSize := txscript.MaxScriptElementSize  
    if len(payload) < chunkSize {  
       chunkSize = len(payload)  
    }  
    chunk := payload[:chunkSize]  
    builder.AddData(chunk)  
    payload = payload[chunkSize:]  
}  
return builder.Script()

3.3 TapScript保證符文的Commitment承諾正確

在Rune銘文協議中,爲了確保蝕刻的合法性和防止搶跑攻擊(Front-running),引入了TapScript和Commitment承諾的概念。TapScript是比特幣的Taproot結構的一部分,提供了一種更高效和更隱私的交易格式。

  1. Taproot結構:Taproot是一種比特幣改進提案(BIP),它允許將多個潛在的交易腳本合併爲一個單一的、高效的腳本,從而減少交易的大小和成本。
  2. TapScript的使用:在Rune協議中,TapScript用於實現對符文名稱的Commitment承諾。這意味着在蝕刻之前,用戶必須通過一筆交易展示他們對特定符文名稱的控制權。
    符文的Commitment計算方法如下:
 
func (r Rune) Commitment() []byte {  
    bytes := r.Value.Big().Bytes()  
    // Reverse bytes to get little-endian representation  
    for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 {  
       bytes[i], bytes[j] = bytes[j], bytes[i]  
    }  
    end := len(bytes)  
    for end > 0 && bytes[end-1] == 0 {  
       end--  
    }  
    return bytes[:end]  
}

接下來我們就可以將TapScript構建出來:

func CreateCommitmentScript(pk *btcec.PublicKey, commitment []byte) ([]byte, error) {  
    builder := txscript.NewScriptBuilder()  
    //push pubkey  
    pk32 := schnorr.SerializePubKey(pk)  
    builder.AddData(pk32)  
    builder.AddOp(txscript.OP_CHECKSIG)  
    //Commitment script  
    builder.AddOp(txscript.OP_FALSE)  
    builder.AddOp(txscript.OP_IF)  
    builder.AddData(commitment)  
    builder.AddOp(txscript.OP_ENDIF)  
    return builder.Script()  
}
  1. Commitment交易:用戶首先創建並廣播一筆“提交交易”(Commit Transaction),該交易向TapScript對應的地址發送一定數量的比特幣,但不立即揭示符文名稱。這筆交易的輸出將作爲後續揭示交易的輸入。

3.3 提交交易和揭示交易間隔6個塊以上

P2TR(Pay-to-Taproot)交易是一種比特幣交易格式,它利用了Taproot結構來提高交易的效率和隱私性。無論是之前的Ordinals協議,BRC20或者是現在的Rune銘文,都是基於P2TR交易。在前面3.1.和3.2步驟中,我們已經構造好了我們要蝕刻銘文的TapScript了,那麼接下來就需要給這個腳本對應的地址轉移一定數量的BTC(這個交易就叫Commit Transaction提交交易),然後等提交交易上鍊成功後,等確認數>=6,則我們可以發起Reverse Transaction揭示交易,在這筆交易中才包含了3.2的TapScript和3.1的OP_RETURN。

  1. 第一筆交易 - 提交交易(Commit Transaction):

    • 這筆交易就是一筆普通的轉載交易,給TapScript所對應的P2TR地址轉一定數量的BTC。注意不要太少,不然可能第二筆揭示交易的時候手續費不夠。
  2. 時間間隔:

    • 提交交易被廣播到網絡並被包含在一個區塊中後,不能立即揭示該蝕刻的詳細信息。
    • 必須等待至少6個區塊被礦工挖出,這個時間間隔提供了一個觀察期,確保提交的蝕刻交易被網絡接受,並且沒有其他衝突的交易。
  3. 第二筆交易 - 揭示交易(Reveal Transaction):

    • 在觀察期過後,可以創建第二筆交易來揭示蝕刻的詳細信息,包括符文的確切名稱和其他可能在提交階段未公開的屬性。
    • 揭示交易通常也會包含一個OP_RETURN輸出,將蝕刻的詳細信息編碼爲一個數據推送(Data Push)。
  4. 上鍊銘文:

    • 這兩筆交易共同完成了一個符文的創建和上鍊過程。第一筆交易確保了名稱的安全性和所有權,而第二筆交易則向網絡揭示了該符文的全部細節。
    • 通過這種方式,Rune銘文協議不僅保護了創建者的利益,防止了名稱被搶注,還確保了網絡中的所有參與者都能驗證和接受新創建的符文。

通過這種分階段的交易過程,Rune銘文協議實現了一種安全且透明的方式來引入新的符文到區塊鏈中。這種機制提高了整個系統的可靠性,並且爲未來的擴展和協議更新提供了靈活性。

4. 鑄造已定義符文

如果我們蝕刻的符文允許Mint,滿足Mint的要求(沒有被Mint完,Mint高度滿足要求等),那麼我們可以通過構造一筆Mint交易來鑄造已蝕刻定義的符文。

4.1 符文ID

符文的Etching蝕刻在3.3揭示交易中上鍊到比特幣網絡,上鍊的區塊高度和該揭示交易所在區塊的交易索引值共同構成了符文的唯一標識:RuneId。我們使用 {區塊高度}:{揭示交易索引值} 來標識將要鑄造的符文,確保其唯一性。我們可以使用Runestone對符文ID進行編碼,代碼爲:

runeIdStr := "2609649:946"  //你要Mint的符文ID
runeId, _ := runestone.RuneIdFromString(runeIdStr)  
r := runestone.Runestone{Mint: runeId}  
data, err := r.Encipher()
 

4.2 OP_RETURN實現Mint上鍊

與蝕刻類似,使用OP_RETURN將鑄造(Mint)操作記錄在區塊鏈上。不同之處在於,我們Mint的時候不再需要P2TR交易,也就是說,我們只需要一筆普通轉賬交易即可,而不是構造兩筆交易。

4.3 系統符文UNCOMMON•GOODS的鑄造

Runes協議的發明人在發佈符文時也在代碼中硬編碼預定義了一個符文:UNCOMMON•GOODS,這個符文大家都可以挖,其符文ID是:1:0,每個交易挖出一個。

5. 轉移符文

在Rune銘文協議中,符文的轉移是通過所謂的“法令”(Edict)來實現的,這是一種特殊的結構,用於指定如何將符文從一個所有者轉移到另一個所有者。以下是符文轉移過程的詳細步驟和規則:

5.1 符文的Edict法令定義

每個Edict包含三個主要字段:

  • id:指定要轉移的符文的ID。
  • amount:要轉移的符文數量。
  • output:指定接收轉移符文的輸出索引。

Edict的設計允許在一個Runestones中包含多個法令,從而在一個比特幣交易中實現多種符文的同時轉移。

5.2 創建Edict法令

  1. 定義Edict:發送者創建一個或多個Edict,每個Edict包含了符文ID、要轉移的符文數量以及指定的輸出索引。
  2. 法令排序:Edict必須按照符文ID進行排序,並進行delta編碼,以優化交易的大小和提高效率。
    以下是構造了兩筆Edict的Runestone並進行編碼的示例代碼:
runeId1, _ := runestone.RuneIdFromString("2755031:186")  
runeId2, _ := runestone.RuneIdFromString("2609649:946")  
r := runestone.Runestone{Edicts: []runestone.Edict{  
    {  
       ID:     *runeId1,  
       Amount: uint128.From64(1000),  
       Output: 1,  
    },  
    {  
       ID:     *runeId2,  
       Amount: uint128.From64(100),  
       Output: 1,  
    },  
}}  
data, err := r.Encipher() //Delta編碼
 

5.3 構建交易

  1. 交易輸入:選擇足夠的未花費交易輸出(UTXO)作爲交易的輸入,這些UTXO包含了要轉移的符文。
  2. 交易輸出:構建交易輸出,包括用於接收符文的輸出和可能的找零輸出。
  3. 包含Edict:在交易中包含上一步創建的Edict二進制編碼,這些法令定義了符文如何從輸入分配到輸出。

5.4 OP_RETURN實現法令上鍊

與蝕刻和鑄造過程類似,轉移符文的法令也通過比特幣腳本的OP_RETURN操作碼上鍊。這確保了轉移操作的透明性和不可逆性,爲所有網絡參與者提供了驗證轉移正確性的能力。

5.5 UTXO的轉移

符文的轉移遵循比特幣的未花費交易輸出(UTXO)模型。在比特幣網絡中,每個交易的輸出(UTXO)都代表了一定數量的比特幣,可以作爲下一個交易的輸入。在Rune協議中,UTXO的概念被用來表示和轉移特定的符文。
也就是說,我在一個Mint交易中Mint了一個符文A,並佔據了1000聰的UTXO。接下來我構造一筆普通轉賬交易,沒有OP_RETURN,把這個UTXO花費掉,並轉賬給用戶B,那麼用戶B將會收到這個符文A。

5.6 法令的處理順序

在Runestones被執行時,其中的法令會按照它們出現的順序被處理。這意味着,如果一個Runestones中有多個法令引用了相同的符文ID,它們將按照在Runestones中出現的順序依次被處理。

5.7 未分配符文的分配

在處理法令之前,所有輸入的符文(包括新鑄造的或預挖的符文)都是未分配的。每個法令會減少相應符文ID的未分配餘額,並增加分配給交易輸出的餘額。
如果一個法令試圖分配的符文數量超過了當前未分配的符文數量,該法令的分配數量將被減少到當前未分配的符文數量。這意味着,所有的未分配符文都將被完全分配。

5.8 特殊法令的使用

  • 如果法令的amount字段爲零,則表示將分配該符文ID的所有剩餘單位。
  • 如果法令的output字段等於交易輸出的數量,則表示將等量的符文分配到每個非OP_RETURN的輸出。

5.9 法令的錯誤處理

如果Runestones中的任何法令引用了無效的符文ID(例如,區塊高度爲零且交易索引非零),或者output字段的值大於交易輸出的數量,那麼整個Runestones將被視爲無效,即成爲“墓碑”(Cenotaph),並且所有輸入的符文都將被銷燬。

6. 銷燬符文

當交易中的Runestones不符合協議規則時,如包含無法識別的標籤或標誌,輸入的符文將被銷燬,這通過Cenotaph(墓碑)結構來表示。

6.1 銷燬機制的觸發

銷燬符文的機制,即所謂的“墓碑”(Cenotaph),會在以下情況下被觸發:

  1. 包含無法識別的標籤:如果Runestones中包含了協議無法識別的偶數標籤(Even Tags),這將導致整個Runestones被視爲無效,觸發銷燬機制。
  2. 包含無法識別的標誌:同樣,如果Runestones中包含了協議無法識別的標誌(Flags),也會導致銷燬。
  3. 法令輸出編號錯誤:如果法令(Edict)中的輸出編號大於交易輸出的實際數量,這將被視爲一個錯誤,觸發銷燬。
  4. 符文ID錯誤:如果遇到一個符文ID,其區塊高度爲零但交易索引非零,這同樣會觸發銷燬機制。
  5. 數據不完整:如果在Runestones的解碼過程中遇到被截斷的數據,如缺少值的標籤或法令中不完整的數據推送,也會觸發銷燬。

6.2 銷燬過程

當觸發銷燬機制時,以下步驟會被執行:

  1. 輸入符文的銷燬:所有作爲輸入包含在觸發銷燬機制的交易中的符文將被“銷燬”,即從流通中永久移除。
  2. 墓碑的創建:儘管輸入的符文被銷燬,但產生的墓碑(Cenotaph)本身並不是完全空的。它可能包含除了被銷燬的符文之外的字段和法令,如蝕刻(Etching)和鑄造(Mint)。
  3. 供應量的減少:如果墓碑是由於蝕刻操作不當而產生,那麼蝕刻的符文將具有零供應量,並且不可鑄造。
  4. 鑄造計數和銷燬:如果墓碑是由於鑄造操作不當而產生,該鑄造將會計入鑄造上限(Cap),並且嘗試鑄造的符文將被銷燬。

總結

Rune銘文協議是一個創新的區塊鏈技術,它利用比特幣網絡的安全性和去中心化特性,爲數字資產的創建、轉移和交易提供了一個獨特和高效的框架。相對BRC-20來說,具有手續費更低,擴展性更好的優點。其中也有多個特殊的名詞,這在之前的區塊鏈生態並不常用,我們需要記住:

  • Etching(蝕刻):用於創建新符文的結構,包含可分割性、預挖數量、符文名稱等字段。
  • RuneId(符文ID):由區塊高度和交易索引編碼而成,確保了符文的唯一性。
  • Edict(法令):用於轉移符文,包含符文ID、數量和輸出索引。
  • Runestones(符文石):包含法令和可選的蝕刻,是協議消息的載體。

通過TapScript+OP_RETURN:用兩筆交易將蝕刻內容上鍊,確保其不可篡改。而且一定記住兩筆交易之間的區塊高度差至少要達到6。
而在Mint、轉賬等行爲時,通過OP_RETURN實現了符文交易的上鍊。

最後的最後,我也在發起一個開源項目“BxE協議”,之前一篇博客有介紹,使用基於改進版Ordinals協議爲基礎,讓比特幣網絡支持EVM圖靈完畢的智能合約。這個項目在比特幣一層網絡實現,無需搭建二層網絡,可以實現比特幣級的去中心化安全性。白皮書參見這裏:https://github.com/bxelab/whitepaper,在該項目中也會接入Runes協議,實現該生態的打通。有興趣的朋友可以一起參與討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章