起因
我想批量生成的方式,获取到一个后缀比较好的地址,使用 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_seed
和 generate_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 个单词,这是有特定原因的。
- 熵和信息量:
- BIP39 助记词的目的是将随机生成的熵(entropy)转换为一组易于记忆和输入的单词。
- 助记词的长度可以是 12、15、18、21 或 24 个单词,每个单词代表一部分熵。
- 每个单词代表 11 比特的熵:
- 2048 个单词可以表示为 2^11,即每个单词代表 11 比特的熵。
- 例如,一个 12 个单词的助记词代表 12 * 11 = 132 比特的熵,再加上 4 比特的校验和,总共 128 比特的熵。
- 助记词长度和熵的对应关系:
- 12 个单词:128 比特的熵 + 4 比特校验和 = 132 比特
- 15 个单词:160 比特的熵 + 5 比特校验和 = 165 比特
- 18 个单词:192 比特的熵 + 6 比特校验和 = 198 比特
- 21 个单词:224 比特的熵 + 7 比特校验和 = 231 比特
- 24 个单词:256 比特的熵 + 8 比特校验和 = 264 比特
- 易于记忆和输入:
- 2048 个单词的列表经过精心挑选,确保单词易于记忆和输入。
- 这些单词通常是常见的、简单的、容易拼写的单词,减少了用户输入错误的可能性。
助记词的生成过程
助记词(Mnemonic Phrase)是加密货币钱包中用于备份和恢复私钥的一组单词。助记词生成过程通常遵循BIP-39(Bitcoin Improvement Proposal 39)标准。以下是助记词生成的一般步骤:
- 生成随机熵(Entropy):
- 首先生成一段随机熵,长度通常为128位、160位、192位、224位或256位。熵的长度决定了助记词的长度。例如,128位熵会生成12个助记词,256位熵会生成24个助记词。
- 计算校验和(Checksum):
- 对生成的熵进行SHA-256哈希运算,然后取哈希值的前若干位作为校验和。校验和的长度取决于熵的长度。例如,128位熵的校验和为4位,256位熵的校验和为8位。
- 组合熵和校验和:
- 将熵和校验和组合在一起,形成一个新的比特串。这个新的比特串的长度是熵长度加上校验和长度。例如,128位熵加4位校验和,总长度为132位。
- 将比特串分割成多个组:
- 将组合后的比特串按每组11位进行分割。每组11位可以表示一个0到2047之间的整数。
- 映射到词汇表:
- 使用BIP-39定义的2048个单词的词汇表,将每个11位的整数映射到相应的单词。这些单词就是助记词。
- 生成助记词:
- 将映射得到的单词按顺序排列,形成最终的助记词短语。
示例
假设我们生成了一段128位的熵:
1100101011110110101110001101101010110000110101110110111100110101
- 计算SHA-256哈希:
SHA-256(1100101011110110101110001101101010110000110101110110111100110101) = 0e339f67c3b0d8f2b4c6e3d9e2d0f0a3c0b4f5e2a3f4b8c7d9e2f0a3c0b4f5e2
- 取前4位作为校验和:
校验和 = 0000
- 组合熵和校验和:
11001010111101101011100011011010101100001101011101101111001101010000
- 分割成11位的组:
11001010111 10110101110 00110110101 01100001101 01110110111 10011010100 00
- 映射到词汇表:
- 假设词汇表中对应的单词分别是:apple, banana, cherry, date, fig, grape, honey
- 生成助记词:
apple banana cherry date fig grape honey