1. 引言
JP Aumasson(BLAK hash function family论文的作者之一),在2022年的zkStudyClub中分享了其审计多个ZKP项目中发现的各种隐患和bug。
主要关注的为:
- 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部件)
- 关于安全的多维思考
3. 什么是zkSNARKs安全?
- 1)soundness,通常为实践中的最大风险:
- invalid proofs应总是被拒绝。
- 应无法伪造、修改、重放valid proofs。
- 2)Zero-knowledge:proofs不应泄露witness信息(私有变量)
- 实践中,large programs的succinct proof仅可泄露一点点数据。
- 3)Completeness:通常DoS/可用性风险可能会被进一步利用:
- valid proofs应总是被接受。
- 所支持的所有programs/circuits应被正确处理。
4. 审计ZKP项目的主要挑战及策略
由于近些年才有实用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
- 社区
5. ZKP总体流程及失败案例
与第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:必须定义、实现并测试输入的有效性:
5.1 Filed arithmetic实现引起的soundness问题
Filed arithmetic实现引起的soundness问题的案例主要有:
- 1)appliedzkp的semaphore项目中,未对nullifier(为a shielded payment的unique ID)进行overflow检查引起的双花问题:
- 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<Self, SynthesisError> {
// 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<Self, SynthesisError> {
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并伪造证明。
5.4 论文理论错误引起的soundness问题
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。
5.6 应用中(shielded)交易之间的关联性引起的zero-knowledge问题
ZCash中(shielded)交易之间的关联性泄露有利用价值的信息,详细见2020年论文 Attacking Zcash Protocol For Fun And Profit。
5.7 Prover端缺少(randomized)blinding引起的zero-knowledge问题
dusk-network的plonk中,Prover端缺少(randomized)blinding来隐藏私有输入,从而存在潜在的ZK loss。。
5.8 Merkle tree中不完整的tree constraints引起的DoS问题
aztec中的不完整tree constraints,会引起rollup validation的冻结,从而造成DoS问题。
详细见2021年博客Vulnerabilities patched in Aztec 2.0。
5.9 DSL/签名 引起的Dos/Completeness问题
starkware-libs的cairo-lang项目中的有效签名被拒问题,该风险初期认为是可忽略的,详细见:
5.10 其它类型的bug
- 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(推测性执行泄露)
参考资料
[1] zkStudyClub: Zero-Knowledge Proofs Security, in Practice [JP Aumasson, Taurus]