HD钱包
HD Wallet(Hierarchical Deterministic Wallet,分层确定性钱包)是基于[BIP32](bips/bip-0032.mediawiki at master · bitcoin/bips (github.com))实现的用于管理私钥的工具。
非确定性钱包和确定性钱包
非确定性钱包可以参考比特币客户端使用随机生成密钥的方式。
比特币客户端会预先生成100个随机私钥缓存在一个密钥池,从最开始就生成足够多的私钥并且每个密钥只使用一次。这种钱包难以管理、 备份以及导入密钥。非确定性钱包的每一个密钥都需要进行备份,如果钱包不可访问时,没有备份的密钥就会失去其资金的控制权
确定性钱包中通过一个主密钥派生出子密钥,这个主密钥称为种子(seed)。
HD钱包是确定性钱包的一种衍生,HD钱包遵循BIP32标准,它通过seed导出树状结构的密钥,使得父密钥可以衍生一系列子密钥,每个子密钥又可以衍生出一系列孙密钥,以此类推,无限衍生。
与HD钱包相关的BIP
BIP32、BIP39和BIP44是Bitcoin Improvement Proposals(比特币改进提案)中定义的三种标准,用于增强比特币和其他加密货币钱包的功能和安全性。
BIP32描述了比特币分层确定性钱包
BIP39描述了助记词的实现
BIP43和BIP44,这两个协议规定了钱包的树结构,即HD 钱包标准路径(主要分析BIP44)
BIP32-HD原理
BIP32由以下几部分来进行深入:
- 密钥序列化格式:密钥的数据结构
- 密钥派生:规定子密钥是如何从父密钥中派生
- 密钥树:父密钥派生子密钥所形成的树状结构
下面通过理论与代码(golang)相结合的方式去理解BIP32。
BIP32实现代码:go-bip32/bip32.go at master · tyler-smith/go-bip32 (github.com)
序列化格式:密钥结构
BIP32规定密钥的序列化格式包括6个类型:
- 版本(version):4 byte,对密钥版本的预定。主网的公钥版本-0x0488B21E,私钥版本-0x0488ADE4(测试网 公钥版本-0x043587CF,私钥版本-0x04358394)
- 深度(depth):1 byte,密钥当前的层级。如:主密钥层级为0(0x00),派生的一级子密钥层级为1 (0x01)
- 父密钥指纹(FingerPrint):4 byte,父密钥hash值的前四位。主密钥的父指纹为0x00000000
- 子编号(child number):4 byte,密钥的索引
- 链码(chain code):32 byte,通过HMAC-SHA512计算的得到右32字节
- 公钥或私钥 (Key): 33 byte ,通过HMAC-SHA512计算的得到左32字节
1 | type Key struct { |
密钥派生:生成子密钥过程
密钥派生有很多种方式推导密钥,比如:父私钥→子私钥、父公钥→子公钥、父私钥→子公钥以及一种不可行的方式,父公钥→子私钥。下面从父私钥推导子公钥的过程进行描述
扩展密钥
用于计算子密钥的一部分
将256位的私钥或公钥扩展为512位的位串,将左256表示为k, 右256位表示为c,得到扩展密钥 (k,c)。其中,k是密钥序列化中的key,c是链码
强化密钥
得到密钥的种类,有根据给的索引大小区分普通密钥和强化密钥
为什么需要强化密钥(强化派生):当黑客拿到你的未硬化的扩展子私钥和扩展父公钥(链码)可以反向推导出父私钥或者所有的姊妹钱包,从而盗取你账户的所有资产
细节深入:HD Wallets: Why Hardened Derivation Matters? | by Blaine Malone | Medium
每个扩展密钥有 2 31个普通子密钥, 2 31个强化子密钥(hardened child keys)。 这些子密钥都有一个索引。 普通子密钥使用索引0到 2 31-1。 强化子密钥使用索引 2 31 到 2 31-1。 为了简化强化子密钥索引的符号,数字i H表示i + 2 31。
父私钥 → 子私钥
- 子密钥派生函数(Child key derivation):CKDprev( (k,c) , i ),其中i是钱包索引,这个索引与构建密钥树有关
- 输入扩展密钥与索引 i
- 将CKD中参数传入到HMAC-SHA512计算
- 判断(k,c)是否是强化密钥,比较i的值:i > 2 31
- 如果i > = 2 31, data = 0x00 || k || i (注意:0x00将私钥补齐到33字节长)
- i < 2 31,data = k || i
- 得到 l = CKDprev( (k,c) , i ),l 是一个长度为 512 的位串。将l 分为两部分lL , lR
- 得到子密钥:Ki = (lL + k) mod n
- 条件: lL < n ,Ki != 0 , len(Ki ) != 32 (否则得到密钥是无效的)
- 这里的n是指secp256k1标准定义的参数(Integers modulo the order of the curve, 简称:n)
- 得到子密钥链码:Ci = lR
代码分析
创建主密钥
主密钥通过种子得到,使用hmac函数计算出位串(Key =“比特币种子”,Data = seed),取lL 作为子密钥,lR作为链码
1 | func NewMasterKey(seed []byte) (*Key, error) { |
父私钥推导子密钥
用seed生成主密钥后,调用该方法去生成一个子密钥
1 | // NewChildKey derives a child key from a given parent as outlined by bip32 |
通过扩展密钥计算lL , lR的位串
使用HMAC-SHA512算法计算,先要对索引进行比较,密钥是否是强化密钥
1 | func (key *Key) getIntermediary(childIdx uint32) ([]byte, error) { |
计算Ki
使用该公式计算 Ki = (lL + k) mod n
1 | func addPrivateKeys(key1 []byte, key2 []byte) []byte { |
钱包结构:HD钱包的路径
前面我们通过CKD函数计算出密钥,计算密钥需要两个参数一个是扩展密钥,一个是索引。通过这个索引我们能够沿着一条路径构建出子密钥,比如:构造一个路径为 “m/0/0/1” 的密钥来控制钱包
- 构造过程,从seed得到主密钥m,通过m去构造路径:
- CKD( CKD( CKD( m, 0), 0 ), 1) = CKD(CKD(m,0), 0)/1 = CKD(m, 0)/0/1 = m/0/0/1
BIP39-助记词标准
BIP39描述了记助词的实现,主要分为两个部分:生成记助词和将记助词转化为二进制种子
为什么需要记助词:BIP32通过输入一个seed就能输出主密钥和一堆子密钥,我们只需记住seed就能控制钱包,但是对于人类来说记忆一串毫无关联的数字是很困难的。所以BIP39提出通过一组容易记住的单词(或者说一个句子)来用于生成seed。
生成记助词
记助词生成过程:
- 生成一个初始熵(entropy),熵的长度在128~256位且必须为32位的倍数。其中ENT(entropy length)= 熵的长度
- 将初始熵进行SHA256计算,取前 ENT/32 位作为校验和(checksum)。其中 CS(checksum length)= ENT/32
- 将熵和校验和拼接(校验和附加在熵后),这个串会被分为11位一组,每一组会对应单词表的索引(0-2047),一共有 MS (mnemonic sentence) = ( ENT + CS ) / 11 组单词
单词表实现:python-mnemonic/src/mnemonic/wordlist/english.txt at master · trezor/python-mnemonic (github.com)
ENT | CS | ENT + CS | MS |
---|---|---|---|
128 | 4 | 132 | 12 |
160 | 5 | 165 | 15 |
192 | 6 | 198 | 18 |
224 | 7 | 231 | 21 |
256 | 8 | 264 | 24 |
记助词生成种子
记助词生成seed时需要两个参数:记助词(mnemonic)和盐(salt,也能叫密码,passphrase)。salt能够保护钱包,比如:黑客必须同时获得你的记助词和密码才能拿到生成主密钥的seed
生成seed的过程
- 向PBKDF2函数输入参数:记助词、盐
- PBKDF2函数计算得到一个512位的seed
BIP44-HD钱包路径标准
BIP32中描述了HD钱包的结构,其每一层大约有40亿的子密钥和40亿的强化密钥而每一层又能继续衍生下去,这导致钱包里账户的路径近乎是无限的。如果没有一个明确的标准去约束密钥派生的路径,那么更换钱包时就可能出现兼容性问题
强化派生路径表示例子: m/1’/0’ , 其路径上有 “‘“作为强化派生的标记
路径级别
BIP44规定的路径有五个级别
m/purpose’/coin_type’/accout’/change/address_index
- purpose:协议BIP44,一般设置为常量44’ (0x8000002C)
- coin_type:币种类型,比特币为0’ (0x80000000)
- accout:账户类型,为用户划分不同身份。从0’开始递增
- change:钱包地址对外部是否可见,0用于外部链,1用于内部链
- index:地址索引,地址从索引0开始按顺序递增编号
引用:
- https://github.com/the-web3/blockchain-wallet/tree/master/basicWallet
- [Web3专题(三) 2种钱包之分层确定性钱包(HD Wallet,BIP32,BIP39,BIP44) | 登链社区 | 区块链技术社区 (learnblockchain.cn)](https://learnblockchain.cn/article/7098#实现一个以太坊钱包(符合 BIP-44 标准的路径))
- BIP: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
- https://github.com/ethereumbook/ethereumbook
吐槽:为什么国内都不喜欢把引用的图源和链接注释出来…