Dynamic Fine-Grained Cobuild OTX(PSBT) Design

This scheme builds upon the original Cobuild design, modifying and supplementing it as needed. To understand this plan, please first review the foundational Cobuild scheme.

CKB Transaction CoBuild Protocol Overview - English / CKB Development & Technical Discussion - Nervos Talk

CKB Open Transaction (OTX) CoBuild Protocol Overview - English / CKB Development & Technical Discussion - Nervos Talk

The main focus here is on simplifying the implementation of dynamic OpenTransactions (OTx) to address intent expressions and stateful contract call simulations in the UTXO model.

DEMO(inprogress) : ckb-cobuild-poc (github.com)

First, regarding the data structure of OTx, the following modifications have been made:

// A complete CKB Transaction must contain only one OtxStart structure, which denotes the start
// of consecutive OTx within the CKB Transaction. OtxStart is marked in input/output cells, as well as in
// cell/header deps, indicating the position of the first component belonging to an OTx.
// It does not need to mark the start of witness, as OtxStart might already denote the start of the
// first OTx-related component within the witness.
//
// Following the OtxStart structure, the witness data will contain several OTx structures, each
// corresponding to an OTx included in the current CKB Transaction.
//
// Depending on the specific position of OtxStart within the witnesses array, the OtxStart structure itself
// might not be covered by any signature in the CKB Transaction.
table OtxStart {
    start_input_cell: Uint32,
    start_output_cell: Uint32,
    start_cell_deps: Uint32,
    start_header_deps: Uint32,
}

table SealPair {
    script_hash: Byte32,
    seal: ByteVec,
}
vector Seals <SealPair>;

// Otx represents a specific OTx structure, and for one OTx, it exclusively has one witness presented
// as an Otx structure. The position of the witness represented as Otx in the CKB Transaction does not have
// a fixed relationship with the positions of the input/output cells contained in that OTx.
table Otx {
    // An Otx, for example, might have 3 input cells, and these input cells are not necessarily under the same lock.
    // They could be under different lock scripts, each signed with a different signing algorithm.
    // As there is only one witness for an OTx, we need to include multiple signatures from different lock scripts
    // in this single witness. Hence, we use seals here, where the use of SealPair is similar to Action,
    // matching first by script hash, and then extracting the Bytes type lock field from the SealPair.
    seals: Seals,
    // The flag will determine whether the following items are allowed to have dynamic parts
    // When flag & 0b00000001 == 1, the number of input_cells can vary
    // When flag & 0b00000010 == 1, the number of output_cells can vary
    // When flag & 0b00000100 == 1, the number of cell_deps can vary
    // When flag & 0b00001000 == 1, the number of header_deps can vary
    // flag & 0b11110000 must equal 0, corresponding to four reserved bits
    flag: byte,
    // fixed_input_cells indicate the number of fixed input cells in the current Otx.
    // output_cells / cell_deps / header_deps are similar
    fixed_input_cells: Uint32,
    fixed_output_cells: Uint32,
    fixed_cell_deps: Uint32,
    fixed_header_deps: Uint32,
    message: Message,
    // If the flag allows dynamic properties, additional quantities can be dynamically appended during transaction assembly, 
    // and both quantity and dynamically appended values are not covered by signatures.
    dynamic_input_cells: Uint32,
    dynamic_output_cells: Uint32,
    dynamic_cell_deps: Uint32,
    dynamic_header_deps: Uint32,
}

If the flag indicates that a feature is not allowed to be dynamic, then the dynamic count for that feature must be zero.

Among these, flag, fixed_input_cells, fixed_output_cells, fixed_cell_deps, fixed_header_deps, message are covered by the signing_hash of the lock within the range of fixed_input_cells; the seals of the lock within dynamic_input_cells covers, in addition to the fixed parts, the dynamic inputs.

When searching for the seal corresponding to a lock in SealPair, for locks in fixed_input_cells, search from the front; for locks in dynamic_input_cells, search from the back. If a lock appears in both areas, it will correspond to two seals, with the seal for fixed_input appearing first and the seal for dynamic_input appearing last.

Another change targets the Cobuild’s Action, where an additional item, script_location, is added to the original Action structure to specify the script’s transaction location—input_lock, input_type, output_type—allowing for more refined control.

table Action {
    script_info_hash: Byte32,   // script info
    script_location: byte,      // script_location 0-input_lock, 1-input_type, 2-output_type
    script_hash: Byte32,        // script hash
    data: Bytes,                // action data
}

