ACP Lock与check_onwer_mode的冲突

在许多逻辑里,我们需要验证交易内是否有Owner参与,比如sUDT的发行等。目前的Owner判定是扫描所有的input_cell内是否有某个cell的lock_hash与Owner的lock_hash是一样的,但如果Owner是acp_lock的话,由于acp的性质,这种Owner判定将完全失去作用。

近期NexisDAO与YokaiSwap开始测试,但同样对接的metamask却有不一样的ckb地址,这种资产割裂让许多用户都感到困惑与不便,经过了解也与acp问题有关,通过修改owner判定,应该可以使这两个地址变得统一。

在与@kylexu讨论了这个问题,即owner的判定除了判断lock_hash相等之外,还必须判定其是否为acp流程。

在原本的acp判定逻辑里,lock只检查Witness的lock域是否为空来判定,如果witness.lock为空,则走acp逻辑,否则走验签逻辑。

int has_signature(int *has_sig) {
  int ret;
  unsigned char temp[MAX_WITNESS_SIZE];

  /* Load witness of first input */
  uint64_t witness_len = MAX_WITNESS_SIZE;
  ret = ckb_load_witness(temp, &witness_len, 0, 0, CKB_SOURCE_GROUP_INPUT);

  if ((ret == CKB_INDEX_OUT_OF_BOUND) ||
      (ret == CKB_SUCCESS && witness_len == 0)) {
    *has_sig = 0;
    return CKB_SUCCESS;
  }

  if (ret != CKB_SUCCESS) {
    return ERROR_SYSCALL;
  }

  if (witness_len > MAX_WITNESS_SIZE) {
    return ERROR_WITNESS_SIZE;
  }

  /* load signature */
  mol_seg_t lock_bytes_seg;
  ret = extract_witness_lock(temp, witness_len, &lock_bytes_seg);
  if (ret != 0) {
    return ERROR_ENCODING;
  }

  *has_sig = lock_bytes_seg.size > 0;
  return CKB_SUCCESS;
}

但新版ckb-production-scripts将这里的逻辑改成了lock要存在并且lock的长度等于signature_size的时候,才走验签逻辑。

/*
 * Load secp256k1 first witness and check the signature
 *
 * This function return CKB_SUCCESS if the witness is a valid WitnessArgs
 * and the length of WitnessArgs#lock field is exactly the SIGNATURE_SIZE
 *
 * Arguments:
 * * witness bytes, a buffer to receive the first witness bytes of the input
 * cell group
 * * witness len, a pointer to receive the first witness length
 *
 * Witness:
 * WitnessArgs with a signature in lock field used to present ownership.
 */
int load_secp256k1_first_witness_and_check_signature(
    unsigned char witness_bytes[MAX_WITNESS_SIZE], uint64_t *witness_len) {
  int ret;
  /* Load witness of first input */
  *witness_len = MAX_WITNESS_SIZE;
  ret = ckb_load_witness(witness_bytes, witness_len, 0, 0,
                         CKB_SOURCE_GROUP_INPUT);
  if (ret != CKB_SUCCESS) {
    return ERROR_SYSCALL;
  }

  if (*witness_len > MAX_WITNESS_SIZE) {
    return ERROR_WITNESS_SIZE;
  }

  /* load signature */
  mol_seg_t witness_lock_seg;
  ret = extract_witness_lock(witness_bytes, *witness_len, &witness_lock_seg);
  if (ret != 0) {
    return ERROR_ENCODING;
  }

  if (witness_lock_seg.size != SIGNATURE_SIZE) {
    return ERROR_ARGUMENTS_LEN;
  }
  return CKB_SUCCESS;
}

我不知道限制长度的好处是什么,但是这种改动使得acp的判定变得极其复杂且不统一。‘

如果采取最简单的判定,只需要检查对应Witness不为且lock不为空即可,由于Witness经过molecule编码以后的模式为 4Bytes的总长度+4bytes的lock_start+4Bytes的input_start+4bytes的output_start+lock数据+input数据+output数据。所以我们只需要从Witness的第四个字节开始,读取八个字节,如果读到的前四个字节小于后四个字节,即可判定为非acp流程。

pub fn check_owner_mode(args: &Bytes) -> Result<bool, Error> {
    // With owner lock script extracted, we will look through each input in the
    // current transaction to see if any unlocked cell uses owner lock and not use acp logic.
    let is_owner_mode = QueryIter::new(load_cell_lock_hash, Source::Input)
        .position(|lock_hash| args[..] == lock_hash[..])
        .map(|index| {
            let mut buf = [0u8; 8];
            let len = load_witness(&mut buf, 4, index, Source::Input)?;
            if len < 12 {
                None
            }

            let start = unpack_number(&buf);
            let end = unpack_number(&buf[4..]);
            if start <= end {
                None
            }

            true
        })
        .is_some();

    Ok(is_owner_mode)
}
2 Likes


deposit的Owner判定。

首先针对这一点:

新版ckb-production-scripts将这里的逻辑改成了lock要存在并且lock的长度等于signature_size的时候,才走验签逻辑。

这个只是一个 lock 的实现细节逻辑,我理解这里要问的问题是:是否应该加一条强制规则,WitnessArgs 中的 lock field 为空,则是 acp 流程,不然走正常验签流程?

