This article introduces a Lock script that can implement Open Transaction on Nervos CKB. Inspired by the design of the previous Open Tx Brainstorm, it has a new ability to reorder and rearrange the signature components in an Open Transaction.

Open Brainstorm: talk.nervos.org/t/open-tx-p…

 

The data structure

 

The hash array

Inspired by the original OpenTx Brainstorming article, we added a new hash_array data structure to the front of the signature used by the composable OpenTx Lock Script. Hash_array contains a list of hash items, as follows:

| NAME | Command | Arg1 | Arg2 |
|------|---------|------|------|
| BITS | 8       | 12   | 12   |
Copy the code

A Hash item contains three 32-bit (4 bytes) long objects. Hash_array does not require a field of length at the beginning; a special command will mark the end of the hash array. In a way, we can think of the hash array as a small virtual machine input program. The purpose of this virtual machine is to provide data to the Blake2b hash function. The hash from the hash function is the signature information that will be used for signing.

The command

This section describes a valid command that accepts a hash item, along with its description and the parameters it accepts.

First, we have some common commands:

| 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      |
Copy the code

When the vm starts executing hash_array, a hash instance of blake2b is created, and most commands generate some data. This data is used as the content of the hash and is put into the Blake2B event. For example, the command 0x00 gets the hash of the currently running transaction through CKB syscall and then feeds the transaction hash as a data fragment to the Blake2b event. We’ll see more commands to generate data for the Blake2b hash object later. Another way to look at hash_array is to see the data that will be generated by each hash object (except for some items that don’t do this, where we can generate empty data from these hash objects), and then wire all the data to a single data entry through the Blake2b hash algorithm and use that as a signature message in the subsequent signature validation phase. The command 0x01 counts the number of input and output cells in the current Lock Script group and hashed the blake2b event with two digits in 64-bit unsigned little endian format. This can be used to prevent the Open TX aggregator from arbitrarily adding unprocessed cells. The 0xf0 command fills a different purpose: on the one hand, it identifies a hash_array, and on the other hand, it tells the small virtual machine to run all the data that has been sent to the virtual machine, and we can now generate the corresponding hash from the Blake2b event. This corresponding hash is also used as the signature message for later in the signature verification phase. Now that we have a general workflow, we can use more commands to generate data:

| 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` |Copy the code

These four commands will first locate the input or output cell, and then generate the data as part of the cell or as a whole. The source of a cell (whether it is an input or output cell) is represented by the command, and the index of the cell is represented by the command and ARG 1:

  • For commands 0x11 and 0x12, ARG 1 represents the absolute index of the cell in the current transaction.
  • For commands 0x19 and 0x1A, ARG 1 represents the offset in the specified cell. Later we’ll see in witness two variables, base input index and Base Output index, as well as hash_array and signature. For command 0x19, adding ARG 1 and base Intput Index yields the absolute index of the specified output cell in the current transaction, whereas for command 0x1A, Adding ARG 1 and Base Output Index will produce the absolute index of the specified input cell in the current transaction. Offset provides a way to reorder cells so that there is room for many non-conflicting Open Tx’s to coexist in an CKB transaction.

The data generated from the cell is determined by ARG 2 or cell mask. The significant bits in the mask include:

| 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   |
Copy the code

Here are some practical examples:

  • 0x11 0x00 0x30 0x21 will get the output cell with an absolute index of 3 in the current transaction, then extract its capacity, and then take the Lock Script parameter as the data hash of the Blake2b event
  • If the base input index is 5,0 x1A 0x01 0x04 0x00, the input cell with the absolute index of 21 in the current transaction will be taken and the entire cell will be used as the data hashed by the blake2b event

In addition to the Cell, there is a CellInput structure associated with each input Cell that provides valuable information, such as since and OutPoint. The following command provides a way to hash CellInput data:

  • CellInput:

Github.com/nervosnetwo…

| 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` |Copy the code

The same program used to locate cells is used to locate the structure of CellInput, the only difference being the actual data to be generated, or the Input mask stored in ARG 2:

| BIT  | INCLUDED DATA                 |
|------|-------------------------------|
| 0x1  | previous_output.tx_hash       |
| 0x2  | previous_output.index         |
| 0x4  | since                         |
| 0x8  | previous_output               |
| 0x10 | The whole CellInput structure |
Copy the code

