HD钱包算法决定了只要给定根扩展私钥,整棵树的任意节点的扩展私钥都可以计算出来。

我们来看看如何利用bitcoinjs-lib这个JavaScript库来计算HD地址:

  1. const bitcoin = require('bitcoinjs-lib');
  2. let
  3. xprv = 'xprv9s21ZrQH143K4EKMS3q1vbJo564QAbs98BfXQME6nk8UCrnXnv8vWg9qmtup3kTug96p5E3AvarBhPMScQDqMhEEm41rpYEdXBL8qzVZtwz',
  4. root = bitcoin.HDNode.fromBase58(xprv);
  5. // m/0:
  6. var m_0 = root.derive(0);
  7. console.log("xprv m/0: " + m_0.toBase58());
  8. console.log("xpub m/0: " + m_0.neutered().toBase58());
  9. console.log(" prv m/0: " + m_0.keyPair.toWIF());
  10. console.log(" pub m/0: " + m_0.keyPair.getAddress());
  11. // m/1:
  12. var m_1 = root.derive(0);
  13. console.log("xprv m/1: " + m_1.toBase58());
  14. console.log("xpub m/1: " + m_1.neutered().toBase58());
  15. console.log(" prv m/1: " + m_1.keyPair.toWIF());
  16. console.log(" pub m/1: " + m_1.keyPair.getAddress());

注意到以xprv开头的xprv9s21ZrQH...是512位扩展私钥的Base58编码,解码后得到的就是原始扩展私钥。

可以从某个xpub在没有xprv的前提下直接推算子公钥:

  1. const bitcoin = require('bitcoinjs-lib');
  2. let
  3. xprv = 'xprv9s21ZrQH143K4EKMS3q1vbJo564QAbs98BfXQME6nk8UCrnXnv8vWg9qmtup3kTug96p5E3AvarBhPMScQDqMhEEm41rpYEdXBL8qzVZtwz',
  4. root = bitcoin.HDNode.fromBase58(xprv);
  5. // m/0:
  6. let
  7. m_0 = root.derive(0),
  8. xprv_m_0 = m_0.toBase58(),
  9. xpub_m_0 = m_0.neutered().toBase58();
  10. // 方法一:从m/0的扩展私钥推算m/0/99的公钥地址:
  11. let pub_99a = bitcoin.HDNode.fromBase58(xprv_m_0).derive(99).getAddress();
  12. // 方法二:从m/0的扩展公钥推算m/0/99的公钥地址:
  13. let pub_99b = bitcoin.HDNode.fromBase58(xpub_m_0).derive(99).getAddress();
  14. // 比较公钥地址是否相同:
  15. console.log(pub_99a);
  16. console.log(pub_99b);

但不能从xpub推算硬化的子公钥:

  1. const bitcoin = require('bitcoinjs-lib');
  2. let
  3. xprv = 'xprv9s21ZrQH143K4EKMS3q1vbJo564QAbs98BfXQME6nk8UCrnXnv8vWg9qmtup3kTug96p5E3AvarBhPMScQDqMhEEm41rpYEdXBL8qzVZtwz',
  4. root = bitcoin.HDNode.fromBase58(xprv);
  5. // m/0:
  6. let
  7. m_0 = root.derive(0),
  8. xprv_m_0 = m_0.toBase58(),
  9. xpub_m_0 = m_0.neutered().toBase58();
  10. // 从m/0的扩展私钥推算m/0/99'的公钥地址:
  11. let pub_99a = bitcoin.HDNode.fromBase58(xprv_m_0).deriveHardened(99).getAddress();
  12. console.log(pub_99a);
  13. // 不能从m/0的扩展公钥推算m/0/99'的公钥地址:
  14. bitcoin.HDNode.fromBase58(xpub_m_0).deriveHardened(99).getAddress();

BIP-44

HD钱包理论上有无限的层级,对使用secp256k1算法的任何币都适用。但是,如果一种钱包使用m/1/2/x,另一种钱包使用m/3/4/x,没有一种统一的规范,就会乱套。

比特币的BIP-44规范定义了一种如何派生私钥的标准,它本身非常简单:

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

其中,purpose总是44coin_typeSLIP-44中定义,例如,0=BTC2=LTC60=ETH等。account表示用户的某个“账户”,由用户自定义索引,change=0表示外部交易,change=1表示内部交易,address_index则是真正派生的索引为0~231的地址。

例如,某个比特币钱包给用户创建的一组HD地址实际上是:

  • m/44’/0’/0’/0/0
  • m/44’/0’/0’/0/1
  • m/44’/0’/0’/0/2
  • m/44’/0’/0’/0/3

如果是莱特币钱包,则用户的HD地址是:

  • m/44’/2’/0’/0/0
  • m/44’/2’/0’/0/1
  • m/44’/2’/0’/0/2
  • m/44’/2’/0’/0/3

小结

实现了BIP-44规范的钱包可以管理所有币种。相同的根扩展私钥在不同钱包上派生的一组地址都是相同的。

读后有收获可以支付宝请作者喝咖啡:

钱包层级 - 图1