逃出生天:不同威胁下的钱包恢复策略

前言

今年看到很多关于「抢劫加密货币」的新闻:

针对于加密货币实施的犯罪不仅存在于比特世界,在原子世界也变得越来越频繁。今天读到《精通比特币》第三版「种子和恢复码」部分,结合自己之前经历过的钱包安全事件,脑海中出现很多经典的犯罪场景,针对于这些场景,我们可以采用哪些技术手段来保证自己的资产安全呢?

💡 在此之前,你需要知道的:

  • 种子技术是一种在加密货币钱包中用于生成私钥和公钥的技术。通过生成一个随机数作为种子,再利用该种子计算出多个私钥和公钥对,从而实现钱包的生成和恢复。

  • 恢复码是一组用于恢复钱包的助记词,是种子的可读形式。常见的恢复码由12到24个单词组成(如 BIP39),这是通过将种子编码为单词序列得到的。用户可以手动记录这组单词,用于在钱包丢失或损坏时恢复钱包。

假设场景

场景 1:劫持威胁

情景描述

加入你遇到了劫匪,劫匪要求你必须交出比特币钱包的恢复码,你不想暴露自己的全部资产,但又要交出一些钱来保命。

困点

不能让劫匪知道你到底有多少钱。

解决方案

使用 BIP39 和 SLIP39 的合理否认机制。在 BIP39 和 SLIP39 恢复码方案中,用户可以设置一个「主恢复码」以及可选的口令。不同的口令会生成不同的密钥树,因此 Alice 可以设置一个包含少量资金的备用口令,用于应对被迫交出恢复码的情况。这样,歹徒即便拿到恢复码和备用口令,也只能获取少量资产。

def generate_seed(mnemonic_phrase, passphrase=""):
    """
    Generate seed from mnemonic phrase and optional passphrase using PBKDF2.
    """
    # BIP39 defines the salt as "mnemonic" + passphrase
    salt = "mnemonic" + passphrase
    # Generate the seed using PBKDF2-HMAC-SHA512 with 2048 iterations
    seed = hashlib.pbkdf2_hmac("sha512", mnemonic_phrase.encode(), salt.encode(), 2048)
    return seed.hex()

# 初始化 BIP39 助记词生成器
mnemo = Mnemonic("english")

# 生成助记词
mnemonic_phrase = mnemo.generate(strength=128)  # 12 个助记词
print(f"助记词: {mnemonic_phrase}")

# 使用不同的口令生成种子
passphrase_1 = "main_passphrase"
passphrase_2 = "secondary_passphrase"

# 生成不同的种子
seed1 = generate_seed(mnemonic_phrase, passphrase_1)
seed2 = generate_seed(mnemonic_phrase, passphrase_2)

print(f"种子 (口令1): {seed1}")
print(f"种子 (口令2): {seed2}")

# 验证不同口令生成的种子是否相同
if seed1 != seed2:
    print("不同口令生成了不同的种子。")
else:
    print("不同口令生成了相同的种子(请检查代码或口令设置)。")

打印示例

助记词: dose grid token call output jungle spot monster ozone spread bunker canal
种子 (口令1): 5a7a35f7a6e08d7a7efb93f9120d94bf7ffb3a7f82e84e2334fa6310a7f216b20b6fc536...
种子 (口令2): 9e40e3d60ff15a54d37dabc8474825c92a5b830d842fbdb77e5fbe5a6b35b7123c973c6...

场景 2:跨国旅行遭遇检查

情景描述

你全球旅行到了一个对加密货币不友好的国家。入境检查时,当地安全人员要求他上交钱包的恢复码和私钥,但你并不想这么做。

困点

携带的私钥无法完整恢复钱包

解决方案

使用 SLIP39(分布式恢复码),可以将种子恢复码分成多个分片,存放在不同位置。比如我们可以设置 2 个分片,自身携带一个,要求比如两个分片在一起时才可以恢复钱包。

# 初始化 BIP39 助记词生成器
mnemo = Mnemonic("english")

# 生成助记词作为种子
mnemonic_phrase = mnemo.generate(strength=128)  # 生成 12 个助记词
print(f"原始助记词: {mnemonic_phrase}")

# 将助记词分成两个分片,设定 2 个分片才能恢复
mnemonics = generate_mnemonics(master_secret=mnemonic_phrase.encode(), group_threshold=1, groups=[(2, 2)])

print("生成的分片:")
for idx, part in enumerate(mnemonics, start=1):
    print(f"分片 {idx}: {part}")

# 恢复过程,使用两个分片组合恢复原始种子
recovered_phrase = combine_mnemonics(mnemonics)
print(f"恢复的助记词: {recovered_phrase.decode()}")

# 验证恢复的助记词是否和原始助记词一致
if recovered_phrase.decode() == mnemonic_phrase:
    print("成功恢复原始助记词!")
else:
    print("恢复失败,助记词不匹配。")

打印示例

原始助记词: stable whisper section invite risk embrace stand educate snack banner mirror result
生成的分片:
分片 1: armed strong acquire jungle plug mutual letter mother door use field mistake
分片 2: quick trip modify island food reduce length renew warm fortune green square
恢复的助记词: stable whisper section invite risk embrace stand educate snack banner mirror result

💡 原始助记词并不是分片 1 和分片 2 的简单集成。Shamir’s Secret Sharing (SSSS) 算法背后的原理并不是将原始助记词简单地拆分成多个部分,而是通过数学方法将原始数据(种子或助记词)转换成多个唯一且独立的分片。每个分片都包含一部分恢复原始数据所需的信息,但单独一个分片本身不能直接恢复原始助记词。因此:

  • 每个分片都包含部分信息,但单独无法还原原始助记词;

  • 多个分片联合才能进行插值重建,进而恢复原始种子。