When OTx’s corresponding properties allow dynamic addition, data can be dynamically appended during transaction assembly.

A simple example:

inputs: 
    Capacity Cell
outputs:
    <empty>
Witness:
    message:
        dob_mint_script_hash
        mint_dynamic_dob
        outputs can be dynamically added

In this example, the user wants to use the dob_mint_script to perform DOB Mint, where the corresponding outputs are empty.

Once the Aggregator receives the corresponding OTx, it can dynamically append outputs and mint according to the following rules: when processing an OTx, if the minting serial number is a multiple of 17, then mint a DOB of rarity 1; if the minting serial number is a multiple of 29, then mint a DOB of rarity 2; otherwise, mint a normal DOB of rarity 0.

During minting, a certain amount of CKB is deducted as a fee.

The Aggregator completes the corresponding DOB output and change Cell according to the rules, and adjusts the number of dynamic_output_cells to match the actual situation.

The corresponding mint script should check:

  1. The correctness of the mint logic, i.e., the Aggregator produced the corresponding outputs according to the rules.
  2. Consistency of the Capacity change, i.e., the Capacity belonging to the user after processing should be returned to the user in the outputs.
4 Likes

Dynamic Fine-Grained Cobuild OTX Design

本方案在基于原本的 Cobuild 方案之上,对其进行了相关修改和补充,如需理解本方案,请先阅读:

CKB Transaction CoBuild Protocol Overview - English / CKB Development & Technical Discussion - Nervos Talk

CKB Open Transaction (OTX) CoBuild Protocol Overview - English / CKB Development & Technical Discussion - Nervos Talk

本方案主要聚焦于简单实现 OpenTransaction 的动态性,从而在 UTXO 模型上解决 Intent 表达,带状态的合约调用模拟等功能。

DEMO(进行中): ckb-cobuild-poc (github.com)

首先,对于 OTX 的数据结构,本方案进行了以下改动。

// 一个完整的 CKB Transaction 中只能包含唯一的一个 OtxStart 结构,用于表示当前 CKB Transaction
// 中,连续出现的 Otx 的开始。OtxStart 中标记在 input / output cells 中,以及
// cell / header deps 中,第一个属于 Otx 的结构的位置。这里不需要标记 witness 的开始位置,
// 因为 OtxStart 本身可能就标记了 witness 中第一个属于 Otx 结构的开始位置。
//
// 紧接着 OtxStart 结构,witness 数据中会继续包含若干个 Otx 结构,每一个 Otx 结构都对应着
// 当前 CKB Transaction 中所包含的一个 OTx
//
// 取决于 OtxStart 在 witnesses 数组中具体所处的位置,OtxStart 结构本身,有可能并不被 CKB
// Transaction 中的任何一个签名覆盖。
table OtxStart {
    start_input_cell: Uint32,
    start_output_cell: Uint32,
    start_cell_deps: Uint32,
    start_header_deps: Uint32,
}

table SealPair {
    script_hash: Byte32,
    seal: ByteVec,
}
vector Seals <SealPair>;

// Otx 用于表示某一个确定的 Otx 结构,对于一个 Otx 来说,它有且仅有惟一一个以 Otx 结构呈现
// 的 witness。同时以 Otx 结构表示的 witness 在 CKB Transaction 中所处的位置,与该 Otx
// 实际包含的 input / output cells 在当前 CKB Transaction 中所处的位置之间没有任何确定关系
table Otx {
    // 一个 Otx 可能比如有 3 个 input cells,这 3 个 input cells,并不都一定是相同的 lock
    // 可能他们都是不同的 lock script,用不同的签名算法分别签名。
    // 同时对一个 Otx 来说,只有 Otx 这一个 witness,
    // 我们需要在这一个 witness 中,塞入多个不同 lock script 的不同签名。
    // 因此这里用了 seals,这里 SealPair 的用法跟 Action 类似,
    // 也是先用 script hash 匹配,匹配到再从 SealPair 中取出 Bytes 类型的 lock 字段
    seals: Seals,
    // flag 将决定下面四项是否允许有 dynamic 的部分
    // 当 flag & 0b00000001 == 1 时,input_cells 的数量可变
    // 当 flag & 0b00000010 == 1 时,output_cells 的数量可变
    // 当 flag & 0b00000100 == 1 时,cell_deps 的数量可变
    // 当 flag & 0b00001000 == 1 时,header_deps 的数量可变
    // flag & 0b11110000 必须等于0,对应四位保留
    flag: byte, 
    // input_cells 表示当前的 Otx 中,有几个固定的input cells,是一个数字。 
    // output_cells / cell_deps / header_deps 也类似
    fixed_input_cells: Uint32,
    fixed_output_cells: Uint32,
    fixed_cell_deps: Uint32,
    fixed_header_deps: Uint32,
    message: Message,
    // 在flag允许对应属性动态后,可以在拼交易的过程中动态追加数量,数量和动态追加的值都不被签名覆盖。
    dynamic_input_cells: Uint32
    dynamic_output_cells: Uint32,
    dynamic_cell_deps: Uint32,
    dynamic_header_deps: Uint32,
}

