一、助记词
从 HD 钱包的创建方式可知,要创建一个 HD 钱包,我们必须首先有一个确定的 512bit(64 字节)的随机数种子。
如果用电脑生成一个 64 字节的随机数作为种子当然是可以的,但是恐怕谁也记不住。
如果自己想一个句子,例如 bitcoin is awesome,然后计算 SHA-512 获得这个 64 字节的种子,虽然是可行的,但是其安全性取决于自己想的句子到底有多随机。像 bitcoin is awesome 本质上就是 3 个英文单词构成的随机数,长度太短,所以安全性非常差。
为了解决初始化种子的易用性问题,BIP-39 规范提出了一种通过助记词来推算种子的算法:
以英文单词为例,首先,挑选 2048 个常用的英文单词,构造一个数组:
const words = ['abandon', 'ability', 'able', ..., 'zoo'];
然后,生成 128~256 位随机数,注意随机数的总位数必须是 32 的倍数。例如,生成的 256 位随机数以 16 进制表示为:
179e5af5ef66e5da5049cd3de0258c5339a722094e0fdbbbe0e96f148ae80924
在随机数末尾加上校验码,校验码取 SHA-256 的前若干位,并使得总位数凑成 11 的倍数。上述随机数校验码的二进制表示为 00010000。
将随机数+校验码按每 11 bit 一组,得到范围是 0~2047 的 24 个整数,把这 24 个整数作为索引,就得到了最多 24 个助记词,例如:
bleak version runway tell hour unfold donkey defy digital abuse glide please omit much cement sea sweet tenant demise taste emerge inject cause link
由于在生成助记词的过程中引入了校验码,所以,助记词如果弄错了,软件可以提示用户输入的助记词可能不对。
生成助记词的过程是计算机随机产生的,用户只要记住这些助记词,就可以根据助记词推算出 HD 钱包的种子。
千万不要自己挑选助记词,原因一是随机性太差,二是缺少校验。
生成助记词可以使用 bip39 这个 JavaScript 库:
const bip39 = require('bip39');
let words = bip39.generateMnemonic(256);
console.log(words);
console.log('is valid mnemonic? ' + bip39.validateMnemonic(words));
某一个执行结果
feature gym dignity ladder spy proud photo marriage cinnamon topic ghost coral vocal upgrade pet alert december border able shoulder police ostrich little exit
is valid mnemonic? true
运行上述代码,每次都会得到随机生成的不同的助记词。
如果想用中文作助记词也是可以的,给 generateMnemonic() 传入一个中文助记词数组即可:
const bip39 = require('bip39');
// 第二个参数rng可以为null:
let words = bip39.generateMnemonic(256, null, bip39.wordlists.chinese_simplified);
console.log(words);
某一个执行结果
红 铃 冠 桌 抢 作 综 泡 夺 续 填 许 拔 场 罪 绍 翻 特 烘 绿 因 按 蔬 吨
注意:同样索引的中文和英文生成的 HD 种子是不同的。各种语言的助记词定义在 bip-0039-wordlists.md。
二、根据助记词推算种子
根据助记词推算种子的算法是 PBKDF2,使用的哈希函数是 Hmac-SHA512,其中,输入是助记词的UTF-8 编码,并设置 Key 为 mnemonic+用户口令,循环 2048 次,得到最终的 64 字节种子。上述助记词加上口令 bitcoin 得到的 HD 种子是:
b59a8078d4ac5c05b0c92b775b96a466cd136664bfe14c1d49aff3ccc94d52dfb1d59ee628426192eff5535d6058cb64317ef2992c8b124d0f72af81c9ebfaaa
该种子即为 HD 钱包的种子。
要特别注意:用户除了需要记住助记词外,还可以额外设置一个口令。
HD 种子的生成依赖于助记词和口令,
丢失助记词或者丢失口令(如果设置了口令的话)都将导致 HD 钱包丢失!
用 JavaScript 代码实现为:
const bip39 = require('bip39');
let words = bip39.generateMnemonic(256);
console.log(words);
let seedBuffer = bip39.mnemonicToSeed(words);
let seedAsHex = seedBuffer.toString('hex');
// or use bip39.mnemonicToSeedHex(words)
console.log(seedAsHex);
某一个执行结果
hero answer gauge cliff clerk sting eager fitness peace only erosion inner hello flower leader mesh funny supply turn bomb badge life spray volcano
26a7a595ee36049289d131e78a0237d2f2c2cc5d614a2a7b624c498ffbf66268b85e7251f4bc7dd9baec7b1bc8ca31d03064a742b638bb15e525030c3a1cc66b
根据助记词和口令生成 HD 种子的方法是在 mnemonicToSeed() 函数中传入 password:
const bip39 = require('bip39');
let words = bip39.generateMnemonic(256);
console.log(words);
let password = 'bitcoin';
let seedAsHex = bip39.mnemonicToSeedHex(words, password);
console.log(seedAsHex);
某一个执行结果
vehicle reveal second identify affair jelly ski foster patrol slab inhale resource sun discover jealous easy auto input all pattern own grocery trap husband
630e4755bcb4ce17a0d85c548abd125919028299c31b1856e05d2aa4e4d7593f97acbf6afedadfdc4f5ed40c82a2af1adccae7a59dfac99b9b6f617b7bb6d0b6
从助记词算法可知,只要确定了助记词和口令,生成的 HD 种子就是确定的。
如果两个人的助记词相同,那么他们的 HD 种子也是相同的。这也意味着如果把助记词抄在纸上,一旦泄漏,HD 种子就泄漏了。
如果在助记词的基础上设置了口令,那么只知道助记词,不知道口令,也是无法推算出 HD 种子的。
把助记词抄在纸上,口令记在脑子里,这样,泄漏了助记词也不会导致 HD 种子被泄漏,但要牢牢记住口令。
最后,我们使用助记词+口令的方式来生成一个 HD 钱包的 HD 种子并计算出根扩展私钥:
const
bitcoin = require('bitcoinjs-lib'),
bip39 = require('bip39');
let
words = 'bleak version runway tell hour unfold donkey defy digital abuse glide please omit much cement sea sweet tenant demise taste emerge inject cause link',
password = 'bitcoin';
// 计算seed:
let seedHex = bip39.mnemonicToSeedHex(words, password);
console.log('seed: ' + seedHex); // b59a8078...c9ebfaaa
// 生成root:
let root = bitcoin.HDNode.fromSeedHex(seedHex);
console.log('xprv: ' + root.toBase58()); // xprv9s21ZrQH...uLgyr9kF
console.log('xpub: ' + root.neutered().toBase58()); // xpub661MyMwA...oy32fcRG
// 生成派生key:
let child0 = root.derivePath("m/44'/0'/0'/0/0");
console.log("prv m/44'/0'/0'/0/0: " + child0.keyPair.toWIF()); // KzuPk3PXKdnd6QwLqUCK38PrXoqJfJmACzxTaa6TFKzPJR7H7AFg
console.log("pub m/44'/0'/0'/0/0: " + child0.getAddress()); // 1PwKkrF366RdTuYsS8KWEbGxfP4bikegcS
执行结果
seed: b59a8078d4ac5c05b0c92b775b96a466cd136664bfe14c1d49aff3ccc94d52dfb1d59ee628426192eff5535d6058cb64317ef2992c8b124d0f72af81c9ebfaaa
xprv: xprv9s21ZrQH143K4ATirFwVJe2ZvmmBACdKcK1cWXtDdMhLFViGUDBDWJnWHLYGz5624M8zFsYKGoGt8UVcPMwu9MR5gveP98ZoRq7uLgyr9kF
xpub: xpub661MyMwAqRbcGeYBxHUVfmyJUobfZfMAyXwDJvHqBhEK8J3R1kVU476z8dvaQYCvwWPTQFzVZUuNTAWWdhNQVY2TjTiFseh8j9goy32fcRG
prv m/44'/0'/0'/0/0: KzuPk3PXKdnd6QwLqUCK38PrXoqJfJmACzxTaa6TFKzPJR7H7AFg
pub m/44'/0'/0'/0/0: 1PwKkrF366RdTuYsS8KWEbGxfP4bikegcS
可以通过 https://iancoleman.io/bip39/ 在线测试 BIP-39 并生成 HD 钱包。
请注意,该网站仅供测试使用。生成正式使用的 HD 钱包必须在可信任的离线环境下操作。
三、小结
BIP-39 规范通过使用助记词+口令来生成 HD 钱包的种子,用户只需记忆助记词和口令即可随时恢复HD 钱包。
丢失助记词或者丢失口令均会导致 HD 钱包丢失。
区块链 第9.3章 助记词