钱包助记词从原理到实践
拥有数字货币资产的人都知道,私钥的备份是非常重要的,有私钥就拥有该私钥对应账户上的所有资产。一般来说私钥都有 256 位,以 64 个字母数字构成的 16 进制字符串表示。直接抄录这 64 个字母数字是很容易搞错的。 助记词是明文私钥的另一种表现形式, 最早是由 BIP39 提案提出, 其目的是为了帮助用户记忆复杂的私钥 (64位的哈希值)。助记词一般由12、15、18、21个单词构成, 这些单词都取自一个固定词库, 其生成顺序也是按照一定算法而来, 所以用户没必要担心随便输入 12 个单词就会生成一个地址。虽然助记词和 Keystore 都可以作为私钥的另一种表现形式, 但与 Keystore 不同的是, 助记词是未经加密的私钥, 没有任何安全性可言, 任何人得到了你的助记词, 可以不费吹灰之力的夺走你的资产。
所谓熵源,即随机性来源。我们生成助记词需要一个 n * 32 位的一个数字作为熵源。熵源的位数越多安全性越高,生成的助记词数量越多。熵源的位数称为ENT(initial entropy length)。ENT 应该在 128 ~ 256 之间。
生成一个私钥在本质上与“在1 到2^256之间选一个数字”无异。只要选取的结果是不可预测或不可重复的,那么选取数字的具体方法并不重要。目前主流使用操作系统底层的随机数生成器来产生256位的熵(随机性)。通常情况下,操作系统随机数生成器由人工的随机源进行初始化,这就是为什么也可能需要不停晃动鼠标几秒钟。
checksum = (ENT / 32)bits of SHA256(initial entropy) 也就是说,熵源通过 SHA256 算法生成的 hash 值的头 ENT / 32 位即为所需校验值
将在第二步生成的校验值添加到第一步生成的熵源后面组成一个新的数字,将该数字每 11 个位为一组进行分组。这样每一组所代表的数字就在 0 ~ 2^11-1 也就是 0 ~ 2047 之间,作为单词表的索引。
以第三步分组得到的索引值从 单词表 里取到相应的单词/字,拼接起来就成为助记词词组了。
下面这张表描述了熵源长度(ENT),校验值长度(CS),与助记词长度(MS)之间的关系。 CS = ENT / 32 MS = (ENT + CS) / 11
ENT(熵源) | CS(校验值) | ENT+CS(熵源+校验值) | MS(助记词) |
---|---|---|---|
128 | 4 | 132 | 12 |
160 | 5 | 165 | 15 |
192 | 6 | 198 | 18 |
224 | 7 | 231 | 21 |
256 | 8 | 264 | 24 |
用户可以输入一个密钥来进一步增强安全性,这样即使助记词被窃取,没有密钥还是无法生成数字货币私钥,进而获取相应资产。 密钥不是必须的,可以用空字符串代替。
PBKDF2 是个密钥导出算法,简单而言就是将salted hash进行多次重复计算,这个次数是可选择的。 DK = PBKDF2(P,S,c,dkLen) 可选项: RPF 基本伪随机函数(hLen表示伪随机函数输出的字节长度) 输入: P 口令,一字节串, 这里为助记词 S 盐值,字节串, 这里为 "mnemonic" + 指定密钥 c 迭代次数,正整数, 这里为2048 dkLen 导出密钥的指定字节长度,正整数,最大约(2^32-1)*hLen 这里为 64 输出: DK 导出密钥,长度dkLen字节
npm install bip39 ethereumjs-wallet ethereumjs-util --save
var bip39 = require('bip39');
var hdkey = require('ethereumjs-wallet/hdkey');
var util = require('ethereumjs-util');
var mnemonic = bip39.generateMnemonic(256, null, bip39.wordlists.chinese_simplified); 取得的 mnemonic code 会像:
详 歪 乐 政 敲 捞 则 乡 干 惯 低 缘 药 胞 泼 暴 源 脸 吸 没 集 如 姆 瓦
先将 mnemonic code 转成 binary 的 seed。
var seed = bip39.mnemonicToSeed(mnemonic);
使用 seed 产生 HD Wallet,就是产生 Master Key 并记录起来。
var hdWallet = hdkey.fromMasterSeed(seed);
产生 Wallet 中第一个帐户的第一组 keypair。可以从 Master Key,根据其路径 m/44'/60'/0'/0/0 推导出来。
var key1 = hdWallet.derivePath("m/44'/60'/0'/0/0")
使用 keypair 中的公钥产生 address。为了避免大家打错 address,一般会用 EIP55: Mixed-case checksum address encoding 再进行编码。
var address1 = util.pubToAddress(key1._hdkey._publicKey, true)
address1 = util.toChecksumAddress(address1.toString('hex'))
最后取得的 Address 会像:
0x01EcB13829fE92409112060c136885a2B73DC94d
看了这篇文章,希望你你已经可以熟练的将助记词应用到自己的产品之中。如有任何疑问,欢迎加入小密圈与我联系,一起践行区块链,磨出伟大的产品。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!