Here are some practical examples:

  • 0x15 0x00 0x00 0x04 takes the CellInput structure with an absolute index of 0 in the current transaction and uses its since value as the hash data for the Blake2b event
  • Assuming base input index is 2,0 x1D 0x00 0x10 0x0C will use the CellInput structure with absolute index 3 in the current transaction, and then use its since value, The serialized previous_output field (which is an OutPoint structure) is then used as the data for the Blake2B event hash

With this background, we can start looking at some of the more complex commands:

| 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 |Copy the code

We have discussed a tiny virtual machine above. But all of these transactions are just sending data for blake2b events. The tiny virtual machine in the composable Open Tx lock script is essentially maintaining a stack internally. The stack can hold up to eight elements, each of which is a 256-bit integer. Commands from 0x21 to 0x2F can be used to push data onto the stack:

  • The command 0x2F joins the values stored in ARG 1 and ARG 2, then converts the resulting value to a 256-bit integer, and then pushes it onto the stack.
  • The commands 0x23, 0x24, 0x2B, and 0x2C first find a cell in the methods described above, then take the capacity of that cell, convert it to a 256-bit integer, and push it onto the stack.
  • The commands 0x21, 0x22, 0x29, and 0x2A will first find a cell in the method described above, then extract the Data from part of the cell in the format defined in Data Format, convert it to a 256-bit integer, and then push it onto the stack. The exact output of the Data format is as follows:
| 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 |Copy the code

Note that the stack can store up to eight elements. When the stack is full, pushing more data causes the lock script to terminate immediately and return an error code.

The commands from 0x41 to 0x44 provide basic operations on the values at the top of the stack. For overflows/underflows, wrapping behavior is used.

As a more complete example, the following program can be used to ensure that only a certain number of sUDT tokens can be drawn from a specific account:

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
Copy the code

The Open Transaction of this program will contain an input cell and an output cell. The signatures provided include the following:

  • The length of the input and output cells in the current script group
  • The account used in the input and output cell
  • SUDT IDS for the input and output cells
  • SUDT token differences between input and output cells
  • Differences in CKB tokens between input and output units

If you think about it, this program doesn’t even force a cell as input. If the Open Tx constructor has multiple cells that meet the requirements, then the aggregator is free to choose any input Cell, while the aggregator can only choose to generate transactions based on the Open Tx constructor’s requirements. That way all the tokens are safe from theft.

Lock Script

A composable Open Transaction Lock Script looks like this:

Code hash: composable open transaction script code hash
Hash type: composable open transaction script hash type
Args: <21 byte identity>
Copy the code

He uses the same Identity as RC Lock (talk.nervos.org/t/rfc-regul…) Data structure:

<1 byte flag> <20 byte identity content>
Copy the code

According to the value of flag, the content of identity can be interpreted differently:

  • 0x0: identity The content represents the Blake160 hash of the secP256K1 public key. The lock script will perform the signature validation of SECP256K1, just like the lock of SECP256K1 /blake160, using the signature message computed by executing the hash_array program shown above.

Later, we might add more checks to the Identity data structure. For example, when exec(github.com/nervosnetwo… Identity type, which loads the new script for the actual identity authentication.

Witness

When unlocking a composable open Transaction lock, the corresponding WITNESS must be a correct WitnessArgs data structure in molecular format. The following data structure must appear in the WitnessArgs lock field:

| BYTES | CONTENT | |---------|-------------------| | 0.. 7 | Base input index | | 8.. 15 | Base output index | | 16.. n | Hash array | | n.. n+65 | Signature |Copy the code

The Base Input index and Base Output index are populated by the Open Transaction aggregator, whereas the hash array and signature are provided by the creator of the Open Transaction.

sample

 

Unlock an 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> <... >Copy the code

 

integration

In actual development, an Open Transaction creator could create an Open Transaction in the same format as a typical Transaction, with both base input index and Base output index populated with 0.

If we think about it, most Open transactions can also be committed and accepted by CKB, but the aggregator of Open transactions would like to consolidate multiple such transactions into a single Transaction to collect payment and save Transaction fees.