Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南阅读约 11 分钟

零知识证明如何工作:证明者、验证者与链上验证的实用指南

技术性解析零知识证明的工作原理,从三个必要属性、交互式协议到非交互式 SNARK/STARK 验证,以及可复现的本地测试工具。

TL;DR

零知识证明允许证明者说服验证者某个陈述为真,而不透露使其为真的秘密。该协议必须满足三个属性:完备性(真陈述被接受)、可靠性(假陈述被拒绝)和零知识性(验证者除了陈述的有效性外学不到任何东西)。在实践中,大多数区块链系统使用通过 Fiat-Shamir 变换从交互式协议派生的非交互式证明,然后在链上或链下进行验证。本指南解释了其机制,展示了一个可运行的本地验证工具,并描述了如何在自己的基础设施上测量证明生成和验证行为。

直接答案与范围

零知识证明(ZKP)的工作原理是:证明者对秘密见证执行计算,并生成一个证明对象,验证者可以对照公开陈述进行检查。如果证明通过检查,验证者确信陈述为真,但证明除了陈述本身已隐含的信息外,不会透露任何关于见证的信息。

本文涵盖协议机制、交互式与非交互式证明的区别、SNARK 和 STARK 在实践层面的差异,以及用于在本地和针对 RPC 端点测试证明验证的可复现流程。它不推荐特定的端点或提供商;如需具体端点选择,请使用 RPC 助手。

  • 证明者:持有秘密见证并生成证明。
  • 验证者:对照公开陈述和验证密钥检查证明。
  • 陈述:被证明的公开声明,例如“我知道这个哈希的原像”。
  • 见证:使陈述为真的私有输入。

先决条件与假设

您应该熟悉基本的密码学概念,如哈希函数和公钥签名,并能在本地运行 Node.js 或 Python。示例假设您有一个可用于 EVM 兼容链的 RPC_URL(如果您想测试链上验证),但本地工具无需任何网络访问即可运行。

遵循概念部分不需要特定的 ZK 库。代码示例使用占位值和标准库调用,以便您将其适配到所选的证明系统。

定义 ZK 证明的三个属性

每个零知识证明系统必须满足三个属性。完备性意味着如果陈述为真且双方诚实遵循协议,验证者接受。可靠性意味着如果陈述为假,任何作弊的证明者都无法说服验证者,除非概率可忽略不计。零知识性意味着验证者除了陈述的真实性外学不到任何东西,形式上由存在一个模拟器来捕获,该模拟器可以在没有见证的情况下生成与真实记录不可区分的记录。

这些属性将 ZK 证明与普通签名或哈希承诺区分开来。签名证明您持有密钥,但不隐藏消息。ZK 证明隐藏见证,同时仍使验证者确信陈述为真。

交互式证明:挑战-响应心智模型

在交互式 ZK 证明中,证明者和验证者交换多条消息。经典结构是承诺-挑战-响应:证明者承诺一个从见证派生的值,验证者发送随机挑战,证明者以只有拥有见证的人才能在多个挑战中一致地做到的方式响应。

阿里巴巴洞穴和色盲朋友的例子说明了直觉。证明者在随机选择的挑战下反复展示知识;作弊的证明者在每一轮只有 50% 的几率猜对,因此在足够多的轮次后,未被发现作弊的概率变得可忽略不计。验证者永远学不到秘密,因为每个记录都可以由没有见证的人模拟。

  • 轮数控制可靠性误差:在最简单的协议中,每一轮将作弊概率减半。
  • 交互在链上成本高昂,因为它需要多笔交易或消息。
  • 交互式证明适用于双方都在线的链下协议。

非交互式证明与 Fiat-Shamir 变换

大多数区块链应用需要非交互式证明:一个单一的证明对象,任何人都可以在之后验证,而无需与证明者交谈。Fiat-Shamir 启发式通过将验证者的随机挑战替换为证明者承诺和公开陈述的哈希,将交互式协议转换为非交互式协议。

这之所以有效,是因为证明者在承诺之前无法预测哈希输出,因此挑战保持不可预测。生成的证明是一条单一消息,将陈述、承诺和响应绑定在一起。安全性取决于哈希函数表现得像随机预言机,这就是为什么在生产系统中哈希的选择和域分离很重要。

SNARK 与 STARK:实际差异

ZK-SNARK(简洁非交互式知识论证)产生小证明和快速验证,但通常需要可信设置仪式来生成证明密钥和验证密钥。ZK-STARK(可扩展透明知识论证)避免可信设置并依赖基于哈希的密码学,这使其具有合理的抗量子性,但证明通常更大,验证可能更昂贵。

