Multi-Address Wallet Design: 多地址钱包设计

因为导出的hex比较ugly,因此是想要为ngwallet钱包定制一个助记词方案。本来构思的是直接通过密钥生成助记词,但是在寻找wordlist的时候仔细看了一下bitcoin的助记词方案,发现hd钱包(Hierachical Deterministic Wallets)这套密钥管理非常有趣且实用。既然在专心做钱包了那就顺带学习借鉴。

BIP32

Key derivation: 派生密钥

我们将使用比特币中公钥加密曲线,即secp256k1定义的字段和曲线参数的椭圆曲线密码术。有关secp256k1的细节这里不做展开。

Extended keys: 拓展密钥

在下面的内容中,我们将定义一个由父密钥派生许多子密钥的函数。 为了防止它们仅依赖于密钥本身,我们首先使用额外的256位"熵"(entropy)来扩展私有密钥和公共密钥。此扩展称为链码chain code,对于相应的私钥和公钥是相同的,并且由32个字节组成。

我们将扩展私钥表示为(k, c),其中k为普通私钥,而c为链码。 扩展的公钥表示为(K, c),其中K = point(k),c为链码。

每个扩展密钥都有2312^{31}个普通子密钥和2312^{31}个强化子密钥。 这些子密钥中的每一个都有一个索引index。普通子键使用从0到23112^{31}-1的index。加固的子密钥使用索引2312^{31}到$2^{32} -1 。为了简化强化子密钥index的表示,用。 为了简化强化子密钥index的表示,用i_H表示表示i + 2^{31}$。

Child key derivation (CKD) functions: 子密钥派生函数

给定父扩展密钥和index i,可以计算相应的子扩展密钥。 这样做的算法取决于子密钥是否是强化密钥(或,等效地,i是否大于等于2312^{31}),以及我们是在谈论私钥还是公钥。

父私钥 → 子私钥

函数CKDpriv((kpar, cpar), i) → (ki, ci)从父扩展私钥计算子扩展私钥

  • Check whether i ≥ 231 (whether the child is a hardened key).

    • If so (hardened child): let I = HMAC-SHA512(Key = cpar, Data = 0x00 || ser256(kpar) || ser32(i)). (Note: The 0x00 pads the private key to make it 33 bytes long.)
    • If not (normal child): let I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)).
  • Split I into two 32-byte sequences, IL and IR.

  • The returned child key ki is parse256(IL) + kpar (mod n).

  • The returned chain code ci is IR.

  • In case parse256(IL) ≥ n or ki = 0, the resulting key is invalid, and one should proceed with the next value for i. (Note: this has probability lower than 1 in 2127.)

父私钥 → 子公钥

函数N((k, c)) → (K, c) 从父扩展公钥计算子扩展公钥。 仅针对未加固的子键定义

  • 返回的密钥K为point(k)。
  • 返回的链码c只是传递的链码。

要计算父私钥的子公钥:

  • N(CKDpriv((kpar, c par), i))(始终有效)。
  • CKDpub(N(kpar, c par), i)(仅适用于非强化的子密钥)。

两者等效,使得非强化的密钥变得有用(一个人可以在不知道任何私钥的情况下派生出给定父密钥的子公钥),也使它们与强化密钥有所区别。 不一直使用非强化密钥(尽管更有用)的原因是安全性考量。

父公钥 → 子公钥

函数 CKDpub((Kpar, cpar), i) → (Ki, ci) 从父扩展公钥计算子扩展公钥。它仅针对未强化的子密钥。

  • 检查是否 i ≥ 231 (即,是否子密钥为强化密钥).
  • 如果是强化密钥,返回失败
  • 如果是普通密钥: 设置 I = HMAC-SHA512(Key = cpar, Data = serP(Kpar) || ser32(i)).
  • 把I 分割成两个32-byte 序列, IL 和 IR.
  • 返回的子密钥 Ki 即为point(parse256(IL)) + Kpar.
  • 返回的链码 ci 为 IR.
  • 如果 parse256(IL) ≥ n 或 Ki 是在无限上的点, 那么得到的密钥无效, 且需要继续处理i的下一个值.
父公钥 → 子私钥

不可能

The key tree: 密钥树

下一步则是串联数个CKD 结构来构造一个树形结构。我们从一个根节点,即主拓展密钥m开始。通过执行多个i值对应的CKDpriv(m,i) , 我们得到一系列level-1的派生节点。由于它们每个都是扩展密钥,因此CKDpriv也可以应用于这些扩展密钥。