4. BIP39、BIP44、BIP32 协议

4.1. HD 钱包导入流程

../../../_images/wallet-BIP.png

4.2. BIP39

熵每次都可以得到不同的助记词


CS = ENT /32 

checksum = SHA256(entropy)

MS = (ENT + CS) / 11 

ENT:熵长度。CS:校验长度。MS:助记词长度。checksum:校验和
校验和: 对熵取SHA256哈希结果前CS位

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

4.2.1. 创建助记词

  1. 创建一个128到256位的随机序列(熵)。

  2. 提出SHA256哈希前几位(熵长/ 32),就可以创造一个随机序列的校验和。

  3. 将校验和添加到随机序列的末尾。

  4. 将序列划分为包含11位的不同部分。

  5. 将每个包含11位部分的值与一个已经预先定义2048个单词的字典做对应。

  6. 生成的有顺序的单词组就是助记码。

4.2.2. 助记词生成种子(seed)

创建助记词之后的7-9步是:

  1. PBKDF2密钥延伸函数的第一个参数是从步骤6生成的助记符。

  2. PBKDF2密钥延伸函数的第二个参数是盐。 由字符串常数“助记词”与可选的用户提供的密码字符串连接组成。

  3. PBKDF2使用HMAC-SHA512算法,使用2048次哈希来延伸助记符和盐参数,产生一个512位的值作为其最终输出。 这个512位的值就是种子。

图5-7显示了从助记词如何生成种子

4.3. BIP32

4.3.1. 分层路径

4.3.2. 从 Seed 创建 HDWallet

HD钱包从单个根种子(root seed)中创建,为128到256位的随机数。常见的通过助记词可以确定种子Root Seed。

HD钱包的所有的确定性都衍生自这个根种子。任何兼容HD 钱包的根种子也可重新创造整个HD钱包。

子私钥推导 HD 钱包使用 CKD(child key derivation) 函数从父密钥(parent keys)推导子密钥(child keys),CKD 由下列三个要素做单向散列哈希(one way hash function) 。
  • 父密钥 (没有压缩过的椭圆曲线推导的私钥或公钥 ECDSA uncompressed key)

  • 链码作为熵 (chain code 256 bits)

  • 子代索引序号 (index 32 bits)

钱包安全的核心在私钥,而公钥则比较容易被找到,如果子节点生成过程只依赖父节点公钥和子节点序号,那么黑客拿到父节点公钥之后就能复原出所有子节点、孙节点的公钥,这样就会破坏隐私性,CKD 里面引入的 Chain Code 则是在整个子节点派生过程中引入确定的随机数,为 HD 钱包的隐私性增加了一重保障。

4.3.2.1. 正常衍生子密钥

4.3.2.2. 强化衍生子密钥

  • 图上标识了 索引号码 根据 正常衍生 和 硬化衍生不同,索引的范围不同,对于正常衍生的索引号范围为 [0x0, 0x7FFFFFFF],而硬化衍生的索引号范围为 [0x80000000, 0xFFFFFFFF]

  • 硬化衍生的索引号太长,一般为了便于阅读,都是会将索引号右上角加上撇号,譬如:0x80000000 记为 0’,0x80000001 记为 1’,以此类推.

针对 扩展密钥 的学习,可以看到有三种生成规则,分别如下:

  • Private parent key -> private child key
    即,从 父私钥 和 父链码 计算生成 子私钥 和 子链码。用公式表示就是:

CKDpriv((kpar, cpar), i) → (ki, ci)
  • 检查 是否 i ≥ 2^31(子私钥)。
    如果是(硬化的子密钥):让I= HMAC-SHA512(Key = cpar,Data = 0x00 || ser256(kpar)|| ser32(i))(注意:0x00将私钥补齐到33字节长。)
    如果不是(普通的普通子密钥):让I= HMAC-SHA512(Key = cpar,Data = serP(point(kpar))|| ser32(i))。

  • 将I分为两个32字节序列,IL和IR。

  • 返回的子密钥ki是parse256(IL)+ kpar(mod n)。

  • 返回的链码ci是IR。

  • 如果parse256(IL)≥n或ki = 0,则生成的密钥无效,并且应继续下一个i值。 (注:概率低于1/2127)

  • Public parent key -> public child key
    即,从 父公钥 和 父链码 计算生成 子公钥 和 子链码。公式表示如下:

