在Solana的ZK ElGamal Proof Program中发现了一个严重的声音漏洞,该漏洞允许恶意证明者伪造sigma OR 证明,绕过机密传输中的费用验证。攻击者可以操纵加密的费用金额来任意铸造或烧毁Token,而无需泄露实际的传输价值。该漏洞的根本原因是Fiat-Shamir转换中的一个微妙错误,即一个由证明者生成的“挑战”值未被吸收到transcript中。
2025 年 6 月,我们发现了 Solana 的 ZK ElGamal 证明程序中的一个关键的可靠性漏洞。该漏洞允许恶意证明者伪造一个 sigma OR 证明,绕过保密传输中的费用验证。通过利用此缺陷,攻击者可以通过操纵加密的费用金额来任意铸造或销毁代币,而无需透露实际的传输价值。
我们通过 GitHub 安全公告 以负责任的方式向 Anza 团队披露了该问题。 响应迅速。 token-2022 程序中的保密传输扩展立即被暂停,并且 ZK ElGamal 证明程序后来在运行时级别被完全禁用。 幸运的是,未发现实际利用。
该漏洞的核心在于 Fiat-Shamir 变换中的一个细微错误。 一个代数组件,即证明者生成的“挑战”值,没有被吸收到记录中。 虽然挑战通常由零知识证明协议中的验证者生成,但 sigma OR 证明会反转此角色。 证明者提供部分挑战,该挑战仍必须包含在 Fiat-Shamir 记录中以保持可靠性。 这个被忽视的值,我们称之为 幻影挑战,悄悄地破坏了协议的安全性。
在本文中,我们将解释错误的起源、sigma OR 证明的结构、这种特殊的遗漏如何导致可靠性中断,以及它揭示的关于零知识协议工程的更广泛的教训。
Solana 的保密传输是 token-2022 程序的扩展,使用户能够在不透露传输金额的情况下传输代币。 此功能依赖于 ZK ElGamal 证明程序来验证零知识证明,包括 sigma 协议和 bulletproofs。
扩展中的所有操作都在加密的余额和传输金额上执行,以确保保密性。 该系统使用 twisted ElGamal 加密方案,该方案提供线性同态:
encrypt(a)+encrypt(b)=encrypt(a+b)
此属性允许网络在传输交易中直接更新密文上的余额:
new_sender_balance_ciphertext := sender_balance_ciphertext - transfer_amount_ciphertext
new_receiver_balance_ciphertext := receiver_balance_ciphertext + transfer_amount_ciphertext
为了确保有效性,发送者必须生成多个零知识证明,例如:
在处理保密传输时,程序首先验证这些证明,然后再更新加密的余额。
保密传输也支持费用:权威机构可以指定 fee_rate
(百分比)和 max_fee
(上限)。 费用始终向上取整。 例如,当 fee_rate = 1%
且 max_fee = 2
时,传输 50
会产生 1
的费用(从 0.5
向上取整),而传输 300
会产生 2
的费用(有上限)。
为了保持机密性,费用金额也会被加密。 该程序使用 sigma OR 证明来强制执行加密费用是传输金额的正确百分比,或者完全等于 max_fee
,而不会透露适用哪种情况。 这样可以防止泄露有关传输金额的信息。 下一节将解释 sigma OR 证明的工作原理。
sigma OR 证明(也称为分离式 sigma 协议)是一种密码学技术,允许证明者证明至少了解两个语句中的一个,而无需透露是哪一个。 例如,证明者可以说服验证者他们知道与两个公钥对应的至少一个私钥。
核心思想是,证明者可以模拟/伪造一个语句的证明,并为另一个语句生成有效证明,而无需透露是哪一个。 在挑战阶段,验证者为证明者生成一个随机挑战 c。 证明者为一个证明选择一个挑战 c1 并为另一个证明设置 c2=c−c1,然后将 (c1,c2) 发送给验证者。 验证者检查 c1+c2=c。 这允许证明者自由选择一个挑战(从而模拟该证明),但强制另一个挑战是诚实的,因为它受到 c 的约束。 验证者不知道证明者选择了哪个挑战,因此无法区分哪个证明是模拟的,哪个是真实的。 它只知道至少有一个证明是有效的。
为了说明这一点,请考虑在椭圆曲线密码学中证明了解两个私钥中的一个。 设 G 为生成器,p 为群的阶。 回顾一下标准的 Schnorr 协议,用于证明公钥 P=x⋅G 背后的私钥 x 的知识:
对于 sigma OR 证明,假设证明者知道 P1=x1⋅G 的 x1,但不知道 P2=x2⋅G 的 x2。 证明者希望说服验证者他们至少知道 x1 或 x2 中的一个。 该协议将两个 Schnorr 证明组合如下:
验证: 验证者检查:
如果所有检查都通过,验证者确信证明者至少知道 x1 或 x2 中的一个。 由于验证者无法判断 c1 或 c2 是否是预先生成的,因此无法区分哪个证明是伪造的。
sigma OR 证明中的一个关键微妙之处是,一些“挑战”(c1 和 c2)由证明者而不是验证者提供。 这个不直观的细节至关重要,正如我们将在下一节中看到的那样,它是幻影挑战错误的根本原因。
Solana 的 ZK ElGamal 证明程序中的 PercentageWithCapProof 是作为 sigma OR 证明实现的。 它的目的是强制执行加密的费用金额是传输金额的固定百分比,或者完全等于 max_fee
,而无需透露适用哪种情况。
在下面的相关代码中,验证者从 Fiat-Shamir 记录中生成一个挑战 c
。 证明者提供 c_max_proof
(“最大费用”分支的挑战),验证者计算“百分比”分支的 c_equality = c - c_max_proof
(这隐式地确保 c = c_max_proof + c_equality
)。 稍后,验证者将各种证明元素吸收到记录中,并导出一个随机数 w
,以将多个等式检查合并为单个多标量乘法。
pub fn verify(...) -> Result<(), PercentageWithCapProofVerificationError> {
// ...existing code...
let c = transcript.challenge_scalar(b"c");
let c_max_proof = self.percentage_max_proof.c_max_proof;
let c_equality = c - c_max_proof;
transcript.append_scalar(b"z_max", &z_max);
transcript.append_scalar(b"z_x", &z_x);
transcript.append_scalar(b"z_delta_real", &z_delta_real);
transcript.append_scalar(b"z_claimed", &z_claimed);
let w = transcript.challenge_scalar(b"w");
let ww = w * w;
let check = RistrettoPoint::vartime_multiscalar_mul(
vec![\
c_max_proof,\
-c_max_proof * m,\
-z_max,\
Scalar::ONE,\
w * z_x,\
w * z_delta_real,\
-w * c_equality,\
-w,\
ww * z_x,\
ww * z_claimed,\
-ww * c_equality,\
-ww,\
],
vec![\
C_max,\
&G,\
&(*H),\
&Y_max,\
&G,\
&(*H),\
C_delta,\
&Y_delta_real,\
&G,\
&(*H),\
C_claimed,\
&Y_claimed,\
],
);
if check.is_identity() {
Ok(())
} else {
Err(SigmaProofVerificationError::AlgebraicRelation.into())
}
}
但是,该实现最初未能将 c_max_proof
吸收到记录中,然后再生成 w
。 这个遗漏允许恶意证明者在看到 w
后自由选择 c_max_proof
,从而使他们能够伪造一个可以通过最终验证等式的证明。 换句话说,证明者可以以一种本应不可能的方式操纵挑战,如果所有证明者生成的值都已正确吸收到 Fiat-Shamir 记录中。
这个错误很微妙,因为在大多数 sigma 协议中,挑战是由验证者生成的,不需要被吸收。 但在 sigma OR 证明中,一些挑战是由证明者生成的,必须包含在记录中以确保可靠性——否则,证明者可以利用这个漏洞。
我们创建了一个概念验证来演示该攻击。 通过仔细选择 c_max_proof
,攻击者始终可以满足最终等式检查。 要重现该问题,请克隆存储库并运行:
cargo test --package solana-zk-sdk:3.0.0 --lib -- sigma_proofs::percentage_with_cap::test::test_fake_proof --exact --show-output
PercentageWithCapProof
主要在 token-2022 中使用,以启用具有费用配置的保密传输。 这个 Fiat-Shamir 漏洞允许恶意证明者绕过费用检查并在代币传输交易中设置任意费用金额。 因此,攻击者可以将费用设置为零以避免支付费用,或者更糟糕的是,操纵费用以从接收者的帐户中销毁代币或向权威机构铸造代币。 由于传输金额和费用也已加密给接收者和权威机构,因此任何利用都是可以检测到的。
在确定该漏洞后,我们立即通过 GitHub 安全公告 向 Anza 团队报告了该漏洞,并附带了一个 概念验证。 Anza 团队迅速响应,禁用了 token-2022 中的保密传输扩展,随后向所有验证者推出了一个补丁,以禁用 Solana 运行时中的 ZK ElGamal 证明程序指令。 通过添加一行代码将 c_max_proof
吸收到 Fiat-Shamir 记录中,最终修复了底层错误:
transcript.append_scalar(b"z_x", &z_x);
+ transcript.append_scalar(b"c_max_proof", &c_max_proof);
transcript.append_scalar(b"z_delta_real", &z_delta_real);
transcript.append_scalar(b"z_claimed", &z_claimed);
没有发现此问题的实际利用。
该漏洞是通过在正常工作时间之外进行的个人研究发现的。 我们要感谢 Anza 团队的快速响应以及更广泛的 zkSecurity 团队对披露过程的支持。
幻影挑战漏洞表明,密码协议设计中的一个细微疏忽如何破坏整个系统的安全性。 由于未能将证明者生成的挑战吸收到 Fiat-Shamir 记录中,sigma OR 证明的可靠性被破坏,从而允许攻击者绕过保密传输中的关键保护措施。
此事凸显了理解高级协议中证明者和验证者的细微角色的重要性。 在像 sigma OR 证明这样的结构中,每个可能影响证明的值——尤其是那些由证明者生成的值——都必须仔细地吸收到记录中。 通过遵循 Fiat-Shamir 的黄金法则可以避免此类问题:“hashed everything the prover sends”。 忽视这些细节可能会产生深远的影响,即使对于经验丰富的团队也是如此。
在 zkSecurity,我们专注于密码协议安全。 如果你正在使用 ZK 证明、同态加密或其他高级密码学进行构建,请与我们联系以加强你的协议。
- 原文链接: blog.zksecurity.xyz/post...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!