总纲
比特币钱包中存储的不是比特币,而是比特币拥有者的密钥。
钱包分类
Nondeterministic Wallets
钱包中存储预先生成的一组随机私钥,该类型又称作 type-0 类型钱包。这种钱包已经不使用。
Deterministic Wallets
通过“种子”,可以把所有的密钥推导出来的钱包,该类型又称作 type-1 类型钱包。该类型钱包只需要备份“种子”,即能将所有的密钥保存。导出/迁移密钥,也只需要导出/迁移种子即可。
Hierarchical Deterministic(HD) Wallets
在deterministic钱包类型基础上发展而来,实现BIP-32和BIP-44协议的钱包。之所以称为hierarchical
,可以看如下示意图:
通过seed生成主密钥,该密钥为深度等于0的根节点,在此节点上可以衍生出子节点,每个子节点即代表了密钥;每个子节点又能继续向后衍生,以此无穷无尽。从数量上,每一个节点,可以有约40亿个子节点(2^32),因此可以算得上是“无穷无尽”了。
在实际使用的时候,某一个节点的密钥专门用作地址用于接收,某一个节点的密钥专门用于付款。由于节点数量的“无限性”,在实际应用的时候,非常容易匹配业务。
钱包技术详解
助记词 Mnemonic Code Words(BIP-39)
助记词是一组单词,用来生成seed,它的规则是:
- 生成一组随机序列(墒),长度为128:32:256比特(最短128比特,按32比特递增)
- 计算该随机序列的哈希值SHA256
- 将哈希值的前4个比特去除,添加到随机序列后
- 将延展的序列按11比特分成12个区间
- 根据预先定义的2038字典表,检索每个区间所对应的单词
- 将这些单词组合起来,就是助记词
得到了助记词后,需要使用PBKDF2(密钥延展函数)生成seed,该函数需要两个参数,因此生成seed步骤,
- 第一个参数即助记词,第二个参数是盐,由”mneonic”+”passphrase”组成,其中”passphrase”可以为空(该规则在BIP-39中定义)
- 使用PBKDF2计算,该函数实际上计算了2048次的HMAC-SHA512,计算结果为seed
PBKDF2的使用,增加了暴力破解的成本;passphrase的引入,增强了安全性,但同时也要注意passphrase的保存。
创建主密钥
对seed使用HMAC-SHA512,将计算结果一分为二,左256位是主私钥m,右256位是主链码(chain code)。通过公式 M = m*G
可以计算得到主公钥。有了主密钥后,就可以生成子密钥了。
子私钥
使用CKD(child key derivation)函数从父密钥中计算子私钥,该函数需要揉合三个参数:
- 父私钥或父公钥
- 256位的chain code:
- 从零开始的索引值,32位
chain code的作用是给确定性数据增加随机性,这样:
a) 只知道父密钥和索引值,无法得知子私钥
b) 知道子私钥无法得知兄弟私钥
该工作流程可以参考下图(截自 Mastering Bitcoin 2nd Edition):
从上面的工作流程,我们可以看到,有了密钥和chain code,就可以生成该节点的所有子孙节点的密钥。因此,比特币中定义了扩展密钥(extend key),它的组成:
public or private key + chain code
扩展密钥会使用Base58Checking进行编码,编码后的扩展私钥以xprv
开头,扩展公钥以xpub
开头。
子公钥衍化
HD钱包可以通过公钥来生成子公钥,通过这种方式:
- 提高安全性:公钥用来生成比特币地址,没有对应的私钥无法交易地址中的币
- 部署在电子商务的服务器上用于生成接收地址
- 冷钱包存储:扩展私钥离线保存,扩展公钥可以在线保存
生成子公钥的流程如图:
子公钥带来了便利,也带来了风险。为了更好理解风险的由来,这里引入几个数学公式。从前面的学习中我们知道公钥的组成是 0x04XY
,XY分别是椭圆曲线上某一点的坐标,即
point(private key) == public key //1
(通过私钥用小写k表示,公钥用大写K表示,为了区分,这里不使用这种写法)
对于子公钥child public key,加上point(i)后,是兄弟子公钥,: child_public_key + point(i)
(i是index)。进一步的,
child_public_key + point(i) == point((child_private_key + i) %p) //2
也就是说,知道了一个子公钥,我们通过计算point(i),可以得到所有的兄弟子公钥(其中p是一个大质数,可以先不管)。
通过图2,我们知道子私钥由父私钥生成:
child_private_key == (parent_private_key + lefthand_hash_output) % G //3
child_public_key == point((parent_private_key + lefthand_hash_output) % G )//4
child_public_key == point(child_private_key) == parent_public_key + point(lefthand_hash_output)//5
那么,在已知扩展公钥的前提下,存在的攻击方式有:
攻击1: 暴力破解该扩展公钥下的所有子孙的chain code
这个很好理解,因为根据图3只需要添加一个整数作为索引,即可生成该扩展公钥下的所有的子孙公钥和子孙chain code
攻击2: 如果获得了该扩展公钥子孙中的某个私钥,那么就能获得所有该私钥下所有的子孙扩展私钥
通过攻击1,我们得知攻击者可以得到所有的子孙chain code,那私钥和chain code结合,即能得到该私钥衍生的所有的私钥(公式3)。
攻击3: 如果攻击者获得了该扩展公钥下的某个子私钥,那么可以得到该扩展公钥的私钥
还是使用公式3,不过需要使用减法运算:
parent_private_key == child_private_key - parent_chain_code
为了解决上述可能的攻击,打破父子公钥之间的关系,现在的钱包使用BIP-44 hardened child key derivation.
Hardened Child Key Derivation
使用私钥生成子私钥和子chain code,工作流程如:
为了区分和正常衍化算法的区别,对index做了区分:
- 正常衍化:i表示,取值范围0-2^31-1
- 硬化衍化:i’表示,取值范围2^31-2^32-1
因此很容易推得: i' = i + 2^31
HD钱包中密钥的标识
基于HD的树形结构,使用”/”表示不同的层级,使用m表示私钥,M表示公钥,如:
m/0'/0: 第一个硬化子私钥衍生的第一个正常私钥
HD钱包导航
因为HD钱包可以生成无穷无尽的密钥,方便业务部门灵活的对应某一个分支。这种灵活性也带来实现的复杂性,为了解决这个问题提出了BIP-43,该改进的核心就是HD钱包应该只使用某一个分支上的密钥。作为BIP-44的扩展,HD钱包应该使用:m/44'
下的分支,具体的结构如下:
m / purpose' / coin_type' / account' / change / address_index
- purpose’: 始终为44’
- coin_type’: 0’-bitcoin, 1’-bitcoin testnet, 2’-litecoin
- account’:表示逻辑子账户
- change: 钱包包含两个子树,一个是找零地址,一个是接收地址;这一层级是正常衍化(注意上面三个层级都是硬化衍化)
- address_index: 在change分支下衍化出来的地址的索引值
参考资料
文中所用图片都来自以下参考资料。
maste bitcoin
bitcoin developer guide
Comments