符文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协议,实现该生态的打通。有兴趣的朋友可以一起参与讨论。

posted @ 2024-04-29 20:17  深蓝  阅读(217)  评论(0编辑  收藏  举报

我要啦免费统计