区块链 第9.2章 钱包层级 区块链 第9.2章 钱包层级

1天前

一、钱包层级

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

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

const bitcoin = require('bitcoinjs-lib');

let
   xprv = 'xprv9s21ZrQH143K4EKMS3q1vbJo564QAbs98BfXQME6nk8UCrnXnv8vWg9qmtup3kTug96p5E3AvarBhPMScQDqMhEEm41rpYEdXBL8qzVZtwz',
   root = bitcoin.HDNode.fromBase58(xprv);

// m/0:
let m_0 = root.derive(0);
console.log("xprv m/0: " + m_0.toBase58());
console.log("xpub m/0: " + m_0.neutered().toBase58());
console.log(" prv m/0: " + m_0.keyPair.toWIF());
console.log(" pub m/0: " + m_0.keyPair.getAddress());

// m/1:
let m_1 = root.derive(1);
console.log("xprv m/1: " + m_1.toBase58());
console.log("xpub m/1: " + m_1.neutered().toBase58());
console.log(" prv m/1: " + m_1.keyPair.toWIF());
console.log(" pub m/1: " + m_1.keyPair.getAddress());

执行结果

xprv m/0: xprv9vV8wfuBFqPwqk1QRQ6W8NWXV7fnTQmf56r5dYXz4YNDafrhGeg6N2dVF7MPhrPUomzdQSSZAj5XXLCirjqKuEVSnbbqWd1BvkxkGPiNxY9
xpub m/0: xpub69UVMBS56CxF4E5sXRdWVWTG39WGrsVWSKmgRvwbcsuCTUBqpBzLupwy6P98uDM99qVb6Y32drbSFz7iaj5TDYPuRgoHL4U1bQRFHygiMZC
prv m/0: L3sZcLifBMDkutRFKy7HKNJ5hjJo8vKt8WrHW5mqenSf2e47SHaT
pub m/0: 1EwGHVkkRNB98FZyhwpmxU9Ax9fVpnBCcZ
xprv m/1: xprv9vV8wfuBFqPwtHcAWksyrMnhFbAmnGBersqGYK34cj9VTExLJBQiUAPcXLHh4Hi66P6BYAAnGEU3DfM7UEneEjhU7CoM5Dn9c8S2xsRGRJ9
xpub m/1: xpub69UVMBS56CxF6mgdcnQzDVjRod1GBiuWE6ksLhSgB4gUL3HUqiiy1xi6NaXQu1annpeKfP72Ff4a2oXBHNMgckE5DCa1DbQva4AiWewuZZy
prv m/1: L19By91R2K4cF3EjV77Xs6iKmfYTKwfXVaXSQ2v6wqj6BztfCBsk
pub m/1: 15tY5cdN2h19xmViD9KqX3YA7DgrkvN9gi

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

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

const bitcoin = require('bitcoinjs-lib');

let
   xprv = 'xprv9s21ZrQH143K4EKMS3q1vbJo564QAbs98BfXQME6nk8UCrnXnv8vWg9qmtup3kTug96p5E3AvarBhPMScQDqMhEEm41rpYEdXBL8qzVZtwz',
   root = bitcoin.HDNode.fromBase58(xprv);

// m/0:
let
   m_0 = root.derive(0),
   xprv_m_0 = m_0.toBase58(),
   xpub_m_0 = m_0.neutered().toBase58();

// 方法一:从m/0的扩展私钥推算m/0/99的公钥地址:
let pub_99a = bitcoin.HDNode.fromBase58(xprv_m_0).derive(99).getAddress();

// 方法二:从m/0的扩展公钥推算m/0/99的公钥地址:
let pub_99b = bitcoin.HDNode.fromBase58(xpub_m_0).derive(99).getAddress();

// 比较公钥地址是否相同:
console.log(pub_99a);
console.log(pub_99b);

执行结果

1GvmxzNG5i8EqXQuD75XaGjqf2oCEmJ87M
1GvmxzNG5i8EqXQuD75XaGjqf2oCEmJ87M

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

const bitcoin = require('bitcoinjs-lib');

let
   xprv = 'xprv9s21ZrQH143K4EKMS3q1vbJo564QAbs98BfXQME6nk8UCrnXnv8vWg9qmtup3kTug96p5E3AvarBhPMScQDqMhEEm41rpYEdXBL8qzVZtwz',
   root = bitcoin.HDNode.fromBase58(xprv);

// m/0:
let
   m_0 = root.derive(0),
   xprv_m_0 = m_0.toBase58(),
   xpub_m_0 = m_0.neutered().toBase58();

// 从m/0的扩展私钥推算m/0/99'的公钥地址:
let pub_99a = bitcoin.HDNode.fromBase58(xprv_m_0).deriveHardened(99).getAddress();
console.log(pub_99a);

// 不能从m/0的扩展公钥推算m/0/99'的公钥地址:
bitcoin.HDNode.fromBase58(xpub_m_0).deriveHardened(99).getAddress();

执行结果

1PVoHWkWFk6uJYDERskP5HSwG7WHfYLacF
TypeError: Could not derive hardened child key

二、BIP-44

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

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

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

其中,purpose 总是 44,coin_type 在 SLIP-44 中定义,例如,0=BTC,2=LTC,60=ETH 等。account 表示用户的某个“账户”,由用户自定义索引,change=0 表示外部交易,change=1 表示内部交易,address_index 则是真正派生的索引为 0~2 的 31 次方的地址。

例如,某个比特币钱包给用户创建的一组 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 规范的钱包可以管理所有币种。

相同的根扩展私钥在不同钱包上派生的一组地址都是相同的。

阅读 17