CKDpub((Kpar, cpar), i) → (Ki, ci)
  • 检查是否 i ≥ 2^31 (子密钥是否是硬化密钥)
    如果是(硬化子密钥):返回失败
    如果不是(普通子密钥):让I= HMAC-SHA512(Key = cpar, Data = serP(Kpar) || ser32(i)).

  • 将I分为两个32字节序列,IL和IR。

  • 返回的子密钥Ki是point(parse256(IL))+ Kpar。

  • 返回的链码ci是IR。

  • 如果parse256(IL)≥n或Ki是无限远的点,则生成的密钥无效,并且应继续下一个i值。

  • Private parent key -> public child key

    即,从 父私钥 和 父链码 计算生成 子公钥 和 子链码。公式表示如下:

N((k, c)) → (K, c)
  • 返回的密钥K是point(k)。

  • 返回的链码c只是传递的链码。

要计算父私钥的公用子密钥:

  • N(CKDpriv((kpar,cpar),i))(总是工作)。

  • CKDpub(N(kpar,cpar),i)(仅适用于非硬化子密钥)。

它们等价的事实是使非硬化密钥有用(可以在不知道任何私钥的情况下导出给定父密钥的子公钥),以及它们与硬密钥的区别。 不总是使用非硬化键(更有用)的原因是安全性; 后面可以了解更详细的信息。

4.4. BIP44

BIP32路径中的5个层次定义:

m / purpose' / coin_type' / account' / change / address_index

路径中的撇号表示使用了经过硬化(hardern)处理的BIP32派生。

  1. purporse': 固定值44'(或0x8000002C), 代表是BIP44

  2. coin_type': 这个代表的是币种, 可以兼容很多种币, 比如BTC是0', ETH是60'
    硬化的派生在这个级别上使用。(所以右上角有 ' 标记)币种,代表一个主节点(种子)可用于无限数量的独立加密币,如比特币等。此级别为每个加密币创建一个单独的子树,避免重用已经在其它链上存在的地址。开发人员可以为他们的项目注册未使用的号码

  3. Account: 一类coin下能够有多个账户,就相当于你的人民币会存在多张银行卡 以顺序递增的方式从索引0开始编号。这个数字在BIP32派生中用作子索引。

  4. Change: 表明为外部链0或内部链1:常量0用于外部链,常量1用于内部链(也称为更改地址)。外部链用于在钱包外部可见的地址(例如用于接收付款)。内部链用于不在钱包外部可见的地址,用于返回交易更改.一个是用来接收地址一个是用来创造找零地址

  5. Index: 被HD钱包衍生的真正可用的地址是第四层级的子级,就是第五层级的树的“address_index”。比如,第三个层级的主账户如M/44'/0'/1'收到比特币真正支付的地址是 M/44'/0'/0'/0/2

最后我们简要介绍一下 BIP44 中的账户发现算法:

  1. 推导出第一个账户节点(生成编号初始值设为 0)

  2. 推导出这个账户的外部链

  3. 依次扫描外部链上的地址,如果连续二十个地址都没有交易记录,停止扫描

  4. 如果外部链上没有发现交易,退出

  5. 如果存在交易,将生成编号的值增加 1,跳转到第一步

一句话概括下BIP44就是:给BIP32的分层路径定义规范

4.5. reference

Ethereum HD Wallet(虚拟货币钱包)-BIP32、BIP39、BIP44
理解开发HD 钱包涉及的BIP32、BIP44、BIP39

基于 BIP-32 和 BIP-39 规范生成 HD 钱包(分层确定性钱包)
分层确定性钱包 HD Wallet 剖析:设计和实现