HD钱包(Hierarchical Deterministic walle,简称 HD钱包)是目前常用的层级确定性钱包,能够通过助记词推导出一些列密钥,可以方便用户备份钱包。
经过一段时间对 HD 钱包的了解,希望通过本篇文章可以为大家梳理一下 HD 钱包的推导密钥流程。首先说明一下 HD钱包
推导密钥的主要流程为:
1. 获取助记词
2. 根据助记词推导种子
3. 根据种子推导主密钥
4. 根据父密钥推导子密钥。(主密钥是首个父密钥)
5. ... (按照第 4 步依次推导)
标准规则
HD 钱包的私钥推导流程主要借助于几个标准规则,有 BIP32,BIP39,BIP44。
BIP32
:该标准定义了,通过一个种子来维护多个私钥的树状结构方式。HD钱包就是依据该标准。
BIP39
:定义了钱包的助记词和种子的生成规则。
BIP44
:在 BIP32 的基础上,给予树状结构的每一层一个特殊的意义。可以让统一种子支援多个币种,多个账户。
算法与函数
推导过程中使用到的算法,简单了解一下。
SHA256
:获取熵校验和的算法
PBKDF2
:根据助记词推算种子的算法。其内部使用了 HMAC-SHA512
算法(2048 次循环)。
HMAC-SHA512
:生成种子时使用,以及推导主密钥,由父密钥推导子密钥时使用。
椭圆曲线算法
:私钥推导公钥的算法。
CKD函数
(child key derivation):从父密钥推导普通子密钥时使用的函数。
HKD函数
(hardened key derivation formula):由父密钥推导增强子密钥时的函数。
1. 获取助记词
获取助记词是 HD钱包
的使用的第一步,有了助记词才能推导种子以及推导后面的密钥操作。
生成助记词的主要步骤为:
1. 生成一个长度为 128 ~ 256 位的随机序列(后面称为熵;
- 熵的取值长度需要为 32 的整数倍的值。所以取值可能为:【128, 160, 192, 224, 256】;
- 目前我们 APP 使用的是 128 位。
2. 获取熵的校验和;
- 取熵哈希后的前 n 位作为校验和(n = 熵长度/32)。
- 校验和的理论值为【4,5,6,7,8】,因为熵的取值范围为 128 ~ 256。
-
3. 生成新的序列;
- 新的序列方式为 = 熵 + 校验和
-
4. 获得 m 个 11 位二进制数。
- 将新生成的序列,按照 11 位进行平分。
- 为什么是 11 位?助记词库总共有 2048 个单词,2^11 正好是 2048。所以用 11 位二进制数刚好可以将所有的助记词定位。
- 新序列的长度取值范围为:【128+4,160+5,192+6,224+7,256+8】即【132,165,198,231,264】
- m 的取值范围为:【12,15,18,21,24】
-
5. 获得 m 个助记词;
- 根据第四步得到的 m 个 11位二进制数,去助记词表定位每一个助记词。
熵、校验和、助记词个数关系如下表所示
熵(bits) | 校验和(bits) | 熵+校验和(bits) | 助记词个数 |
---|---|---|---|
128 | 4 | 132 | 12 |
160 | 5 | 165 | 15 |
192 | 6 | 198 | 18 |
224 | 7 | 231 | 21 |
256 | 8 | 264 | 24 |
简单说流程为:
1. 生成随机序列熵。(128 ~ 256 位随机序列,而且为 32 的倍数)
2. 生成新的随机序列。 (熵 + 哈希(熵)/32)
3. 获取 m 个助记词对应序号。m = (熵 + 哈希(熵)/32) / 11
4. 分别获取对应的助记词。
获取助记词流程图如下:
2. 根据助记词推导种子(seed)
了解了第一步,就知道如何生成助记词,有了助记词就可以进行推导种子了。
通过助记词生成种子,需要使用 PBKDF2算法
来生成。PBKDF2算法
具体如下:
参数 1:mnemonic: 助记词
参数 2:salt: 盐,目的是为了增加算法难度
具体算法为:
采用 HMAC-SHA512 算法,进行 2048 次哈希来延伸助记词和盐参数。
产生一个 512 位的最终输出,这个 512 位输出就是种子(seed)
seed生成算法如图所示:
- ⑦:就是助记词。PBKDF2 的算法参数
- ⑧:salt。PBKDF2 算法参数,目的是为了增加算法难度。
- ⑨:使用sha512算法,进行 2048 次 hash
- 结果:产成一个 512 bit 的种子。
3. 根据种子推导主密钥和主链码
经过前面几步,了解到了如何获取助记词,以及如何通过助记词推导种子流程。接下来开始推导主密钥。
推导主密钥的方法为,从种子通过 HMAC-SHA512
算法计算一次,就会生成一个 512 位的序列。将其从中间分开,左侧 256 位为主私钥
,右侧 256 位为主链码
。
- 主私钥:可以推导主公钥,也可以进行推导子密钥等
- 链码:用作推导子密钥的熵。
推导主密钥流程图如下:
4. 根据父密钥推导子密钥
通过前面几步,已经了解到从如何生成助记词到如何创建主密钥,接下来就需要了解如何推导子密钥。
子密钥是通过父密钥来进行推导的,当然首个父密钥就是主密钥
。
推导子密钥的方法将分三步来学习
- 子私钥推导方式
- 子公钥推导方式
- 增强子密钥推导方式
4.1 子私钥推导
子私钥的推导方式,可以使用 CKD (child key derivation) 函数从父密钥
推导出子密钥。
CKD 函数为:CDKpriv((Kpar, Cpar), i) → (Ki, Ci)
具体推导步骤为:
- 通过
父私钥
通过椭圆曲线算法推导出父公钥
- 通过
父公钥
、父链码
、索引序号
通过HMAC-SHA512
函数进行一次算法。得到子私钥
和子链码
- 通过
子私钥
通过椭圆曲线算法推导出子公钥
。
索引序号决定了可以推导出子密钥的个数,其取值范围为:0 ~ 2^32 。其中索引号 0 ~ 2^31 - 1 推导出子密钥的过程被称为正常衍生
,索引号 2^31 ~ 2^32 - 1 推导出子密钥的过程被称为增强衍生
为什么引入链码?
钱包安全的核心在私钥,而公钥则比较容易被找到,如果子节点生成过程只依赖父节点公钥和子节点序号,那么黑客拿到父节点公钥之后就能复原出所有子节点、孙节点的公钥,这样就会破坏隐私性,CKD 里面引入的 Chain Code 则是在整个子节点派生过程中引入确定的随机数,为 HD 钱包的隐私性增加了一重保障。
流程图如下:
4.2 扩展密钥
在该推导过程中,父密钥和链码结合起来统称为扩展密钥
。即 256位的父密钥+256的链码结合
父私钥和链码结合起来称为扩展私钥
,扩展私钥可用来推导子私钥
,子私钥可以继续推导子公钥
父公钥和链码结合起来称为扩展公钥
,扩展公钥可用来推导子公钥
扩展密钥使用 Base58Check 来编码。扩展私钥编码后前缀为xprv
,扩展公钥编码后前缀为xpub
。
4.3 子公钥推导
通过 4.1 可以通过 CKD 函数配合父私钥和链码以及索引号推导出子私钥,然后通过子私钥推导子公钥。
但是层级确定性钱包的特点就是可以不通过私钥而直接通过公钥派生出子公钥的能力,可以增强其安全性。所以就有了两种推导子公钥的方式:1、通过子私钥推导。2、通过父公钥推导
子公钥的推导过程和子私钥推导过程流程基本一样,差异之处为:
- 将私钥推导过程替换为公钥
- 使用子公钥推导出其子链码
子公钥推导流程如下图所示:
4.4 普通子密钥钥推导的安全问题
通过扩展公钥推导出子公钥的能力很重要,但是也会有一些风险。比如并不能得到访问子私钥的途径。扩展公钥拥有链码,如果子私钥被泄露,链码就可以用来衍生其他所有的子密钥。也就是说泄露了父链码
和子私钥
就会暴露所有的子密钥,而且,子私钥
和父链码
可以推导出父私钥
。如下图:
4.4.1 疑问:怎么由子私钥+父链码推导出父私钥?
在 CKD 的算法的源码中,通过以上得知,父密钥推导子密钥有两种方法:1、父私钥 → 子私钥,2、父公钥 → 子公钥。这两种方法得到的子链码是相同的。
其算法过程为
- 父公钥 + 链码 经过 HAMC-SHA512 算法得到 512 位结果。
其中对 512 结果进行从中间分开。左侧256位和右侧256位 - 子私钥 = 左256位 + 父私钥,子公钥 = 左256位 + 父公钥。
- 子链码 = 右侧 256 位
当某一个环节的子私钥
泄露后。在加上扩展公钥
近乎是公开的,是可以拿到的,通过扩展公钥,可以得到完整的512位结果,就可以得到左256位。结合上述公式,也就可以从子私钥推导出父私钥了。流程为:
- 拿到扩展公钥。(公开的)
- 进行 HAMC-SHA512 得到 512 位。
- 取左 256 位。
- 子私钥泄露 !!!!!
- 父私钥 = 子私钥 - 左 256 位
4.5 增强子密钥推导
为此HD 钱包使用了新的推导函数 HDK( hardened key derivation formula )增强密钥推导。HDK 中通过父私钥
去推导子链码
,而不是父公钥
。
流程图如下:

HD 路径 | 密钥描述 |
---|---|
m/0 | 从主私钥(m) 推导出的第一代的第一个子私钥 |
m/0/0 | 从第一代子密钥m(0)推导出的第二代第一个孙私钥 |
m/0'/0 | 从第一代增强密钥 (m/0')推导出得第二代第一个孙密钥 |
m/1/0 | 从第一代的第二个子密钥推导出的第二代第一个孙密钥 |
BIP44 指定了 5 个预定义树状层级结构:
m / purpose' / coin_type' / account' / change / address_index
- m: 固定为主密钥
- purpose: 固定 44' (或者 0x8000002C)貌似是代表 BIP44 的意思。
- coin_type: 代表币种。比如:0代表 BTC,60 代表 ETH,194 代表 EOS。
- account: 代表这个币的索引账户。从 0 开始
- change: 常量 0 或 1。
- 0 用于外部链。外部链:用于在钱包外可见的地址。(比如收款等,也是我们经常使用的)
- 1 用于内部链。内部链:用于在钱包外部不可见的地址,用于返回交易变更。
- address_index:地址索引号。代表生成的第几个地址。官方建议,不要超过 20 个地址,第 0 个没使用不建议使用第1个。
通过BIP44标准制定的路径为:
例如:BTC:m / 44' / 0' / a' / 0 / n
例如:ETH:m / 44' / 60' / a' / 0 / n
例如:EOS:m / 44' / 194' / a'/ 0 / n
其中 a 就代表的是账户,n 代表的是第几个索引值。