---
title: "如何使用零知识证明在以太坊上构建隐私应用"
description: "一种可复用的模式为以太坊上的匿名投票、混币器、空投和会员系统提供了动力。了解承诺-无效化器-证明循环，以及零知识工具如何使其在今天变得切实可行。"
author: "菲利普·克劳斯"
team: "EF Builder Growth"
tags:
  - "零知识证明"
  - "隐私"
published: 2026-05-12
image: /images/developers/blog/latest-post-header-2.png
breadcrumb: "以太坊上的隐私应用"
lang: zh
---

以太坊在设计上是极其公开的。任何拥有区块浏览器的人都可以看到每个地址、余额、交易、合约调用和事件。当你需要可验证性时，这种透明度是有用的。但当用户需要投票、申领、提款或证明成员身份，而不希望将每次操作都关联回同一个钱包时，这就成了一个问题。

匿名成员身份是一种可复用的模式，为以太坊上的大量隐私应用提供了动力。人们首先注册，然后证明他们属于该群体，而不透露他们具体是哪位成员。零知识证明是注册钱包和操作钱包之间的桥梁，这座桥梁不会透露是谁穿过了它。

周边的产品会发生变化，但隐私骨架保持不变。

## 通过匿名投票解释该模式 {#the-pattern-explained-through-anonymous-voting}

该模式包含三个部分。承诺用于注册每个成员。默克尔树将这些承诺汇聚成一个群体。证明和无效化器（nullifier）让一名成员可以操作一次，而不透露是哪位成员进行了操作。

### 第一步：注册 {#step-one-registering}

每个投票者在链下创建两个私有值：秘密值（secret）和无效化器（nullifier）。投票者将这些值哈希成一个公开的承诺，然后将该承诺注册到链上。

承诺是公开的注册记录。秘密值和无效化器是投票者稍后需要的私密票据（note）。如果丢失了该票据，投票者就无法证明其成员身份。如果泄露了它，其他人就可能代替该用户进行投票。

因为承诺是一个哈希，观察者无法恢复其中的私有值。承诺表明“有人注册了”，但不会透露稍后谁将使用该注册。

### 第二步：构建群体 {#step-two-building-the-crowd}

随着越来越多投票者的注册，应用将他们的承诺收集到一棵默克尔树中。默克尔树将一长串值压缩成一个单一的哈希，称为根（root）。更改列表中的任何值，哈希就会改变，因此根充当了整个集合的防篡改摘要。

这棵树就是你的匿名集。如果树中有十个用户，观察者可以将随后的操作范围缩小到这十个人之一。如果树中有一万个用户，就很难将操作与某个人联系起来。一个匿名集很小的隐私应用通常不是非常私密，即使密码学是正确的。

### 第三步：匿名操作 {#step-three-acting-anonymously}

当投票开始时，投票者不应使用注册承诺的同一个钱包进行投票。使用注册钱包投票会直接将投票与注册者联系起来，从而使隐私保护工作前功尽弃。相反，投票者会创建一个零知识证明。该声明被编码为一个电路，表示：“我知道能生成已注册承诺的私有值，并且我正在为这次投票揭示正确的无效化器哈希。”

该证明使验证者合约确信该声明是真实的。它不会透露秘密值、无效化器，也不会透露使用了哪个承诺。

无效化器是防止重复投票的机制。在提供证明的同时，投票者会发布一个无效化器哈希。投票合约在接受投票后会存储该哈希。如果同一私密票据再次用于同一次投票，它将生成相同的无效化器哈希，合约将拒绝第二次投票。结合证明，这使得合约只知道某个注册投票者操作了一次，而不知道具体是哪一个。

## 可复用的门控 {#the-reusable-gate}

同样的证明和无效化器组合不仅适用于投票。抛开投票的场景，你得到的是一个用于智能合约函数的隐私门控。

在函数运行之前，合约会检查默克尔根，验证证明，确认无效化器哈希未被使用，并将公共输入绑定到正确的应用、链、投票、申领或提款上。如果这些检查通过，它会将无效化器标记为已使用，并运行函数的其余部分。

将这个门控放在投票前面，你就得到了匿名投票。将它放在空投申领前面，你就得到了匿名申领。将它放在提款函数前面，你就得到了混币器式提款流程的核心。相同的承诺树，相同的无效化器理念，相同的证明模式。改变的只是函数体和周边的应用逻辑。

