HD 钱包助记词生成实战

助记词(Mnemonic Phrase)在加密货币领域,尤其是钱包中,被广泛用于生成和管理私钥。它们是基于 BIP-39 标准生成的。

一. 助记词生成

1.助记词生成原理

1.1.随机熵生成

首先生成一段随机熵(Entropy)。熵的长度可以是 128 到 256 位,并且是 32 的倍数。常见的熵长度有 128 位(12 个助记词)和 256 位(24 个助记词)。

1.2.计算校验和

对熵进行 SHA-256 哈希计算,并取哈希值的前几位作为校验和。校验和的长度取决于熵的长度。例如,128 位熵需要 4 位校验和(因为 128 / 32 = 4),256 位熵需要 8 位校验和。

1.3.组合熵和校验

将校验和附加到熵的末尾,形成一个新的二进制序列。这个序列的总长度为 (熵的长度 + 校验和的长度)。

1.4.分割为助记词索引

将组合后的二进制序列分割成每组 11 位的片段,每个片段转换为一个数字,这个数字作为助记词列表中的索引。

1.5.映射为助记词

使用这些索引从预定义的 2048 个助记词列表(BIP-39 词库)中提取相应的助记词。这些助记词就是最终的助记词短语。

2. 生成过程代码实战

2.1.NodeJs

const bip39 = require('bip39');
const crypto_ts = require('crypto');

// 1. 生成 128 位随机熵
const entropy = crypto_ts.randomBytes(16); // 128 位是 16 字节

// 2. 计算校验和 (SHA-256)
const hash = crypto_ts.createHash('sha256').update(entropy).digest();
const checksum = hash[0] >> 4; // 取前 4 位

// 3. 组合熵和校验和
let bits = '';
for (let i = 0; i < entropy.length; i++) {
    bits += entropy[i].toString(2).padStart(8, '0');
}
bits += checksum.toString(2).padStart(4, '0');

// 4. 分割为助记词索引
const indices = [];
for (let i = 0; i < bits.length; i += 11) {
    const index = parseInt(bits.slice(i, i + 11), 2);
    indices.push(index);
}

// 5. 映射为助记词
const wordlist = bip39.wordlists.english;
const mnemonic = indices.map(index => wordlist[index]).join(' ');

2.2.python

def generate_mnemonic():
    # 1. 生成 128 位随机熵 (16 字节)
    entropy = os.urandom(16)

    # 2. 计算校验和 (SHA-256)
    hash_bytes = hashlib.sha256(entropy).digest()
    checksum_bits = bin(hash_bytes[0])[2:].zfill(8)[:4]  # 取前 4 位

    # 3. 组合熵和校验和
    entropy_bits = ''.join([bin(byte)[2:].zfill(8) for byte in entropy])
    combined_bits = entropy_bits + checksum_bits

    # 4. 分割为助记词索引
    indices = [int(combined_bits[i:i + 11], 2) for i in range(0, len(combined_bits), 11)]

    # 5. 映射为助记词
    wordlist = bip39.INDEX_TO_WORD_TABLE
    mnemonic = ' '.join([wordlist[index] for index in indices])

    return mnemonic


# 生成并打印助记词
mnemonic_phrase = generate_mnemonic()
print(f"Generated mnemonic phrase: {mnemonic_phrase}")

二. 助记词验证过程

1. 助记词验证原理

1.1.检查单词数量

助记词的单词数量通常为 12、15、18、21 或 24 个单词。如果给定的助记词的单词数量不在这些范围内,则助记词无效。

1.2.检查单词是否在词汇表中

每个助记词单词必须存在于 BIP-39 标准的 2048 个单词的词汇表中。如果有任何一个单词不在词汇表中,则助记词无效。

1.3.将助记词转化成位串

将每个助记词单词转换为它在词汇表中的索引。每个索引表示一个 11 位的二进制数。将所有的二进制数连接起来形成一个位串。

1.4.提取种子和校验和

位串的长度应该是助记词单词数乘以 11。例如,12 个单词的助记词对应的位串长度为 132 位。位串的前 128 位是种子,后 4 位是校验和。

1.5.计算校验和

