RFC:可组合的 Open Transaction lock script
本文介绍了一个在 Nervos CKB 上能实现 Open Transaction 的 lock script。它的灵感来自于之前 Open Tx Brainstorm 的设计,具有在 Open Transaction 中重新排序和重新安排签名组件的新能力。
Open Tx Brainstorm:
https://talk.nervos.org/t/open-tx-protocol-brainstorm-4-an-implementation-proposal/4427
数据结构
哈希阵列
受最初的 Open Tx 头脑风暴的文章的启发,我们在可组合的 OpenTx lock script 使用的签名前面添加了一个新的 hash_array 的数据结构。hash_array 包含一个 hash item 列表,如下所示:
| NAME | Command | Arg1 | Arg2 |
|------|---------|------|------|
| BITS | 8 | 12 | 12 |
一个 Hash item 包含 3 个 32 位(4 字节)长的物件。hash_array 不要求在开始处是有长度的字段,一个特殊的命令将标记哈希阵列的结束。在某种程度上,我们可以将哈希阵列看作是一个小型的虚拟机输入程序。这个虚拟机的目的是为了给 blake2b 哈希函数提供数据。来自哈希函数的哈希将用来作签名用的签名信息。
命令
本节将介绍接受 hash item 的有效命令,以及描述和所接受的参数。
首先,我们有一些常见的命令:
| 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 表示:
对于命令 0x11 和 0x12,ARG 1 表示当前交易中的 cell 的绝对索引。 对于命令 0x19 和 0x1A,ARG 1 表示在指定 cell 中的 offset(偏移量)。稍后我们将看到在 witness 中两个变量 base input index 和 base output index,还有 hash_array 和签名。对于命令 0x19,添加 ARG 1 和 base intput index 将产生当前交易中指定输出 cell 的绝对索引,而对于命令 0x1A,添加 ARG 1 和 base output index 将产生当前交易中指定输入 cell 的绝对索引。offset 提供了一种重新排序 cell 的方法,因此一个 CKB 交易中有让许多不冲突的 Open Tx 并存的空间。
| 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 |
0x11 0x00 0x30 0x21 将获取当前交易中绝对索引为 3 的输出 cell ,然后提取其 capacity ,然后将 lock script 参数作为 blake2b 事件的数据哈希 假设 base input index 是 5,0x1A 0x01 0x04 0x00 会取当前交易中绝对索引为 21 的输入 cell,然后将整个 cell 作为 blake2b 事件所哈希的数据
CellInput:
https://github.com/nervosnetwork/ckb/blob/85d04c329d4478df5ca40e4161152f3eab858d59/util/types/schemas/blockchain.mol#L41-L44
| 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` |
| BIT | INCLUDED DATA |
|------|-------------------------------|
| 0x1 | previous_output.tx_hash |
| 0x2 | previous_output.index |
| 0x4 | since |
| 0x8 | previous_output |
| 0x10 | The whole CellInput structure |
0x15 0x00 0x00 0x04 会取当前交易中绝对索引为 0 的 CellInput 结构,然后使用它的 since 值作为 blake2b 事件的哈希数据 假设 base input index 为 2,0x1D 0x00 0x10 0x0C 将在当前交易中使用绝对索引 3 的 CellInput 结构,然后使用它的 since 值,然后使用序列化的 previous_output 字段(这是一个 OutPoint 结构)作为 blake2b 事件哈希的数据
| 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 |
命令 0x2F 将连接存储在 ARG 1 和 ARG 2 中的值,然后将结果值转换为 256 位整数,然后将其推入堆栈中。 命令 0x23, 0x24, 0x2B 和 0x2C 会在上面描述的方法中首先找到一个 cell,然后取该 cell 的 capacity,将其转换为 256 位整数,然后将其推入堆栈。 命令 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 |
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
当前脚本组中输入和输出 cell 的长度 输入和输出 cell 中使用的帐户 用于输入和输出 cell 的 sUDT ID 输入和输出 Cell 之间的 sUDT token 的差异 输入和输出单元之间 CKB token 的差异
Lock Script
Code hash: composable open transaction script code hash
Hash type: composable open transaction script hash type
Args: <21 byte identity>
<1 byte flag> <20 byte identity content>
0x0: identity 内容表示 secp256k1 公钥的 blake160 哈希。锁脚本将执行 secp256k1 的签名验证,就像 secp256k1 /blake160 这个 lock 一样,使用通过执行上面所示的 hash_array 程序计算出来的签名消息。
Witness
| BYTES | CONTENT |
|---------|-------------------|
| 0..7 | Base input index |
| 8..15 | Base output index |
| 16..n | Hash array |
| n..n+65 | 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>
<...>
整合