作者:CaptainZ,前 GametaverseDAO 研究员 来源:X,@hiCaptainZ
最近两周我在研究 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 升级之前,比特币的容量上限是 1 MB,而 SegWit 之后,虽然单纯交易的容量上限仍旧是 1M,但隔离见证空间的大小达到了 4 MB。
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 的总量是 2100 万枚,一枚 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)阶段。
提交(Commit)阶段:在这个阶段,用户提交他们的信息(如投票选择或竞标价格),但这个信息是加密的。通常,用户会生成这个信息的哈希值(即信息的加密摘要),然后将这个哈希值发送到区块链上。由于哈希函数的特性,它们可以生成一个独特的输出(哈希值),这个输出对于原始信息来说是不可逆的。这意味着无法从哈希值推断出原始信息。这个过程确保了信息在提交时的保密性。
揭示(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 从而促进多链生态繁荣和融合?我似乎看到了更多的可能性。
参考资料
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
https://thebitcoinmanual.com/articles/pay-to-taproot-p2tr/
此文由 比特币官网 编辑,未经允许不得转载!:首页 > 比特币新闻 » Ordinal铭文协议的原理与技术细节讨论