如果 flag 指示某一项不允许dynamic,则对应项的dynamic数量必须等于0。

其中 flag, fixed_input_cells, fixed_output_cells, fixed_cell_deps, fixed_header_deps, message 将被处于 fixed_input_cells 范围里的 lock 的 signing_hash 覆盖;而处于 dynamic_input_cells 的 lock 的 seal,除了 Fixed 部分所涵盖的,还会额外覆盖全部的dynamic_inputs。

在 SealPair 中寻找 lock 对应 的 seal 时,对于 fixed_input_cells 的 lock,从前往后找,对于 dynamic_input_cells 的 lock,从后往前找。所以如果一个lock同时出现在这两个区域,则它会对应两个 seal,fixed_input 对应的 Seal 将在前面,dynamic_input 对应的 seal 将在后面。

另一个改动是针对 Cobuild 的 Action,在原本的Action结构中,增加了一项 script_location,用于指明被调用的脚本处于交易的位置,目前有 input_lock,input_type, output_type 三种类型可选,从而实现更精细的控制。

table Action {
    script_info_hash: Byte32,   // script info
    script_location: byte,          // script_location 0-input_lock, 1-input_type, 2-output_type
    script_hash: Byte32,        // script
    data: Bytes,                // action data
}

当 Otx 的对应属性允许动态追加后,在拼交易的过程中,可以动态追加数据。

一个简单的例子:

inputs: 
	Capacity Cell
outputs:
	<empty>
Witness:
	message:
		dob_mint_script_hash
		mint_dynamic_dob
		outputs 可以被动态添加

在上面这个例子中,用户想使用调用 dob_mint_script 进行 DOB Mint,对应的 outputs 是空的。

Aggregator 收到对应 OTX 后,可以动态追加outputs,并根据以下规则mint,当处理 OTX 时 mint 的序号为 17 的倍数时,则 mint 稀有度 1 的 DOB, 当处理 OTX 时 mint 的序号为 29 的倍数时,则 mint 稀有度为 2 的 DOB,否则mint稀有度为0的普通DOB。

在 mint 时,扣除一定量的 CKB 作为费用。

Aggregator根据规则补上对应 DOB 输出以及找零 Cell,并更改 dynamic_output_cells 的数量以符合实际情况。

对应的mint脚本应检查:

  1. mint逻辑的正确性,即 aggregator 按规则产生了对应输出。
  2. Capacity变化的前后一致,即处理结束后还属于用户的 Capacity,应在输出中返还给用户。
1 Like

Should be script_location?

1 Like

Yes, I was considering which name to use, which led to some inconsistency.

1 Like

Hey @orange-xc, thank you for taking your time to create and document this design, it seems promising!! :hugs:

I’d like to point out one general issue for reviewers: scroll bars due to long lines in code blocks make the review unnecessarily more difficult.

In your future proposals/designs feel free try these solutions:

  1. Extract the explanation text outside code blocks into normal text as much as you can.
  2. Split the big code blocks into smaller code blocks, so to keep code & relative explanation still locally nearby, but also maximizing the explanation readability.
  3. Use shorter comment lines.

As I review more designs I’ll be sure to point out readability issues when I see them.

Love & Peace, Phroi

Hey @orange-xc, I think that I got the gist of it, interesting design. I was wondering a detail:

Fee and Fee rate are volatile, definitely not fixed, more like a free market.

Could you make an example of which rules should be in place to prevent an Aggregator form robbing users with unnecessarily high tx fees? How do you envision this check workings?

Love & Peace, Phroi

just like ETH Rollup tx fee, it’s may not accurate for txs, just an approximation.

1 Like