Coldcard Entropy 漏洞:Air-gapped Wallet 为什么仍会丢失 Bitcoin
目录
引言
Air-gapped hardware wallet 解决的是 private key 的存储与签名隔离,却有一个容易被忽略的前提:最初生成 private key 的 randomness 必须不可预测。如果 seed 一开始只来自很小的 candidate set,设备之后再离线、Secure Element 再坚固,也只是把一个可枚举的 secret 锁进保险箱。
2026 年 7 月披露的 Coldcard 事件正是这种 failure mode。本文参考 Decrypt 的 Coldcard Bitcoin Exploit Explained 进行中文重写,并以 Coinkite Security Advisory、Coinkite Technical Deep Dive 和 Block Engineering 分析 校正技术细节。
先说结论:这不是 secp256k1 被破解,也不需要 malware、phishing 或接触设备。Firmware integration error 让 wallet generation 错误调用 deterministic software PRNG,而不是预期的 hardware TRNG,导致 attacker 可以在自己的机器上重建 candidate seed,再利用公开 address 或 XPUB 验证猜测。
Decrypt 在 2026 年 8 月 4 日援引 Galaxy Research 的阶段性统计称,三轮已确认事件涉及 1,596 BTC,另有尚未确认的第四轮。这类 on-chain attribution 会持续变化,金额不能当成最终审计结果;但漏洞已处于 active exploitation 状态,不再是 theoretical risk。
先分清 Seed Phrase、Entropy 与 Private Key
BIP-39 wallet 的简化过程如下:
| |
24 个 words 并不自动代表拥有 256 bits 的安全性。它只能表示一个 264-bit 编码,其中包含 256-bit entropy field 和 8-bit checksum;如果 entropy field 实际只可能来自 2^40 个输入,最终 mnemonic 仍只有约 40 bits 的不确定性。
Hash 同样不能凭空创造 entropy。假设输入 X 只有一万亿种可能,无论执行多少次 SHA-256:
| |
输出可以看起来分布均匀,但 attacker 仍然只需遍历原来的 candidate input。这正是“statistically random”和“cryptographically unpredictable”的区别。
Root Cause:一个值为 0 的 Build Flag
2021 年,Coldcard 将 elliptic-curve operation 迁移到 Bitcoin Core 同样使用的 libsecp256k1。Cryptographic library 本身没有问题,问题发生在 integration:wallet generation 从 ckcc.rng_bytes() 改为 ngu.random.bytes(),后者最终解析到了 MicroPython 的 Yasmarang fallback PRNG。
Decrypt 用“八年没有被发现”描述这段历史并不严谨。MicroPython fallback code 在 2018 年就已存在,但它到 2021 年 3 月才进入 Coldcard wallet seed generation;Coinkite 也专门澄清,八年是 upstream fallback 的代码年龄,不是 Coldcard 用户暴露于该 regression 的持续时间。
问题可以用下面这段简化后的 preprocessor logic 表示:
| |
Coldcard board configuration 将相关 macro 定义为 0,因为项目另有自己的 hardware RNG wrapper。LibNgU 使用 #ifndef 检查 macro 是否存在,于是 safety guard 没有触发;MicroPython 则按 macro 的数值编译 software fallback。两个实现又提供了兼容的 function signature,linker 正常完成,build 看起来完全成功。
旧版 Mk2/Mk3 的 fallback state 主要来自 MCU UID、SysTick 与 RTC state。这些值可能带来 operational uncertainty,却不是合格的 cryptographic entropy:UID 是固定 metadata,timer 可被限制或重建,而且它们之间并不独立。知道初始 state 和 call history 后,Yasmarang output 是 deterministic 的。
Mk4、Mk5 与 Q 额外混入 Secure Element data,因此情况有所改善;但 Block 的 code analysis 指出,digest 最终只有 4 bytes 进入 reseed(),最多区分 2^32 个 secure-element-derived stream,且并没有初始化一个标准 cryptographic DRBG。
为什么 Review 和测试没有发现
这次事故最值得 security engineer 关注的不是某一行 C,而是 verification boundary 错了。
检查了“代码存在”,没有检查“调用可达”
预期的 TRNG implementation 确实存在于 firmware binary,reviewer 也确认过它。缺失的是 end-to-end 证明:从 generate_seed() 出发,最终被调用的 global symbol 是否真来自 board-specific TRNG object。
PRNG 输出可以通过表面 Randomness Test
Deterministic PRNG 的目的就是生成“看起来随机”的序列。检查 byte 是否重复、分布是否均匀,只能发现 stuck-at-zero 一类故障,不能证明 attacker 无法从较小的 state space 重放整个 stream。
Build Configuration 也是 Cryptographic Code
漏洞跨越 application code、LibNgU submodule、MicroPython 与 preprocessor flag。逐个文件看都像合理实现,组合后的 symbol resolution 却改变了 security property。对于 cryptographic system,compiler flag、linker input 和 dependency pin 与算法代码同等重要。
40 bits、72 bits 到底意味着什么
每增加 1 bit,brute-force search space 翻倍;反过来,每丢失 1 bit,attacker 的工作量减半。
| Security level | Candidate count | 实际含义 |
|---|---|---|
| 40 bits | 约 1.10 × 10^12 | 已进入可工程化枚举范围,但实际成本取决于 timer、call history 与每个 candidate 的 derivation cost |
| 72 bits | 约 4.72 × 10^21 | 远强于 40 bits,但仍比 128 bits 少 2^56 倍 |
| 128 bits | 约 3.40 × 10^38 | 在 classical computing 下通常视为不可穷举 |
这里不能把不同报告里的数字混为一谈:
- Coinkite 初步估算 Mk2/Mk3 的 effective search space 约 40 bits,Mk4/Mk5/Q 约 72 bits。
- Block 对 source path 的分析更细:固定 fallback state 与 call history 后,旧设备可以是 deterministic;新设备一次成功 reseed 真正引入的 secure uncertainty 至多为 32 bits。
- Block 给出的
<2^40.7与<2^73.3是把未知 timer 等状态纳入后的 loose ceiling,并明确提醒它们不是同等强度的 cryptographic entropy。
因此,“72-bit wallet”只是便于沟通的上界描述,不等于存在一个均匀、不可观察的 72-bit secret。实际 exploit cost 还取决于 attacker 掌握多少 UID、boot timing、RNG call history 与公开 wallet metadata。Block 也明确表示没有完成所有型号的 end-to-end empirical exploit benchmark。
还有一个常见误区:2^128 虽然巨大,但并不比可观测宇宙的原子估算更多。它之所以安全,是因为穷举所需 energy、hardware 与 time 不现实,而不是因为一个夸张但错误的类比。
AI 找到了漏洞吗
目前没有公开证据证明 attacker 使用了 AI。Coinkite 的表述是“必须假设有人用 AI review 旧 firmware”,同时承认他们自己使用 frontier model review 时并未发现问题。
更准确的结论是:AI 可能降低了跨 repository 查找 call path、比较 build flag 与枚举 historical commit 的成本,但 attack attribution 仍未知。把“AI-assisted”写成已证实 root cause,会把一次 build、test 与 review process failure 错误包装成神秘的 AI attack。
哪些设备和 Seed 需要处理
风险由“生成 secret 时使用的 firmware”决定,不由设备当前 firmware 或购买时间决定。根据 2026 年 8 月 1 日更新的官方 advisory:
| Model / release track | 需要视为受影响的 seed | 修复新 seed generation 的版本 |
|---|---|---|
| Mk2 / Mk3 | 官方列出 4.0.1–4.1.9;Block 将 vulnerable path 追溯到 4.0.0 | 4.2.0 或更高 |
| Mk4 / Mk5 Standard | 在 5.6.0 之前生成 | 5.6.0 或更高 |
| Mk4 / Mk5 Edge | 在 6.6.0X 之前生成 | 6.6.0X 或更高 |
| Q Standard | 在 1.5.0Q 之前生成 | 1.5.0Q 或更高 |
| Q Edge | 在 6.6.0QX 之前生成 | 6.6.0QX 或更高 |
如果使用 Mk2/Mk3 4.0.0,或者无法确认生成 seed 时的确切版本,安全做法是按 affected seed 处理。TAPSIGNER、OPENDIME 与 SATSCARD 使用不同 codebase,官方称不受此次问题影响。
正确的 Incident Response
1. 先升级,再生成全新 Seed
从 Coldcard 官方渠道安装对应 model 与 release track 的 fixed firmware。Firmware update 只修复以后生成的 randomness,不会改变已经存在的 mnemonic。 把旧 seed import 到更新后的 Coldcard、另一台 hardware wallet 或 mobile wallet,都不会增加原有 entropy。
2. 建立新的 Wallet Identity
在 fixed firmware 上生成完全不同的 seed,离线记录并复核 backup;重启设备后再次确认 wallet fingerprint 与 receive address。先发送 small test transaction,验证恢复路径和收款地址,再迁移剩余资金。旧 backup 应保留到迁移完成并取得足够 confirmation,之后再按既定 secret-destruction policy 退役。
3. 不要把 Weak Passphrase 当作补丁
Strong、unique、secret 且与 mnemonic 分开保存的 BIP-39 passphrase 可以形成额外的 unknown input,降低即时暴露;短语、名言、复用密码或已泄露 password 不算有效保护。Passphrase 的任意 typo 都会生成另一个看似正常的 wallet,因此迁移前必须核对 fingerprint。官方建议即使已有 strong passphrase,也应最终更换受影响 seed。
4. Dice 只有满足条件才算独立 Entropy
官方认为,在最初生成 seed 时加入至少 50 次 fair、independent、private 且未记录的 dice rolls,可单独提供约 128 bits 的理想 entropy;99 rolls 接近 256 bits。如果骰子受操控、roll 之间不独立、过程被 camera 记录,或者不确定自己当时用了多少次,就不应依赖该例外。
这不是“骰子一定比 hardware 好”,而是 independent entropy source 能阻止单一组件 silent failure。真正重要的是来源独立、输入保密、组合方式经过 review,并且 recovery procedure 可重复验证。
5. Multisig 要检查 Quorum,而不是设备数量
如果 2-of-3 wallet 的三个 signer 都由受影响 RNG 生成,multisig 并不会自动恢复 128-bit security;attacker 只要恢复 quorum 就能签名。应逐个核对 signer 的 seed provenance,确保满足 threshold 数量的 key 来自独立且安全的 entropy source。
对 Security Engineering 的启示
这次事故给任何依赖 cryptographic randomness 的系统留下了几条直接可执行的规则:
第一,使用 binary-level assertion 验证最终 symbol。CI 不只要证明 hardware_rng.o 存在,还要失败于 fallback object 被链接、错误 rng_get() 被解析或 call graph 未到达预期 implementation。
第二,entropy path 必须 fail closed。TRNG initialization、health check 或 reseed 失败时,应停止 secret generation,而不是回退到 non-cryptographic PRNG 后继续运行。
第三,测试 security property,而不是只测 output appearance。需要验证 entropy provenance、state size、reseed width、prediction resistance,以及同一 device state 下是否可能重放;statistical test 只能作为 health monitoring 的一部分。
第四,把 build configuration 纳入 threat model。Submodule revision、macro value、link map、compiler optimization 与 release artifact 都应进入 reproducible build 和 security review,而不能只审查 source tree 中“看起来正确”的函数。
第五,为 key-generation regression 设计迁移方案。Cryptographic secret 一旦以弱 entropy 生成,软件 patch 无法 retroactively repair;系统必须能够识别受影响 cohort、轮换 identity、验证新 key,并安全撤销旧 trust relationship。
总结
Coldcard 事件击穿的不是 Bitcoin cryptography,而是 key generation 的最底层假设。Air gap 可以阻止 private key 被网络直接读取,却无法阻止 attacker 在别处重新计算一个可预测的 key。
真正的教训也不是“不要相信 hardware wallet”或“必须使用骰子”,而是不要把 randomness 当成某个芯片自动提供的属性。Entropy 从 physical source 到 firmware API、PRNG state、mnemonic 和最终 private key 的整条 data path,都必须可以验证;任何一段 silent fallback,都可能把 2^128 的安全边界压缩成一个现实可搜索的集合。
参考资料
- Decrypt:Coldcard Bitcoin Exploit Explained
- Coinkite:Coldcard Security Advisory
- Coinkite:Technical Deep Dive into the Entropy Issue
- Block Engineering:Predictable RNG Fallback and 32-Bit Reseed in COLDCARD Firmware
- Bitcoin BIPs:BIP-39 Mnemonic Code