## 各部分在哪里运行 {#what-runs-where}

隐私工作通常在链下进行。用户存储票据，客户端应用构建见证数据并运行证明者来生成证明。索引器跟踪承诺和默克尔根。捆绑器将用户操作（UserOperation）传播到链上，并由 ERC-4337 代付合约赞助 Gas，因此一个全新的钱包不需要首先从用户已知的钱包中获取 ETH。

公开的执行发生在链上。验证者合约检查证明。应用合约检查有效的根和未使用的无效化器，存储无效化器哈希，并运行公开操作。

敏感的用户体验（UX）在于票据处理。应像对待密钥一样对待秘密值和无效化器。不要将它们放入分析、日志、URL、错误报告或常规的服务器端遥测中。一旦票据泄露，无论证明多么强大，隐私都将不复存在。

## 工具已经跟上 {#the-tooling-caught-up}

你不需要手动编写底层的密码学代码。一种常见的途径是使用高级零知识语言编写电路，生成一个 Solidity 验证者，然后从应用合约中调用该验证者。

合适的技术栈取决于具体任务。使用 snarkjs 的 Circom 是应用级电路的一条历史悠久的途径。使用 Barretenberg 的 Noir 是一条较新的、对开发者友好的途径。Halo2 和 gnark 是更底层的电路库。诸如 RISC Zero 或 SP1 之类的 zkVM 可以证明普通程序，但其证明成本可能比小型自定义电路更高。

对于匿名成员身份，在编写自己的电路之前，请先寻找现有的协议。Semaphore 将群组成员身份和基于无效化器的防双重使用机制打包到了合约和 JavaScript 库中。对于隐私投票和治理，MACI 是专门的途径，因为它增加了抗共谋的特性。成熟的协议通常比新电路更安全。

## 仅有证明是不够的 {#the-proof-is-not-enough}

如果钱包流程泄露了关联，即使是完美的证明也会失效。从钱包 A 注册，随后又从钱包 A 操作，任何观察者都能将这些交易联系起来。在操作之前直接从钱包 A 向钱包 B 提供资金，这笔资金交易也会产生同样的问题。

这就是为什么捆绑器和代付合约如此重要。操作钱包应该是全新的，并且它不需要从用户试图与操作分离开的钱包中接收 ETH。

同样的问题也存在于链下。从同一个 IP 地址、RPC 提供商或会话提交注册和操作交易，可能会削弱电路提供的隐私保护。前端可能会通过分析、本地存储和支持日志发生泄露。零知识证明隐藏了证明内部的值。它并不能隐藏交易周围的一切。

公共输入是隐私应用失败的另一个地方。在电路中标记为公开的、作为事件发出的、包含在调用数据（calldata）中的，或由合约存储的任何内容都是可见的。审查公共输入应像审查 Solidity 合约上的访问控制一样仔细。

## 这为构建者带来了哪些改变 {#what-this-changes-for-builders}

以太坊上的隐私是可交付的。构建者可以将各个部分组合成实际的应用。该技术栈包括用于私有声明的电路、用于证明检查的验证者、用于公共规则的应用合约、用于默克尔数据的索引器，以及用于无关联提交和 Gas 赞助的捆绑器加代付合约。

困难的部分在于产品设计、密钥管理、元数据卫生、审计以及扩大匿名集。如果其中任何一个环节出错，证明所提供的隐私就会荡然无存。

## 延伸阅读 {#further-reading}

1. [零知识证明 (ethereum.org)](https://ethereum.org/zero-knowledge-proofs/)
2. [Semaphore 文档](https://docs.semaphore.pse.dev/)
3. [MACI 文档](https://maci.pse.dev/)
4. [Circom 文档](https://docs.circom.io/)
5. [Noir 文档](https://noir-lang.org/)
6. [Halo2 手册](https://zcash.github.io/halo2/)
7. [gnark 文档](https://docs.gnark.consensys.io/)
8. [RISC Zero 文档](https://dev.risczero.com/api/)
9. [SP1 文档](https://docs.succinct.xyz/docs/sp1/introduction)
10. [EIP-4337：通过 EntryPoint 合约实现账户抽象](https://eips.ethereum.org/EIPS/eip-4337)