ZK 应用 — 匿名投票与 MACI
MACI 设计动机(防贿选)、Coordinator 架构、消息加密 + ZK 处理
日期: 2026-12-19 方向: ZK工程 / 电路开发 阶段: Phase 4 - ZK电路开发实战 (Day 223-243) 标签: #ZK #MACI #voting #anti-collusion #quadratic-funding
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | MACI 设计动机(防贿选)、Coordinator 架构、消息加密 + ZK 处理 |
| 实操 | 研究 MACI v1.x 架构图、对比 Semaphore,理解 trust assumptions |
| 产出 | maci.md(架构 + 流程 + 攻击模型分析) |
背景与定位
为什么投票需要超越「匿名」?/ Why voting needs more than anonymity?
匿名投票(Semaphore-style)解决了「谁投了」隐私,但不能防贿选:
- 贿赂者:「投票给 A 我给你 100 USDC」
- 选民:投完,把 ZK proof 给贿赂者证明自己投了 A
- 这种「可验证投票」反而帮助了贿赂
MACI 的关键创新:Coordinator + 不可拒绝的可重写权
- 选民通过 Coordinator 公钥 加密投票消息发到链上
- 选民可以随时发新消息覆盖之前的投票,且贿赂者无法验证哪个是最终意图
- Coordinator 负责解密 + 处理消息 + 用 ZK proof 证明计票正确
MACI = Minimal Anti-Collusion Infrastructure
由 Barry WhiteHat、Kobi Gurkan、Koh Wei Jie 提出(2019),Vitalik 2020 年 推荐文章。
核心架构图 / Core Architecture
┌────────────────────────────────────────────────────────┐
│ SETUP PHASE │
│ │
│ Coordinator (cKey) → generates ECDH keypair │
│ publishes cPubKey │
│ Voters (1..N) → each generates voterPubKey │
│ register on-chain (commitment) │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ VOTING PHASE │
│ │
│ Voter i: │
│ msg = (newPubKey, voteOption, voteWeight, nonce) │
│ encMsg = ECDH-encrypt(msg, cPubKey) │
│ submit on-chain (msg pushed to message tree) │
│ │
│ Voter i can override by submitting another encMsg with │
│ higher nonce. Briber CAN'T tell which is final. │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ TALLY PHASE (Coordinator) │
│ │
│ Coordinator decrypts each message off-chain. │
│ Process each message, applying valid ones: │
│ - check signature │
│ - check nonce monotonic │
│ - update voter pubkey (key change) │
│ - update vote tally │
│ │
│ Generate ZK proof: │
│ "I correctly processed all messages and computed │
│ the tally" │
│ │
│ Submit (tallyResult, proof) to chain. │
│ Contract verifies proof, accepts result. │
└────────────────────────────────────────────────────────┘
MACI 关键技术细节
1. 消息结构
message = (
voterIndex, // who am I in the registry
newPubKey, // for key rotation
voteOption, // which proposal
voteWeight, // how much weight (in QF: sqrt of contribution)
nonce, // monotonic, prevents replay
signature, // signed with current voterPrivKey
)
encryptedMessage = encrypt(message, sharedKey)
sharedKey = ECDH(voterPrivKey, coordinatorPubKey)
2. 三层 ZK 电路
| 电路 | 输入 | 输出 | 作用 |
|---|---|---|---|
| MessageProcessor | 一批 encrypted msgs | 更新后的 state root | 解密 + 处理消息 |
| VoteTally | state root | 各候选人得票 | 累加票数 |
| SubsidyCalculator | tally | QF subsidy | (optional)二次方融资 |
每个电路约束 ~50万到几百万级别,coordinator 在自己机器上 prove。
3. State Tree
StateTree leaves: (pubKey, voteTally[], voiceCreditsLeft)
MessageTree: all encrypted messages submitted
每个 message 处理后 StateTree 的对应 leaf 更新。最终 root 提交链上。
4. Bribery resistance 数学
贿赂者 B 给选民 V:
- B 让 V 公开 secret key?V 投了之后 B 可以验证。但 V 可以事先生成 keypair(V'),然后用 V' 给 B 看,自己用 V 投。
- B 让 V 不投?这是「buying abstention」,MACI 解决不了,但成本与每张票相当(不可扩展)。
- B 让 V 在投完后给 secret key?V 投了之后还可以再投一次覆盖前票,B 没法验证最终票。
数学上:briber 不能区分(投A 然后改投B)和(直接投B)的链上 trace(都是加密消息)。
MACI v1.x 实现细节
真实合约
| 合约 | 地址 | 作用 |
|---|---|---|
| MACI.sol | 0x... | 入口:注册 / signup / publishMessage |
| PollFactory.sol | 0x... | 部署 Poll |
| Poll.sol | per-poll | message tree |
| MessageProcessor.sol | per-poll | verify processor proof |
| Tally.sol | per-poll | verify tally proof |
| VkRegistry.sol | 0x... | 注册各 verifier 的 vkey |
真实使用案例
| 项目 | 用途 | 规模 |
|---|---|---|
| clr.fund / Gitcoin clr.gg | QF 资助 | ~$300k 单次 round |
| MACI Round by PSE | demo | ~100 voters |
| OpenZeppelin DAO governance | 计划中 | - |
Gas 与 prover 数据
- Sign-up:~200k gas(注册 voter)
- PublishMessage:~100k gas(提交 encrypted msg)
- MessageProcessor proof:~10 min on 32-core CPU(处理 100 messages)
- Tally proof:~5 min
- 链上 verify:~600k gas (Groth16 verify × 2)
MACI 与 Semaphore 对比
| 维度 | Semaphore | MACI |
|---|---|---|
| 防双投 | ✓ (nullifier) | ✓ (state nonce) |
| 匿名 | ✓ | ✓ |
| 防贿选 | ✗ | ✓ |
| 中心化 | 无(任何人 verify) | 需要 Coordinator(活性 + 解密) |
| Coordinator 信任 | - | 不信任 privacy(强加密),信任 liveness |
| 计票方式 | 链上事件聚合 | Coordinator + ZK proof |
| 适用场景 | 简单匿名投票 / signaling | 高价值 + 防贿选场景(QF / treasury vote) |
Coordinator 信任假设 / Trust Assumptions
Coordinator 能做什么坏事?
- ✗ 解密某个 voter 的票 — 加密已发生,但 coordinator 不公开就行(隐私 OK)
- ✗ 造假 tally — ZK proof 强制 coordinator 必须按规则算
- ✓ 拒绝服务(不出 proof) — 投票永远不被计票
应对:
- Coordinator 私钥用 MPC 拆分给多人
- 投票期结束后 N 天内 coordinator 必须 proof,否则惩罚
- v2.0 提出 trustless coordinator 用 TEE 或 MPC
真实漏洞案例 / Real Bugs
Bug 1: MACI v1.0 Underconstrained Tally
PSE 团队 2022 年内部审计发现:tally circuit 某 sumcheck 约束写漏,coordinator 可以在「无作弊」proof 中 accumulate 错误的票数。修复:commit aaff7ac,约束补全。
Bug 2: Encrypted Message Replay
早期版本:voter 发了 nonce=1 message,攻击者复制相同 calldata 再发一次。修复:链上跟踪 (voterIndex, nonce) 防 replay。
Bug 3: Coordinator key compromise
如果 Coordinator 私钥泄露:所有过去 vote 可被解密(隐私失效),但 tally 仍正确(ZK 强制)。真实事件:clr.fund 早期 round 用单个 Coordinator key,之后改用 MPC 分散。
clr.fund 实战流程
clr.fund 是 MACI + Quadratic Funding 的合体:
1. Round 启动:matching pool 资金锁定
2. Project 注册:项目方提交(白名单审核)
3. Donor signup:donor 验证身份(BrightID / GitHub)→ 得到 voice credits
4. Voting period (14 天):
donor 给 project 分配 voice credits(vote weight = sqrt(credits))
可以多次改票
5. Tally period:
coordinator 处理消息,生成 proof
QF 公式计算 matching: sum_i ( sqrt(c_i) )^2 - sum_i c_i
6. Distribution: matching pool 按比例分给 projects
真实数据:clr.fund Round 7 (2022):
- Donors: ~700
- Projects: ~30
- Total contribution: ~$50k
- Matching pool: ~$300k
- Coordinator proof time: ~30 min total
关键速查
// MACI SDK
import { MACI } from "maci-sdk";
const maci = new MACI(...);
await maci.signup(voterPubKey);
await maci.publishMessage(encryptedMsg, signature);
// Coordinator 端
await maci.processMessages(); // generate processor proof
await maci.tally(); // generate tally proof
await maci.publishResult();
面试题
-
Q: Semaphore 解决了「匿名」,为什么投票还需要 MACI? A: Semaphore 防止识别投票人,但没法防贿选:选民可以把 proof 给贿赂者验证。MACI 引入 Coordinator + 加密消息 + 可重写票,让贿赂者无法验证最终票,从根本上破坏贿选可执行性。
-
Q: MACI 的 Coordinator 是中心化角色,会不会破坏去中心化? A: Coordinator 不能破坏 privacy(加密 + ZK 强制规则),但可以拒绝出 proof(活性问题)。生产中:(a) MPC 拆分 coordinator key;(b) 时间限制 + slashing;(c) 终极方案是 trustless coordinator(TEE 或 MPC + ZK)。
-
Q: 为什么 MACI 的 ZK 电路这么大(百万级约束)? A: 单个 proof 要处理 batch of messages:每个 message 涉及 ECDH 解密、签名验证、state tree update(Merkle proof + hash)、nonce check。一个 batch 100 messages 就要几百万约束,所以 prover 是 Coordinator 而非用户。
-
Q: MACI 与 Quadratic Funding 是什么关系? A: MACI 是基础设施(防贿选 / 防 collusion);QF 是分配机制(数学上 sum-of-square-roots-then-square)。clr.fund / Gitcoin Grants 把两者结合,因为 QF 高度激励 collusion,必须用 MACI 防御。
明日预告
Day 233 — ZK 应用:游戏(Dark Forest 与 ZK 游戏模式)。Web3 ZK 游戏的开山之作 Dark Forest,分析其「fog of war」电路。