12. HD钱包

在比特币的链上,实际上并没有账户的概念,某个用户持有的比特币,实际上是其控制的一组UTXO,而这些UTXO可能是相同的地址(对应相同的私钥),也可能是不同的地址(对应不同的私钥)。

出于保护隐私的目的,同一用户如果控制的UTXO其地址都是不同的,那么很难从地址获知某个用户的比特币持币总额。但是,管理一组成千上万的地址,意味着管理成千上万的私钥,管理起来非常麻烦。

能不能只用一个私钥管理成千上万个地址?实际上是可以的。虽然椭圆曲线算法决定了一个私钥只能对应一个公钥,但是,可以通过某种确定性算法,先确定一个私钥k1,然后计算出k2、k3、k4……等其他私钥,就相当于只需要管理一个私钥,剩下的私钥可以按需计算出来。

这种根据某种确定性算法,只需要管理一个根私钥,即可实时计算所有“子私钥”的管理方式,称为HD钱包。

HD是Hierarchical Deterministic的缩写,意思是分层确定性。先确定根私钥root,然后根据索引计算每一层的子私钥:

root
│
├─────────────┬─────────────┐
│             │             │
▼             ▼             ▼
k0            k1            k2 ...
│             │             │
├──┬──┐       ├──┬──┐       ├──┬──┐
│  │  │       │  │  │       │  │  │
▼  ▼  ▼       ▼  ▼  ▼       ▼  ▼  ▼
k0 k1 k2 ...  k0 k1 k2 ...  k0 k1 k2 ...

对于任意一个私钥k,总是可以根据索引计算它的下一层私钥\( k_n\) :\( k_n=hdkey(k,;n)\)

即HD层级实际上是无限的,每一层索引从0~\(2^{32}\) ,约43亿个子key。这种计算被称为衍生(Derivation)。

现在问题来了:如何根据某个私钥计算下一层的子私钥?即函数hdkey(k, n)如何实现?

HD钱包采用的计算子私钥的算法并不是一个简单的SHA-256,私钥也不是普通的256位ECDSA私钥,而是一个扩展的512位私钥,记作xprv,它通过SHA-512算法配合ECC计算出子扩展私钥,仍然是512位。通过扩展私钥可计算出用于签名的私钥以及公钥。

简单来说,只要给定一个根扩展私钥(随机512位整数),即可计算其任意索引的子扩展私钥。扩展私钥总是能计算出扩展公钥,记作xpub:

xprv ────────> xpub
  │              │
  │              │
  │              │
  ▼              ▼
xprv-0 ──────> xpub-0

从xprv及其对应的xpub可计算出真正用于签名的私钥和公钥。之所以要设计这种算法,是因为扩展公钥xpub也有一个特点,那就是可以直接计算其子层级的扩展公钥:

xpub
│
├───────┬───────┐
│       │       │
▼       ▼       ▼
xpub-0  xpub-1  xpub-2  ...

因为xpub只包含公钥,不包含私钥,因此,可以安全地把xpub交给第三方(例如,一个观察钱包),它可以根据xpub计算子层级的所有地址,然后在比特币的链上监控这些地址的余额,但因为没有私钥,所以只能看,不能花。

因此,HD钱包通过分层确定性算法,实现了以下功能:

  • 只要确定了扩展私钥xprv,即可根据索引计算下一层的任何扩展私钥;
  • 只要确定了扩展公钥xpub,即可根据索引计算下一层的任何扩展公钥;
  • 用户只需保存顶层的一个扩展私钥,即可计算出任意一层的任意索引的扩展私钥。

从理论上说,扩展私钥的层数是没有限制的,每一层的数量被限制在\( 0~2^{32}\) ,原因是扩展私钥中只有4字节作为索引,因此索引范围是0~\( 2^{32}\) 。

通常把根扩展私钥记作m,子扩展私钥按层级记作m/x/y/z等:

m
│
├──────────────────────┐
│                      │
▼                      ▼
m/0                    m/1 ...
│                      │
├─────┬─────┐          ├─────┬─────┐
│     │     │          │     │     │
▼     ▼     ▼          ▼     ▼     ▼
m/0/0 m/0/1 m/0/2 ...  m/1/0 m/1/1 m/1/2 ...

例如,m/0/2表示从m扩展到m/0(索引为0)再扩展到m/0/2(索引为2)。

安全性

HD钱包给私钥管理带来了非常大的方便,因为只需要管理一个根扩展私钥,就可以管理所有层级的所有衍生私钥。

但是HD钱包的扩展私钥算法有个潜在的安全性问题,就是如果某个层级的xprv泄露了,可反向推导出上层的xprv,继而推导出整个HD扩展私钥体系。为了避免某个子扩展私钥的泄漏导致上层扩展私钥被反向推导,HD钱包还有一种硬化的衍生计算方式(Hardened Derivation),它通过算法“切断”了母扩展私钥和子扩展私钥的反向推导。HD规范把索引0~\( 2^{31}\) 作为普通衍生索引,而索引\( 2^{31}\) ~\( 2^{32}\) 作为硬化衍生索引,硬化衍生索引通常记作0'、1'、2'……,即索引0'=\( 2^{31}\) ,1'=\( 2^{31}\) +1,2'=\( 2^{31}\) +2,以此类推。

因此,m/44'/0表示的子扩展私钥,它的第一层衍生索引44'是硬化衍生,实际索引是$ 2^{31}$ +44=2147483692。从m/44'/0无法反向推导出m/44'

在只有扩展公钥的情况下,只能计算出普通衍生的子公钥,无法计算出硬化衍生的子扩展公钥,即可计算出的子扩展公钥索引被限制在0~\( 2^{31}\) 。因此,观察钱包能使用的索引是0~\( 2^{31}\) 。

BIP-32

比特币的BIP-32规范详细定义了HD算法原理和各种推导规则,可阅读此文档以便实现HD钱包。

小结

  • HD钱包采用分层确定性算法通过根扩展私钥计算所有层级的所有子扩展私钥,继而得到扩展公钥和地址;
  • 可以通过普通衍生和硬化衍生两种方式计算扩展子私钥,后者更安全,但对应的扩展公钥无法计算硬化衍生的子扩展公钥;
  • 通过扩展公钥可以在没有扩展私钥的前提下计算所有普通子扩展公钥,此特性可实现观察钱包。
下一节:HD钱包算法决定了只要给定根扩展私钥,整棵树的任意节点的扩展私钥都可以计算出来。