分层确定性钱包, 简称 HD 钱包, 是一种可以从单个 seed
生成多个密钥对的加密货币钱包系统。
从 seed
生成拓展秘钥, 拓展秘钥又可以派生出多个子拓展秘钥。每个子拓展秘钥又可以继续派生其下层拓展秘钥, 如此往复, 理论上可以无限派生。每个拓展秘钥都可以计算出一个公私钥作为比特币账户。所以 HD 钱包的优点显而易见, 通过一个 seed
就可以生成无限多的比特币账号, 且无需备份每个私钥, 只需要备份 seed
即可。
派生密钥
BIP32 是 HD 钱包的基础, 定义了如何从 seed
派生出密钥。
seed
是一个随机数。通过 HMAC-SHA512
哈希函数由 seed
生成拓展秘钥(extended key
), 拓展秘钥由主私钥和链码(chain code
)组成。
拓展秘钥
HMAC-SHA512
函数的输入是 seed
和密码(BIP 32 规定这个密码是 Bitcoin seed), 输出是 64 字节的拓展秘钥(从 seed
生成的拓展秘钥也叫主拓展秘钥 master extended key
)。 前 32 字节作为主私钥, 后 32 字节作为链码。链码作为派生子密钥的 HMAC-SHA512
的密码。
拓展秘钥有两种:
- 拓展私钥: 主私钥和链码
- 拓展公钥: 主私钥对应的公钥和链码
这两种拓展秘钥都能单独的生成子密钥, 从拓展私钥可以派生出子拓展私钥, 但从拓展公钥只能派生出子拓展公钥。从拓展私钥派生出的子拓展私钥所对应的公钥与拓展公钥派生出的子拓展公钥相同。
拓展秘钥根据索引生成子密钥, 索引大小为 4 字节, 可以表达的最大值是 4294967295
, 即最多可以派生出 4294967296
个子密钥。按照索引值将其分成两种派生方式:
Normal Derivation
- 普通派生, 索引范围在 0 - 2147483647:Hardened derivation
- 硬化派生, 索引范围在 2147483648 - 4294967295:
普通派生
如果索引小于 2147483648
, 则为普通派生。
普通派生过程如下:
- 将索引转换为 4 字节的大端字节序
- 将压缩公钥和索引拼接, 生成 37 字节的数据作为
HMAC-SHA512
加密数据 - 将拓展秘钥的链码作为
HMAC-SHA512
密码 HMAC-SHA512
生成 64 字节的I
, 前 32 字节为派生因子, 后 32 字节为子链码- 已知父私钥: 派生因子与父私钥相加, 并对椭圆曲线的阶取模, 得到子私钥
- 仅知道父公钥: 派生因子乘以椭圆曲线的基点并与父公钥相加, 得到子公钥
拓展秘钥
子私钥或子公钥和子链码组合又可以作为拓展秘钥, 根据索引继续生成下一级子密钥。
普通派生的要求必须要知道父公钥, 但如果只知道父公钥, 就只能派生出子公钥, 则无法派生出子私钥。
硬化派生
如果索引大于等于 2147483648
, 则为硬化派生 hardened derivation
。
硬化派生过程如下:
- 将索引转换为 4 字节的大端字节序
- 将主私钥和索引拼接后, 最前面补充一个字节 0, 生成 37 字节的数据作为
HMAC-SHA512
加密数据 - 将拓展秘钥的链码作为
HMAC-SHA512
密码 HMAC-SHA512
生成 64 字节的I
, 前 32 字节为派生因子, 后 32 字节为子链码, 派生因子与主私钥相加, 并对椭圆曲线的阶取模, 得到子私钥。
硬化派生与普通派生过程基本一致, 区别在于硬化派生以主私钥和索引拼接, 而普通派生则压缩公钥和索引拼接。
秘钥序列化
BIP32 定义了拓展秘钥序列化格式。序列化格式字段包括:
- 版本号 - 4 字节
- 主网: 拓展私钥 -
xprv
(0x0488ADE4), 拓展公钥 -xpub
(0x0488B21E) - 测试网: 拓展私钥 -
tprv
(0x04358394), 拓展公钥 -tpub
(0x043587CF)
- 主网: 拓展私钥 -
- 深度 - 1 字节, 表示节点在树中的深度
- 父指纹 - 4 字节, 父节点公钥进行
Hash160
运算后取前 4 字节作为指纹 - 索引 - 4 字节, 节点在当前层的索引
- 链码 - 32 字节, 拓展秘钥的后 32 字节
- 秘钥 - 33 字节, 压缩公钥或私钥
将上述字段按顺序拼接, 生成 78 字节的序列化数据。并对序列化数据进行 Base58Check
编码, 生成拓展秘钥的字符串表示。
拓展秘钥
在已知序列化格式的拓展秘钥情况下, 可以通过 Base58Check
解码, 得到拓展秘钥的各个字段。
助记词
助记词是 seed
的人类可读形式, 通常是 12 或 24 个单词。通过助记词可以生成 seed
, 再由 seed
生成多个拓展秘钥。
BIP39 定义了助记词的生成规则, 过程如下:
-
生成随机数 - 这个随机数叫
entropy
, 大小为 4 的倍数的字节, 范围在 16 - 32 字节。即entropy
大小可以是 16, 20, 24, 28 或 32 字节。entropy
的比特位长度称作ENT
-
计算
checksum
- 取SHA256(entropy)
的前 ENT/32 比特位作checksum
-
将
entropy
和checksum
比特位拼接, 并按照每11比特位分组。每组转换为10进制作为 单词表 的索引从单词表中取出单词, 生成助记词。 -
通过
PBKDF2
函数将助记词转换为512位的seed
,PBKDF2
函数的密码是mnemonic${passphrase}
,passphrase
是用户自定义的密码。
助记词生成器
结合 BIP39
和 BIP32
, 可以总结出生成多个比特币账户的过程:
- 通过助记词生成
seed
- 通过
seed
生成主拓展秘钥 - 通过主拓展秘钥生成多层子密钥
- 每个子密钥对应一个比特币账户
助记词的目的是为了提供一种更友好的方式来记忆 seed
。 如果你有更安全且方便的方法来保存 seed
, 那么助记词就不是必须的。例如, 你可以用你喜欢的一句话转成 16 进制作为 seed
。
使用助记词生成 seed
时, 更安全的做法是设置 passphrase
, 这样即使助记词泄露, 也无法生成 seed
。
派生路径
派生路径用于定位从 seed
生成的子密钥的位置。从秘钥树的第 0 层开始, 每一层的索引作为路径的一部分, 用 /
分隔。如果是硬化派生, 索引需要减去 并在后面加上 '
例如: m/8'/2/0'
:
m
表示从seed
生成的主拓展秘钥, 是密钥树的第 0 层,8'
表示从m
派生出的第一层索引是2
表示从m/8'
派生出的第二层索引是2
0'
表示从m/8'/2
派生出的第三层索引是 个子密钥。
在 BIP44 中定义了派生路径固定的结构:
purpose
- 目的, 例如是 BIP44, 则为44'
, BIP49 则为49'
, BIP84 则为84'
等coin_type
- 币种, 一个seed
可以生成多个币种的密钥, 每个币种有一个唯一的索引。比特币的索引是0'
, 比特币测试网的索引是1'
, 以太坊的索引是60'
等account
- 账户索引, 从 0 开始change
- 0 表示外部地址, 1 表示内部地址。外部地址用于钱包外可见的地址, 例如接收付款。内部地址用于钱包内部的地址, 例如找零地址。address_index
- 地址索引
BIP44 路径是 m/44'/0'/0'/0/0
, 定义了生成 P2PKH
地址使用的公私钥。
BIP49 路径是 m/49'/0'/0'/0/0
, 定义了生成 P2SH-P2WPKH
地址使用的公私钥。
BIP84 路径是 m/84'/0'/0'/0/0
, 定义了生成 P2WPKH
地址使用的公私钥。
BIP86 路径是 m/86'/0'/0'/0/0
, 定义了生成 P2TR
地址使用的公私钥。