本文介紹了一個在 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 的聚合器會喜歡將多個此類交易合併到一個單一的交易中,以便於收取付款並節省交易費用。