藏川线前段

--- 摄于 2017 年 9 月 藏川线前段

omnilock 因为支持多种算法和多种模式,在 script args 上有需要标注的内容,用来区分当前 omnilock 用的是什么算法模式,这样解锁签名和验证的时候,能够对应使用正确的模式和算法,这部分内容就在这篇加以说明。args 分为两部分,Auth 部分为必须存在,omnilock args 部分为可选存在,组合起来就是 script 的 args 字段:

script args = auth + omnilock args

auth: <1 byte flag> <20 bytes auth content>

omnilock args: <1 byte Omnilock flags> <32 byte AdminList cell Type ID, optional> <2 bytes minimum ckb/udt in ACP, optional> <8 bytes since for time lock, optional> <32 bytes type script hash for supply, optional>

Auth 部分

这部分数据确定算法,根据算法的不同,生成不同的 auth content。下面分别介绍

PubkeyHash

flag:0

auth content:secp256k1 公钥序列化(压缩版)之后的 bytes,然后 blake2b_256 hash 之后取 20 位。blake2b 的 personal 是 b"ckb-default-hash"

用代码来表达就是:

fn build_auth(flags: u8, pubkey: secp256k1::PublicKey) -> Vec<u8> {
    let raw_key = pubkey.serialize();
    let r = ckb_hash::blake2b_256(&raw_key);
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(&r[..20]);
    auth
}

Ethereum

flag:1

auth content:secp256k1 公钥序列化( 非压缩版)之后的 bytes,去掉第一位,然后用 keccak256 hash 之后取前 20 位。

用代码来表达是:

fn serialize_pubkey(pubkey: secp256k1::PublicKey) -> [u8: 64] {
    let raw = pubkey.serialize_uncompressed();
    let mut pubkey = [0u8; 64];
    pubkey.copy_from_slice(&raw[1..65]);
    pubkey
}

fn build_auth(flags: u8, pubkey: secp256k1::PublicKey) -> Vec<u8> {
    let r = keccak256(&serialize_pubkey(pubkey));
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(&r[..20]);
    auth
}

EthereumDisplay

flag:18

auth content:与 Ethereum 一致

Bitcoin

flag:4

auth content:根据 vtype 不同有不同的生成方式:

最后统一将数据再次 ripemd160(sha256(data))

用代码来表达是:

pub fn bitcoin_hash160(buf: &[u8]) -> [u8; 20] {
    calculate_ripemd160(&calculate_sha256(buf))
}

pub fn btc_auth(pubkey: secp256k1::PublicKey, vtype: BTCSignVtype) -> [u8; 20] {
    match vtype {
        BTCSignVtype::P2PKHUncompressed => {
            bitcoin_hash160(&pubkey.serialize_uncompressed())
        }
        BTCSignVtype::P2PKHCompressed | BTCSignVtype::SegwitBech32 => {
            bitcoin_hash160(&pubkey.serialize())
        }
        BTCSignVtype::SegwitP2SH => {
            let mut pk_data = vec![0; 22];
            pk_data[0] = 0;
            pk_data[1] = 20;
            pk_data[2..].copy_from_slice(&bitcoin_hash160(&pubkey.serialize()));
            bitcoin_hash160(&pk_data)
        }
    }
}

fn build_auth(flags: u8, pubkey: secp256k1::PublicKey, vtype: BTCSignVtype) -> Vec<u8> {
    let r = btc_auth(pubkey, vtype);
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(&r[..20]);
    auth
}

Eos

flag:2

auth content:只支持 P2PKHUncompressedP2PKHCompressed,分别用压缩和未压缩的公钥序列化进行 blake2b_256 hash,并取前 20 位,personal 为 b"ckb-default-hash"

用代码来表达是:

pub fn eos_auth(pubkey: secp256k1::PublicKey, vtype: BTCSignVtype) -> [u8; 20] {
    let buf = match vtype {
        BTCSignVtype::P2PKHUncompressed => pubkey.serialize_uncompressed(),
        BTCSignVtype::P2PKHCompressed => pubkey.serialize(),
        _ => panic!("unsupport vtype"),
    };
    blake2b_256(buf)[..20].try_into().unwrap()
}

fn build_auth(flags: u8, pubkey: secp256k1::PublicKey, vtype: BTCSignVtype) -> Vec<u8> {
    let r = eos_auth(pubkey, vtype);
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(&r[..20]);
    auth
}

Dogecoin

flag:5

auth content:与 Bitcoin 一致

Tron

flag:3

auth content:与 Ethreum 一致

Solana

flag:19

auth content:ed25519 公钥序列化之后的 bytes,然后 blake2b_256 hash 之后取 20 位。blake2b 的 personal 是 b"ckb-default-hash"

用代码来表达是:

fn build_auth(flags: u8, pubkey: ed25519_dalek::VerifyingKey) -> Vec<u8> {
    let raw_key = pubkey.serialize();
    let r = ckb_hash::blake2b_256(&raw_key);
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(&r[..20]);
    auth
}

Multisign

这个模式是 secp256k1 的多签模式,需要设置参与的私钥数量和达到多少阈值之后,该 lock 可以解锁

flag:6

auth content

blake2b 的 personal 是 b"ckb-default-hash"

