{"content":{"title":"Ordinal NFT实现原理及Bitcoin Regtest测试网Mint教程","body":"最近BTC社区推出了首个NFT项目[Ordinals](https://ordinals.com/),该项目非常的火爆，也体现出BTC社区强大的开发能力。鉴于其官方文档对基本原理的描述没有那么直观，并且实际操作Mint也非常复杂，因此本文将说明其核心原理和在BTC regtest本地网络上如何操作(主网操作方法移步:[全节点](https://docs.ordinals.com/guides/inscriptions.html)和[借助机器人不需要全节点](https://www.theblockbeats.info/news/34501?search=1))。\r\n>  🚀🚀🚀目前我们已经发布了[测试网](https://app.regtest.thords.io/),也欢迎关注我们测试网上列出的[twitter](https://twitter.com/thordsio)和[discrod](https://discord.com/invite/5vkYenAt)。\r\n\r\n# 基本原理\r\nOrdinal的技术分为两层：Ordinal number和铭刻(Inscriptions),前者用于为比特币的每一聪(sat)指定一个唯一序号，后者则是借助于Taproot来进行NFT的Mint。\r\n> 注：虽然项目发起人很不屑将其称为NFT，而是称为Digital Artifacts,但是这里为便于理解将其简称为NFT。\r\n\r\n## [Ordinal number理论](https://docs.ordinals.com/overview.html)\r\n该理论提供了一种给聪(sat)指定一个唯一编号的机制，注意是每一个sat不是UTXO! 由于UTXO是不可再分的最小交易单元，因此sat只能存在于UTXO中，且UTXO包含了一定范围的sats，且只能在花费某一UTXO后产生新的输出中对sats编号进行拆分。sat的编号--Orinal number(一个sat和一个Ordinal number一一对应，为避免歧义，下文中sat和Ordinal number等价)主要根据以下两个规则确定:\r\n1. 编号:每一个sat以他们被mint出来的顺序进行编号\r\n2. 转移:按照先进先出规则，从交易的输入转移到输出\r\n\r\n第一条规则相对简单,它决定了Orinal number**只能由挖矿奖励中的coinbase_tx生成**。例如，若第一个区块的挖矿奖励(不包含交易手续费)为50个btc(1 btc = 10^8 sat),则第一个区块会mint出[0;1;2;...;4,999,999,999]范围的sats；第二个区块奖励也为50 btc时，则第二个区块会mint出，[5,000,000,000;5,000,000,001;...;9,999,999,999]范围的sats。\r\n这里比较难理解的部分在于，由于UTXO实际上包含很多个聪，那么这个UTXO中的每一个聪看起来都一样，怎么给他们排序呢？这个实际上是第二条规则决定的，举一个简单的例子吧:\r\n\r\n假设中本聪的地址`addr_a`在创世区块mint了50个btc(1 btc = 10^8 sat),因此产生了[0;1;2;...4,999,999,999]这些sats(这些sats本身的相对顺序在转账交易后才能确定):\r\n```\r\n### inputs\r\n### outputs\r\naddr_a:[0 -> 4,999,999,999] sats\r\n```\r\n然后中本聪发起一笔交易，需要给addr_b转20个btc，那么:\r\n```\r\n### inputs\r\n# 50 btc\r\naddr_a: [0 -> 4,999,999,999] sats\r\n\r\n### outputs\r\n# 30 btc to addr_a, index=0\r\naddr_a: [0 -> 2,999,999,999] sats\r\n# 20 btc to addr_b, index=1\r\naddr_b: [3,000,000,000 -> 4,999,999,999] sats\r\n```\r\n可以看出经过转账之后不同的UTXO集中包含的sat的顺序，是通过交易输出的index顺序按照先进先出被决定。\r\n\r\n如果中本聪想进一步给addr_c转账10个btc，同时他还想保留第2,599,999,999个sat(因为这个sat上可能绑定了一个稀有NFT)，那么就需要精细地控制转账的顺序,确保这个sat还保存在自己钱包的UTXO集中:\r\n```\r\n### inputs\r\n# 30 btc\r\naddr_a: [0 -> 2,999,999,999] sats\r\n\r\n### outputs\r\n# 10 btc to addr_c, index=0\r\naddr_c: [0 -> 999,999,999] sats\r\n# 20 btc to addr_a, index=1\r\naddr_a: [1,000,000,000 -> 2,999,999,999] sats\r\n```\r\n\r\n根据上述规则，每一个区块交易的UTXO中包含的sats可以通过以下[python代码](https://github.com/casey/ord/blob/master/bip.mediawiki#specification)表示:\r\n```\r\n# subsidy of block at given height\r\ndef subsidy(height):\r\n  return 50 * 100_000_000 >> height // 210_000\r\n\r\n# first ordinal of subsidy of block at given height\r\ndef first_ordinal(height):\r\n  start = 0\r\n  for height in range(height):\r\n    start += subsidy(height)\r\n  return start\r\n\r\n# assign ordinals in given block\r\ndef assign_ordinals(block):\r\n  first = first_ordinal(block.height)\r\n  last = first + subsidy(block.height)\r\n  coinbase_ordinals = list(range(first, last))\r\n\r\n  for transaction in block.transactions[1:]:\r\n    ordinals = []\r\n    for input in transaction.inputs:\r\n      ordinals.extend(input.ordinals)\r\n\r\n    for output in transaction.outputs:\r\n      output.ordinals = ordinals[:output.value]\r\n      del ordinals[:output.value]\r\n\r\n    coinbase_ordinals.extend(ordinals)\r\n\r\n  for output in block.transaction[0].outputs:\r\n    output.ordinals = coinbase_ordinals[:output.value]\r\n    del coinbase_ordinals[:output.value]\r\n```\r\n\r\n这就是Ordinal NFT的核心技术支撑，非常的简洁但是却能衍生出很多好玩的东西! Ordinal Number甚至可以用来表示域名等。\r\n\r\n## Ordinal Number表示方法和[稀缺性](https://docs.ordinals.com/overview.html#rarity)\r\nOrdinal Number有很多种表示方式，这里主要解释其*度数表示法*(Degree Notation),其由4个字段构成:\r\n```\r\nA°B′C″D‴\r\n│ │ │ ╰─ Index of sat in the block(每10分钟一个块)\r\n│ │ ╰─── Index of block in difficulty adjustment period(每2016个块调整一次，~2周)\r\n│ ╰───── Index of block in halving epoch(每210,000个块减半，~4年一次)\r\n╰─────── Cycle, numbered starting from 0(减半和难度调整时间重合，~24年一次，最近一次大约在2032年)\r\n```\r\n\r\n虽然相同的度数会对应很多个sats，但是这种表示法有趣的地方在于，它根据比特币自身的周期性特征，人为地为sat创造了一种稀缺性：\r\n- common: 所有不是区块mint出的第一个sat的sats\r\n- uncommon: 该sat是某区块挖出的第一个sat(D==0)\r\n- rare: 难度调整时挖出的第一个sat(C==0&&D==0)\r\n- epic: 减半时挖出的第一个sat(B==0&&D==0)\r\n- legendary: 发生Cycle轮换时挖出的第一个sat(B==C==D==0)\r\n- mythic: 创世区块挖出的第一个sat(A==B==C==D==0)\r\n\r\n比如，可在Ordinal 浏览器查看[`1°0′0″0‴`](https://ordinals.com/sat/1%C2%B00%E2%80%B20%E2%80%B30%E2%80%B4)这个传奇(legendary)sat,有趣的是作者根据UTXO包含的sats中稀有度级别，为每个UTXO标上了颜色。\r\n\r\n还有一个表示法是每一个sat都有一个唯一的name，这个name随着挖出的区块越靠后越短，这又创造了一种稀缺性。\r\n\r\n> 注: 虽然社区中很多人都fomo去mint punk之类的，花了高昂手续费也没mint到，但是我的观点倾向于和作者一致，最稀缺的是Ordinal Number而不是mint的内容中包含什么东西。仅为一家之言。\r\n\r\n# [Inscriptions](https://docs.ordinals.com/inscriptions.html)\r\n给Ordinal绑定相应的内容，如图片、文字等的过程叫做Inscriptions，我更喜欢叫mint。它主要基于2022年BTC Taproot升级之后引入的特性，可以在taproot脚本中存储任意数据(不超过4M)，并且同样可以利用隔离见证的1/4手续费打折，非常便宜且NFT完全上链。\r\n\r\n由于taproot脚本具有很好的隐蔽性，只有在花费该解锁脚本时才能揭示脚本的内容，因此mint过程分为两步:\r\n1. commit: 创建一个taproot输出(该输出会提交到一个包含mint内容的解锁脚本，此时script本身还未上链，只是生成了一条taproot script路径), 并提交该交易;\r\n2. reveal: 花费上述交易生成的UTXO，由于此时必须将包含mint内容的脚本作为输入来解锁上述UTXO，因此就在链上揭示了mint的内容。\r\n\r\n> 注: 这里涉及到很多Taproot和隔离见证的基础知识，限于篇幅就不在此展开，可以参考文末的参考资料。\r\n\r\n通过`OP_FALSE OP_IF … OP_ENDIF`将mint的内容序列化到`tapscript`中，由于`OP_FALSE`这个判定条件为`False`，因此内容部分永远不会被执行，其丝毫不影响正常脚本的执行。比如包含`hello world`字符串在脚本中被序列化为:\r\n```\r\nOP_FALSE\r\nOP_IF\r\n  OP_PUSH \"ord\"\r\n  OP_1\r\n  OP_PUSH \"text/plain;charset=utf-8\"\r\n  OP_0\r\n  OP_PUSH \"Hello, world!\"\r\nOP_ENDIF\r\n```\r\n\r\n由于taproot的data push输入不能超过520字节，因此如果数据很大，需要创建多个data pushs。\r\nInscription的内容包含在reveal交易的第一个输入中，并且mint在此交易的第一个输出的第一个sat上。\r\n\r\n# Regtest测试网Mint Ordinal NFT\r\n主要包括下载bitcoin core和ord。\r\n## bitcoin core下载安装\r\n当前Ordinal NFT要求bitcoin core版本大于24,且使用的是bitcoind命令，因此mac的话注意不能下载`XX.dmg`文件，因为它不包含这个命令。\r\n\r\n在[https://bitcoin.org/en/download]中找到自己适合自己操作系统的版本，然后下载。mac arm架构芯片(m1和m2)命令:\r\n```\r\nhttps://bitcoincore.org/bin/bitcoin-core-24.0.1/\r\ncd bitcoin-24.0.1-osx64\r\n```\r\n将`bitcoin-22.0-osx64/bin`目录配置到path中，便于全局执行。\r\n修改其中`bitcoin.conf`配置文件:\r\n```\r\n[regtest]\r\ndbcache=10240\r\ntxindex=1\r\nupnp=1\r\nrpcuser=username\r\nrpcauth=username:hash\r\nrpcpassword=password\r\ndaemon=1\r\nserver=1\r\nrest=1\r\nrpcallowip=0.0.0.0/0\r\n#local testnet\r\nfallbackfee=0.0001\r\ndatadir=/Users/cjf/bitcoin/data\r\n```\r\n其中`rpcuser`对应的`username`可以自己指定,`rpcpassword`和`rpcauth`则需要运行(这一步比较坑，bitcoin官方文档也没说清楚...):\r\n```\r\npython3 ./share/rpcauth/rpcauth.py username\r\n```\r\n\r\n启动本地测试版本,bitcoin共有4个网络可选(main,test,signet,regtest):\r\n```\r\nbitcoind -regtest\r\n```\r\n然后最新版bitcoind默认启动后还不会自动生成钱包，这点也是个坑，[官网文档](https://developer.bitcoin.org/reference/intro.html)根本没说，因此还需要自己生成钱包:\r\n\r\n```\r\nbitcoin-cli -regtest createwallet walletname\r\nbitcoin-cli -regtest loadwallet walletname\r\n```\r\n> 注意walletname尽量不要指定为ord，否则后面会与ord默认创建的钱包名冲突，导致需要额外配置，比较麻烦。\r\n\r\n然后mint100个区块，给自己发50个btc:\r\n```\r\nbitcoin-cli -regtest -rpcwallet=test -generate 100\r\nbitcoin-cli -regtest -rpcwallet=test -generate 1  #由于本地环境上述命令不会自动mint下一个区块，需要手动mint一个块以确认上述区块\r\nbitcoin-cli -regtest -rpcwallet=test getbalance\r\n```\r\n\r\n## Mint Ordinal NFT \r\n\r\n安装并检查:\r\n```\r\ncurl --proto '=https' --tlsv1.2 -fsLS https://ordinals.com/install.sh | bash -s\r\nord -version  #0.4.2\r\n```\r\n\r\n创建ord的bitcoin钱包；\r\n```\r\nord --cookie-file .cookie -r wallet create\r\n```\r\n其中`.cookie`由自己创建，里面写入`bitcoin.conf`中的`username:password`，注意不要有换行符或空格;`-r`表示是`regtest`本地测试网。如：`btc:AaAC6QHHktRkQuXBWWlkfyOGQX4SoY_M1XtkqB5uPrs=`。这一步也很坑，官方也没怎么说清楚，如果不指定`--cookie-file`,会提示如下错误:\r\n```\r\nerror: failed to connect to Bitcoin Core RPC at 127.0.0.1:18443/wallet/ord\r\nbecause: I/O error: No such file or directory (os error 2)\r\n```\r\n\r\n由于mint Ordinal NFT也需要像正常的btc交易一样花费btc，因此需要先创建个地址:\r\n```\r\nord --cookie-file .cookie -r wallet receive\r\n# output your_addr\r\n```\r\n然后用`bitcoin-cli`给这个地址发送点btc:\r\n```\r\nbitcoin-cli -regtest -rpcwallet=test  sendtoaddress your_addr 10\r\n```\r\n可以看到该交易id:\r\n```\r\nord --cookie-file .cookie -r wallet transactions\r\n#output tx id\r\n```\r\n生成一个区块以确认该交易:\r\n```\r\nbitcoin-cli -regtest -rpcwallet=test -generate 1\r\n```\r\n然后随意指定一个图片或文字文件`FILE`，mint该NFT:\r\n```\r\nord --cookie-file .cookie -r wallet inscribe FILE\r\n```\r\n前文提到会生成两笔交易，因此上述输出类似于:\r\n```\r\n{\r\n  \"commit\": \"7043fecea8b46d0542262d48cd166883346cd302d610ab78b74a9a749c914b63\",\r\n  \"inscription\": \"00a47525e9378ed5f92f7e0498199e7ac183fd854dddf688ab573eb6d424b642i0\",\r\n  \"reveal\": \"00a47525e9378ed5f92f7e0498199e7ac183fd854dddf688ab573eb6d424b642\",\r\n  \"fees\": 23008\r\n}\r\n```\r\n同样，生成2个区块确认交易；\r\n```\r\nbitcoin-cli -regtest -rpcwallet=test -generate 2\r\n```\r\n\r\n然后可以查看该NFT信息:\r\n```\r\nord --cookie-file .cookie -r wallet inscriptions\r\n```\r\n该输出类似于:\r\n```\r\n[\r\n  {\r\n    \"inscription\": \"00a47525e9378ed5f92f7e0498199e7ac183fd854dddf688ab573eb6d424b642i0\",\r\n    \"location\": \"00a47525e9378ed5f92f7e0498199e7ac183fd854dddf688ab573eb6d424b642:0:0\",\r\n    \"explorer\": \"http://localhost/inscription/00a47525e9378ed5f92f7e0498199e7ac183fd854dddf688ab573eb6d424b642i0\"\r\n  }\r\n]\r\n```\r\n运行`ord --cookie-file .cookie -r server`就可以在上述`explorer`链接中查看mint的NFT内容。\r\n\r\n# 与以太坊NFT的对比\r\n|  Ordinal Digital Artifacts   | Etherum-based NFT  |\r\n|  ----  | ----  |\r\n| 数据完全上链  | 除了Uniswap等一些NFT外，大多数都存储在中心化服务器或第三方去中心化存储上 |\r\n| 费用较低，它像普通的交易一样安全地mint，且可借助于隔离见证实现手续费缩减  | 每一种NFT都需要创建一个合约，gas费很高，且每一个NFT合约都需要审计等成本很高 |\r\n| 抗审查，不可篡改  | 需要考虑NFT合约的admin key权限问题，存在被修改的风险 |\r\n| 完全无许可，通过tapscript即可实现mint和sell功能，不需要向合约授权  | 需要向mint合约授权，sell需要向opensea等授权,风险很高 |\r\n\r\n# 主要参考资料\r\n- [Ordinal Handbook](https://docs.ordinals.com/introduction.html)\r\n- [Ordinal Number Specification](https://github.com/casey/ord/blob/master/bip.mediawiki#specification)\r\n- [Taproot transactions](https://docs.lightning.engineering/the-lightning-network/taro/taro-protocol)\r\n- [constructing-and-spending-taproot-outputs](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs)\r\n- [tapscript-example-with-tap](https://github.com/bitcoin-core/btcdeb/blob/master/doc/tapscript-example-with-tap.md)\r\n- [segregated-witness](https://medium.com/softblocks/segregated-witness-for-dummies-d00606e8de63)"},"author":{"user":"https://learnblockchain.cn/people/720","address":null},"history":"QmRzW5dCcphbMEL2shKsByLvhc2SKvEZykNMeUszXeH9su","timestamp":1682841017,"version":1}