将种子通过 SHA-256 哈希函数计算出一个哈希值,然后取哈希值的前 4 位作为计算得到的校验和。

1.6. 验证校验和

比较提取的校验和和计算得到的校验和。如果两者匹配,则助记词有效,否则无效。

2. 验证过程代码实现

def validate_mnemonic(mnemonic, wordlist):
    words = mnemonic.split()

    # 检查单词数量
    if len(words) not in [12, 15, 18, 21, 24]:
        return False

    # 检查单词是否在词汇表中
    for word in words:
        if word not in wordlist:
            return False

    # 将助记词转化成位串
    binary_string = ''
    for word in words:
        index = wordlist.index(word)
        binary_string += format(index, '011b')

    # 提取种子和校验和
    seed_bits_length = (len(words) * 11) - (len(words) // 3)
    seed_bits = binary_string[:seed_bits_length]
    checksum_bits = binary_string[seed_bits_length:]

    # 计算校验和
    import hashlib
    seed_bytes = int(seed_bits, 2).to_bytes(len(seed_bits) // 8, byteorder='big')
    hash_value = hashlib.sha256(seed_bytes).hexdigest()
    hash_bits = bin(int(hash_value, 16))[2:].zfill(256)
    calculated_checksum = hash_bits[:len(words) // 3]

    # 验证校验和
    return checksum_bits == calculated_checksum

# Example usage:
mnemonic = "legal winner thank year wave sausage worth useful legal winner thank yellow"
wordlist = [...]  # BIP-39 wordlist
is_valid = validate_mnemonic(mnemonic, wordlist)
print("Is valid mnemonic:", is_valid)

三. 编码解码过程

和生成验证过程类似,这里不做过多的赘述

四. 调用 BIP-39 词库生成助记词

1.Node 代码实现

1.1. 代码封装

const bip39  = require('bip39');
const bip32  = require('bip32');

export function createHelpWord(number: number, language: string) {
    switch (language) {
        case 'chinese_simplified':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.chinese_simplified);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.chinese_simplified);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.chinese_simplified);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.chinese_simplified);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.chinese_simplified);
            } else {
                return "unsupported"
            }
        case 'chinese_traditional':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.chinese_traditional);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.chinese_traditional);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.chinese_traditional);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.chinese_traditional);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.chinese_traditional);
            } else {
                return "unsupported"
            }
        case 'english':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.english);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.english);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.english);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.english);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.english);
            } else {
                return "unsupported"
            }
        case 'french':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.french);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.french);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.french);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.french);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.french);
            } else {
                return "unsupported"
            }
        case 'italian':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.italian);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.italian);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.italian);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.italian);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.italian);
            } else {
                return "unsupported"
            }
        case 'japanese':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.japanese);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.japanese);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.japanese);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.japanese);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.japanese);
            } else {
                return "unsupported"
            }
        case 'korean':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.korean);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.korean);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.korean);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.korean);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.korean);
            } else {
                return "unsupported"
            }
        case 'spanish':
            if(number === 12) {
                return bip39.generateMnemonic(128, null, bip39.wordlists.spanish);
            } else if(number === 15) {
                return bip39.generateMnemonic(160, null, bip39.wordlists.spanish);
            } else if(number === 18) {
                return bip39.generateMnemonic(192, null, bip39.wordlists.spanish);
            } else if(number === 21) {
                return bip39.generateMnemonic(224, null, bip39.wordlists.spanish);
            } else if(number === 24) {
                return bip39.generateMnemonic(256, null, bip39.wordlists.spanish);
            } else {
                return "unsupported"
            }
        default:
           return "unsupported"
    }
}

export function wordsToEntropy(mnemonic: string, language: string) {
    switch (language) {
        case 'chinese_simplified':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.chinese_simplified);
        case 'chinese_traditional':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.chinese_traditional);
        case 'english':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.english);
        case 'french':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.french);
        case 'italian':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.italian);
        case 'japanese':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.japanese);
        case 'korean':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.korean);
        case 'spanish':
            return bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.spanish);
        default:
            return "unsupported"
    }
}

