零知识证明(ZKP)是一种协议,证明者通过该协议说服验证者某个陈述为真,而不泄露该陈述真实性之外的任何信息。核心属性是完备性、可靠性和零知识性。本指南解释了心智模型,使用占位 RPC 端点演示了一个可运行的验证工作流,并展示了如何测量证明生成和验证行为,而不编造基准数据。它还涵盖了常见故障模式以及 ZK 证明在区块链和身份系统中的适用场景。
零知识证明实际保证了什么
零知识证明是一种密码学协议,其中一方(证明者)说服另一方(验证者)某个陈述为真。其决定性约束是验证者除了该陈述的真实性之外学不到任何东西:既不知道秘密输入,也不知道中间计算过程,更不知道用于构造证明的任何见证数据。
这与简单地将数据隐藏在加密之后不同。加密保护传输中或静态存储的数据,但接收方仍会解密并看到明文。ZK 证明让验证者能够检查某个声明,而底层数据对所有人(包括验证者)保持隐藏。这一区别对区块链系统至关重要,因为区块链上的每笔交易和状态转换默认都是公开的。
三个属性定义了一个有效的零知识协议。完备性意味着拥有真实陈述的诚实证明者总能说服验证者。可靠性意味着不诚实的证明者无法说服验证者相信一个虚假陈述,除非概率可忽略不计。零知识性意味着验证者在交互中的视图可以在没有见证的情况下被模拟,因此不会泄露关于秘密的任何信息。
- 完备性:真实陈述会被接受。
- 可靠性:虚假陈述以压倒性概率被拒绝。
- 零知识性:证明除有效性外不泄露任何信息。
证明者-验证者心智模型与具体示例
经典的直觉是阿里巴巴洞穴:Peggy 声称她知道打开一个有两个入口的圆形洞穴中魔法门的秘密咒语。Victor 想要证明,但绝不能知道咒语。Peggy 随机进入一条隧道,Victor 喊出她必须从哪条隧道出来,她要么直接从那出来,要么使用秘密咒语穿过门。经过多轮后,Peggy 在不知道咒语的情况下每次都猜对的概率变得可忽略不计,而 Victor 始终不知道咒语本身。
一个更贴近区块链的例子是余额范围证明。用户想证明其账户余额高于某个阈值,而不透露确切余额。证明者构造一个证明,证明承诺值位于所需范围内,验证者根据公开承诺检查该证明。验证者只得知范围条件成立。
相同的结构出现在身份系统中:证明你年满 18 岁而不透露出生日期,证明你持有有效凭证而不透露凭证内容,或证明交易有效而不透露发送方、接收方或金额。每种情况都有一个公开陈述、一个私有见证,以及一个将二者绑定的证明。
交互式证明与非交互式证明
交互式证明需要证明者和验证者之间来回通信,如洞穴示例所示。它们适用于教学以及双方都在线的协议,但对区块链不实用,因为智能合约无法与每个用户运行多轮挑战-响应会话。
非交互式零知识证明(NIZK)将交互压缩为单条消息。证明者生成一个证明,任何人都可以在之后验证而无需进一步通信。Fiat-Shamir 启发式是标准转换方法:证明者通过对公开输入和承诺进行哈希来推导挑战,用证明者在承诺之前无法预测的确定性值替代验证者的随机挑战。
在实践中,大多数区块链 ZK 系统使用非交互式证明。zk-SNARK(简洁非交互式知识论证)生成小证明且验证快速,但在许多构造中需要可信设置。zk-STARK(可扩展透明知识论证)避免可信设置并依赖基于哈希的密码学,通常证明尺寸更大。正确的选择取决于验证成本、证明大小以及可信设置对你的威胁模型是否可接受。
- 交互式:多轮通信,适用于直接对等协议。
- 非交互式:单条证明消息,适用于链上验证。
- Fiat-Shamir:通过哈希将交互式证明转换为非交互式证明。
测试 ZK 工作流前的假设与前提条件
在运行任何验证程序之前,确认你要证明什么以及谁来验证。写下公开陈述、私有见证和验证函数。如果陈述含糊不清,证明可能有效但对你的应用毫无用处。
你还需要一个验证者环境。对于链上验证,这意味着一个已部署的验证者合约和一个能够执行读取调用和提交交易的 RPC 端点。对于链下验证,这意味着一个实现与链上验证者相同验证算法的库或 CLI。证明者和验证者之间的版本不匹配是导致误报失败的常见原因。
最后,决定如何测量行为。记录证明生成时间、证明大小、验证时间,以及链上验证时的 gas 成本。不要假设其他项目的数字适用于你的项目;电路复杂度、硬件和网络条件都会改变结果。
- 已定义公开陈述和私有见证。
- 已固定验证者合约或库版本。
- RPC 端点可用于读写调用。
- 针对生成、大小、验证和 gas 的测量计划。
使用 curl 和 JavaScript 的可运行验证工作流
以下过程假设你有一个已部署的验证者合约,其中包含 verifyProof 函数,以及由你的电路工具链生成的证明工件。将 RPC_URL、VERIFIER_ADDRESS 和证明字段替换为你自己环境中的值。目标是确认有效证明被接受,无效证明被拒绝。
首先,检查验证者合约是否可达,以及 RPC 端点是否响应。这是一个只读调用,不需要私钥。
- 成功响应返回十六进制编码的布尔值或 revert 数据。
- revert 通常意味着证明或公开输入格式错误。
- 重复测试时使用专用端点以避免速率限制。
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [{
"to": "VERIFIER_ADDRESS",
"data": "0xVERIFY_PROOF_CALLDATA"
}, "latest"]
}'用于有效和无效证明的 JavaScript 测试框架
下一个代码片段使用 ethers.js 调用验证者两次:一次使用真实证明,一次使用变异证明。预期结果是有效证明返回 true,变异证明返回 false 或 revert。这个负面测试至关重要;一个接受一切的验证者是不可靠的。
保持两次调用之间的公开输入完全相同,以便唯一区别是证明字节。如果两次调用都返回 true,则你的验证者或电路配置有误。
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const verifier = new ethers.Contract(
process.env.VERIFIER_ADDRESS,
["function verifyProof(bytes proof, uint256[] inputs) view returns (bool)"],
provider
);
const validProof = process.env.VALID_PROOF;
const inputs = JSON.parse(process.env.PUBLIC_INPUTS);
const mutatedProof = validProof.slice(0, -2) + "00";
const validResult = await verifier.verifyProof(validProof, inputs);
const invalidResult = await verifier.verifyProof(mutatedProof, inputs);
console.log({ validResult, invalidResult });
if (validResult !== true || invalidResult !== false) {
throw new Error("Verifier behavior does not match expectations");
}在验证表中记录结果
多次运行工作流并将结果记录在表中。下表是一个模板;填入你自己的测量值,而不是复制其他项目的数字。包括电路版本、证明系统和环境,以便结果可复现。
| 运行 | 电路版本 | 证明系统 | 生成时间 | 证明大小 | 验证时间 | 使用的 Gas | 有效证明被接受 | 无效证明被拒绝 |
|---|---|---|---|---|---|---|---|---|
| 1 | v1 | zk-SNARK | 测量值 | 测量值 | 测量值 | 测量值 | 是/否 | 是/否 |
| 2 | v1 | zk-SNARK | 测量值 | 测量值 | 测量值 | 测量值 | 是/否 | 是/否 |
如果无效证明被接受,请停止并修复验证者,然后再测量性能。如果有效证明被拒绝,请逐字节比较证明者输出和验证者调用之间的公开输入。编码不匹配、字节序差异和域元素排序是常见原因。
排查常见的 ZK 验证失败
大多数失败属于几类。证明编码错误发生在证明者序列化域元素的方式与验证者期望不同时。公开输入不匹配发生在验证者接收到的输入顺序或表示与电路定义不同时。版本漂移发生在电路重新编译但验证者合约未重新部署时。
链上验证也可能因 gas 限制、缺少预编译或目标链不支持的操作码而失败。如果验证在一个网络上有效但在另一个网络上无效,请检查验证者是否依赖目标链未暴露的椭圆曲线预编译。对于跨链或多网络测试,为每个网络使用可靠的端点,并将验证者地址和链 ID 保留在配置中。
如果你正在比较 RPC 提供商以进行重复验证测试,RPC Assistant 端点指南涵盖了具体的提供商选择,而本文侧重于验证方法论。
- 重新编码证明字节并与验证者 ABI 比较。
- 在证明者和验证者两侧记录公开输入。
- 将电路、证明者和验证者版本固定在一起。
- 确认目标链上的预编译和操作码支持。
局限性、权衡以及何时不应使用 ZK 证明
零知识证明功能强大但并非没有代价。证明生成计算成本高昂,尤其是对于大型电路,而且证明者需要访问见证。验证通常很便宜,这就是 ZK 证明适合链上用例的原因,但电路设计和审计的前期成本很高。
可信设置给某些证明系统引入了仪式依赖。如果设置被破坏,可靠性可能被破坏。透明证明系统避免了这一点,但通常产生更大的证明和更高的验证成本。这种权衡并非纯粹技术性的;它还涉及运营风险和可审计性。
ZK 证明本身也不能解决数据可用性问题。证明可以表明计算被正确执行,但底层数据可能仍需要在某处可用才能使系统运行。同样,ZK 证明不能替代访问控制或密钥管理;它们是对这些的补充。
ZK 证明在区块链和身份系统中的适用场景
在区块链网络上,ZK 证明支持隐私转账、余额范围证明、可验证计算以及 ZK rollup 等扩展方案。在身份系统中,它们支持选择性披露:证明凭证的某个属性而不透露凭证本身。在认证中,它们可以证明知道某个秘密而不传输它。
如果你正在部署验证者合约或运行证明者服务,周边基础设施与密码学同样重要。节点可靠性、端点延迟和一致的链状态会影响你的验证测试是否可复现。OnFinality 的网络目录列出了支持的链,当你需要可预测的吞吐量进行重复证明验证时,可以使用专用节点选项。
对于构建支持 ZK 的应用的团队,从小型电路、清晰的陈述和负面测试开始。只有在验证者可靠地拒绝无效证明之后才进行扩展。
- 隐私支付和机密转账。
- 范围证明和偿付能力检查。
- 选择性披露和凭证验证。
- 可验证计算和 ZK rollup。
构建和测试 ZK 工作流的后续步骤
通过定义一个陈述、构建证明它的最小电路,并编写正面和负面验证测试,从概念转向实现。将你的测量结果记录在上表中,并保持环境文档化。
当你准备针对实时网络进行测试时,选择与你的验证负载匹配的 RPC 端点。RPC Assistant 可以帮助你选择具体的端点,而 Learn 指南涵盖了节点托管和多链端点策略。如果你预计会有持续的验证流量,请查看 RPC 访问定价,并在共享端点引入可变性时考虑使用专用节点。
- 定义陈述和见证。
- 构建并审计最小电路。
- 运行正面和负面验证测试。
- 测量生成、大小、验证和 gas。
- 选择与你的负载匹配的端点策略。