用代码来表达是:

fn multisign_auth(require_first_n: u8, threshold: u8, sign_addresses: &[H160]) -> H160 {
    let mut res = vec![
        0, // reserved_byte
        require_first_n,
        threshold,
        sign_addresses.len() as u8,
    ];
    for sighash_address in sighash_addresses {
    	res.extend_from_slice(sighash_address.as_bytes());
	}
    H160::from(&res[0..20])
}

fn build_auth(flags: u8, require_first_n: u8, threshold: u8, sign_addresses: &[H160]) -> Vec<u8> {
    let r = multisign_auth(require_first_n, threshold, sign_addresses);
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(r.as_bytes());
    auth
}

Ownerlock

这个模式比较特殊,任意该模式下的 cell 如果需要解锁,交易中至少要有两个 input,一个是 ownerlock 的 cell,另一个是 ownerlock 的 args 中对应的 script 指向的 cell,ownerlock 本身并不验证签名,它验证的是 script 指向的 cell 是否能解锁,如果解锁成功,则 ownerlock 也同时解锁,相当于将验证的功能代理给了任意使用 ownerlock args 中对应的 script 的 cell。

flag:0xFC

auth content:对应指向的 script 的 hash 前 20 位

用代码来表达是:

fn build_auth(flags: u8, script: Script) -> Vec<u8> {
    let r = script.calc_script_hash();
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(r.as_bytes());
    auth
}

Exec

这个模式是基于 ckb vm 中的 exec syscall,它在 omnilock 中指定验证过程中使用的 script 和 exec 相关参数,以及对应 script 验证的 pubkey hash,将验证过程完全委托于该 script,整个过程都是 exec 验证的过程。

flag:0xFD

auth content

上面内容连起来之后,blake2b 256 hash 取前 20 位

用代码来表达是:

fn preimage(script: Script, place: u8, bounds: [u8; 8], pubkey_hash: H160) -> Vec<u8> {
    let mut preimage = Vec::with_capacity(32 + 1 + 1 + 8 + 20);

    preimage.put(script.code_hash().as_slice());
    preimage.put(script.hash_type().as_slice());
    preimage.put_u8(place);
    preimage.put(bounds.as_slice());
    preimage.put(pubkey_hash.as_bytes());
    preimage
}

fn build_auth(flags: u8, script: Script, place: u8, bounds: [u8; 8], pubkey_hash: H160) -> Vec<u8> {
    let r = blake160(primage(script, place, bounds, pubkey_hash));
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(r.as_bytes());
    auth
}

DL

这个模式基于 dynamic loading on ckb vm,它同样在 omnilock 中指定验证过程中使用的 script 和对应 script 验证的 pubkey hash,将验证过程完全委托于该 script,与 exec 不同的是,指定的 script 需要支持 dynamic loading,例如 rsa 的动态库

flag:0xFE

auth content

上面内容连起来之后,blake2b 256 hash 取前 20 位

用代码来表达是:

fn preimage(script: Script, pubkey_hash: H160) -> Vec<u8> {
    let mut preimage = Vec::with_capacity(32 + 1 + 1 + 8 + 20);

    preimage.put(script.code_hash().as_slice());
    preimage.put(script.hash_type().as_slice());
    preimage.put(pubkey_hash.as_bytes());
    preimage
}

fn build_auth(flags: u8, script: Script, pubkey_hash: H160) -> Vec<u8> {
    let r = blake160(primage(script, pubkey_hash));
    let mut auth = vec![flags; 21];
    auth[1..].copy_from_slice(r.as_bytes());
    auth
}

到这,auth 部分所有内容已经完成了,接下来就是 omnilock args 部分,相较于 auth 来说,omnilock args 是可选配置,并不是所有 omnilock script 都存在。

Omnilock args 部分

首先在 auth 内容后,跟上一个 u8 flag 表达后续的 omnilock args,默认是 0,所有可选 args 可以同时存在,flag 用 bit 表达他们的是否存在:

bitflags! {
    pub struct OmniLockFlags: u8 {
        /// administrator mode, flag is 1, affected args:  RC cell type ID, affected field:omni_identity/signature in OmniLockWitnessLock
        const ADMIN = 1;
        // anyone-can-pay mode, flag is 1<<1, affected args: minimum ckb/udt in ACP
        const ACP = 1<<1;
        /// time-lock mode, flag is 1<<2, affected args: since for timelock
        const TIME_LOCK = 1<<2;
        /// supply mode, flag is 1<<3, affected args: type script hash for supply
        const SUPPLY = 1<<3;
    }
}

Regulation Compliance Extension (RCE)

将对应 rc_cell 的 type script hash 拼接到后面,是一个 H256,这个 rc cell 必须是 type id script。

ACP

将 acp 的两个配置 ckb_minimum 和 udt_minimum 拼接在后面

Time lock

将 since 字段拼接在后面

Supply

将发行的 cell 的 type script hash 拼接在后面

小结

至此,我们已经了解了 omnilock 的 script args 如何正确构建,并通过 args 能表达解锁需要的算法和特殊模式,但需要注意的是,btc vtype 相关的 omnilock 是无法通过 script args 反推 vtype 的,这个是需要注意的地方。

评论区

加载更多

登录后评论