正确的选择取决于您的约束条件。如果链上验证成本占主导,通常首选 SNARK。如果透明性和设置假设更重要,STARK 可能更可取。两者都是非交互式的,并且在正确实现时都满足三个 ZK 属性。

  • SNARK:证明尺寸小,验证快速,可能需要可信设置。
  • STARK:透明设置,证明较大,基于哈希的安全假设。
  • 两者都需要仔细的电路设计;电路中的错误可能破坏可靠性。

可运行的本地验证工具

以下 Node.js 脚本模拟非交互式证明检查的验证者端。它不实现真正的证明系统;它演示了您集成真实验证器库时将使用的控制流。将占位证明和验证密钥替换为您所选证明系统的输出。

设置环境变量后,使用 node verify.js 运行它。脚本成功时退出代码为 0,失败时为 1,这使其易于接入 CI。

// verify.js
const crypto = require('crypto');

const statement = process.env.STATEMENT || 'I know the preimage of this commitment';
const proof = process.env.PROOF || 'placeholder-proof';
const vk = process.env.VERIFICATION_KEY || 'placeholder-vk';

function verify(statement, proof, vk) {
  // Replace this with a real verifier call, e.g. snarkjs.groth16.verify(vk, publicSignals, proof)
  const digest = crypto.createHash('sha256').update(statement + proof + vk).digest('hex');
  return digest.length === 64;
}

const ok = verify(statement, proof, vk);
console.log(ok ? 'PROOF ACCEPTED' : 'PROOF REJECTED');
process.exit(ok ? 0 : 1);

诊断流程:测量证明验证行为

要了解 ZK 系统在您的基础设施上的行为,请测量三件事:证明生成时间、证明大小和验证时间。每次测量至少运行 30 次,并记录中位数和 95 百分位数。不要依赖单次运行,因为证明时间随见证大小和机器负载而变化。

使用以下流程将结果收集到表格中,以便跨机器或证明系统进行比较。下表是模板;请填入您自己测量的值,而不是假设任何数字。

  • 步骤 1:固定电路和见证大小,使运行具有可比性。
  • 步骤 2:使用单调时钟计时证明生成,并记录证明字节大小。
  • 步骤 3:单独计时验证,包括任何反序列化成本。
  • 步骤 4:重复 30 次并计算中位数和 p95。
  • 步骤 5:记录机器类型、库版本和使用的哈希函数。
| Run | Prove time (ms) | Proof size (bytes) | Verify time (ms) |
|-----|-----------------|--------------------|------------------|
| 1   |                 |                    |                  |
| 2   |                 |                    |                  |
| ... |                 |                    |                  |
| p50 |                 |                    |                  |
| p95 |                 |                    |                  |

通过 RPC 进行链上验证

如果您的验证者是智能合约,您可以通过 RPC 端点调用合约的 verify 函数来测试验证。以下 curl 示例使用占位 RPC_URL 和合约地址。请将它们替换为您自己的值。有关端点选择和可靠性考虑,请参阅 RPC 定价和计划 和 网络目录。

根据验证者是视图函数还是状态改变函数,调用返回布尔值或交易收据。视图函数测试成本更低,因为它不需要交易。

curl -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [{
      "to": "0xYourVerifierContract",
      "data": "0xYourEncodedProofAndPublicInputs"
    }, "latest"],
    "id": 1
  }'

预期验证结果与故障排除

验证成功返回 true 或状态为 1 的收据。验证失败返回 false、回滚或返回状态 0。如果看到回滚,在假设证明无效之前先解码回滚原因;常见原因包括公共输入不匹配、验证密钥过时或为不同电路版本生成的证明。

如果本地验证成功但链上失败,请检查公共输入是否以合约期望的相同顺序和格式编码。如果验证缓慢,请测量 gas 使用量,并考虑验证者合约是否可以优化,或者带有链上证明的链下验证者是否更合适。

  • 公共输入不匹配:重新编码并逐字节比较。
  • 验证密钥错误:确认密钥与电路和证明系统版本匹配。
  • 哈希不匹配:确保 Fiat-Shamir 记录使用相同的域分隔符。
  • Gas 限制:如果节点强制执行限制,请增加视图调用的 gas 限制。

局限性与权衡

零知识证明并非没有代价。证明计算成本高昂,且成本随电路复杂性增长。可信设置引入了必须仔细管理的仪式假设。证明系统还有特定版本的怪癖,由一个库版本生成的证明可能无法用另一个版本验证。

零知识并不意味着默认匿名。陈述本身可能泄露元数据,证明的时间或大小可能与见证属性相关。将 ZK 视为隐私设计的一个组成部分,而不是完整的解决方案。

后续步骤

从小型电路和本地验证者开始,然后在本地工具持续通过后转向链上验证。在优化之前,使用上述测量流程建立基线。当您准备针对实时链进行测试时,通过 RPC 助手 选择端点,如果需要可预测的吞吐量,请查看 专用节点选项。

有关 RPC 端点如何融入区块链工作流程的更广泛背景,请参阅 RPC 端点指南。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始