起因

我想批量生成的方式,获取到一个后缀比较好的地址,使用 Python 实现了脚本也成功拿到了一个比较新颖的地址。关于助记词和私钥的保存,我是分成两部分保存的,也就是使用两个密码保存工具 A 和 B,A 保存一半助记词,B 保存另一半助记词。然后我在复制助记词的时候,复制漏掉了一个单词,不过幸好私钥还在。

从理论上来讲,有了私钥也就不需要助记词了,因为私钥本身就是助记词计算出来的,私钥也能正常管理钱包。

但是,私钥总归是不方便,助记词可以帮助我记忆。

原理

我有私钥和11 位助记词,只需要通过暴力计算一个一个单词去匹配,直到生成的私钥和我手上的私钥一致就行了。

动手

我先找到了之前用来生成钱包的脚本如下:

from bip_utils import Bip39SeedGenerator, Bip44, Bip44Changes, Bip44Coins, Bip44Levels
from eth_keys import keys
from mnemonic import Mnemonic


def generate_mnemonic(num_words=12):
    mnemo = Mnemonic("english")
    strength = 128 if num_words == 12 else 256
    return mnemo.generate(strength=strength)


def mnemonic_to_seed(mnemonic, passphrase=""):
    seed_generator = Bip39SeedGenerator(mnemonic)
    return seed_generator.Generate(passphrase)


def generate_eth_wallet_from_mnemonic(mnemonic, passphrase=""):
    seed = mnemonic_to_seed(mnemonic, passphrase)

    # 使用BIP44派生路径 m/44'/60'/0'/0/0
    bip44_mst_ctx = Bip44.FromSeed(seed, Bip44Coins.ETHEREUM)
    bip44_acc_ctx = bip44_mst_ctx.Purpose().Coin().Account(0)
    bip44_chg_ctx = bip44_acc_ctx.Change(Bip44Changes.CHAIN_EXT)
    bip44_addr_ctx = bip44_chg_ctx.AddressIndex(0)

    private_key_bytes = bip44_addr_ctx.PrivateKey().Raw().ToBytes()
    private_key = keys.PrivateKey(private_key_bytes)
    public_key = private_key.public_key
    address = public_key.to_checksum_address()

    return {
        "mnemonic": mnemonic,
        "private_key": private_key,
        "public_key": public_key,
        "address": address,
    }


if __name__ == "__main__":
    mnemonic = generate_mnemonic(num_words=12)
    wallet = generate_eth_wallet_from_mnemonic(mnemonic)
    print(f"{wallet['mnemonic']}\n")
    print(f"{wallet['private_key']}\n")
    print(f"{wallet['public_key']}\n")
    print(f"{wallet['address']}\n\n")

mnemonic_to_seedgenerate_eth_wallet_from_mnemonic 函数可以直接复用的,我只需要将 12 位助记词按顺序组合,再依次调用这两个方法即可。

一共有 2048 个单词,通过 mnemo.wordlist 获取,关于为什么是 2048 个单词在本文结尾会科普到。

实现一个 find_missing_word 函数,依次接收三个参数:已知的助记词、助记词缺少的下标、已知的私钥,具体实现如下:

def find_missing_word(known_words, missing_index, known_private_key):
    mnemo = Mnemonic("english")
    word_list = mnemo.wordlist

    for word in word_list:
        candidate_mnemonic = known_words[:]
        candidate_mnemonic[missing_index] = word
        candidate_mnemonic_str = " ".join(candidate_mnemonic)

        try:
            wallet = generate_eth_wallet_from_mnemonic(candidate_mnemonic_str)
        except Exception as e:
            continue

        if wallet["private_key"] == known_private_key:
            return candidate_mnemonic_str

    return None

假如现有有一个钱包缺少了最后一个助记词,剩余11 个助记词和私钥如下:

twelve pact library equip steak cherry choice caught method treat door
0xFF427470D1C41BFF7B8D0A45D98A37FBF778D7C073092B4AE1E18F624F88A8C9

让我们来运行完整的代码如下:

from bip_utils import Bip39SeedGenerator, Bip44, Bip44Changes, Bip44Coins, Bip44Levels
from eth_keys import keys
from mnemonic import Mnemonic


def mnemonic_to_seed(mnemonic, passphrase=""):
    seed_generator = Bip39SeedGenerator(mnemonic)
    return seed_generator.Generate(passphrase)


def generate_eth_wallet_from_mnemonic(mnemonic, passphrase=""):
    seed = mnemonic_to_seed(mnemonic, passphrase)

    # 使用BIP44派生路径 m/44'/60'/0'/0/0
    bip44_mst_ctx = Bip44.FromSeed(seed, Bip44Coins.ETHEREUM)
    bip44_acc_ctx = bip44_mst_ctx.Purpose().Coin().Account(0)
    bip44_chg_ctx = bip44_acc_ctx.Change(Bip44Changes.CHAIN_EXT)
    bip44_addr_ctx = bip44_chg_ctx.AddressIndex(0)

    private_key_bytes = bip44_addr_ctx.PrivateKey().Raw().ToBytes()
    private_key = keys.PrivateKey(private_key_bytes)
    public_key = private_key.public_key
    address = public_key.to_checksum_address()

    return {
        "mnemonic": mnemonic,
        "private_key": private_key,
        "public_key": public_key,
        "address": address,
    }


