--- 摄于 2017 年 9 月 藏川线前段
上一篇,简单介绍了一下 omnilock 的诞生背景以及复杂性。这一篇还是介绍前置内容,同时给 omnilock 的解锁流程打下一个简单的轮廓基础。我们从一般交易的构建流程开始讲起。
首先,我们要理解一般意义上的交易验证是怎么回事,以 ckb 默认的 secp256k1_black2b_hashall 为例,它的验证过程是在 vm 中重新构建 message,然后用交易中提供的 signature 和构建的 message 恢复出公钥,与 script 上的 args 进行对比,如果一致,即通过验证,如果不一致,该交易非法。
在区块链世界中,大部分的交易验证流程从本质上来说,就是如此,不同的链可能有不同的算法,比如 ed25519 和 secp256k1 的区别,或者是 message 生成时,hash 算法的区别,比如 sha256 与 keccak256,这里有历史原因也有巧合因素。
在 ckb 上,lock script 是可以随意添加的,也就是说,任意算法在 ckb 上支持都是有可能性的,而 omnilock 支持多种算法就是一个案例,从理论上说,如果 ckb 上一个 lock script 的验证逻辑和 message 生成的逻辑与其他链一致,那么这些链的钱包,都可以被伪装成 ckb 的钱包使用,这也是 ckb 灵活性带来的可能性,omnilock 的诞生也与之相关。
任何 lock script 首先考虑的不是花里胡哨的验证过程,而是怎么保证它的安全性,lock 的设计需要慎重考虑这一点。所谓的安全性,是指非本人(本私钥)意愿下,该 lock 无法被任何人解开或者篡改。
ckb 上的交易验证比上面说的更复杂一点,因为它是以 script group 为最小单位进行验证的,关于这一点可以看之前写的文章 —— CKB VM Verification Rules,一个交易可以有多个 script group 同时存在,不同的 script group 几乎不相互干扰。
作为签名的 message,一般意义上来说,需要将交易中需要完全确定,不能篡改的地方在生成的时候完全覆盖掉,这样验证的过程就保证了它的一致性,因为 message 的生成就是将需要的数据 hash 的过程,message 一致即 hash 值一致。
在 ckb 上构建交易的过程,一般如下:
这样,一个未签名但完整的 transaction 就构建完成了。接下来就是按照 script group 生成对应的 message 并签名,然后替换之前用全零填充的 signature 位。
一般意义上,保证交易不被篡改的 message 生成需要这些数据,按顺序填充至 hash 算法中:
transaction hash
同 script group 下,第一个 witness 的长度和内容(signature 位置是全零填充的)
同 script group 下,除第一个 witness 以外的所有其他 witness 的长度和内容
超出 input 数量的 witness 的长度和内容
用代码来描述就是:
pub fn generate_message(
tx: &TransactionView,
script_group: &ScriptGroup,
zero_lock: Bytes,
) -> Result<Bytes, ScriptSignError> {
if tx.witnesses().item_count() <= script_group.input_indices[0] {
return Err(ScriptSignError::WitnessNotEnough);
}
let witnesses: Vec<packed::Bytes> = tx.witnesses().into_iter().collect();
let witness_data = witnesses[script_group.input_indices[0]].raw_data();
let mut init_witness = if witness_data.is_empty() {
WitnessArgs::default()
} else {
WitnessArgs::from_slice(witness_data.as_ref())?
};
init_witness = init_witness
.as_builder()
.lock(Some(zero_lock).pack())
.build();
// Other witnesses in current script group
let other_witnesses: Vec<([u8; 8], Bytes)> = script_group
.input_indices
.iter()
.skip(1)
.filter_map(|idx| witnesses.get(*idx))
.map(|witness| {
(
(witness.item_count() as u64).to_le_bytes(),
witness.raw_data(),
)
})
.collect();
// The witnesses not covered by any inputs
let outter_witnesses: Vec<([u8; 8], Bytes)> = if tx.inputs().len() < witnesses.len() {
witnesses[tx.inputs().len()..witnesses.len()]
.iter()
.map(|witness| {
(
(witness.item_count() as u64).to_le_bytes(),
witness.raw_data(),
)
})
.collect()
} else {
Default::default()
};
let mut blake2b = new_blake2b();
blake2b.update(tx.hash().as_slice());
blake2b.update(&(init_witness.as_bytes().len() as u64).to_le_bytes());
blake2b.update(&init_witness.as_bytes());
for (len_le, data) in other_witnesses {
blake2b.update(&len_le);
blake2b.update(&data);
}
for (len_le, data) in outter_witnesses {
blake2b.update(&len_le);
blake2b.update(&data);
}
let mut message = vec![0u8; 32];
blake2b.finalize(&mut message);
Ok(Bytes::from(message))
}
要注意的是,该 blake2b 算法的 personal 字段是 b"ckb-default-hash"
。
由于 omnilock 支持 cobuild,这种模式下,message 生成的规则有一定的不同,它需要的字段也不同,具体而言:
用代码描述就是:
pub fn cobuild_generate_signing_message_hash(
message: &Option<bytes::Bytes>,
tx_dep_provider: &dyn TransactionDependencyProvider,
tx: &TransactionView,
) -> Bytes {
// message
let mut hasher = match message {
Some(m) => {
let mut hasher = new_sighash_all_blake2b();
hasher.update(m);
hasher
}
None => new_sighash_all_only_blake2b(),
};
// tx hash
hasher.update(tx.hash().as_slice());
// inputs cell and data
let inputs_len = tx.inputs().len();
for i in 0..inputs_len {
let input_cell = tx.inputs().get(i).unwrap();
let input_cell_out_point = input_cell.previous_output();
let (input_cell, input_cell_data) = (
tx_dep_provider.get_cell(&input_cell_out_point).unwrap(),
tx_dep_provider
.get_cell_data(&input_cell_out_point)
.unwrap(),
);
hasher.update(input_cell.as_slice());
hasher.update(&(input_cell_data.len() as u32).to_le_bytes());
hasher.update(&input_cell_data);
}
// extra witnesses
for witness in tx.witnesses().into_iter().skip(inputs_len) {
hasher.update(&(witness.len() as u32).to_le_bytes());
hasher.update(&witness.raw_data());
}
let mut result = vec![0u8; 32];
hasher.finalize(&mut result);
Bytes::from(result)
}
如果 cobuild 的 message 存在,blake2b 算法的 personal 是 b"ckb-tcob-sighash"
,否则是 b"ckb-tcob-sgohash"
。
再次强调一下,如果一个交易验证的 message 一致,算法一致,那么它就可以用对应链的钱包进行签名操作,钱包的功能从理论上来说,最核心的地方就是对一段 message 签名并生成 signature。
请登录后评论
评论区
加载更多