ZKP 项目方看过来
作者:Saya,Bryce,Beosin 安全研究专家
1. 什么是零知识证明
零知识证明(Zero-Knowledge Proof,后文简写 ZKP)是一种密码学概念,它可以用来证明某个声明的真实性,而无需透露有关该声明的任何具体信息。在零知识证明中,证明者可以向验证者证明某个陈述是正确的,而验证者只会得到一个结果:要么接受该陈述的真实性,要么拒绝它,而无需了解证明的具体细节。
这个概念可以用一个简单的例子来解释。假设有两个人,一个是证明者和一个是验证者。证明者想向验证者证明自己知道一个秘密的密码,而不泄露密码本身。在传统的方式中,证明者可能会告诉验证者密码是什么,但在零知识证明中,证明者可以使用特殊的协议来向验证者证明他知道密码的正确性,而不泄露密码本身。
目前常见的零知识证明系统算法包括 zk-SNARKs、zk-STARKs、BulletProofs 等。
2. ZKP 在区块链中的应用
在区块链技术中,ZKP 有多种应用,例如提升隐私、改善可扩展性和安全性等。以下是 ZKP 在区块链中的一些关键应用:
1 隐私保护:
区块链是公共的,这意味着任何人都可以查看链上的所有交易。然而,有时候,用户可能希望保持他们的交易信息保密。ZKP 允许用户证明他们拥有足够的资金进行交易,同时不必公开他们的资金总额。这大大增强了用户的隐私保护。例如,Zcash 是一种使用零知识证明技术的加密货币,它允许用户隐藏交易的发送者、接收者和金额。
2 计算压缩与区块链扩容:
区块链的可扩展性是一个挑战,尤其是在大规模应用中。ZKP 可以用于减轻节点的负担,提高整个系统的可扩展性。通过使用 ZKP 验证交易的有效性,节点无需查看完整的交易历史记录,从而减少了存储和处理的负担,目前应用最广泛的 ZK Rollup 是一种扩展性解决方案,旨在提高以太坊及其他区块链网络的吞吐量和效率。它结合了 Rollup 和 ZKP 技术的优势,提供了高性能的去中心化应用程序(DApps)扩展方案。在传统的以太坊网络上,每个交易都需要被验证和记录在区块链上,这导致了交易处理的延迟和高成本。而 ZK Rollup 通过将大量的交易批量处理并压缩为单个区块,ZKP 则用于证明批量交易的有效性,从而确保交易的正确性和安全性。
3 身份验证:
零知识证明可以用于验证用户的身份而无需透露敏感的个人信息。例如,一个人可以使用零知识证明向网络证明他们满足某个特定的年龄要求或拥有某种特定的证书,而无需揭示他们的确切年龄或其他身份信息。
4 去中心化存储:
服务器可以向用户证明他们的数据被妥善保存,并且不泄露数据的任何内容。
总的来说,区块链的零知识证明在隐私保护、计算压缩与扩容、身份验证、去中心化存储等方面有着广泛的应用。它为区块链技术提供了更多的功能和选择,推动了区块链在不同领域的发展和应用。
3. ZKP 应用中的双花攻击
zk-SNARK(Zero-Knowledge Succinct Non-Interactive Argument of Knowledge)是一种基于零知识证明的技术,可以在不泄露真实信息的情况下证明某个声明的真实性。它是一种非常高效的零知识证明技术,可以在非常短的时间内生成和验证证明,同时保护隐私和安全性,所以应用非常广泛。但是,伴随着应用的扩展,其安全性也越来越受到关注。我们在不久前就曾发现了其通用漏洞:ZKP 项目中如果未正确校验 verify 函数中参数 input 的取值范围,攻击者可以伪造多个 input 通过校验,造成双花攻击。这种攻击影响范围非常广,涉及多个 zk-SNARK 算法包括:groth16、plonk 等,并且 solidity、js 等多种开发语言均存在该漏洞。该漏洞最开始由 poma 在零知识证明项目 Semaphore 上首次发现,并给出了两笔成功实施的交易示例,具体如下图所示:
该漏洞具体的攻击原理是,如果要在以太坊中生成和验证 zk-SNARK 证明,需要使用 F_p-arithmetic 有限域椭圆曲线电路,其中 p 值用于确定椭圆曲线有限域的范围,所以电路的 input 取值范围为 [0,1,…,p-1]。不同的曲线拥有不同的 p 值:
EIP-196 中定义的 BN254 曲线(也称为 ALT_BN128 曲线):
p = 21888242871839275222246405745257275088548364400416034343698204186575808495617
circom2 引入了两个新的素数,即 BLS12-381 曲线:
p = 52435875175126190479447740508185965837690552500527637822603658699938581184513
以及 plonk2:
18446744069414584321
但是验证代码实现时,不同的编程语言中整数变量类型可能远远大于电路中 input 的取值范围,如:solidity 中 uint256 能表示的数值范围为 ,java 中 lont int 为 [-2^63,2^63-1],javascript 的 bigint 等,此时存在下列多个 input 经过验证代码中的模 p 运算后,可以通过校验: 综上,只要知道了其中一个合法的证明参数 x,变量取值范围内的 都可通过校验。于是攻击者如果获取到任意已经验证通过的 x,即可构造 个 可以通过校验,具体的攻击流程如下:
随后 Semaphore 方确认并修复了该漏洞,ZoKrates、snarkjs 等 zk 库也同步进行了紧急修复,但 Beosin 安全研究员发现该问题目前并未存在一个统一的解决方案,例如 Semephore 协议将约束写到 pairing 库中并未在外层业务逻辑中显式校验数据的有效范围;而 circom 生成的合约代码以及 Tornado.Cash 则在 verify 函数中显式地校验 SNARK_SCALAR_FIELD,这种混乱不统一的解决方式可能会对很多新的 zk DApp 项目方造成困扰并出现安全隐患,因此我们希望能够使用一种标准化的方式来解决这个问题。
4. ERC-1922 中的双花攻击
目前以太坊具有一个 zk 相关的标准 EIP-1922,该标准介绍了用于验证 zk-SNARK 的 Verify 合约标准接口,具体代码如下:
pragma solidity ^0.5.6;
/// @title EIP-XXXX zk-SNARK Verifier Standard
/// @dev See https://github.com/EYBlockchain/zksnark-verifier-standard
/// Note: the ERC-165 identifier for this interface is 0xXXXXXXXX.
/// ⚠️ TODO: Calculate interface identifier
interface ERC1922 /* is ERC165 */ {
/// @notice Checks the arguments of Proof, through elliptic curve
/// pairing functions.
/// @dev
/// MUST return `true` if Proof passes all checks (i.e. the Proof is
/// valid).
/// MUST return `false` if the Proof does not pass all checks (i.e. if the
/// Proof is invalid).
/// @param proof A zk-SNARK.
/// @param inputs Public inputs which accompany Proof.
/// @param verificationKeyId A unique identifier (known to this verifier
/// contract) for the Verification Key to which Proof corresponds.
/// @return result The result of the verification calculation. True
/// if Proof is valid; false otherwise.
function verify(uint256[] calldata proof, uint256[] calldata inputs, bytes32 verificationKeyId) external returns (bool result);
}
其中,零知识证明 proof、inputs 变量类型都为 uint256[],该变量类型是目前 ZKP 算法中椭圆曲线运算最常用的,但是该接口中也未增加对应的安全防护,因此同样存在双花攻击的巨大安全隐患。
5. ERC-7520 解决方案
Beosin 根据上述问题,提出了 EIP-7520 防范这种安全风险,具体为以太坊生态中所有使用了 zk 技术的 DApp 项目方在 compliant verifier contract 中,都必须实现该接口从而使用规范统一而又安全的方式对所有 input 进行有效范围校验,具体接口如下:
pragma solidity ^0.5.6;
/// @title EIP-XXXX zk-SNARK public inputs Verifier Standard
/// Note: the ERC-165 identifier for this interface is 0xXXXXXXXX.
/// ⚠️ TODO: Calculate interface identifier
interface EIP7520 /* is ERC165 & ERC1922 */ {
/// @notice Checks the arguments of Inputs are within the scalar field
/// @dev
/// MUST return `true` if Inputs passes range check (i.e. the Inputs are
/// valid).
/// MUST return `false` if the Inputs does not pass range check (i.e. if the
/// Inputs are invalid).
/// @param inputs Public inputs which accompany Proof.
/// @param p Public input which accompany the curve.
function verifyPublicInput(uint256[] inputs,uint256 p) external returns (bool result);
}
verifyPublicInput 函数是这个标准的核心,涉及到的参数具体含义如下:
- inputs :定义为 uint256[] 类型,代表了 ZKP 项目中 verify 函数涉及到的公共信号参数
- p :定义为 uint256 类型,该值对应算法中使用的椭圆曲线的 p 值
下面将对比实现与未实现 EIP-7520 接口的两种情况下,针对该该攻击的不同表现,以向各位项目方表明风险:
1 假设我们在不调用本 eip 接口 verifyPublicInput 的情况下,直接使用 verify 合约代码进行证明验证,具体代码如下:
function verify(uint[] memory input, Proof memory proof) internal view returns (uint) {
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 (uint i = 0; i < input.length; i++)
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
vk_x = Pairing.addition(vk_x, vk.IC[0]);
if (!Pairing.pairingProd4(
Pairing.negate(proof.A), proof.B,
vk.alfa1, vk.beta2,
vk_x, vk.gamma2,
proof.C, vk.delta2
)) return 1;
return 0;
}
原始的证明验证通过的实验结果截图:
同时,可以伪造如下 4 个证明同样可以通过验证,造成双花攻击:
使用其中一个伪造的证明,验证结果如下图所示:
2 如果调用了本 eip 中的 verifyPublicInput 接口,上述伪造的证明则会验证失败,部分合约代码如下,其余详细部分可以参考 Reference Implementation:
function verifyx(uint[] memory inputs, Proof memory proof, bytes32 verificationKeyId,uint256 p) public returns (uint){
require(verifyPublicInput(inputs,p),"verifier-over-snark-scalar-field");
require(verify(inputs,proof,verificationKeyId),"verify fail");
return true;
}
function verifyPublicInput(uint256[] inputs,uint256 p) internal view returns (bool) {
for (uint i = 0; i < input.length; i++) {
require(input < p,"verifier-gte-snark-scalar-field");
}
return true;
}
实验结果如下图所示:
综上,可以发现如果不使用本接口对公共信号值进行取值范围的有效性校验,那么可能存在双花攻击风险。
免责声明:作为区块链信息平台,本站所发布文章仅代表作者及嘉宾个人观点,与 Web3Caff 立场无关。文章内的信息仅供参考,均不构成任何投资建议及要约,并请您遵守所在国家或地区的相关法律法规。