助记词、私钥、公钥、地址

  1. 通过熵来生成助记词
  2. 通过助记词来生成seed
  3. 通过seed来生成私钥公钥和地址
  4. 派生私钥和地址
    1. normal派生
    2. hardened派生
  5. bip32 path
  6. 使用web3.js
  7. ether.js

通过熵来生成助记词

熵的生成是完全随机的,多少位根据不同的规范来,但必须是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个单词组成的助记词

整体流程可以通过下图直观的体现
039dd96b-7b12-4f63-ac78-7529f5def047-mnemonic.jpg
用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)

运行结果,每次运行的结果都会不一样
06ad7dc8-0a84-4d0c-88bb-f1e8d855ae55-image.png

怎么验证我们生成的助记词是正确的呢,Mnemonic Code Converter这个网站可以线上进行生成助记词,我们只需要把我们生成的熵填进去,他就会帮我们生成助记词

64f1f429-6557-49bf-b91d-63d3772d8ed1-image.png

通过助记词来生成seed

有了助记词,我们生成私钥和地址之前,还需要生成seed,私钥和地址是通过seed来生成的

seed是一个512位的hash值,通过pbkdf2算法来生成,下面函数中有一个salt方法,这个是用户自己定义的密码,默认密码是mnemonic固定值,如果我们填了自己的密码,那么就直接拼到默认密码的后面,然后把助记词和这个密码一起进行pbkdf2计算,hash算法是sha512,迭代次数2048,生成64字节的值,然后转为16进制,最终得到最后的seed

如图所示
e880a356-c872-4f2d-8173-0cbc3f33c766-seed.jpg
代码实现

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)

由于我们每次生成的助记词不一样,所以下面获取到的结果会和上面不一样,当然你也可以手动保存之前生成的熵继续进行操作

a52876e7-abab-4a50-9736-a8f055c52171-image.png

4d64144c-05dd-4b52-a85e-366435026105-image.png

通过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。

如下图所示
74b87cd3-ba30-46b4-985b-50a4f3ab99cf-seedToKey.jpg
代码实现

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派生

151a29fd-c67a-4e54-aada-82193f10cb27-deriveNormalChild.jpg

代码实现

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派生

77ef4223-e522-4827-a3a5-a64912ae8536-deriveHardenedChild.jpg

代码实现

// 需引入与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),理论上可以无限派生,如下图所示
20257296-e9f2-44b3-bdcd-8befcdc141bd-image.png

为了规范秘钥的使用方式,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相同才对

8964f95f-cf93-491e-9d90-1e5fdc62d5d9-image.png

48a56678-84c6-4f95-b94d-766caae6b97c-image.png

5ac02627-988a-44da-b243-34a43ddb4618-image.png

我们看到,地址和我们自己生成的地址是一样的。

使用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