这里其实忽略了一个点:目前的 acp lock 在 acp 流程解锁时不需要任何参数其实是一个巧合,并不是一个普适性的规则。例如我同样可以设计一个新的用于某些 token issuance 流程的 acp lock,只有在某个时间点之后(当然我们可以讲说时间锁可以用 since,这里只是举个例子),或是二级增发满足某种需求的情况下,启用 acp 规则,大家可以向一个 acp cell 中发送资产。在这种情况下,WitnessArgs 中的 lock field 里,完全可以放一个 header hash,或者是 header dep index,当前 lock 从对应的 header 中读取信息,满足校验条件之后,就可以无需签名解锁;换个方式,一个中间 cell 也可以来提供某种信息,促成一个全新的 acp lock,启用 acp 的条件。这里我想说的是,acp 规则中,充值是解锁需要满足的一个必要条件,但是没法说有充值行为,就一定可以解锁。WitnessArgs 中的 lock field 里,有可能存放的也是 acp 解锁需要的条件信息。当然这里可以说,lock 小于某种长度就认为是 acp 解锁,长于某种长度是签名,但是这个长度如何界定?我个人不希望 CKB 中,逐渐因为 legacy 因素,而多了很多诡异的规则。

最近其实对这样的问题讨论很多,比如长短地址的讨论,比如 pw/unipass/das 各自有 CKB 地看起来很像但是又无法兼容的讨论。包括这个帖子里面对 acp 的讨论,其实这些所有的问题,归根结底都是一个问题:CKB 的 lock 模式带来了一个全新的 pattern。一个 lock 里可能有多种验签逻辑,甚至有相同验签逻辑的 lock,也可能有各种各样不同的解锁条件。但是在目前的 dapp 中,却忽略了这件事,引入了各种各样的限制条件:

  • 看到 secp256k1 公钥,可能就会假设这是一个单签 lock,但是如果我有一个使用 1-of-1 模式的多签 lock 的 cell,为什么我的私钥不能解锁呢?
  • 看到 ETH 公钥,可能就会假设这是一个支持 ACP 的 lock,为什么不能同样有一个不支持 ACP 的 lock,使用同样的 ETH 公钥呢?
  • 我甚至听到一个更神奇的例子:有的地方对给定一个 CKB 地址的话,先假设是单签地址,解析失败后,就直接认为是多签地址。在这个场景里,ACP cell 接入之后直接就失败了。幸好这个 case 中,私钥还在,资产并没有丢。

我想先强调一点的是,我举这些例子不是在讲我们的 dapp 开发者们有什么问题。相反,我觉得现在每一个在 CKB 上做东西的开发者,都值得我们脱帽致敬,这是一群真的用于探索一个没有人走过的前沿方向的勇士。That being said,每一个上述的例子出现,甚至每一次看到 “xxx 应用实现了对 yyy 钱包方案的支持” 这样的新闻都会让我觉得非常痛心。可能有很多原因会促成上述的这些问题,但是归根接底,我觉得这里的核心问题,还是源自于生态的缺乏:

  1. 首先对于不同的公私钥对,比如 secp256k1(CKB flavor),secp256k1(ETH flavor),schnorr 等等,最近生态里还有 unipass,DAS 这些的出现。我们能不能提供一个完善的协议,让这些种种不同的 identity 类型,都和谐并存在一起?
  2. 我们能否在 CKB 中,实现 identity 与 lock 的解耦,就像上述例子中指出的,我可以只关心我的公私钥对,或者换句话说,我在 unipass 中,DAS 中或是 metamask 中的地址,而这些地址,应该可以无缝适配更多的 lock,比如带有 ACP 模式的 lock,比如没有 ACP 模式的 lock。对于我来说,我为什么要关心某一个地址发 sUDT 不安全,而另外一个地址安全?这些为什么不是工具来帮我解决处理的问题?
  3. 其实我们去看以太的生态,虽然有种种问题,但是有一点做的非常好:通过 Web3 这样一个协议层,至少把 dapp 与钱包完全解耦开。metamask 并不需要单独去适配 uniswap,sushiswap 也不需要单独去适配 imtoken,大家对接的都是 Web3 这样一个协议层,各司其职在一个生态中完美的并存。

这些,在我看来,是上述所有问题的核心,也是 CKB 目前亟需解决的部分。

具体来说,我们希望 mercury 解决 1 和 2 的问题,我们同时也在构建一个属于 CKB, 类似 Web3 的 universal protocol,希望这样一个 protocol,能真正把生态中的 dapp 与身份提供方解耦,提供对问题 1 和 3 的一个答案。在有了这样的解耦之后,ACP 的问题自然就会解决,一个发币的工具中,就应该直接确保使用的 owner lock,并不支持 acp 的模式。我觉得这样才是真正的解决问题,前面在 acp 上面加限制条件的做法,在我看来,只能是隔靴搔痒。我们现在已经有这样的 lock 的尝试: rc lock 正是一个可以选择开启与关闭 acp 功能的 cell,同时也可以看到 rc lock 中定义了一个 identity 的格式,这正是我们对上面 1 的问题,提供的一个解决方案:我们想通过一个 identity 结构的定义,真正做到 lock 中,逻辑的部分,与验签部分的真正解耦,来解决 CKB 生态中,现在正面临的问题。

2 Likes

这样是最好的,把lock像合约插件一样对接,比如acp的eth地址和非acp的eth地址,通过一个中间层来解决割裂问题。

比如用户通过metamask连接以后,所有被eth解锁的资产都被统一展示和控制(即便lock不同),用户感受不到资产割裂,这个中间层将帮助用户在不同的情况使用不同的lock,看起来就像一个钱包内部一样,而不会像现在一样,DAS、NexisDAO以及Yokai,虽然都使用相同的eth公私钥连接了Metamask,但却有不同的地址和资产展示界面。

如果通过中间层,使得这些lock不同,公私钥相同的资产可以被统一展示和管理,那样用户就不会感受到资产割裂了,资产割裂比较影响用户体验。

对,这个应该是 CKB 前进的愿景。