JP Aumasson(BLAK hash function family论文的作者之一),在2022年的zkStudyClub中分享了其审计多个ZKP项目中发现的各种隐患和bug。 视频解说见:
- Zero Knowledge Summit Amsterdam 2022
主要关注的为:
- Fully succinct = O ( 1 ) =O(1) =O(1) proof size and O ( circuit size ) O(\text{circuit size}) O(circuit size) verification time的zkSNARKs方案
主要基于审计以下项目的经验:
- Groth16:ZCash、Filecoin等。
- Marlin:Aleo使用的,为universal zkSNARKs。
这些经验也可用于其它如Plonk、SONIC、STARK等系统。
2. 为何需要学习zkSNARKs安全?对于区块链项目,zkSNARKs的主要风险在于:
- 复杂性+创新性 会引起 non-trivial bugs
- 事关重大(很多钱$ 以及 用户数据和隐私)
作为一名密码学家,当今最有趣的密码:
- 解决现实世界问题,并大规模部署(好论文+好代码!)
- 具有重要组件的复杂结构
- “简单但复杂”(非交互式,很多moving部件)
- 关于安全的多维思考
- 1)soundness,通常为实践中的最大风险:
- invalid proofs应总是被拒绝。
- 应无法伪造、修改、重放valid proofs。
- 2)Zero-knowledge:proofs不应泄露witness信息(私有变量)
- 实践中,large programs的succinct proof仅可泄露一点点数据。
- 3)Completeness:通常DoS/可用性风险可能会被进一步利用:
- valid proofs应总是被接受。
- 所支持的所有programs/circuits应被正确处理。
由于近些年才有实用zkSNARKs,审计者审计ZKP项目的主要挑战在于:
- 1)审计ZKP项目的经验有限
- 2)对理论和实现技巧的了解有限
- 3)bug和bug类别的“checklist”有限
- 4)工具和方法有限
大多数bug是由内部或类似项目的团队发现。 为此,对于zkSNARKs这种新密码学应用,要采用新方法来审计:
- 1)与开发者和设计者紧密合作(如联合review、Q&A等)。
- 2)更多威胁分析,以了解应用程序的独特/新风险。
- 3)更多实践经验:写PoC、circuit、proof system等。
- 4)学习之前的失败案例,来源不限于:
- public disclosures and exploits
- 其他审计报告
- 跟踪issue和PR
- 社区
与第3)节的zkSNARKs安全定义呼应,破坏zkSNARKs安全主要体现在:
- 1)破坏soundness,如利用:
- Contraint system无法有效地强化特定constraints。
- proving keys未安全生成 或 未安全保护。
- 2)破坏zero-knowledge,如利用:
- 将私有数据当成了公开变量。
- 协议层面的"metadata attack"。
- 3)破坏completeness,如利用:
- 边缘情况下R1CS合成行为不正确(例如,关于私有变量数)
- gadget i/o值与类型不匹配引起的gadget组合失败
- 4)破坏(链下)软件,通过任意bug导致:
- 数据泄露,包括通过side channels、encodings(“ZK execution”)。
- 任何形式的不安全状态(code execution, DoS)。
- 5)通过以下方式,使supply-chain被compromise:【即各依赖环节】
- Truste setup的代码和execution.
- build and release process integrity
- software dependencies
- 6)通过合约bug、逻辑缺陷等破坏(链上)软件(包括verifier)。
将ZKP系统分层表示为:
- 1)下层的故障可能会危及所有上层的安全:
- 2)子组件中的故障可能会危及所有上层的安全:
- 3)Security 101:必须定义、实现并测试输入的有效性:
Filed arithmetic实现引起的soundness问题的案例主要有:
- 1)appliedzkp的semaphore项目中,未对nullifier(为a shielded payment的unique ID)进行overflow检查引起的双花问题:
- https://kovan.etherscan.io/tx/0x5e8bf35ad76a086b98698f9d20bd7b6397ccc90aa6f85c1c5debc0262be5458a
- https://kovan.etherscan.io/tx/0x9a47cc8daec9d0a5e9a860ada77730190124f9864a5917dcb8f41773d94cfc1a
- 2)eee-oasis的baseline项目中未对a public input进行overflow检查。
- 3)appliedzkp的semaphore项目中未对a public input进行overflow检查。
uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
VerifyingKey memory vk = verifyingKey();
require(input.length + 1 == vk.IC.length, "verifier-bad-input");
// Compute the linear combination vk_x
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
for (uint256 i = 0; i < input.length; i++) {
// 注意此处!做overflow检查。
require(input[i] < snark_scalar_field, "verifier-gte-snark-scalar-field");
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
}
5.2 R1CS引起的soundness问题
arkworks-rs的r1cs-std项目中的constraint system未对Field element inversion属性进行强化,从而引发soundness问题。
/// Returns `(self / d)`.
/// The constraint system will be unsatisfiable when `d = 0`.
fn mul_by_inverse(&self, d: &Self) -> Result {
// Enforce that `d` is not zero.
d.enforce_not_equal(&Self::zero())?;
self.mul_by_inverse_unchecked(d)
}
/// Returns `(self / d)`.
///
/// The precondition for this method is that `d != 0`. If `d == 0`, this
/// method offers no guarantees about the soundness of the resulting
/// constraint system. For example, if `self == d == 0`, the current
/// implementation allows the constraint system to be trivially satisfiable.
fn mul_by_inverse_unchecked(&self, d: &Self) -> Result {
let cs = self.cs().or(d.cs());
match cs {
// If we're in the constant case, we just allocate a new constant having value equalling
// `self * d.inverse()`.
ConstraintSystemRef::None => Self::new_constant(
cs,
self.value()? * d.value()?.inverse().expect("division by zero"),
),
// If not, we allocate `result` as a new witness having value `self * d.inverse()`,
// and check that `result * d = self`.
_ => {
let result = Self::new_witness(ark_relations::ns!(cs, "self * d_inv"), || {
Ok(self.value()? * &d.value()?.inverse().unwrap_or(F::zero()))
})?;
result.mul_equals(d, self)?;
Ok(result)
},
}
}
5.3 hash validation引起的soundness问题
详细参看2019年博客 Tornado.cash got hacked. By us.。 iden3的circomlib中实现MIMC哈希函数的编码错误,Tornado使用circomlib库来构建deposit merkle tree,该编码错误使得可伪造witness的Merkle root并伪造证明。
BCTV14-Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture 论文中setup描述中的理论缺陷(未清除敏感数据)引起的soundness问题,详细见ZCash 2019年博客Zcash Counterfeiting Vulnerability Successfully Remediated。
5.5 应用中未正确设置nonce值引起的zero-knowledge问题Aztec中,因未正确设置nonce值引起的zero-knowledge问题,从而破坏了隐私性。详细见博客 Aztec 2.0 Pre-Launch Notes。
ZCash中(shielded)交易之间的关联性泄露有利用价值的信息,详细见2020年论文 Attacking Zcash Protocol For Fun And Profit。
dusk-network的plonk中,Prover端缺少(randomized)blinding来隐藏私有输入,从而存在潜在的ZK loss。。
aztec中的不完整tree constraints,会引起rollup validation的冻结,从而造成DoS问题。 详细见2021年博客Vulnerabilities patched in Aztec 2.0。
starkware-libs的cairo-lang项目中的有效签名被拒问题,该风险初期认为是可忽略的,详细见:
- Valid signature rejection not that negligible and possibly exploitable #39
- 1)密码学问题,如:
- Pedersen bases generation/uniqueness基于Pedersen的生成和唯一性
- algebraic hashes 和 commitments的padding方案
- 密码学方案的不合规实现(如 Poseidon algebra bugs)
- Insufficient data being “Fiat-Shamir’d" from the transcript:详细可参见博客 Bulletproofs和Plonk等ZKP系统中Fiat-Shamir实现漏洞Frozen Heart。
- 2)组合性问题:嵌套proof systems之间的不安全交互。
- 3)Side channels问题: Non-ct code, RAM leakage, speculative execution leaks(推测性执行泄露)
对ZKP的安全不必过于恐慌,因为:
- 已有强健的代码和框架,如arkworks和zkcrypto等Rust项目。
- 使用DSL(如Cairo、Leo等)有助于写更安全的代码,以及使用reusable gadgets/chips。
- 实践中相对窄的attack surface。
对ZKP的安全需要恐慌的原因在于:
- 只有少部分人理解zkSNARKs,只有更少部分人能发现bug。
- 缺少工具(如testing、fuzzing、verification)
- 使用更多的ZKP意味着质押更多的钱,也就意味着漏洞研究人员更大的责任。
我们需要更多的:
- 测试和(smart) fuzzing、formal verification工具
- 现实世界的说明文档(如代码即文档——cargo spec)
- 信息分享,详细可得的文章,如Bulletproofs和Plonk等ZKP系统中Fiat-Shamir实现漏洞Frozen Heart
[1] zkStudyClub: Zero-Knowledge Proofs Security, in Practice [JP Aumasson, Taurus] [2] Security of ZKP projects: same but different