--- 摄于 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 content。下面分别介绍
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
}
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
}
flag
:18
auth content
:与 Ethereum 一致
flag
:4
auth content
:根据 vtype 不同有不同的生成方式:
ripemd160(sha256(pubkey.serialize))
最后统一将数据再次 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
}
flag
:2
auth content
:只支持 P2PKHUncompressed
和 P2PKHCompressed
,分别用压缩和未压缩的公钥序列化进行 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
}
flag
:5
auth content
:与 Bitcoin 一致
flag
:3
auth content
:与 Ethreum 一致
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
}
这个模式是 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
}
这个模式比较特殊,任意该模式下的 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
}
这个模式是基于 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
}
这个模式基于 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 都存在。
首先在 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;
}
}
将对应 rc_cell 的 type script hash 拼接到后面,是一个 H256,这个 rc cell 必须是 type id script。
将 acp 的两个配置 ckb_minimum 和 udt_minimum 拼接在后面
将 since 字段拼接在后面
将发行的 cell 的 type script hash 拼接在后面
至此,我们已经了解了 omnilock 的 script args 如何正确构建,并通过 args 能表达解锁需要的算法和特殊模式,但需要注意的是,btc vtype 相关的 omnilock 是无法通过 script args 反推 vtype 的,这个是需要注意的地方。
请登录后评论
评论区
加载更多