Open Tx 协议头脑风暴[2]: 在 CKB 上设计一个实用的协议

Cipher Wang

Cipher Wang

Nervos Core Team

动机

Open TX 设计一个协议,我们需要遵循一些基本原则,比如签名规则,组合规则和安全问题。在本文中,我想谈谈这些问题。

签名规则

通常,每个有意义的 lock script 都需要一个非对称加密签名来解锁。对于默认的 SIGHASH_ALL 方案,它首先会计算完整的交易的哈希,任何对哈希结果进行签名。对于 OTX,为了确认所有权,它的签名必须覆盖每一个 OTX 的输入和它所涉及的字段。

otx-02

如上图所示,一个完整的交易由两个 open TXs 组成,OTX#0OTX#1。如果这是一笔传统的完整的交易,并假设所有的输入都使用同样的 lock script,那么它的哈希应该覆盖交易内的所有数据。但是对于 open TX 方案来说,这里有两个 OTX 有它们各自的签名来包含内部的条件,从而形成一笔完整的交易。

让我们以一个最简单的场景为例。Alice 有 100 个 USDT,放在了 input#0 中,然后她要发送给 Bob 20 USDT,找零的 80 USDT 需要放在 output#1 中。这个 OTX#0 是无效的,因为:

  1. 它产生了一个额外的输出 cell,而这个 cell 需要额外的 CKBytes 来进行创建(比如说需要用 100 CKBytes)
  2. 它不提供任何转账手续费。其他人需要将 OTX#1OTX#0 组合在一起,从而形成一笔有效的交易。比如,它用 1000 CKBytes 作为输入,890 CKBytes 作为输出。所以,OTX#1 其实为 output#1 提供了 100 CKBytes,同时也提供了 10 CKBytes 作为转账手续费。

在这个过程中,OTX#0 的签名应该包括 input#0output#0output#1,而 OTX#1 则应该包括 input#1output#2。这里的签名有两个目的:

  1. 判断输入的所有权
  2. 确保 TX/OTX 的完整性

为了实现这些,签名应该包括即将被花费的输入和签名者关心的交易中的其他部分。这些内容的范围定义有四个不同的级别。

范围级别 0,TX(交易)

这意味签名包含了一笔交易的所有数据,换句话说,这个级别的签名与比特币中 SIGHASH_ALL 是一样的。它从交易中加载所有的数据,然后进行哈希并签名。没有人可以修改这笔交易,因此它不能支持 open tx 方案。

范围级别 1,Cells

在这个级别上,OTX 的签名包括了特定的 cells,就像上面演示的那样,第一个 OTX 对一些输入和输出进行签名,第二个 OTX 对剩下的进行签名,然后它们可以相互组合,最终形成一笔有效的交易。

这中模式需要一个很好的 OTX 定义方法来识别它所设计的 cells。例如,每个签名都需要附上一个 cell 索引位图作为参数(在 witnesses 字段中)来描述它所包含的 cells。

范围级别 2,Fields(字段)

在这个级别上,OTX 定义在一笔交易的 cells 中哪些字段是它所关心的。例如,它约束了 output#0type scriptdatalock scriptcode hash 字段,而不去管 lock scriptargs

范围级别 3,Logics(逻辑)

这一级别将为 OTX 提供更灵活的约束条件。例如,output#0capacity 必须大于 1000,或者要求一个输出的 data 字段必须等于它的 lock script 的哈希。

可组合性

OTX 协议的可组合性有两个要求,一个是不同的 OTX 之间的组合,另一个是 OTX 协议脚本呢和用户自定义脚本之间的组合。

多个 OTXs

在我们初始化一个 OTX 时,并不知道有多少个其他的 OTXs 会附加到这个 OTX 上来,也不知道这些 OTXs 的 lock scripts/type scripts 是什么。因此,不同的 OTXs 应该在相同的协议下保持可组合性。

这里的关键信息是,引用目标 cells 的签名规则必须使用一些相对索引,否则它们将在不同的栈序列中被打乱。

OTX 功能作为一个扩展

为了支持 OTX 功能,输入的 cells 的 lock script 需要遵守 OTX 规则。为了让整个事情变简单,并使得 OTX 之间能够实现互相兼容,最好创建一个通用的 OTX lib,然后每个 lock script 都调用它。

/* pseudo-code: OTX ready SECP256K1 lock script,
note that the demo code cannot achieve scope level 3 (logic) constrans.
*/
int main() {
// load lock script args
lock_args = load_script_args(CKB_SOURCE_GROUP_INPUT);
// load OTX lib
otx_handle = load_lib(OTX_LIB_HASH);
// load OTX parameters in witness, which defines the signature coverage
otx_parameters = load_witness(0, CKB_SOURCE_GROUP_INPUT);
// hash the items defined in otx_parameters,
// including all inputs with the same lock_script
hash_init(digest);
while (message = load_tx_input(CKB_SOURCE_GROUP_INPUT)) {
hash_update(digest, message);
}
// extract items in TX
TX = load_tx();
items = otx_handle.invoke(TX, otx_parameters);
// hash the items
for (item in items) {
message = load_tx_item(item);
hash_update(digest, message);
}
// load signature
signature = load_witness(1, CKB_SOURCE_GROUP_INPUT);
// recover pk
pubkey = secp256k1_ecdsa_recover(digest, signature);
// calculate pkhash
pk_hash = hash(pubkey);
// return 0 if pkhash matches lock_args
return match(pk_hash, lock_args);
}

回顾

OTX 必须识别约束条件,并且这里有四个等级的约束条件细分。为了使得约束条件不可变,我们需要对它们进行签名。我们还需要考虑 OTXs 的可组合性和代码的的可复用性,所以我们最好提供一个共享库来帮助锁定脚本获得 OTX 的功能。

原文链接