Cardano SecondFi 签名事件: 一条链上交易, Ed25519 私钥为何泄露
6 月底, Cardano 生态的 SecondFi 钱包(前身是知名的 Yoroi)出事, 圈内一片哗然。没有智能合约被黑, 没有私钥从设备上被偷走, 甚至没有钓鱼——攻击者只是"读"了区块链。短短三天, 374 个钱包约 1600 万 ADA(当时约 240 万美元)被洗劫一空, 官方又紧急把约 1.29 亿 ADA 转移到第三方托管抢救。
更反常的是官方的警告: “请不要把助记词恢复到其他 Cardano 钱包”。通常只有助记词泄露才需要换钱包, 这次为什么反过来?
因为这次的"伤", 伤在签名上。
签名也会"受伤"
Cardano 用的是 Ed25519 签名。要理解它为什么出事, 先看签名是怎么算出来的:
flowchart TB
A[交易哈希 M] --> C
B[密钥材料 kR<br/>保密] --> C
C["nonce r = SHA-512(kR || M)<br/>保密且不可预测"] --> D[签名运算]
E[私钥] --> D
D --> F[签名 → 永久上链]签名靠一个"一次性秘密"保护, 在 Ed25519 里叫 nonce r: 它必须既保密、又不可预测。正确实现里, r 由密钥材料加交易哈希一起推导出来, 别人算不出 r, 也就没法从公开签名反推私钥。
SecondFi 的 Android 版(6 月 8 日 10.0.3 更新)在这出了致命错误: r 只用了公开的交易哈希来推导, 完全丢掉了密钥成分:
flowchart TB
A[公开交易哈希 M] --> C["nonce r = SHA-512(M)<br/>公开可算!"]
C --> D[签名 → 永久上链]
D --> G[任何人都能反推私钥]后果是数学上的必然: 只要某个地址在链上签过一笔交易, 任何人都能顺着公开数据算出 nonce, 再反推私钥。不需要入侵手机, 不需要第二次签名, 只是"读区块链"而已。有安全研究员当场用主网签名重建出了私钥, 社区评价这比 2011 年早期的比特币钱包漏洞更糟。
而且签名永久上链, 没有"删除"键——密钥一旦暴露, 就是永久暴露。
随机数为什么那么必要
我一开始也以为: 这不是随机数的问题, 是代码 bug 啊。对, 但这类 bug 之所以致命, 恰恰因为密码学对"不可预测性"有硬性要求。
不管 Ed25519 还是更常见的 ECDSA(比特币、以太坊都在用), 签名安全都建立在一个"每次签名都必须无法被他人预测的一次性秘密"上:
- ECDSA 里它是个随机数 k。k 泄露, 私钥 d 可直接解出; k 复用两次, 两条签名联立也能解出 d。
- Ed25519 里它是确定性派生的 r, 但推导必须掺入保密材料, 否则等于公开。
这不是纸上谈兵。索尼 PS3 当年把 k 写死成常数, 2010 年整个签名密钥被公开; 2013 年一批 Android 钱包因为系统伪随机数被弱化, 大量私钥被推导出来。SecondFi 是同一条战线上的新伤员——只是这次连随机数都不是, 是"秘密材料干脆没参与"。
而且这不是孤例: SecondFi 宣布永久关闭的同一天, Zilliqa 披露其 Ledger 应用存在结构相同的 nonce 漏洞——签名例程里一个缓冲拷贝错误把每次生成的 nonce 高 64 位清零, 熵被砍掉一大截, 攻击者收集大约五条链上签名就能重建私钥。一个漏洞一条签名就够, 另一个要五条, 但本质相同: 一旦 nonce 的熵不足或可被算出, 私钥就守不住。
一句话: 签名算法的私钥, 就差在每一次签名的"不可预测"上。任何让这个秘密变得公开或可算的改动, 都等于把私钥送到门口。
冷钱包为什么能躲过
这次事件里最值得注意的一点: 用硬件钱包(冷钱包)的用户基本不受影响。SecondFi 是在自己的 App 里实现签名, 硬件钱包走的是另一套独立的签名代码路径——私钥、nonce 推导、签名运算全在设备内的安全芯片里完成, 随机源和实现都是独立且经过审计的。
反过来看软件钱包的风险就清楚了:
- 随机源不可控: 钱包跑在通用操作系统上, 系统随机数一旦被污染、被篡改, 生成的签名就不可信。
- 签名库可能被换: SecondFi 正是绕过生态里经过审计的开源签名库, 用了自定义的未审计实现, 才埋下这个雷。软件更新里塞进一段有问题的签名代码, 用户根本无感知。
- 私钥和随机数暴露在同一台联网设备上: 设备一旦被攻破, 一切归零。
长期持有, 别信软件钱包的随机数
对打算长期持有加密资产的人, 我的建议很直接: 不要信任任何软件钱包的随机数, 更别让签名发生在联网设备上。
私钥在硬件里生成、签名在硬件里完成、交易在硬件屏幕上一笔笔确认——这才是"签名那一刻"该发生的地方。冷钱包是目前普通用户能负担得起、也最接近"自己保护自己"的组合。
SecondFi 事件没有复杂的攻击链, 只是少了一行把秘密掺进 nonce 的代码。而这类"少一行"的错, 正是软件钱包的随机数与签名实现里, 最防不胜防的东西。