中译
本文中,我将介绍一些在摘要中提及的重要的技术和功能,并将其中一部分扩展延申到未来可能的应用场景中去。
关于 pw-lib
我们都知道,可定制的密码学原语是 CKB 的一大亮点,以至于我们在项目介绍的核心关键词和 AMAs 中多次听到这个特性。但是为什么这很重要呢?我们又可以在 CKB 上部署哪些密码学原语呢?我们应该如何使用它们?让我们向你展示一些关于我们将在 pw-lib 中引入的密码学库的想法吧。
- Keccak-256 哈希算法
Keccak,有时候也叫做 SHA-3,是以太坊的哈希函数。事实上,Keccak-256 是 SHA-3 的超集,最终确定的 NIST SHA-3 才是标准的“SHA-3”,它不同于以太坊采用的哈希算法。
Keccak-256 提供 256 位的哈希。它在以太坊中用于生成交易哈希,区块哈希,以及地址。在 pw-core 中,Keccak-256 被用来生成要签名的哈希,并放在 witness 中。但是真正不可缺少的是在 pw-lock 中的 Keccak-256,我们需要从公钥中恢复以太坊地址。在 EIP-712 类型的数据签名中也需要 Keccak-256 作为唯一的哈希算法。
- SHA-256 哈希算法
SHA-256 是 Secure Hask Algorithm-256 bit 的缩写。虽然它看起来要比上面提到的 SHA-3 算法“强大”很多,但实际上它属于 SHA-2 家族,自然是要比 SHA-3 弱的。
SHA-256 最著名的用例就是比特币挖矿。它也被用来生成比特币地址,并被广泛地用于 EOS 的哈希算法。pw-lock 有必要支持比特币钱包和 EOS 钱包。
- Secp256r1(ECC P-256)非对称加密算法
与哈希算法不同,哈希算法主要用于将任意大小的数据以单向的形式映射到固定位数的字符串,非对称加密算法主要用于加密或数据的数字签名。
ECC 是 Eliptic Curve Cryptography(椭圆曲线加密)的简称,Secp256k1 和 Secp256r1 都属于这个算法家族。自从比特币首次使用 Secp256k1 作为签名算法以来,几乎所有的后继者都采用了这个设计,CKB 亦是如此。而 Secp256r1 算法,即 P-256,我们可以为 CKB 打开更加广阔的应用场景。
- 更多的期待
随着场景的增长,对 CKB 上的密码库的需求也将会越来越大。比如 Tron 适配的 SHA3-256,以及用于和安全的 Web 应用进行交互的 RSA。通过 RISC-V 虚拟机和 CKB 的 Cell 模型,我们可以轻松地持续地集成这些库,而不会对开发者和用户造成任何中断。
关于 pw-lock
- 以太坊签名算法的支持(包括 EIP-712)
/* secp256k1_keccak256_sighash_all.c */
keccak_init(&sha3_ctx);
unsigned char eth_prefix[28]= {
0x19, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x20, 0x53,0x69,
0x67, 0x6e, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61,0x67, 0x65,
0x3a, 0x0a, 0x33, 0x32
};
keccak_update(&sha3_ctx, eth_prefix, 28);
keccak_update(&sha3_ctx, message, 32);
keccak_final(&sha3_ctx, message);
/* Personal hash */
ret = verify_signature(message, lock_bytes, args_bytes_seg.ptr);
if(ret == CKB_SUCCESS){
return CKB_SUCCESS;
}
/* Typed Data hash */
ret = calculate_typed_data(message, message);
if(ret != CKB_SUCCESS){
return ret;
}
return verify_signature(message, lock_bytes, args_bytes_seg.ptr)
这是 pw-lock 代码中的一部分,它展示了在 witness 中验证签名的逻辑。其中,‘keccak_int’ 为哈希计算初始化了文本,‘keccak_update’ 将消息追加到了原始的字符串中,‘keccak_final’ 完成计算。正如我们所看到的,lock 首先尝试从消息中恢复并验证 personal_sign 签名的公钥,如果失败,则将签名视为类型化的数据签名。完整代码:https://github.com/lay2dev/ckb-system-scripts/blob/master/c/secp256k1_keccak256_sighash_all.c
- 如何支持 EOS 签名
EOS 的账户模型和以太坊不同。首先,EOS 的账户不是由公钥生成的,它包含有 12 个字符的长度,可以包含字母 a-z 和数字 1-5。实际上,密钥对仅作为解锁账户的密码。其次,可以有几个不同的密钥关联到同一个账户,并且具有不同的权限。例如,EOS 账户“zhixiandaren”有两个权限(默认就有两个):
Associated public keys for EOS account 'zhixiandaren':
- active: EOS6P9tLLAQYJbmPzoTEG9vpS6GMHJyWW4bUeppqtAwcUuJpZNXYo
- owner : EOS6P9tLLAQYJbmPzoTEG9vpS6GMHJyWW4bUeppqtAwcUuJpZNXYo
在上面的情况下,只使用了一个密钥,但通常情况下应该有两个不同的密钥,owner 密钥具有最高权限,不应该频繁使用,比如交易、投票等常见操作应该使用 active 密钥进行处理。
为了支持 EOS 签名,pw-lock 需要比较 recovered 密钥和 active 密钥进行对比,哪一个需要被记录到脚本中的 args 字段中去。需要注意的是,这里针对的是普通的 EOS 账户,其他比如合约权限(@eos.code)等目前还不包括在内。
关于 pw-core
- 如何将其他区块链地址映射到 CKB 地址上
我们先来讨论一下 CKB 地址到底代表了什么,我们都知道,如果 Alice 想给 Bob 发送加密货币,她只需要知道 Bob 的地址,就像给 Bob 发邮件一样,但是 Bob 如何实际使用接受到的资产,比如登陆到他的电子邮箱账户然后转发电子邮件。这就涉及到 lock script,这个脚本决定了你是否能够解锁这个资产,就像是用这个脚本来检测 Bob 是否具有登陆 “[email protected]” 这个邮箱的正确密码一样。
我们的区块链账户有一对密钥:公钥和私钥。地址通常是由公钥转换而来的(例如,以太坊地址是公钥的最后20个字节)。当我们发送一笔交易时,我们必须用我们的私钥创建一个数字签名,并将其与交易信息一起发送。当区块链验证你是否具有访问权限时,首先从签名中恢复公钥,然后将其转换成一个地址,将该地址将与我们声明拥有的地址进行比较。
对于具有固定解锁逻辑的普通区块链来说,使用签名和地址来确认所有权已经足够了。但是 CKB 非常灵活,每个人都可以用任何逻辑来创建自己的 lock script。因此,除了来自公钥 “地址” 部分之外,还有另一部分用于确定使用哪一个 lock script 来验证 “地址”。加上一些其他的部分,比如前缀和校验和,以及一些变换,我们最终就可以得到一个有效的 CKB 地址,像这样:
args: /* the 'address' transformed from public key */
b39bbc0b3673c7d36450bc14cfcdad2d559c6c64
code_hash: /* which lock script to use */
9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8
address generated:
ckb1jda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3vumhs9nvu786dj9p0q5elx66t24n3kxgj53qk
让我们把地址拆开,看清楚:
ckb1
- the human readable part
jda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3vumhs9nvu786dj9p0q5elx66t2
- the Base32 encoded payload part, payload format: 0x02/0x04 | code_hash | args
4n3kxgj53qks
- the checksum
关于 CKB 地址的更多信息,请参考:rfcs/rfcs/0021-ckb-address-format/0021-ckb-address-format.md at master · nervosnetwork/rfcs · GitHub
最后,我们了解到 CKB 的地址不仅包含公钥信息,还包含要使用的 lock script。现在我们就可以解释为什么以太坊地址可以映射到 CKB 地址:我们创建了一个与以太坊的 lock 具有相同逻辑的 lock script(放在 code_hash 部分),并将以太坊地址放在 args 中。就是这样,听起来很容易,不是吗?是的,但只在 CKB 上(可以如此简单容易地实现)。
- 如何在其他区块链钱包内签名 CKB 交易
让我们以 MetaMask 为例:
/* personal_sign */
const DefaultSinger = (from, message) =>
new Promise((resolve, reject) => {
const params = [message, from]
const method = 'personal_sign'
window.web3.currentProvider.sendAsync({ method, params, from },
function(err, result) {
err && reject(err)
result.error && reject(result.error)
resolve(result.result)
})
}
/* signTypedData */
const TypedDataSigner = (from, message, { inputCapacity, outputs }) =>
new Promise((resolve, reject) => {
const typedData = buildTypedData(inputCapacity, outputs, message)
const params = [from, typedData]
const method = 'eth_signTypedData_v4'
window.web3.currentProvider.sendAsync({ method, params, from },
function(err, result) {
err && reject(err)
resolve(result.result)
})
}
以上是在以太坊 web3 环境中调用签名方法的常用方法。签名一笔以太坊交易和一笔 CKB 交易之间唯一的区别就是 “message” 部分。现在我们已经知道 pw-lock 能够通过 Keccak-256 从签名中恢复以太坊地址,所有我们唯一需要改变的是用 Keccak-256 算法去对 CKB 的交易进行哈希,然后我们将它传递给要签名交易的人。当涉及到 EIP-712 签名时,情况会略有不同,因为我们必须将消息以类型化数据的形式组合起来。