def find_missing_word(known_words, missing_index, known_private_key):
    mnemo = Mnemonic("english")
    word_list = mnemo.wordlist

    for word in word_list:
        candidate_mnemonic = known_words[:]
        candidate_mnemonic[missing_index] = word
        candidate_mnemonic_str = " ".join(candidate_mnemonic)

        try:
            wallet = generate_eth_wallet_from_mnemonic(candidate_mnemonic_str)
        except Exception as e:
            continue

        if wallet["private_key"] == known_private_key:
            return candidate_mnemonic_str

    return None


words = [
    "twelve",
    "pact",
    "library",
    "equip",
    "steak",
    "cherry",
    "choice",
    "caught",
    "method",
    "treat",
    "door",
    None,  # 使用 None 表示缺失的助记词
]

# 已知的私钥
known_private_key = keys.PrivateKey(
    bytes.fromhex("FF427470D1C41BFF7B8D0A45D98A37FBF778D7C073092B4AE1E18F624F88A8C9")
)

# 找到缺失的助记词
complete_mnemonic = find_missing_word(words, 11, known_private_key)

if complete_mnemonic:
    print("Complete Mnemonic:", complete_mnemonic)
else:
    print("Could not find the missing word.")

结果如下:

Complete Mnemonic: twelve pact library equip steak cherry choice caught method treat door stuff

其他

为什么是 2048 个单词?

BIP39(Bitcoin Improvement Proposal 39)定义了一种助记词(Mnemonic)生成和使用的标准,用于生成加密货币钱包的种子。BIP39 助记词列表包含 2048 个单词,这是有特定原因的。

  1. 熵和信息量
    • BIP39 助记词的目的是将随机生成的熵(entropy)转换为一组易于记忆和输入的单词。
    • 助记词的长度可以是 12、15、18、21 或 24 个单词,每个单词代表一部分熵。
  2. 每个单词代表 11 比特的熵
    • 2048 个单词可以表示为 2^11,即每个单词代表 11 比特的熵。
    • 例如,一个 12 个单词的助记词代表 12 * 11 = 132 比特的熵,再加上 4 比特的校验和,总共 128 比特的熵。
  3. 助记词长度和熵的对应关系
    • 12 个单词:128 比特的熵 + 4 比特校验和 = 132 比特
    • 15 个单词:160 比特的熵 + 5 比特校验和 = 165 比特
    • 18 个单词:192 比特的熵 + 6 比特校验和 = 198 比特
    • 21 个单词:224 比特的熵 + 7 比特校验和 = 231 比特
    • 24 个单词:256 比特的熵 + 8 比特校验和 = 264 比特
  4. 易于记忆和输入
    • 2048 个单词的列表经过精心挑选,确保单词易于记忆和输入。
    • 这些单词通常是常见的、简单的、容易拼写的单词,减少了用户输入错误的可能性。

助记词的生成过程

助记词(Mnemonic Phrase)是加密货币钱包中用于备份和恢复私钥的一组单词。助记词生成过程通常遵循BIP-39(Bitcoin Improvement Proposal 39)标准。以下是助记词生成的一般步骤:

  1. 生成随机熵(Entropy)
    • 首先生成一段随机熵,长度通常为128位、160位、192位、224位或256位。熵的长度决定了助记词的长度。例如,128位熵会生成12个助记词,256位熵会生成24个助记词。
  2. 计算校验和(Checksum)
    • 对生成的熵进行SHA-256哈希运算,然后取哈希值的前若干位作为校验和。校验和的长度取决于熵的长度。例如,128位熵的校验和为4位,256位熵的校验和为8位。
  3. 组合熵和校验和
    • 将熵和校验和组合在一起,形成一个新的比特串。这个新的比特串的长度是熵长度加上校验和长度。例如,128位熵加4位校验和,总长度为132位。
  4. 将比特串分割成多个组
    • 将组合后的比特串按每组11位进行分割。每组11位可以表示一个0到2047之间的整数。
  5. 映射到词汇表
    • 使用BIP-39定义的2048个单词的词汇表,将每个11位的整数映射到相应的单词。这些单词就是助记词。
  6. 生成助记词
    • 将映射得到的单词按顺序排列,形成最终的助记词短语。

示例

假设我们生成了一段128位的熵:

1100101011110110101110001101101010110000110101110110111100110101
  1. 计算SHA-256哈希
SHA-256(1100101011110110101110001101101010110000110101110110111100110101) = 0e339f67c3b0d8f2b4c6e3d9e2d0f0a3c0b4f5e2a3f4b8c7d9e2f0a3c0b4f5e2
  1. 取前4位作为校验和
校验和 = 0000
  1. 组合熵和校验和
11001010111101101011100011011010101100001101011101101111001101010000
  1. 分割成11位的组
11001010111 10110101110 00110110101 01100001101 01110110111 10011010100 00
  1. 映射到词汇表
    • 假设词汇表中对应的单词分别是:apple, banana, cherry, date, fig, grape, honey
  2. 生成助记词
apple banana cherry date fig grape honey

蜀ICP备17033099号-7