ZK 应用 — 身份(Semaphore / World ID / Sismo)
三大 ZK 身份协议架构、commitment + nullifier 在身份场景的复用
日期: 2026-12-18 方向: ZK工程 / 电路开发 阶段: Phase 4 - ZK电路开发实战 (Day 223-243) 标签: #ZK #Semaphore #WorldID #Sismo #identity #anonymous-signaling
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 三大 ZK 身份协议架构、commitment + nullifier 在身份场景的复用 |
| 实操 | 部署 Semaphore demo(创建 identity → join group → broadcast signal) |
| 产出 | semaphore_demo/(合约 + JS SDK 调用 + 完整流程) |
背景与定位
为什么 ZK 身份是 Killer App?
- 传统身份:要么完全公开(钱包地址)要么完全私有(KYC)。
- ZK 身份:「证明我是 X 类人」(成年/真人/某 DAO 成员)但不暴露具体是谁。
- 核心三大场景:
- Proof of Personhood — 反女巫、UBI(World ID, BrightID)
- Anonymous Membership — 匿名投票、whistleblowing(Semaphore)
- Selective Disclosure — 选择性披露属性(Sismo, Polygon ID)
三大协议架构对比
| 维度 | Semaphore | World ID | Sismo |
|---|---|---|---|
| 维护者 | PSE (Ethereum Foundation) | Worldcoin / Tools for Humanity | Sismo Labs |
| 身份来源 | 自创建 (任意 secret) | 虹膜扫描 (Orb) | 链上行为聚合 |
| 反女巫 | 由 group 维护者保证 | 生物识别(一人一证) | aggregated badges |
| 协议 ZK | Circom + Groth16 | Semaphore-based | Pythia (Hydra-S2) |
| Hash | Poseidon | Poseidon | Poseidon |
| 树深度 | 20 | 30 | 20 |
| 隐私强度 | 极强 | 极强(生物 ID 不上链) | 中等(badges 公开) |
| 主要场景 | 匿名投票/聊天/空投 | UBI / 真人证明 | 链上 reputation |
Semaphore 深度解析
核心数据结构
Identity = (trapdoor, nullifier) ← 用户秘密
Commitment = Poseidon(trapdoor, nullifier, 0)
Group = Merkle tree of commitments
ExternalNullifier = epoch / topic / scope
NullifierHash = Poseidon(nullifier, externalNullifier)
Signal = anything (vote, message)
SignalHash = keccak256(signal) >> 8
电路(简化)
Public:
root — group merkle root
externalNullifier — context (e.g., poll ID)
nullifierHash — derived
signalHash — bound to message
Private:
trapdoor, nullifier
pathElements[20], pathIndices[20]
Constraints:
commitment = Poseidon(trapdoor, nullifier, 0)
MerkleProof(commitment, pathElements, pathIndices) === root
nullifierHash === Poseidon(nullifier, externalNullifier)
signalHash * signalHash === signalHash * signalHash // bind signal
nullifierHash 让 (identity, externalNullifier) 唯一——同一人在同一 poll 只能投一次,但跨 poll 不可关联。
完整 Semaphore Demo 代码
1. 创建 Identity
// scripts/create_identity.ts
import { Identity } from "@semaphore-protocol/identity";
import * as fs from "fs";
const identity = new Identity();
console.log("commitment :", identity.commitment.toString());
console.log("trapdoor :", identity.trapdoor.toString());
console.log("nullifier :", identity.nullifier.toString());
fs.writeFileSync("identity.json", JSON.stringify({
trapdoor: identity.trapdoor.toString(),
nullifier: identity.nullifier.toString(),
}));
2. 部署 Group 合约
// contracts/AnonymousVoting.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
contract AnonymousVoting {
ISemaphore public semaphore;
uint256 public groupId;
uint256 public pollId; // externalNullifier
address public coordinator;
event ProposalCreated(uint256 pollId);
event Voted(uint256 indexed pollId, uint256 vote, uint256 nullifierHash);
constructor(address _semaphore, uint256 _groupId) {
semaphore = ISemaphore(_semaphore);
groupId = _groupId;
coordinator = msg.sender;
}
function joinGroup(uint256 commitment) external {
// typically restricted; here open for demo
semaphore.addMember(groupId, commitment);
}
function createPoll() external {
require(msg.sender == coordinator, "only coord");
pollId++;
emit ProposalCreated(pollId);
}
function vote(
uint256 vote, // YES=1 / NO=0
uint256 merkleTreeRoot,
uint256 nullifierHash,
uint256[8] calldata proof
) external {
semaphore.verifyProof(
groupId,
merkleTreeRoot,
vote, // signal
nullifierHash,
pollId, // externalNullifier
proof
);
emit Voted(pollId, vote, nullifierHash);
}
}
3. JS 投票脚本
// scripts/vote.ts
import { Identity } from "@semaphore-protocol/identity";
import { Group } from "@semaphore-protocol/group";
import { generateProof } from "@semaphore-protocol/proof";
import { ethers } from "hardhat";
import * as fs from "fs";
async function main() {
const json = JSON.parse(fs.readFileSync("identity.json", "utf8"));
const identity = new Identity(json.trapdoor + ":" + json.nullifier);
// load group state from chain (omitted: subscribe to MemberAdded events)
const groupId = 1;
const group = new Group(groupId, 20);
const memberCommits: bigint[] = await fetchAllMembers();
memberCommits.forEach(c => group.addMember(c));
const voteValue = 1n; // YES
const pollId = 1n; // externalNullifier
const { proof, merkleTreeRoot, nullifierHash } = await generateProof(
identity,
group,
pollId, // externalNullifier
voteValue, // signal
{ wasmFilePath: "./node_modules/@semaphore-protocol/proof/snark-artifacts/semaphore.wasm",
zkeyFilePath: "./node_modules/@semaphore-protocol/proof/snark-artifacts/semaphore.zkey" }
);
const voting = await ethers.getContractAt("AnonymousVoting", process.env.VOTING!);
const tx = await voting.vote(voteValue, merkleTreeRoot, nullifierHash, proof);
const r = await tx.wait();
console.log("voted, gas =", r!.gasUsed.toString());
}
main();
4. 部署脚本
// scripts/deploy.ts
import { ethers } from "hardhat";
async function main() {
// Semaphore deployed addresses (mainnet/testnet)
const SEMAPHORE = "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131"; // sepolia
const Voting = await ethers.getContractFactory("AnonymousVoting");
const voting = await Voting.deploy(SEMAPHORE, 1 /* groupId */);
await voting.waitForDeployment();
console.log("Voting:", await voting.getAddress());
}
main();
真实合约地址 / Real Contract Addresses
| 协议 | 网络 | 地址 |
|---|---|---|
| Semaphore (V3) | Ethereum mainnet | 0x4B62B05F2cD60adAec5060daF73Ee5Ed01b3712b |
| Semaphore (V4) | Sepolia | 0x...(最新见 docs.semaphore.pse.dev) |
| World ID | Optimism | 0x57f928158C3EE7CDad1e4D8642503c4D0201f611 |
| World ID | Polygon | 0x515f06B36E6D3b707eAecBdeD18d8B384944c87f |
| Sismo Hub | Mainnet | 0x44a8e4F9bCa67E0CB4533ee2d05D77ABe7c80Bd9 |
真实数据 / Real Data
Semaphore proof
- proof size: 256 bytes (Groth16 + 8 public)
- verify gas: ~250,000
- prove time(浏览器): ~3 sec
- 约束数:~12,000
World ID
- 注册用户:~5M+ orbs verified(截至 2024)
- 应用:Worldcoin grants(每周 ~3 WLD UBI)、Discord/Reddit 反 sybil
- 协议:Semaphore + 「世界级别 group」(所有 Orb 验证用户)
Sismo
- 累计 badges minted:~500k
- 主要 use case:DAO 空投定向(如 Lens)、协议 boosted user
- 已停止主要运营(2024 后转向 Sismo Connect)
安全教训 / Security Lessons
漏洞 1:Semaphore V1 trusted setup
2020 年 Semaphore V1 ceremony 出现某个 contributor 没有正确销毁 toxic waste 的怀疑。V2 重做 ceremony,扩大参与者到 50+。教训:trusted setup 透明度直接影响协议可信度。
漏洞 2:World ID 隐私争议
虹膜数据虽不上链,但 Worldcoin 中心化服务器存储模板。批评者:「ZK 在前端,集权在后端」。Tools for Humanity 后续推出 Personal Custody 让用户本地存储。
漏洞 3:MACI v1 underconstrained
Privacy & Scaling Explorations 的 MACI v1(基于 Semaphore)2022 年审计发现:投票电路某约束允许 prover 提交多次相同 nullifier。修复:v1.1 加强约束。
漏洞 4:Nullifier 范围攻击
如果 externalNullifier 选错(如对所有 polls 用同一个),nullifierHash 会跨 poll 被关联,破坏匿名性。Semaphore docs 强烈建议每个 poll 用独立 externalNullifier。
生产经验
- 离线 group 同步:Semaphore 链上只存 root,client 必须从事件重建 leaves。大群(100k+ members)同步可能慢,需要后端服务(如 zk-kit Bandada)。
- proof 在浏览器 vs 后端:3 秒可接受;如要支持手机端,考虑 server-side prover(牺牲一点信任)。
- gas 优化:Semaphore V4 把 verifier gas 从 ~280k 降到 ~210k(custom verifier),重要因为投票场景每用户都要 verify 一次。
关键速查
// SDK 调用速查
import { Identity } from "@semaphore-protocol/identity";
import { Group } from "@semaphore-protocol/group";
import { generateProof, verifyProof } from "@semaphore-protocol/proof";
const identity = new Identity();
const group = new Group(groupId, treeDepth);
group.addMember(identity.commitment);
const { proof, merkleTreeRoot, nullifierHash } =
await generateProof(identity, group, externalNullifier, signal, snarkArtifacts);
await verifyProof(proof, treeDepth);
面试题
-
Q: Semaphore 的核心 ZK 证明在证明什么? A: 证明 (1) 我知道某个 trapdoor/nullifier 对应一个 commitment;(2) 这个 commitment 在 Group 的 Merkle tree 中;(3) 同时输出
Poseidon(nullifier, externalNullifier)作为 nullifierHash 防双投。整个过程不暴露 trapdoor/nullifier。 -
Q: World ID 与 Semaphore 是什么关系? A: World ID 是 Semaphore 协议的特化应用:群组就是「所有通过 Orb 虹膜验证的人」。技术栈复用 Semaphore,但身份准入由 Tools for Humanity 中心化运营 Orb 设备控制。
-
Q: 为什么 nullifier 要和 externalNullifier 一起 hash? A: 让同一身份在不同上下文(不同 poll)产生不同 nullifierHash,避免跨 poll 关联(保护匿名性),同时在同一 poll 内防双投。这是 ZK 身份协议的精妙设计。
-
Q: World ID 的「proof of personhood」是真正的 ZK 吗? A: 链上部分是 ZK(与 Semaphore 同),但身份获取(虹膜扫描)依赖中心化 Orb 设备。批评者称为「ZK on-chain, trust off-chain」。
明日预告
Day 232 — ZK 应用:投票 / MACI。Vitalik 倡导的 Minimal Anti-Collusion Infrastructure,解决「贿选」问题。