export function entropyToWords(encrytMnemonic:string, language:string) {
    switch (language) {
        case 'chinese_simplified':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.chinese_simplified);
        case 'chinese_traditional':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.chinese_traditional);
        case 'english':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.english);
        case 'french':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.french);
        case 'italian':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.italian);
        case 'japanese':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.japanese);
        case 'korean':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.korean);
        case 'spanish':
            return bip39.entropyToMnemonic(encrytMnemonic, bip39.wordlists.spanish);
        default:
            return "unsupported"
    }
}

export function mnemonicToSeed(mnemonic, password){
    return bip39.mnemonicToSeed(mnemonic, password)
}

export function mnemonicToSeedHex(mnemonic, password){
    return bip39.mnemonicToSeedHex(mnemonic, password);
}

export function validateMnemonic(mnemonic, language) {
    switch (language) {
        case 'chinese_simplified':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.chinese_simplified);
        case 'chinese_traditional':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.chinese_traditional);
        case 'english':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.english);
        case 'french':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.french);
        case 'italian':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.italian);
        case 'japanese':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.japanese);
        case 'korean':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.korean);
        case 'spanish':
            return bip39.validateMnemonic(mnemonic, bip39.wordlists.spanish);
        default:
            return "unsupported"
    }
}

1.2.码测试用例

test('test create mnemonic', async () => {
    const mnemonic_12_english = createMnemonic(12, "english")
    const mnemonic_12_chinese = createMnemonic(12, "chinese_simplified")
    console.log(mnemonic_12_chinese)
    console.log(mnemonic_12_chinese)

    const mnemonic_to_entropy = mnemonicToEntropy(mnemonic_12_chinese, "chinese_simplified")
    console.log(mnemonic_to_entropy)


    const entropy_to_mnemonic = entropyToMnemonic(mnemonic_to_entropy, "chinese_simplified")
    console.log(entropy_to_mnemonic)

    const mnemonic_to_seed = mnemonicToSeed(mnemonic_to_entropy, "")
    console.log(mnemonic_to_seed)

    const mnemonic_to_seed_sync = mnemonicToSeedSync(mnemonic_to_entropy, "")
    console.log(mnemonic_to_seed_sync)

    const vm = validateMnemonic(mnemonic_to_entropy, "")
    console.log(vm)
});

2. Python 代码实现

2.1.代码封装

class Bip39Mnemonic:
    def __init__(self):
        pass

    def createMnemonic(self, number):
        mnemonic = bip39.get_entropy_bits(number)
        return mnemonic

    def mnemonicToEntropy(self,  mnemonic):
        decode_words = bip39.decode_phrase( mnemonic)
        return decode_words
    def entropyToMnemonic(self, entropy):
        nemonic_entropy = bip39.encode_bytes(entropy)
        return nemonic_entropy

    def mnemonicToSeed(self, mnemonic):
        nemonic_to_seed = bip39.phrase_to_seed(mnemonic)
        return nemonic_to_seed

    def validateMnemonic(self, mnemonic):
        return bip39.check_phrase(mnemonic)

    def generateMnemonic(self):
        # 1. 生成 128 位随机熵 (16 字节)
        entropy = os.urandom(16)

        # 2. 计算校验和 (SHA-256)
        hash_bytes = hashlib.sha256(entropy).digest()
        checksum_bits = bin(hash_bytes[0])[2:].zfill(8)[:4]  # 取前 4 位

        # 3. 组合熵和校验和
        entropy_bits = ''.join([bin(byte)[2:].zfill(8) for byte in entropy])
        combined_bits = entropy_bits + checksum_bits

        # 4. 分割为助记词索引
        indices = [int(combined_bits[i:i + 11], 2) for i in range(0, len(combined_bits), 11)]

        # 5. 映射为助记词
        wordlist = bip39.INDEX_TO_WORD_TABLE
        mnemonic = ' '.join([wordlist[index] for index in indices])

        return mnemonic

2.2.Python 测试用例

import bip

bip39_mnemonic = bip.Bip39Mnemonic()
mnemonic_phrase = bip39_mnemonic.generateMnemonic()
print(f"Generated mnemonic phrase: {mnemonic_phrase}")

mnemonic_12_phrase = bip39_mnemonic.createMnemonic(12)
print(f"create mnemonic phrase: {mnemonic_phrase}")

全部评论(0)