符文没有规范
符文的参考实现,即ord
,是符文协议的规范性规格说明。
您在这里或其他地方阅读的内容,除了ord
的代码之外,都不是规格说明。这篇关于符文协议的描述是作为ord
行为的指南提供的,而ord
的代码本身应始终被查询以确认任何描述的正确性。
如果由于ord
中的一个错误,本文档与ord
的实际行为出现偏差,并且改变ord
的行为实际上是不切实际的,那么本文档将被修订以符合ord
的实际行为。
使用替代实现的用户需自担风险,强烈建议希望整合符文的服务使用ord
本身来进行符文交易,并确定符文、铸币和余额的状态
符石
符文协议消息被称为 "符石 "。
符文协议在区块840,000激活。早期区块中的符石将被忽略。
抽象地,符石包含以下字段:1
#![allow(unused)] fn main() { struct Runestone { edicts: Vec<Edict>, etching: Option<Etching>, mint: Option<RuneId>, pointer: Option<u32>, } }
符文是通过蚀刻创建的:
#![allow(unused)] fn main() { struct Etching { divisibility: Option<u8>, premine: Option<u128>, rune: Option<Rune>, spacers: Option<u32>, symbol: Option<char>, terms: Option<Terms>, } }
其中可能包含铸造术语:
#![allow(unused)] fn main() { struct Terms { amount: Option<u128>, cap: Option<u128>, height: (Option<u64>, Option<u64>), offset: (Option<u64>, Option<u64>), } }
符文通过法令转移:
#![allow(unused)] fn main() { struct Edict { id: RuneId, amount: u128, output: u32, } }
符文 ID 被编码为蚀刻符文的交易的区块高度和交易索引:
#![allow(unused)] fn main() { struct RuneId { block: u64, tx: u32, } }
符文 ID 在文本中表示为BLOCK:TX
。
符文名称被编码为修改后的 26 进制整数:
#![allow(unused)] fn main() { struct Rune(u128); }
破译
解读符石是通过以下步骤从交易中解码得到的:
-
查找第一个其脚本公钥以
OP_RETURN
OP_13
开头的交易输出。 -
将所有后续数据推送连接到一个有效载荷缓冲区中。
-
从有效载荷缓冲区解码一系列 128 位的 LEB128 整数。
-
将整数序列解析为未类型化消息。
-
将未类型化消息解析为符石。
解读可能会产生一个格式错误的符石,称为纪念碑。
定位符石输出
搜索第一个脚本公钥以 OP_RETURN
OP_13
开头的输出。如果解读失败,不会考虑后续匹配的输出。
组装有效载荷缓冲区
有效载荷缓冲区是通过将匹配的脚本 pubkey 中 OP_13
之后的数据推送连接起来而组装成的。
数据推送是操作码 0 到 78 之间的操作码。如果遇到大于或等于操作码 79 的操作码,则解密的符文是一个没有雕刻、铸造或法令的纪念碑。
解码整数序列
从有效载荷中解码一系列 128 位整数作为 LEB128 变长整数。
LEB128 变长整数被编码为一系列字节,每个字节的最高有效位都被设置,最后一个字节除外。
如果 LEB128 变长整数包含超过 18 个字节,会溢出一个 u128,或者是截断的,意味着在遇到未设置继续位的字节之前达到有效载荷缓冲区的末尾,解码的符石是没有铭刻、铸造或法令的纪念碑。
解析消息
将整数序列解析为未类型化消息。
#![allow(unused)] fn main() { struct Message { fields: Map<u128, Vec<u128>>, edicts: Vec<Edict>, } }
整数被解释为一系列标签/值对,重复的标签将其值附加到字段值上。
如果遇到值为零的标签,则所有后续的整数都被解释为一系列四整数法令,每个法令包括一个符文ID块高度、符文ID交易索引、数量和输出。
#![allow(unused)] fn main() { struct Edict { id: RuneId, amount: u128, output: u32, } }
法令中的符文ID块高度和交易索引采用增量编码。
解码法令符文ID时,起始于基础块高度和交易索引均为零。在解码每个符文ID时,首先将编码的块高度增量加到基础块高度上。如果块高度增量为零,则下一个整数是交易索引增量。如果块高度增量大于零,则下一个整数改为绝对交易索引。
这意味着在将法令编码进符石之前,必须先按符文ID对法令进行排序。
例如,要编码以下法令:
区块 | TX | 数量 | 输出 |
---|---|---|---|
10 | 5 | 5 | 1 |
50 | 1 | 25 | 4 |
10 | 7 | 1 | 8 |
10 | 5 | 10 | 3 |
它们首先按区块高度和交易索引排序:
区块 | TX | 数量 | 输出 |
---|---|---|---|
10 | 5 | 5 | 1 |
10 | 5 | 10 | 3 |
10 | 7 | 1 | 8 |
50 | 1 | 25 | 4 |
然后 delta 编码为:
block delta | TX delta | 数量 | 输出 |
---|---|---|---|
10 | 5 | 5 | 1 |
0 | 0 | 10 | 3 |
0 | 2 | 1 | 8 |
40 | 1 | 25 | 4 |
如果法令输出大于交易的输出数量,则遇到块为零且交易索引非零的法令符文 ID,或者字段被截断,意味着遇到没有值的标签,解码后的符文石是纪念碑 。
请注意,如果这里制作了一个纪念碑,那么这个纪念碑并不是空的,意味着它包含了字段和法令,这可能包括一种蚀刻和铸币。
解析符石
符石
#![allow(unused)] fn main() { struct Runestone { edicts: Vec<Edict>, etching: Option<Etching>, mint: Option<RuneId>, pointer: Option<u32>, } }
使用以下标签从未签名的消息中解析:
#![allow(unused)] fn main() { enum Tag { Body = 0, Flags = 2, Rune = 4, Premine = 6, Cap = 8, Amount = 10, HeightStart = 12, HeightEnd = 14, OffsetStart = 16, OffsetEnd = 18, Mint = 20, Pointer = 22, Cenotaph = 126, Divisibility = 1, Spacers = 3, Symbol = 5, Nop = 127, } }
请注意,标签按奇偶性分组,即奇数还是偶数。无法识别的奇数标签将被忽略。无法识别的偶数标签会产生纪念碑。
All unused tags are reserved for use by the protocol, may be assigned at any time, and should not be used.
主体
主体
标签标记了符石字段的结束,导致所有后续的整数被解释为法令。
标记
标记
字段包含一个标志的位图,其位置为 1 << FLAG_VALUE
:
#![allow(unused)] fn main() { enum Flag { Etching = 0, Terms = 1, Turbo = 2, Cenotaph = 127, } }
Etching
标志表示此交易包含蚀刻。
Terms
标志表示此交易的蚀刻具有开放的铸币条款。
Turbo
标记将此交易的蚀刻设置为选择未来协议可以更改。这些协议更改可能会增加轻客户端验证成本,或者仅仅是高度退化的。
Cenotaph
标志表示无法识别
如果在移除已识别标志后,标志字段的值非零,则该符石为纪念碑。
符文
符文 Rune
字段包含正在蚀刻的符文的名称。如果设置了蚀刻Etching
标志但省略了符文Rune
字段,则会分配一个保留的符文名称。
预挖
预铸Premine
字段包含预铸符文的数量。
上限
上限Cap
字段包含允许的铸币次数。
数量
数量Amount
字段包含每个铸币交易接收的符文数量。
起始高度和结束高度
起始高度
和结束高度
字段分别包含铸币的起始和结束的绝对区块高度。铸币从具有起始高度
的区块开始,并在具有结束高度
的区块中关闭。
起始偏移和结束偏移
起始偏移
和结束偏移
字段包含铸币的起始和结束区块高度,相对于蚀刻被挖掘的区块。铸币从高度为起始偏移
+ 蚀刻高度
的区块开始,并在高度为结束偏移
+ 蚀刻高度
的区块中关闭。
铸造
铸造Mint
字段包含此交易中将要铸造的符文的符文ID。
指针
The Pointer
field contains the index of the output to which runes unallocated by edicts should be transferred. If the Pointer
field is absent, unallocated runes are transferred to the first non-OP_RETURN
output. If the pointer is greater than the number of outputs, the runestone is a cenotaph.
纪念碑
纪念碑Cenotaph
字段无法识别。
可分性
可分性Divisibility
字段,提升十的幂次,是一个超级单位符文中的子单位数量。
例如,不同符文的数量1234
,其可分性为0至3,显示如下:
可分性 | 显示 |
---|---|
0 | 1234 |
1 | 123.4 |
2 | 12.34 |
3 | 1.234 |
间隔符
间隔符Spacers
字段是一个•
位字段,用于表示符文名称字母之间是否应显示间隔符。
位字段的第N个字段,从最不重要的位开始,决定从符文名称的左侧开始,是否在符文名称的第N个字符和第N+1个字符之间显示间隔符。
例如,符文名AAAA
在不同间隔符的渲染下:
间隔符 | 显示 |
---|---|
0b1 | A•AAA |
0b11 | A•A•AA |
0b10 | AA•AA |
0b111 | A•A•A•A |
尾随间隔符将被忽略。
符号
符号Symbol
字段是符文货币符号的Unicode代码点,应在该符文金额之后显示。如果符文没有货币符号,则应使用通用货币字符 ¤
。
例如,如果符号
是#
,可分性为2,那么1234
单位的金额应显示为12.34#。
Nop
Nop
字段无法识别。
墓碑
纪念碑具有以下效果:
-
包含纪念碑的交易中的所有输入符文都将被销毁。
-
如果产生纪念碑的符石包含蚀刻,那么蚀刻的符文供应量为零且无法铸造。
-
如果产生纪念碑的符石是铸币,那么铸币将计入铸币上限,且铸造的符文将被销毁。
如果符石包含无法识别的偶数标签、无法识别的标志、输出编号大于输入数量的法令、区块为零且交易索引非零的符文ID、格式错误的varint、符石输出脚本公钥中的非数据推送指令、没有后续值的标签或不属于法令的尾随整数,则可能创建纪念碑。
执行符石
符石按照其交易被包含在区块中的顺序执行。
蚀刻
符石可能包含蚀刻:
#![allow(unused)] fn main() { struct Etching { divisibility: Option<u8>, premine: Option<u128>, rune: Option<Rune>, spacers: Option<u32>, symbol: Option<char>, terms: Option<Terms>, } }
rune
是要蚀刻的符文的名称,编码为修改后的26进制整数。
符文名称由字母A至Z组成,编码如下:
名字 | 编码 |
---|---|
A | 0 |
B | 1 |
… | … |
Y | 24 |
Z | 25 |
AA | 26 |
AB | 27 |
… | … |
AY | 50 |
AZ | 51 |
BA | 52 |
依此类推。
符文名称AAAAAAAAAAAAAAAAAAAAAAAAAAA
及以上被保留。
如果省略rune
,则按以下方式分配保留的符文名称:
#![allow(unused)] fn main() { fn reserve(block: u64, tx: u32) -> Rune { Rune( 6402364363415443603228541259936211926 + (u128::from(block) << 32 | u128::from(tx)) ) } }
6402364363415443603228541259936211926
对应于符文名称 AAAAAAAAAAAAAAAAAAAAAAAAAAA
.
如果存在 rune
,则它必须在蚀刻出现的区块中解锁。
最初,所有长度为十三及以上的符文名称,直到第一个保留的符文名称,都被解锁。
符文从840,000区块开始解锁,即符文协议激活的区块。
此后,每个17,500区块周期,连续解锁下一个最短长度的符文名称。因此,在840,000到857,500区块之间,解锁十二字符的符文名称,在857,500到875,000区块之间,解锁十一字符的符文名称,依此类推,直到在1,032,500到1,050,000区块之间解锁一个字符的符文名称。具体的解锁时间表请参见ord代码库。
为了防止对已广播但未挖掘的蚀刻进行前置操作,如果正在蚀刻非保留的符文名称,则蚀刻交易必须包含对正在蚀刻的名称的有效承诺。
承诺 commitment
包括在输入见证 tapscript
中推送的符文名称数据,编码为省略尾随零字节的小端整数,其中被花费的输出至少有六次确认。
如果没有有效的承诺 commitment
,蚀刻将被忽略。
铸造
符石可以通过在铸币Mint
字段中包含符文的ID来铸造符文。
如果铸币是开放的,铸币金额将添加到交易输入中的未分配符文中。这些符文可以使用法令转移,并且否则将转移到第一个非OP_RETURN
输出,或由指针Pointer`字段指定的输出。
铸币可以在蚀刻之后的任何交易中进行,包括在同一个区块中。
转移
符文通过法令转移:
#![allow(unused)] fn main() { struct Edict { id: RuneId, amount: u128, output: u32, } }
符石可以包含任意数量的法令edicts,这些法令edicts按顺序处理。
在处理法令edicts之前,输入符文以及铸造或预铸的符文(如果有)是未分配的。
每个法令将rune id
的未分配余额减少,并将rune id
的余额增加到交易输出。
如果法令edict将分配的符文数量超过当前未分配的符文,则数量将减少到当前未分配的符文数量
。换句话说,法令edict分配了runeid
的所有剩余未分配单位。
因为在蚀刻被包含在区块之前不知道蚀刻的符文的ID,所以使用ID 0:0
表示此交易中正在蚀刻的符文(如果有)。
金额amount
为零的法令分配了rune id
的所有剩余单位。
输出output
等于交易输出数量amount
的法令将金额符文分配给每个非OP_RETURN
输出。
金额amount
为零且输出等于交易输出output
数量的法令将所有未分配的rune id
单位平均分配给每个非OP_RETURN
输出。如果未分配的符文数量不能被非OP_RETURN
输出的数量整除,则前R
个非OP_RETURN
输出将分配1个额外的符文,其中R
是将未分配的rune id
单位余额除以非OP_RETURN
输出数量后的余数。
如果符石中的任何法令具有区块
为零且tx
大于零的符文ID,或输出
大于交易输出数量,则符石是纪念碑。
注意,纪念碑中的法令不会被处理,所有输入符文都将被销毁。