一次签名即致命:SecondFi钱包的Ed25519实现缺陷导致私钥泄露

當前語言暫無此文章翻譯,已顯示原文。
6月21日至23日,Cardano主网上数以千计的钱包遭遇攻击,约 1600 万枚 ADA 和大量 NFT 被转移,总价值约 250 万美元。此外,约 1.29 亿枚 ADA(价值约 2000 万美元)被 SecondFi 以安全原因自行转移到第三方托管机构。

6月21日至23日,Cardano主网上数以千计的钱包遭遇攻击,约 1600 万枚 ADA 和大量 NFT 被转移,总价值约 250 万美元。此外,约 1.29 亿枚 ADA(价值约 2000 万美元)被 SecondFi 以安全原因自行转移到第三方托管机构。

漏洞分析

此次的核心问题在 Ed25519 的实现。Ed25519签名算法在设计之初就通过确定性nonce生成彻底杜绝了随机数故障。按 RFC 8032,Ed25519 签名时应从私钥派生出两部分:签名标量 s 和秘密前缀 prefix。签名 nonce r的应计算为:

r = SHA512(prefix || M) mod L

其中 prefix 是密钥扩展(SHA-512(seed))的前32字节,这正是Ed25519安全的关键。而SecondFi的实现恰恰遗漏了这个prefix。在密钥从BIP32-Ed25519派生层传递到签名适配层的过程中,nonce r的秘密前缀被彻底遗漏,只有签名标量被传递给了底层签名器。最终签名器实际执行的运算是:

r = SHA512(M) mod L

这使得nonce r 从秘密值变成公开可计算值。而在Ed25519中,还有以下标准对象:

B:基点,也叫 base point / generator。它是 Ed25519 曲线上预定义的固定点。私钥标量乘以 B 会得到公钥点。

A:公钥点。

如果私钥签名标量是 s,那么:

A = s * B

L:基点 B 所在子群的阶,也就是标量运算的模数。Ed25519 的所有标量计算都在 mod L 下进行。

M:被签名的消息。在普通 Ed25519 里,M 就是调用签名函数时传入的消息字节串。

所以签名流程可以简化理解为:

s = private key signing scalarA = s * B # public key

M = message to be signed

r = SHA512(prefix || M) mod LR = r * B

k = SHA512(R || A || M) mod LS = r + k * s mod L

最终签名通常包含 R 与 S,如果 r 公开可计算,攻击者只要拿到链上一笔由受影响钱包签出的交易,就能得到公开的M、R、A、S,并可以从公开的 M 算出 r,再用

s = (S - r) * inverse(k) mod L

直接恢复私钥签名标量 s。也就是说,某个地址只要曾经用受影响实现签过一次交易,它对应的私钥标量就已经泄露。

下面 PoC 用一个小素数阶的EdDSA代数模型复现漏洞原理,并不支持解析真实的Cardano交易和真实的链上签名:

import hashlib

L = 2**127 - 1 # not Ed25519's real group order, just for demonstration

def H(*parts): h = hashlib.sha512() for p in parts: h.update(p if isinstance(p, bytes) else str(p).encode()) return int.from_bytes(h.digest(), "little") % L

def inv(x): return pow(x, -1, L)

# Victim private/public scalar in a toy additive group.secret_s = 98765432123456789public_A = secret_s # In real Ed25519: A = s * B

M = b"cardano tx body hash, toy example"

# Buggy signer: prefix was dropped, so r is public and message-only.r = H(M)R = r # In real Ed25519: R = r * Bk = H(R, public_A, M)S = (r + k * secret_s) % L

print("[victim signature]")print("A =", public_A)print("R =", R)print("S =", S)

# Attacker only needs public A, R, S, and M.r_public = H(M)fingerprint = (R == r_public)recovered_s = ((S - r_public) * inv(k)) % L

print("\n[attacker]")print("bug fingerprint:", fingerprint)print("recovered secret:", recovered_s)print("success:", recovered_s == secret_s)

结语

本次SecondFi钱包安全事件的根本原因为Ed25519规范实现层面的错误,这一疏忽导致了灾难性的后果:任何使用SecondFi钱包在Cardano主网上签名过的地址,其私钥均已公开可推导。其开发团队应当使用测试向量对签名逻辑的实现进行严格验证,确保签名器接收到的内容与派生阶段产生的内容完全一致。

分享至:

作者:Beosin

本文為PANews入駐專欄作者的觀點,不代表PANews立場,不承擔法律責任。

文章及觀點也不構成投資意見

圖片來源:Beosin如有侵權,請聯絡作者刪除。

關注PANews官方賬號,一起穿越牛熊
PANews APP
Venice AI獲6500萬美元A輪融資,估值達10億美元
PANews 快訊