本文介绍了一个在 Nervos CKB 上能实现 Open Transaction 的 lock script。它的灵感来自于以前 Open Tx Brainstorm 的设计,具备在 Open Transaction 中从新排序和从新安排签名组件的新能力。git
Open Tx Brainstorm:
https://talk.nervos.org/t/ope...
受最初的 Open Tx 头脑风暴的文章的启发,咱们在可组合的 OpenTx lock script 使用的签名前面添加了一个新的 hash_array 的数据结构。hash_array 包含一个 hash item 列表,以下所示:github
| NAME | Command | Arg1 | Arg2 | |------|---------|------|------| | BITS | 8 | 12 | 12 |
一个 Hash item 包含 3 个 32 位(4 字节)长的物件。hash_array 不要求在开始处是有长度的字段,一个特殊的命令将标记哈希阵列的结束。在某种程度上,咱们能够将哈希阵列看做是一个小型的虚拟机输入程序。这个虚拟机的目的是为了给 blake2b 哈希函数提供数据。来自哈希函数的哈希将用来做签名用的签名信息。
算法
本节将介绍接受 hash item 的有效命令,以及描述和所接受的参数。express
首先,咱们有一些常见的命令:segmentfault
| COMMAND | DESCRIPTION | ARG 1 | ARG 2 | |---------|-------------------------------------------------------------|-----------------------|--------------| | 0x00 | Hash the full current transaction hash | ignored | ignored | | 0x01 | Hash length of input & output cells in current script group | ignored | ignored | | 0xF0 | Terminate and generate the final blake2b hash | ignored | ignored |
当虚拟机开始执行 hash_array 时,一个 blake2b 的哈希事件(hash instance)会被建立,大多数命令会生成一些数据。这些数据做为要哈希的内容,并放入 blake2b 事件中。例如,命令 0x00 将经过 CKB syscall 获取当前正在运行的交易的哈希值,而后将交易哈希值做为数据片断提供给 blake2b 事件。稍后咱们将看到更多为 blake2b 哈希物件生成数据的命令。
看到 hash_array 的另外一种方法是,每一个哈希物件将生成的数据(除了一些项目不这么作之外,咱们能够把这些哈希物件生成空数据),而后将全部数据链接到经过 blake2b 哈希算法的单一数据入口,并用之做为后续签名验证阶段的签名消息。
命令 0x01 会计算当前 lock script 组中输入和输出的 cell 的数量,并用 64 位无符号小端序格式的两个数字提供给 blake2b 事件来进行哈希。这可用于防止 Open tx 聚合器任意添加未处理的 cell。
命令 0xf0 填补了另外一个不一样的目的:一方面,它标示着 hash_array,另外一方面,它通知这个小型虚拟机在此运行全部已经传送到虚拟机的数据,并且咱们如今还能够从 blake2b 事件生成相应的哈希。此相应的 hash 也用做签名消息,用于稍后的签名验证阶段。
有了大致的工做流程后,咱们就可使用更多生成数据的命令了:安全
| COMMAND | DESCRIPTION | ARG 1 | ARG 2 | |---------|-----------------------------------------------|-----------------------|--------------| | 0x11 | Hash part or the whole output cell | index of output cell | `Cell mask` | | 0x12 | Hash part or the whole input cell | index of input cell | `Cell mask` | | 0x19 | Hash part or the whole output cell | offset of output cell | `Cell mask` | | 0x1A | Hash part or the whole input cell | offset of input cell | `Cell mask` |
这 4 个命令将首先定位在输入或输出 cell ,而后生成做为一部分或整个 cell 的数据。cell 的来源(不管它是输入或输出 cell)由命令表示,cell 的索引则由命令和 ARG 1 表示:数据结构
从 cell 生成的数据,由 ARG 2 或 Cell mask 肯定,mask 中的有效位包括:app
| BIT | INCLUDED DATA | |-------|------------------| | 0x1 | Capacity | | 0x2 | type.code_hash | | 0x4 | type.args | | 0x8 | type.hash_type | | 0x10 | lock.code_hash | | 0x20 | lock.args | | 0x40 | lock.hash_type | | 0x80 | Cell data | | 0x100 | Type script hash | | 0x200 | Lock script hash | | 0x400 | The whole cell |
如下是一些实际的例子:ide
除了 Cell ,还有一个 CellInput 结构与每一个输入 cell 相关联,提供有价值的信息,如 since 和 OutPoint。下面的命令提供了一种将 CellInput 数据进行哈希的方法:函数
| COMMAND | DESCRIPTION | ARG 1 | ARG 2 | |---------|-----------------------------------------------|-----------------------|--------------| | 0x15 | Hash part or the whole cell input structure | index of input cell | `Input mask` | | 0x1D | Hash part or the whole cell input structure | offset of input cell | `Input mask` |
定位 cell 的相同程序也用于定位 CellInput 的结构,惟一的区别在于要生成的实际数据,或保存在 ARG 2 中的 Input mask:
| BIT | INCLUDED DATA | |------|-------------------------------| | 0x1 | previous_output.tx_hash | | 0x2 | previous_output.index | | 0x4 | since | | 0x8 | previous_output | | 0x10 | The whole CellInput structure |
这里是一些实际的范例:
有了这些背景知识,咱们能够开始看一些更复杂的命令:
| COMMAND | DESCRIPTION | ARG 1 | ARG 2 | |---------|------------------------------------------------------------------------------------------------------------------------------|-----------------------|---------------| | 0x21 | Push cell data to stack | index of output cell | `Data format` | | 0x22 | Push cell data to stack | index of input cell | `Data format` | | 0x23 | Push capacity to stack | index of output cell | ignored | | 0x24 | Push capacity to stack | index of input cell | ignored | | 0x29 | Push cell data to stack | offset of output cell | `Data format` | | 0x2A | Push cell data to stack | offset of input cell | `Data format` | | 0x2B | Push capacity to stack | index of output cell | ignored | | 0x2C | Push capacity to stack | index of input cell | ignored | | 0x2F | Concatenate ARG 1 and ARG 2, push the resulting value to stack | higher 12 bit | lower 12 bit | | 0x40 | Pop the top value from stack, then convert it to data of 32 bytes to hash | ignored | ignored | | 0x41 | Pop top 2 values from stack, add them, then push the result back to stack | ignored | ignored | | 0x42 | Pop top 2 values from stack, subtract them, then push the result back to stack | ignored | ignored | | 0x43 | Pop top 2 values from stack, multiply them, then push the result back to stack | ignored | ignored | | 0x44 | Pop top 2 values from stack, divide them, then push the result back to stack. When divisor is zero, exit with an error code. | ignored | ignored |
咱们已经在上面讨论了一个微型虚拟机。可是上面全部的交易,只是为 blake2b 事件发出数据。可组合的 Open Tx 锁脚本中的微型虚拟机,其实是在内部维护一个堆栈。堆栈最多能够容纳 8 个元素,每一个元素都是 256 位整数。从 0x21 到 0x2F 的命令能够用来将数据推送到堆栈:
命令 0x21, 0x22, 0x29 和 0x2A 将首先在上面描述的方法中找到一个 cell ,而后按照 Data format 中定义的格式提取部分 cell 的数据,将其转换为 256 位整数,而后将其推入堆栈。Data format 的精确输出以下:
| BITS | MEANING | |--------|--------------------------------------------------------------------------------------------------------------| | 0 | Endianness, 0 for little endian, 1 for big endian | | 1 - 3 | Length of data to extract, expressed in power of 2, for example, 3 here means 8 bytes, 5 here means 32 bytes | | 4 - 11 | Start offset of data to extract |
注意,堆栈最多能够存储 8 个元素。当堆栈已满时,推入更多数据将致使锁脚本当即终止,并返回错误代码。
从 0x41 到 0x44 的命令提供了对堆栈顶层的值的基本操做。对于溢出/下溢(overflows / underflows),将使用环绕(wrapping)行为。
做为一个更完整的例子,下面的程序能够用来确保,只能从一个特定的账户提取必定数量的 sUDT token:
0x01 0x00 0x00 0x00 // Hash the length of input & output cells in current script group 0x1A 0x00 0x03 0x00 // Hash the lock script(account) and type script(sUDT ID) for the // input cell at offset 0 0x19 0x00 0x03 0x00 // Hash the lock script(account) and type script(sUDT ID) for the // output cell at offset 0 0x29 0x00 0x04 0x00 // Take the output cell at offset 0, extract the first 16 bytes of // data in little endian format(sUDT amount), and push the resulting // value to stack 0x2A 0x00 0x04 0x00 // Take the input cell at offset 0, extract the first 16 bytes of // data in little endian format(sUDT amount), and push the resulting // value to stack 0x42 0x00 0x00 0x00 // Substract the top 2 values on stack 0x40 0x00 0x00 0x00 // Hash the top value on stack 0x2B 0x00 0x00 0x00 // Take the output cell at offset 0, push the capacity to stack 0x2C 0x00 0x00 0x00 // Take the input cell at offset 0, push the capacity to stack 0x42 0x00 0x00 0x00 // Substract the top 2 values on stack 0x40 0x00 0x00 0x00 // Hash the top value on stack 0xF0 0x00 0x00 0x00 // Terminate and generate the resulting hash
此程序的 Open Transaction 将包含一个输入 cell 和一个输出 cell 。所提供的签名包括如下部分:
若是你仔细想一想,这个程序甚至没有强制使用某个 cell 做为输入。若是 Open Tx 的构造者有多个知足需求的 Cell,那么聚合器能够自由选择任何输入 cell,而同时聚合器只能选择根据 Open Tx 构造者的需求来生成交易。这么一来全部的代币都是安全的,不会被偷。
一个可组合的 Open Transaction Lock Script 看起来以下:
Code hash: composable open transaction script code hash Hash type: composable open transaction script hash type Args: <21 byte identity>
他使用与 RC Lock 相同的 Identity(https://talk.nervos.org/t/rfc-regulation-compliance-lock/5788)数据结构:
<1 byte flag> <20 byte identity content>
根据 flag 的值, identity 的内容有不一样的解释:
稍后,咱们可能会向 identity 数据结构添加更多检查。例如,当 exec(https://github.com/nervosnetw...准备就绪时,咱们可能还会添加另外一种 identity 类型,它将加载用于实际的 identity 验证的新脚本。
当解锁一个可组合的 open transaction lock 时,相应的 witness 必须是一个分子格式的正确 WitnessArgs 数据结构,如下数据结构必须出如今 WitnessArgs 的 lock 字段中:
| BYTES | CONTENT | |---------|-------------------| | 0..7 | Base input index | | 8..15 | Base output index | | 16..n | Hash array | | n..n+65 | Signature |
Base input index 和 base output index 由 Open Transaction 聚合器填充,然而 hash array 和 signature 则是有 Open Transaction 的建立人所提供。
CellDeps: <vec> Composable Open Transaction Lock Script Cell Inputs: <vec> Open Transaction Cell Capacity: 100 Lock: code_hash: Composable Open Transaction Lock args: <flag: 0x0> <pubkey hash 1> <...> Outputs: <vec> Open Transaction Cell Capacity: 50 Lock: code_hash: Composable Open Transaction Lock args: <flag: 0x0> <pubkey hash 1> <...> Witnesses: WitnessArgs structure: Lock: base input index: 0 base output index: 0 hash array: <a valid hash array program> <...>
在实际开发中,Open Transaction 的建立者能够建立与典型交易相同格式的 Open Transaction,base input index 和 base output index 都填充为 0。
若是咱们考虑一下,大多数 Open Transaction 也能够被 CKB 提交和接受,但 Open Transaction 的聚合器会喜欢将多个此类交易合并到一个单一的交易中,以便于收取付款并节省交易费用。