场景 3:监守自盗

情景描述

你和几个朋友一起开了一家公司,希望共同管理 BTC 资产。但不希望任何人单独获得恢复码。

困点

任何一个恢复码都无法单独获得所有资产。

解决方案

SLIP39 或 Codex32 的分布式密钥分片。可以将种子恢复码分成多个分片(如 8 个),并将不同的分片分给朋友,同时设定恢复门限为 5 个分片。这样,即使有部分分片丢失或泄露,攻击者仍无法获得完整的恢复码。

💡 Codex32 是一种新的、仍在开发中的恢复码方案,目前尚未有正式的 Python 库或标准实现,但我们可以根据分布式密钥分片的原理,模拟 Codex32 的工作方式。Codex32 类似于 Shamir 的秘密共享方案,但它设计为可以通过物理方式(如纸质打印和手动验证)来生成和验证密钥分片。具体逻辑可以参考 SLIP39 代码片段。

当然,目前很多 EVM 链或图灵完备的公链大多数会采用多签钱包或 AA 钱包的方式来解决此类问题。但由此可能会引申出一个新的问题 - 如果双方各掌握 50% 的权限,那么当意见出现分歧时,很容易陷入僵局。

场景 4:设备丢失或被盗

情景描述

你的手机被盗,而且手机中存放着恢复码的照片。

困点

恢复码照片泄露导致资金被盗。

解决方案

**Aezeed(口令身份验证)。**Aezeed 的口令验证可以防止用户输入错误密码,而攻击者即便拥有恢复码,若不知道正确口令,依然无法生成有效的密钥。

使用 Aezeed 方案,你的恢复码是加密的,并且包含口令身份验证。在恢复钱包时,如果输入错误的密码,会立即显示验证错误,不会生成错误的密钥树。

技术特点

从上述描述中,我们可能感觉 AezeedBIP39SLIP39 在功能上有一定相似性,特别是在使用口令生成不同密钥树方面,但 BIP39 和 SLIP39 的设计旨在生成单一恢复码或分片恢复码,通过可选口令来生成不同的密钥树,但缺少验证机制。输入错误口令时,BIP39 和 SLIP39 仍会生成有效密钥树,可能导致用户误解和丢失资金。但 Aezeed 会通过校验,告知用户输入的是错误的口令。

不仅如此 Aezeed 相比 BIP39 和 SLIP39 还有一些独特的特性,包括其设计目的是为 闪电网络提供更好的支持,并且包含更多的安全特性,如口令验证钱包生日信息,以及多层版本控制。这些特性使得 Aezeed 更适合复杂应用场景,比如高频、实时的交易需求。

由于目前 Aezeed 还没有正式的库实现,但我们可以模拟其通过口令生成不同种子的流程,同时添加口令验证,以确保在用户输入错误口令时返回错误信息。

def generate_seed_with_aezeed(mnemonic_phrase, passphrase=""):
    """
    Generate an Aezeed-style seed from mnemonic phrase and optional passphrase.
    Adds basic password validation by checking a derived hash.
    """
    # Aezeed 风格的种子生成函数
    salt = "mnemonic" + passphrase
    seed = hashlib.pbkdf2_hmac("sha512", mnemonic_phrase.encode(), salt.encode(), 2048)
    # 验证哈希值的前 8 字节作为基本校验
    checksum = seed[:8]
    return seed.hex(), checksum

def verify_passphrase(mnemonic_phrase, passphrase, expected_checksum):
    """
    Verify if the provided passphrase generates the expected checksum.
    """
    _, checksum = generate_seed_with_aezeed(mnemonic_phrase, passphrase)
    return checksum == expected_checksum

# 初始化 BIP39 助记词生成器
mnemo = Mnemonic("english")

# 生成助记词作为种子
mnemonic_phrase = mnemo.generate(strength=128)  # 生成 12 个助记词
print(f"助记词: {mnemonic_phrase}")

# 使用主口令生成种子
main_passphrase = "correct_password"
seed, checksum = generate_seed_with_aezeed(mnemonic_phrase, main_passphrase)
print(f"主种子: {seed}")
print(f"校验码: {checksum.hex()}")

# 测试其他口令是否会产生不同种子,并进行验证
test_passphrase = "wrong_password"
is_valid = verify_passphrase(mnemonic_phrase, test_passphrase, checksum)

print(f"口令验证结果(错误口令): {'有效' if is_valid else '无效'}")

# 验证正确口令
is_valid = verify_passphrase(mnemonic_phrase, main_passphrase, checksum)
print(f"口令验证结果(正确口令): {'有效' if is_valid else '无效'}")

打印示例

助记词: stable whisper section invite risk embrace stand educate snack banner mirror result
主种子: 5a7a35f7a6e08d7a7efb93f9120d94bf7ffb3a7f82e84e2334fa6310a7f216b20b6fc536...
校验码: 5a7a35f7a6e0
口令验证结果(错误口令): 无效
口令验证结果(正确口令): 有效

场景 5:网络黑客攻击

情景描述

你担心黑客攻击威胁到恢复码的安全,因此希望找到一种完全离线的存储方法。

困点

离线存储且可分片(防止单一丢失)

解决方案

Codex32 的手动离线分布式存储。Codex32 恢复码方案允许用户通过打印说明书、剪刀等物理方式生成分片恢复码,避免电子设备参与。Eve 可以将恢复码分片离线打印并分散存放,确保恢复码不会受到网络攻击威胁。

总结

Subscribe to Simon 写字的地方
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.