返回 Expert 笔记
Expert Day 232

ZK 应用 — 匿名投票与 MACI

MACI 设计动机(防贿选)、Coordinator 架构、消息加密 + ZK 处理

2026-12-19
Phase 4 - ZK电路开发实战 (Day 223-243)
ZKMACIvotinganti-collusionquadratic-funding

日期: 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解密 + 处理消息
VoteTallystate root各候选人得票累加票数
SubsidyCalculatortallyQF 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.sol0x...入口:注册 / signup / publishMessage
PollFactory.sol0x...部署 Poll
Poll.solper-pollmessage tree
MessageProcessor.solper-pollverify processor proof
Tally.solper-pollverify tally proof
VkRegistry.sol0x...注册各 verifier 的 vkey

真实使用案例

项目用途规模
clr.fund / Gitcoin clr.ggQF 资助~$300k 单次 round
MACI Round by PSEdemo~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 对比

维度SemaphoreMACI
防双投✓ (nullifier)✓ (state nonce)
匿名
防贿选
中心化无(任何人 verify)需要 Coordinator(活性 + 解密)
Coordinator 信任-不信任 privacy(强加密),信任 liveness
计票方式链上事件聚合Coordinator + ZK proof
适用场景简单匿名投票 / signaling高价值 + 防贿选场景(QF / treasury vote)

Coordinator 信任假设 / Trust Assumptions

Coordinator 能做什么坏事?

  1. 解密某个 voter 的票 — 加密已发生,但 coordinator 不公开就行(隐私 OK)
  2. 造假 tally — ZK proof 强制 coordinator 必须按规则算
  3. 拒绝服务(不出 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();

面试题

  1. Q: Semaphore 解决了「匿名」,为什么投票还需要 MACI? A: Semaphore 防止识别投票人,但没法防贿选:选民可以把 proof 给贿赂者验证。MACI 引入 Coordinator + 加密消息 + 可重写票,让贿赂者无法验证最终票,从根本上破坏贿选可执行性。

  2. Q: MACI 的 Coordinator 是中心化角色,会不会破坏去中心化? A: Coordinator 不能破坏 privacy(加密 + ZK 强制规则),但可以拒绝出 proof(活性问题)。生产中:(a) MPC 拆分 coordinator key;(b) 时间限制 + slashing;(c) 终极方案是 trustless coordinator(TEE 或 MPC + ZK)。

  3. Q: 为什么 MACI 的 ZK 电路这么大(百万级约束)? A: 单个 proof 要处理 batch of messages:每个 message 涉及 ECDH 解密、签名验证、state tree update(Merkle proof + hash)、nonce check。一个 batch 100 messages 就要几百万约束,所以 prover 是 Coordinator 而非用户。

  4. 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」电路。