通过熵来生成助记词
熵的生成是完全随机的,多少位根据不同的规范来,但必须是32的整数倍
以生成12个助记词来举个例子,12个助记词一共需要132位的二进制串来生成,因为一个助记词11位,总共需要11乘以12得到132位,那么熵要是几位呢,是132位吗,肯定不行,因为132不是32的整数倍,最近的是128位,那么还差4位来补全,这里的4位,就是checksum
又有个疑问,为什么助记词需要11位,因为bip39规范助记词是由2048个不同的单词组成,11位的二进制数正好可以包含2048,2的11次方正好是2048,助记词列表
checksum是怎么得来的呢,个数又是怎么确定的?bip39规范里明确指出,checksum的长度就是 ent / 32,正好是4。
下面这个表格可以很清晰的得出几者的对应关系
CS = ENT / 32
MS = (ENT + CS) / 11
ENT(熵) | CS(checksum) | ENT+CS | MS(助记词个数) |
---|---|---|---|
128 | 4 | 132 | 12 |
160 | 5 | 165 | 15 |
192 | 6 | 198 | 18 |
224 | 7 | 231 | 21 |
256 | 8 | 264 | 24 |
那么具体怎么通过熵来生成助记词的呢,以12个助记词为例,首先我们获取一个完全随机的128位二进制数,这个就是熵,然后对这个熵进行sha256哈希,再截取前4位,这个4位就是checksum,然后把熵和checksum拼接,得到132位的二进制数,再按照每11位分组,得到12组11位的二进制数,再对把每个二进制数转为10进制,再按照wordlist取每个对应的助记词,得到一个12个单词组成的助记词
整体流程可以通过下图直观的体现
用js代码实现
// 生成助记词
const utils_1 = require("@noble/hashes/utils");
const sha256_1 = require("@noble/hashes/sha256");
const wordlist = require('./english_wordlist.json');
function lpad(str, padString, length) {
while (str.length < length) {
str = padString + str;
}
return str;
}
function bytesToBinary(bytes) {
return bytes.map((x) => lpad(x.toString(2), '0', 8)).join('');
}
function binaryToByte(bin) {
return parseInt(bin, 2);
}
function deriveChecksumBits(entropyBuffer) {
const ENT = entropyBuffer.length * 8;
const CS = ENT / 32;
const hash = sha256_1.sha256(Uint8Array.from(entropyBuffer)); // 对熵进行sha256哈希
return bytesToBinary(Array.from(hash)).slice(0, CS); // 然后截取4位作为checksum
}
function generateEntropy(strength) {
strength = strength || 128;
if (strength % 32 !== 0) {
throw new TypeError(INVALID_ENTROPY);
}
const randomBytes = Buffer.from(utils_1.randomBytes(strength / 8)); // 16字节的随机数 (128位)
const entropyBits = bytesToBinary(Array.from(randomBytes)); // 将16字节的随机数转为二进制
const checksumBits = deriveChecksumBits(randomBytes); // 获得4位的checksum,先将熵进行sha256计算,然后截取4位,长度根据熵的长度计算 ent / 32
return { entropyBits, checksumBits}
}
function entropyToMnemonic(entropy,checksumBits, wordlist) {
const bits = entropy + checksumBits;
const chunks = bits.match(/(.{1,11})/g); // 按11位进行分组,得到12组11位的二进制数
const words = chunks.map((binary) => {
const index = binaryToByte(binary); // 根据二进制数获取助记词的index
console.log(index, binary)
return wordlist[index];
});
return words
}
const entropy = generateEntropy(128)
console.log(entropy)
const {entropyBits, checksumBits} = entropy
const mnemonic = entropyToMnemonic(entropyBits, checksumBits, wordlist)
console.log(mnemonic)
运行结果,每次运行的结果都会不一样
怎么验证我们生成的助记词是正确的呢,Mnemonic Code Converter这个网站可以线上进行生成助记词,我们只需要把我们生成的熵填进去,他就会帮我们生成助记词
通过助记词来生成seed
有了助记词,我们生成私钥和地址之前,还需要生成seed,私钥和地址是通过seed来生成的
seed是一个512位的hash值,通过pbkdf2算法来生成,下面函数中有一个salt方法,这个是用户自己定义的密码,默认密码是mnemonic
固定值,如果我们填了自己的密码,那么就直接拼到默认密码的后面,然后把助记词和这个密码一起进行pbkdf2计算,hash算法是sha512,迭代次数2048,生成64字节的值,然后转为16进制,最终得到最后的seed
如图所示
代码实现
const pbkdf2_1 = require("@noble/hashes/pbkdf2");
const sha512_1 = require("@noble/hashes/sha512");
function salt(password) {
return 'mnemonic' + (password || '');
}
function mnemonicToSeedSync(mnemonic, password) {
const mnemonicBuffer = Uint8Array.from(Buffer.from(mnemonic, 'utf8'));
const saltBuffer = Uint8Array.from(Buffer.from(salt(password), 'utf8'));
const res = pbkdf2_1.pbkdf2(sha512_1.sha512, mnemonicBuffer, saltBuffer, {
c: 2048,
dkLen: 64,
});
return Buffer.from(res).toString('hex');
}
const entropy = generateEntropy(128)
const {entropyBits, checksumBits} = entropy
const mnemonic = entropyToMnemonic(entropyBits, checksumBits, wordlist)
const seed = mnemonicToSeedSync(mnemonic.join(' '), '123')
console.log(seed)
由于我们每次生成的助记词不一样,所以下面获取到的结果会和上面不一样,当然你也可以手动保存之前生成的熵继续进行操作
通过seed来生成私钥公钥和地址
有了seed之后,私钥和地址的生成,需要我们遵循bip32的协议,先生成一个Master Private Key,再用这个Master Private Key派生出来无数个地址和私钥。
Master Key的生成同样使用HMAC-SHA512算法,我们需要加上一个key,即Bitcoin seed
,把这个key和seed一起进行HMAC-SHA512,最后得到一个64bytes的Master Private Extended Key,然后把这个64 bytes的key两等分,前32 bytes就是我们的Master Private Key,后32 bytes是我们的Master Chain Code。
有了Master Key,我们可以使用椭圆算法secp256k1计算出Master Public Key,注意这里计算出来的结果是33 bytes。
如下图所示
代码实现
function hmacSHA512(key, seed) {
var hmac = crypto.createHmac("sha512", key);
var signed = hmac.update(seed).digest('hex');
return signed
}
// 椭圆算法生成公钥
function secp256k1ToPublicKey(IL) {
return Buffer.from(secp256k1.getPublicKey(Buffer.from(IL, 'hex'))).toString('hex')
}
function seedToKey(key, seed) {
const I = hmacSHA512(Buffer.from(key, 'utf-8'), Buffer.from(seed, 'hex'));
const IL = I.slice(0, 64);
const IR = I.slice(64);
// 椭圆算法生成public key
const publicKey = secp256k1ToPublicKey(IL)
const extendedPrivateKey = I.toString('hex')
const masterPrivateKey = IL.toString('hex')
const masterChainCode = IR.toString('hex')
return {
extendedPrivateKey,
masterPrivateKey,
masterChainCode,
publicKey
}
}
const { masterPrivateKey, masterChainCode, publicKey } = seedToKey('Bitcoin seed','aae763e1121f0055f66e7f7405d1ea2caa040fd7c6b85ce381c1662c1f1ee705d99f03310363c8581d4bbc0ceb3a7e8a4dcfb2eebfa64e05724d99298be400f7')
console.log(masterPrivateKey) // e9ec65a12ecc438c3e2177e925cd65df5b7c49c4130d670fe495d6cbd3ccbec6
console.log(masterChainCode) // 59c345d031203b83b632a95b278731b50c324458b7d4c17ae2e03e6877db7207
console.log(publicKey) // 02bffc59925aa6ffd7ec3d132a180de94bf4ef50a9c910c68aa62297446957be63
派生私钥和地址
有了Master Key扩展秘钥之后,我们就可以派生出大量的私钥和公钥,我们有两种派生方式,一种是normal派生,一种是hardened派生,通过一个index标记是noraml派生还是hardened派生,normal派生的index取值在0到2^31 -1之间,而hardend派生的index在2^31 到 2^32 -1之间
normal派生和hardened派生除了index的取值不同之外,其内部实现的算法也有一点点稍微不用,hardened派生使用的是私钥,而normal派生使用的是公钥,下面两图分别展示其实现的方式
normal派生
代码实现
const BigNumber = require('bignumber.js');
const { secp256k1 } = require('@noble/curves/secp256k1')
const n = secp256k1.CURVE.n // 115792089237316195423570985008687907852837564279074904382605163141518161494337n
function hmacSHA512(key, seed) {
var hmac = crypto.createHmac("sha512", key);
var signed = hmac.update(seed).digest('hex');
return signed
}
// 椭圆算法生成公钥
function secp256k1ToPublicKey(IL) {
return Buffer.from(secp256k1.getPublicKey(Buffer.from(IL, 'hex'))).toString('hex')
}
function deriveNormalChild(privateKey, publicKey, chainCode, index) {
if (index >= 2 ** 31 || index < 0) {
throw new Error('index 只能在 [0,2147483648),即 (2**0 - 2**31-1) 之间取值')
}
// 公钥和index拼接(16进制下拼接),对于normalChild,index在 2**0 - 2**31 之间取值
const data = Buffer.alloc(37);
Buffer.from(publicKey, 'hex').copy(data, 0)
data.writeUInt32BE(index, 33);
const I = hmacSHA512(Buffer.from(chainCode, 'hex'), Buffer.from(data, 'hex'))
const IL = I.slice(0, 64);
const IR = I.slice(64);
const normal_child_private_key = new BigNumber(IL, 16).plus(new BigNumber(privateKey, 16)).modulo(new BigNumber(n)).toString(16) // (IL + parent_key) % n
const normal_child_public_key = secp256k1ToPublicKey(normal_child_private_key)
const normal_child_chain_code = IR
return {
normal_child_private_key,
normal_child_public_key,
normal_child_chain_code
}
}
hardened派生
代码实现
// 需引入与normal派生相同的公共代码
function deriveHardenedChild(privateKey, chainCode, index) {
if (index >= 2 ** 32 || index < 2 ** 31) {
throw new Error('index 只能在 [2147483648,4294967296),即 (2**31 - 2**32-1) 之间取值')
}
// 公钥和index拼接(16进制下拼接)
const data = Buffer.alloc(37);
data[0] = 0x00;
Buffer.from(privateKey, 'hex').copy(data, 1) // 0x00 + private_key + index
data.writeUInt32BE(index, 33);
const I = hmacSHA512(Buffer.from(chainCode, 'hex'), Buffer.from(data, 'hex'))
const IL = I.slice(0, 64);
const IR = I.slice(64);
const hardened_child_private_key = new BigNumber(IL, 16).plus(new BigNumber(privateKey, 16)).modulo(new BigNumber(n)).toString(16) // (IL + parent_key) % n
console.log('hardened_child_private_key', hardened_child_private_key)
const hardened_child_public_key = secp256k1ToPublicKey(hardened_child_private_key)
const hardened_child_chain_code = IR
return {
hardened_child_private_key,
hardened_child_public_key,
hardened_child_chain_code
}
}
bip32 path
由于我们的派生机制,每层都可以派生出2^32个地址(包含normal 和 hardened),理论上可以无限派生,如下图所示
为了规范秘钥的使用方式,bip44约定了各个层级代表的含义,即m / purpose' / coin_type' / account' / change / address_index
,路径规范中用'
标记的表示是hardened派生,没有的则为normal派生,下面来看看路径每层分别表示什么东西
- 第一层是m,即master Key,只会存在一个,由seed生成,然后在这个master key下再进行派生
- 第二层是purpose,是一个常量,用来标记是使用何种规范,44表示使用的是bit43提案来
- 第三层是coin type,标记使用的是何种加密货币,需要去SLIP-0044 : Registered coin types for BIP-0044注册
- 第四层是account,这一层将密钥空间分割为独立的用户身份,钱包就永远不会混合不同帐户之间的代币
- 第五层是change,只有两个值,0或者1,常量0用于外链,常量1用于内链,外部链用于在钱包外部可见的地址,内部链用于钱包外部不可见的地址,用于返回交易更改。
- 第六层是address_index,地址从索引 0 开始按顺序递增的方式编号。该数字用作 BIP32 推导中的子索引
更详细的请看bip44
有了这个规范我们来验证一下我们代码的实现有没有问题
下面贴出全量代码,里面注释的计算结果部分可以忽略,因为每次生成的都不一样
const crypto = require('crypto')
const { secp256k1 } = require('@noble/curves/secp256k1')
const BigNumber = require('bignumber.js');
const base_1 = require("@scure/base");
const sha256_1 = require("@noble/hashes/sha256");
const sha3 = require("js-sha3");
const { ethers, utils } = require("ethers")
const utils_1 = require("@noble/hashes/utils");
const wordlist = require('./english_wordlist.json');
const pbkdf2_1 = require("@noble/hashes/pbkdf2");
const sha512_1 = require("@noble/hashes/sha512");
function lpad(str, padString, length) {
while (str.length < length) {
str = padString + str;
}
return str;
}
function bytesToBinary(bytes) {
return bytes.map((x) => lpad(x.toString(2), '0', 8)).join('');
}
function binaryToByte(bin) {
return parseInt(bin, 2);
}
function deriveChecksumBits(entropyBuffer) {
const ENT = entropyBuffer.length * 8;
const CS = ENT / 32;
const hash = sha256_1.sha256(Uint8Array.from(entropyBuffer)); // 对熵进行sha256哈希
return bytesToBinary(Array.from(hash)).slice(0, CS); // 然后截取4位作为checksum
}
function generateEntropy(strength) {
strength = strength || 128;
if (strength % 32 !== 0) {
throw new TypeError(INVALID_ENTROPY);
}
const randomBytes = Buffer.from(utils_1.randomBytes(strength / 8)); // 16字节的随机数 (128位)
const entropyBits = bytesToBinary(Array.from(randomBytes)); // 将16字节的随机数转为二进制
const checksumBits = deriveChecksumBits(randomBytes); // 获得4位的checksum,先将熵进行sha256计算,然后截取4位,长度根据熵的长度计算 ent / 32
return { entropyBits, checksumBits }
}
function entropyToMnemonic(entropy, checksumBits, wordlist) {
const bits = entropy + checksumBits;
const chunks = bits.match(/(.{1,11})/g); // 按11位进行分组,得到12组11位的二进制数
const words = chunks.map((binary) => {
const index = binaryToByte(binary); // 根据二进制数获取助记词的index
return wordlist[index];
});
return words
}
const entropy = generateEntropy(128)
const { entropyBits, checksumBits } = entropy
let Mnemonic = entropyToMnemonic(entropyBits, checksumBits, wordlist)
function salt(password) {
return 'mnemonic' + (password || '');
}
function mnemonicToSeedSync(mnemonic, password) {
const mnemonicBuffer = Uint8Array.from(Buffer.from(mnemonic, 'utf8'));
const saltBuffer = Uint8Array.from(Buffer.from(salt(password), 'utf8'));
const res = pbkdf2_1.pbkdf2(sha512_1.sha512, mnemonicBuffer, saltBuffer, {
c: 2048,
dkLen: 64,
});
return Buffer.from(res).toString('hex');
}
// Mnemonic = [
// 'slam', 'banner',
// 'pattern', 'result',
// 'resist', 'release',
// 'call', 'sure',
// 'sustain', 'engine',
// 'symbol', 'assist'
// ]
console.log(Mnemonic.join(' '))
const seed = mnemonicToSeedSync(Mnemonic.join(' '))
console.log('seed', seed)
const _bs58check = (0, base_1.base58check)(sha256_1.sha256);
const bs58check = {
encode: (data) => _bs58check.encode(Uint8Array.from(data)),
decode: (str) => Buffer.from(_bs58check.decode(str)),
};
const n = secp256k1.CURVE.n // 115792089237316195423570985008687907852837564279074904382605163141518161494337n
// 通过seed生成Extended Key
function hmacSHA512(key, seed) {
var hmac = crypto.createHmac("sha512", key);
var signed = hmac.update(seed).digest('hex');
return signed
}
// 椭圆算法生成公钥
function secp256k1ToPublicKey(IL) {
return Buffer.from(secp256k1.getPublicKey(Buffer.from(IL, 'hex'))).toString('hex')
}
function seedToKey(key, seed) {
const I = hmacSHA512(Buffer.from(key, 'utf-8'), Buffer.from(seed, 'hex'));
const IL = I.slice(0, 64);
const IR = I.slice(64);
// 椭圆算法生成public key
const publicKey = secp256k1ToPublicKey(IL)
const extendedPrivateKey = I.toString('hex')
const masterPrivateKey = IL.toString('hex')
const masterChainCode = IR.toString('hex')
return {
extendedPrivateKey,
masterPrivateKey,
masterChainCode,
publicKey
}
}
function deriveNormalChild(privateKey, publicKey, chainCode, index) {
if (index >= 2 ** 31 || index < 0) {
throw new Error('index 只能在 [0,2147483648),即 (2**0 - 2**31-1) 之间取值')
}
// 公钥和index拼接(16进制下拼接),对于normalChild,index在 2**0 - 2**31 之间取值
const data = Buffer.alloc(37);
Buffer.from(publicKey, 'hex').copy(data, 0)
data.writeUInt32BE(index, 33);
const I = hmacSHA512(Buffer.from(chainCode, 'hex'), Buffer.from(data, 'hex'))
const IL = I.slice(0, 64);
const IR = I.slice(64);
const normal_child_private_key = new BigNumber(IL, 16).plus(new BigNumber(privateKey, 16)).modulo(new BigNumber(n)).toString(16) // (IL + parent_key) % n
const normal_child_public_key = secp256k1ToPublicKey(normal_child_private_key)
const normal_child_chain_code = IR
return {
normal_child_private_key,
normal_child_public_key,
normal_child_chain_code
}
}
function deriveHardenedChild(privateKey, chainCode, index) {
if (index >= 2 ** 32 || index < 2 ** 31) {
throw new Error('index 只能在 [2147483648,4294967296),即 (2**31 - 2**32-1) 之间取值')
}
// 公钥和index拼接(16进制下拼接)
const data = Buffer.alloc(37);
data[0] = 0x00;
Buffer.from(privateKey, 'hex').copy(data, 1) // 0x00 + private_key + index
data.writeUInt32BE(index, 33);
const I = hmacSHA512(Buffer.from(chainCode, 'hex'), Buffer.from(data, 'hex'))
const IL = I.slice(0, 64);
const IR = I.slice(64);
const hardened_child_private_key = new BigNumber(IL, 16).plus(new BigNumber(privateKey, 16)).modulo(new BigNumber(n)).toString(16) // (IL + parent_key) % n
console.log('hardened_child_private_key', hardened_child_private_key)
const hardened_child_public_key = secp256k1ToPublicKey(hardened_child_private_key)
const hardened_child_chain_code = IR
return {
hardened_child_private_key,
hardened_child_public_key,
hardened_child_chain_code
}
}
function deriveNormalChildByPublicKey(publicKey, chainCode, index) {
if (index >= 2 ** 31 || index < 0) {
throw new Error('index 只能在 [0,2147483648),即 (2**0 - 2**31-1) 之间取值')
}
const data = Buffer.alloc(37);
Buffer.from(publicKey, 'hex').copy(data, 0)
data.writeUInt32BE(index, 33);
const I = hmacSHA512(Buffer.from(chainCode, 'hex'), Buffer.from(data, 'hex'))
const IL = I.slice(0, 64);
const IR = I.slice(64);
const publicKey_child_chain_code = IR
const point_hmac = secp256k1ToPublicKey(IL)
const Point = secp256k1.ProjectivePoint;
const public_potint = Point.fromHex(publicKey)
const hmac_point = Point.fromHex(point_hmac)
const a = hmac_point.add(public_potint)
const publicKey_child_public_key = a.toHex()
return {
publicKey_child_public_key,
publicKey_child_chain_code
}
}
// 序列化格式
// 扩展的公钥和私钥如下序列化:
// 4字节:版本字节(mainnet:0x0488B21E public,0x0488ADE4 private; testnet:0x043587CF public,0x04358394 private)
// 1字节:深度:主节点为0x00,级别1派生密钥为0x01。
// 4字节:父密钥的指纹(如果主密钥为0x00000000)
// 4字节:子数字。这是对于i在xi = xpar / i中的ser32(i),其中xi是键序列化。 (如果主密钥为0x00000000)
// 32字节:链码
// 33字节:公钥或私钥数据(公钥的serP(K),私钥的0x00 || ser256(k))
const network = {
public: 0x0488b21e,
private: 0x0488ade4,
testPublic: 0x04358394,
testPrivate: 0x043587cf
}
function serialize(net = 'public', depth = 0x00, parentFingerprint = 0x00000000, index = 0x00000000, chainCode, key, isPrivateKey = true) {
const version = network[net]
const buffer = Buffer.allocUnsafe(78);
// 4 bytes: version bytes
buffer.writeUInt32BE(version, 0);
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
buffer.writeUInt8(depth, 4);
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
buffer.writeUInt32BE(parentFingerprint, 5);
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
// This is encoded in big endian. (0x00000000 if master key)
buffer.writeUInt32BE(index, 9);
// 32 bytes: the chain code
Buffer.from(chainCode, 'hex').copy(buffer, 13);
// 33 bytes: the public key or private key data
if (isPrivateKey) {
// 0x00 + k for private keys
buffer.writeUInt8(0, 45);
Buffer.from(key, 'hex').copy(buffer, 46);
}
else {
// encoding for public keys
Buffer.from(key, 'hex').copy(buffer, 45);
}
return bs58check.encode(buffer);
}
// const { masterPrivateKey, masterChainCode, publicKey } = seedToKey('Bitcoin seed','aae763e1121f0055f66e7f7405d1ea2caa040fd7c6b85ce381c1662c1f1ee705d99f03310363c8581d4bbc0ceb3a7e8a4dcfb2eebfa64e05724d99298be400f7')
// serialize('private', 0x00, 0x00000000, 0x00000000, '59c345d031203b83b632a95b278731b50c324458b7d4c17ae2e03e6877db7207', 'e9ec65a12ecc438c3e2177e925cd65df5b7c49c4130d670fe495d6cbd3ccbec6', true) // xprv9s21ZrQH143K2xC1jNamwmpgTJBwApRyYd8knmoADFbS6LWQ6S3tfCQQqTrpoffTTCMW1regwrydv5zdEhydoHMiLAJKS7CsBtsZnz8JjcD
// deriveNormalChild(masterPrivateKey, publicKey, masterChainCode, 0)
// deriveNormalChildByPublicKey(publicKey, masterChainCode, 0)
// deriveHardenedChild(masterPrivateKey, masterChainCode, 2147483648)
// bip44
// 派生路径 m / 44' / 60' / 0' / 0 / 0
// m
const { masterPrivateKey: m_masterPrivateKey, masterChainCode: m_masterChainCode } = seedToKey('Bitcoin seed', seed)
console.log('m')
console.log('master private key\t', m_masterPrivateKey) //e9ec65a12ecc438c3e2177e925cd65df5b7c49c4130d670fe495d6cbd3ccbec6
console.log('master chain code\t', m_masterChainCode) //59c345d031203b83b632a95b278731b50c324458b7d4c17ae2e03e6877db7207
console.log("\n")
// m / 44'
console.log(`m / 44'`)
const {
hardened_child_private_key: m_44_hardened_child_private_key,
hardened_child_public_key: m_44_hardened_child_public_key,
hardened_child_chain_code: m_44_hardened_child_chain_code
} = deriveHardenedChild(m_masterPrivateKey, m_masterChainCode, 2147483648 + 44)
console.log('private key\t', m_44_hardened_child_private_key) // ae73d6d803b8b33226a0932381fde2b560da61b84b1d28e581054b88ccddec35
console.log('public key\t', m_44_hardened_child_public_key) // 02d4233f730c1ecfeed512e474ae5cdcc5762a626dd4f9c7c1e1498e43fbed58f4
console.log('chain code\t', m_44_hardened_child_chain_code) // 9011690ea7eec3fad3177de3ab67b652c1d1676807879aff8784633e950d6790
console.log("\n")
// m / 44' / 60'
console.log(`m / 44' / 60'`)
const {
hardened_child_private_key: m_44_60_hardened_child_private_key,
hardened_child_public_key: m_44_60_hardened_child_public_key,
hardened_child_chain_code: m_44_60_hardened_child_chain_code
} = deriveHardenedChild(m_44_hardened_child_private_key, m_44_hardened_child_chain_code, 2147483648 + 60)
console.log('private key\t', m_44_60_hardened_child_private_key) // a60917bd2d2b742b74922ab851cc70c180d2db2f1ffd31602f2d5b8d464ab9dd
console.log('public key\t', m_44_60_hardened_child_public_key) // 03648dce0172f2cf3f21b55a61927d8f0348e38c5b387721a66bf053f9e781e364
console.log('chain code\t', m_44_60_hardened_child_chain_code) // c0da7f9aab3ee03f4dd7b07b1afe036a2c79b44feb59e4d1ac7f9acc0170ac59
console.log("\n")
// m / 44' / 60' / 0'
console.log(`m / 44' / 60' / 0'`)
console.log(m_44_60_hardened_child_private_key, m_44_60_hardened_child_chain_code)
const {
hardened_child_private_key: m_44_60_0_hardened_child_private_key,
hardened_child_public_key: m_44_60_0_hardened_child_public_key,
hardened_child_chain_code: m_44_60_0_hardened_child_chain_code
} = deriveHardenedChild(m_44_60_hardened_child_private_key, m_44_60_hardened_child_chain_code, 2147483648)
console.log('private key\t', m_44_60_0_hardened_child_private_key) // 316ff35d15955ed1bc846d73397f9df8c11d2bc8d73d9c8119f536eccfeec54b
console.log('public key\t', m_44_60_0_hardened_child_public_key) // 033098fb7a20647516022f9badc21e32c8c1ae49051a7026ea9609a0f3b110c66c
console.log('chain code\t', m_44_60_0_hardened_child_chain_code) // ab69fce2ac6afe4a5e453debb702b3fb5edad621e8fdd40f05a228ab5dd4b7f2
console.log("\n")
// m / 44' / 60' / 0' / 0
console.log(`m / 44' / 60' / 0' / 0`)
const {
normal_child_private_key: m_44_60_0_0_normal_child_private_key,
normal_child_public_key: m_44_60_0_0_normal_child_public_key,
normal_child_chain_code: m_44_60_0_0_normal_child_chain_code
} = deriveNormalChild(m_44_60_0_hardened_child_private_key, m_44_60_0_hardened_child_public_key, m_44_60_0_hardened_child_chain_code, 0)
console.log('private key\t', m_44_60_0_0_normal_child_private_key) // 7c17cb84ad65ce2c4ee939fd2bb3bde3b11bd8026aaa470014e0813361fdace2
console.log('public key\t', m_44_60_0_0_normal_child_public_key) // 03db321ab2fad6d13d243cced48ebfd2134689e33f35b5dab4c118a845a623b325
console.log('chain code\t', m_44_60_0_0_normal_child_chain_code) // 9d9a962d748e34a22d0fc6bafb768a5a580a7936891497929f8f78196ce458ef
console.log("\n")
// m / 44' / 60' / 0' / 0 / 0 ===> address 1
console.log(`m / 44' / 60' / 0' / 0 / 0 \t\t address1`)
const {
normal_child_private_key: m_44_60_0_0_0_normal_child_private_key,
normal_child_public_key: m_44_60_0_0_0_normal_child_public_key,
normal_child_chain_code: m_44_60_0_0_0_normal_child_chain_code
} = deriveNormalChild(m_44_60_0_0_normal_child_private_key, m_44_60_0_0_normal_child_public_key, m_44_60_0_0_normal_child_chain_code, 0)
console.log('private key\t', m_44_60_0_0_0_normal_child_private_key) // 82366d0c8da4fdd0e0466b0458fe5dc7d63dff2753b890e79968e10e2ade5a33 ==> 0xae61f8662B52E78FB8246A1EBb5b6a853a2E1180
console.log('public key\t', m_44_60_0_0_0_normal_child_public_key) // 027771e7a5d367fe8390a51a29d277b1ce4de23034c68c551a83f927f4da6381ed
console.log('chain code\t', m_44_60_0_0_0_normal_child_chain_code) // 700a8c04ccff4d820bbd5857c94d1ef7b82223355e9842bcfa2b7a9f2a694bc6
console.log('address \t', new ethers.Wallet(m_44_60_0_0_0_normal_child_private_key).address)
console.log("\n")
// m / 44' / 60' / 0' / 0 / 1 ===> address 2
console.log(`m / 44' / 60' / 0' / 0 / 1 \t\t address2`)
const {
normal_child_private_key: m_44_60_0_0_1_normal_child_private_key,
normal_child_public_key: m_44_60_0_0_1_normal_child_public_key,
normal_child_chain_code: m_44_60_0_0_1_normal_child_chain_code
} = deriveNormalChild(m_44_60_0_0_normal_child_private_key, m_44_60_0_0_normal_child_public_key, m_44_60_0_0_normal_child_chain_code, 1)
console.log('private key\t', m_44_60_0_0_1_normal_child_private_key) // f4e7a23354f92209bfde311b4e23bbf49cd33180a42ba413ee32117904e4bebf ==> 0x3403064DB6E3Bc9EB8E3779Bea018d760488B61a
console.log('public key\t', m_44_60_0_0_1_normal_child_public_key) // 02ee93da25a8b498f1c325b5db18ad5bb1b02fe83b1da48bbf96fca3acffcaea70
console.log('chain code\t', m_44_60_0_0_1_normal_child_chain_code) // dde70e0b0f1045ba36ba2c21d3ae957f249f715b42bdeaad319b16aa0b62b97d
console.log('address \t', new ethers.Wallet(m_44_60_0_0_1_normal_child_private_key).address)
console.log("\n")
// m / 44' / 60' / 0' / 0 / 2 ===> address 3
console.log(`m / 44' / 60' / 0' / 0 / 2 \t\t address3`)
const {
normal_child_private_key: m_44_60_0_0_2_normal_child_private_key,
normal_child_public_key: m_44_60_0_0_2_normal_child_public_key,
normal_child_chain_code: m_44_60_0_0_2_normal_child_chain_code
} = deriveNormalChild(m_44_60_0_0_normal_child_private_key, m_44_60_0_0_normal_child_public_key, m_44_60_0_0_normal_child_chain_code, 2)
console.log('private key\t', m_44_60_0_0_2_normal_child_private_key) // 37b8d8babc074af0289c0ca08db9f157295450f083c9b8ed257dc6638897a48a ==> 0x5BABEbEacBB48cb89169b026163567C2766d522a
console.log('public key\t', m_44_60_0_0_2_normal_child_public_key) // 03c5b5c1812d9158ba9f1603ff49664ab5e124ac0bf23b9565f8d77231ab32fee1
console.log('chain code\t', m_44_60_0_0_2_normal_child_chain_code) // 9f4da6bf7b5cb56d1d31b15c59f3864c168d7446f64bcc85b580b4062fee0de0
console.log('address \t', new ethers.Wallet(m_44_60_0_0_2_normal_child_private_key).address)
console.log("\n")
// ...
// 使用ethers.js进行地址验证
// const private_key = "1f760218ed5e78469c2f5629f435c5e8125559444d80f77fb18050ac856d2305";
// const my_wallet = new ethers.Wallet(private_key)
// const address = my_wallet.address
// console.log(address) // 0xae61f8662B52E78FB8246A1EBb5b6a853a2E1180 0xd796160F39B8FCAeeB9bb33aDF2597b7e9A375F0
// const publicKey = my_wallet.publicKey
// console.log(publicKey)
注意我们的执行过程,每一层都是根据上一层的结果来的,并且我们的coin type是60,代表的是以太坊
Mnemonic champion cabbage toy struggle gesture hat thing weasel door digital hard skull
seed fc72cb09fe4945760c07d58c8d6ca0b41096e832620ceaec6f45b000cffc3dd730c31dffa9da0dbbbcc6b0cbca0baea68a90ccef166a36db5d3064e78be3e1c0
m
master private key 7b195fbe3c22e4a5bea3dec458d569dcfc651b5f3d0deb4726ff941841a80e8d
master chain code 73e2e8c403b81b7519b4397b04f6fec33842671d590743dc6d314a7b568710d6
m / 44'
private key 29821685d17b9290c7673cc2023f5483b985e37a0417393e3d99d17fa3d520d4
public key 0371db0a35a9663b94149c2c67264b0118b43ab3582ba9ea786529155eb27a1e1c
chain code 0bacc0445d5b07157019057629a489f55b1c692931b7768a51fddf8104098704
m / 44' / 60'
private key 7fd9c8d8a4f08d89d040d73cacaa226ead205f5935658d4d18621b7d4515509f
public key 027709505e63414854e726c4f5dc41692c78124bf2b31408e12c9e02f2b12aedbd
chain code ae3dcd9307342354a3d78ebacd787f0106e8ea61a8b1546ae83815159527529e
m / 44' / 60' / 0'
private key 8ac2b9b018f5bccae139fcaf30b4da0a991137a55f1af2f89dc61265d42cbd3d
public key 037407142f6a78b29da97caa84890cf68fc41185dea144ebc2c9d426cbf88acd13
chain code 0776d860eb346f070fdf11aeff5266aa3e1c107bbef038a9886d2064e9648f7f
m / 44' / 60' / 0' / 0
private key 1a52ada38fbbbf012b8f4687eed2a1866a30ee0ddfa5580285fa47c99821b207
public key 0321f4cd9f9b7612c5df4a35c2efb89344569fd180a1f2d6bbcf9fa3669eadfffc
chain code 392e55acd44d827b22151ebcdd79e667f51b245468a429dfbb08516c97b5a04b
m / 44' / 60' / 0' / 0 / 0 address1
private key 5d59d31e3a267fb8f6bb7b7ac4a77d539285efe878264254cc9dd83a77c5e8f2
public key 02bb3b7a4cd7925966e568dd723229b6728e23bee5c2cefa2301652c6b05662333
chain code f8e1d50841da90ea94fc262f930167e254fe83d87bed64560d56129cff935beb
address 0xa63e0541B6139ED1f49572AD4701C2bfB62085d6
m / 44' / 60' / 0' / 0 / 1 address2
private key 3fdb30a1380601a7b526e040340f6043461e43890a833cd4c9062e04a01ce8b5
public key 024f25e1c9255510f2be83a1ed49d370c1072e66de54781a61d2a2d063d8340d69
chain code 18783873a4314ab8afb2e155b5d1b89898990b8688e736b215bf6e8c3bd4af8b
address 0xFfee95e8803C42B77655D28Ea9af0126b2f4E9d9
m / 44' / 60' / 0' / 0 / 2 address3
private key a3d8a45d84e3145b20530b052fd3f172c960a2f307aac1448d94cf0b47400dc9
public key 0325b422277388f364a1ff49e3b282b41b93cfefaa6d10d8b30c71ed168233f151
chain code 4506027ab095aa232304f1be2369d80111b5040dac977ebe3e00b0ee497afcc0
address 0x9a1aeDB88cd378b6cCaf94CEa4D3aeb639Cea77E
我们把结果中的助记词导入到小狐狸钱包中,小狐狸钱包默认是从0开始生成地址的,那我们导入之后,也生成三个地址,结果应该是和上面的三个address相同才对
我们看到,地址和我们自己生成的地址是一样的。
使用web3.js
我们洋洋洒洒的写了一大篇,其实我们写的程序还是有很多考虑不足的地方,不过好在已经有人帮我们造好了轮子,我们不需要重复的造,上面的代码只作为学习,不能在生产开发中使用,在生产中我们还是使用现成的库比较好一点,会更加安全,这里介绍一下web3.js。
使用web3.js,我们生成地址只需要调用一个方法
const { Web3 } = require('web3')
const web3 = new Web3()
const accounts = web3.eth.accounts.wallet.create(3)
console.log(accounts)
我们创建了3个钱包地址,并且每个地址都会有签名和加密的方法,十分方便。
Wallet(3) [
{
address: '0x2274356Ce5764CeFF5D4753D0e52034187E0ff2f',
privateKey: '0x2767973bc6a101bb7c7b5719caf500df8fc37274cea552b36122acb5786a96d9',
signTransaction: [Function: signTransaction],
sign: [Function: sign],
encrypt: [Function: encrypt]
},
{
address: '0x20a619b5F97603d761e396AfB312D008B5383fE7',
privateKey: '0x36a382a825fb4744e69556062061d3dda21e143df849f2baad38ada63dcf0dca',
signTransaction: [Function: signTransaction],
sign: [Function: sign],
encrypt: [Function: encrypt]
},
{
address: '0xfC3B3a08Ed47060E7201c271040d3442385d6Cc4',
privateKey: '0x59c6929e9fb9137ab11d2fb59d8ec3a667fb6915df886ca305d03caf8e26a1d4',
signTransaction: [Function: signTransaction],
sign: [Function: sign],
encrypt: [Function: encrypt]
},
_accountProvider: {
create: [Function: createWithContext],
privateKeyToAccount: [Function: privateKeyToAccountWithContext],
decrypt: [Function: decryptWithContext]
},
_addressMap: Map(3) {
'0x2274356ce5764ceff5d4753d0e52034187e0ff2f' => 0,
'0x20a619b5f97603d761e396afb312d008b5383fe7' => 1,
'0xfc3b3a08ed47060e7201c271040d3442385d6cc4' => 2
},
_defaultKeyName: 'web3js_wallet'
]
ether.js
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com