零知识证明与隐私计算:zk-SNARK/zk-STARK 与应用

深入零知识证明的数学核心与工程实践:Groth16/PLONK 证明系统对比、Circom 电路编写、Tornado 隐私交易原理、zkRollup 与 zkEVM 扩容,以及可验证凭证在身份体系中的应用。

零知识证明(Zero-Knowledge Proof, ZKP)被誉为密码学的"圣杯":证明者可以不泄露任何信息地让验证者相信某个陈述为真。它既是隐私币(Zcash、Tornado Cash)的基石,也是 zkRollup 扩容方案与新型身份体系的引擎。本文将从数学直觉出发,拆解 zk-SNARK/zk-STARK 的异同,并带你在 Circom 中编写第一个电路。

一、零知识证明的核心思想

什么是零知识?

一个零知识证明系统包含两个角色:

证明者 Prover ──► 证明 π(不含任何秘密信息) ──► 验证者 Verifier
     │                                               │
     │ 知道秘密 w(如哈希原像)                        │ 只看到公开输入 x 与证明 π
     │ 声称:"我知道 x 对应的秘密 w"                   │ 验证 π,接受或拒绝

一个安全的证明系统必须满足三条性质:

性质英文含义
完备性Completeness若陈述为真且证明者诚实,验证者必然接受
可靠性Soundness若陈述为假,恶意证明者几乎不可能伪造可接受的证明
零知识Zero-Knowledge验证者除"陈述为真"外,学不到任何关于秘密的信息

交互式 vs 非交互式

最早的零知识协议需要多轮交互(挑战-响应)。Fiat-Shamir 变换通过哈希模拟挑战,把交互协议转化为单条消息——这正是"非交互式"(Non-Interactive)的来源,也是链上验证的前提:

交互式:
Prover ──承诺──► Verifier ──随机挑战──► Prover ──响应──► Verifier 验证

Fiat-Shamir 变换:
Prover 用 hash(承诺) 作为"挑战",自己完成对话,输出 (承诺, 响应)
Verifier 重算 hash(承诺) 并校验响应

一句话:零知识证明的本质是"用多项式承诺与算术电路,把’我知道秘密’这一事实压缩成可验证的数学命题"。

二、zk-SNARK:简洁非交互式知识论证

定位

zk-SNARK(Succinct Non-interactive Argument of Knowledge)是目前链上使用最广泛的证明系统。其核心优势是证明体积小、验证极快,代价是通常需要可信设置(Trusted Setup)。

Groth16:经典的配对友好证明系统

Groth16(2016 年论文)是当前最紧凑的 zk-SNARK:

  • 证明大小:128 字节(2 个 G1 元素 + 1 个 G2 元素,BN254 曲线)
  • 验证:单个双线性配对方程,约 1-3 ms
  • 可信设置:每个电路都需要一次设置仪式(Generate/Prove/Verify 三个 key)
  • 用于 Zcash、Tornado Cash、以及大量 zkRollup 内部
Groth16 证明 π = (π_A ∈ G1, π_B ∈ G2, π_C ∈ G1)

验证方程(简化):
e(π_A, π_B) = e(g, h)^α · e(π_C, h) · e(vk_x, h)^γ · ...

其中 e 为双线性配对,vk_x 由公开输入决定

可信设置仪式(Trusted Ceremony)

可信设置的核心风险是:若参与者销毁了随机数(toxic waste),系统就是安全的;否则可伪造证明。因此:

  1. Zcash 2016 年仪式:6 名参与者,其中一人可攻破(后被审计修正)
  2. 2019 年 Powers of Tau 仪式:Power of Tau 支持通用设置(一次仪式服务所有电路),201 名参与者
  3. PLONK 等系统利用"结构化参考字符串"(SRS),将可信设置从"每电路"降到"每曲线"

一句话:Groth16 用"每电路一次可信设置"换来了全场最小证明与最快验证,适合对安全仪式有信心的场景。

三、zk-STARK:可扩展透明论证

zk-STARK(Scalable Transparent Argument of Knowledge)在 2018 年由 Eli Ben-Sasson 等人提出,从根上消除可信设置:

特性具体表现
透明(Transparent)只用公开随机数,无需可信设置
抗量子基于哈希(Merkle 树 + Reed-Solomon 编码),非椭圆曲线
证明大小数十到数百 KB(简单语句约 45 KB,复杂语句可达 1 MB+)
验证复杂度O(log² n),多项式对数级别
证明者复杂度准线性(Prover 较慢,内存开销大)

底层技术:FRI 协议

STARK 的核心是 FRI(Fast Reed-Solomon Interactive Oracle Proof):

1. 把计算约束编码为多项式 P(x)
2. 在大量点上对 P 求值(域扩张,提高冗余度)
3. 进行多轮"折叠":P(x) = P_even(x²) + x·P_odd(x²)
4. 每轮用哈希承诺,验证者随机抽查少量点
5. 最终用 Merkle 根固定整个轨迹

StarkWare(Starknet、dYdX v3)与 RISC Zero、Succinct(SP1)是主要工程推动者。

代表性进展

  • STIR(2024):把 FRI 的证明规模再降一个数量级
  • 递归证明:把多个证明"折叠"成一个,实现无限规模验证(如 Plonky2/Plonky3)
  • 预计算证明:验证成本可摊销到每笔交易低于 100k gas(如 Polygon Miden)

一句话:STARK 用更大的证明体积换来了"无需信任任何仪式"与"抗量子",是未来证明系统的主航道之一。

四、SNARK vs STARK 对比

维度zk-SNARK(Groth16)zk-SNARK(PLONK)zk-STARK
可信设置每电路通用(一次/曲线)无(透明)
证明大小~128 B~数百 B数十~数百 KB
验证时间~ms(1 次配对)~ms(多次配对)~ms(哈希运算)
Prover 速度快中慢(内存高)
抗量子否(椭圆曲线)否是(哈希)
递归支持困难良好良好(Plonky2 等)
代表Zcash、TornadoAztec、zkSync(部分)Starknet、RISC Zero

一句话:选 Groth16 看重最小体积,选 PLONK 看重通用设置与递归,选 STARK 看重透明与抗量子。

五、电路与证明系统:Circom / SnarkJS / PLONK

算术电路:把程序变成多项式约束

零知识电路用约束(constraints)描述计算。Circom 2 是最流行的电路语言:

// 电路:证明"我知道秘密 s 使得 out = s * s - 1,且 s != 0"
pragma circom 2.1.9;

template SquareLessOne() {
    signal input s;
    signal output out;

    signal sq <== s * s;      // <== 生成乘法约束
    out <== sq - 1;           // 线性约束

    // 约束 s != 0:引入逆元 inv,使 s * inv == 1
    signal inv;
    inv <-- s != 0 ? 1 / s : 0;  // <-- 仅做赋值,不做约束
    s * inv === 1;               // === 显式约束:只有 s != 0 才可能
}

component main = SquareLessOne();

用 SnarkJS 完成全流程

# 1. 编译电路为 R1CS + wasm
circom square.circom --r1cs --wasm --sym -o build

# 2. 初始化 Powers of Tau(Phase 1,通用)
snarkjs powersoftau new bn128 12 build/pot12_0000.ptau -v
snarkjs powersoftau contribute build/pot12_0000.ptau build/pot12_0001.ptau --name="Alice" -v

# 3. Phase 2:为电路生成 zkey(PLONK/Groth16)
snarkjs plonk setup build/square.r1cs build/pot12_0001.ptau build/square.zkey
snarkjs zkey export verificationkey build/square.zkey build/verification_key.json

# 4. 计算 witness
echo '{"s": 5}' | node -e "const w=require('fs').readFileSync(0); process.stdout.write(w)" \
  && snarkjs wtns calculate build/square_js/square.wasm input.json build/witness.wtns

# 5. 生成证明并验证
snarkjs plonk prove build/square.zkey build/witness.wtns build/proof.json build/public.json
snarkjs plonk verify build/verification_key.json build/public.json build/proof.json

链上验证:Solidity Verifier

// Groth16 链上验证器(由 snarkjs 生成,核心调用)
interface IVerifier {
    function verifyProof(
        uint256[2] calldata a,      // π_A
        uint256[2][2] calldata b,   // π_B
        uint256[2] calldata c,      // π_C
        uint256[4] calldata input   // 公开输入(含电路公共输出)
    ) external view returns (bool);
}

contract MyZKApp {
    IVerifier private immutable verifier =
        IVerifier(0x...); // 部署后的 Verifier 合约地址

    // 通过零知识证明验证"成年人身份":只公开年龄 ≥ 18 的事实
    function verifyAdult(uint256[2] calldata a,
                         uint256[2][2] calldata b,
                         uint256[2] calldata c,
                         uint256[4] calldata input) external returns (bool) {
        require(verifier.verifyProof(a, b, c, input), "invalid proof");
        return true;
    }
}

PLONK 的工程优势

PLONK(Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge)由 Aztec 团队 2019 年提出:

  • 通用设置:一次 Powers of Tau 仪式可用于任意电路
  • 自定义门:可定义高级门(如加法、乘、范围检查),减少约束数量
  • 查表(Plookup):把 SHA-256、Keccak 等复杂操作"查表"化,大幅压缩电路
  • 递归友好:证明可以验证另一条证明,是 zkVM 递归折叠的基础

一句话:PLONK 是"电路语言"与"证明系统"解耦的胜利,让开发者聚焦业务约束而非密码学细节。

六、隐私交易:Tornado Cash 的混币原理

流程

Tornado Cash 通过 Merkle 树 + 零知识证明实现"存款隐私":

存款流程(公开):
用户生成随机秘密 (nullifier, secret)
commitment = PedersenHash(nullifier || secret)
将 commitment 存入合约(同时存入等额 ETH)
→ 将 commitment 追加进 20 层 Merkle 树

提款流程(隐私):
用户构造 Merkle 证明 path(证明 commitment 在树中)
构造 Groth16 证明 π:
    - 存在某叶子 L 等于我的 commitment(用 Merkle proof 验证)
    - 揭示 nullifier(用于防双花)
    - 我拥有秘密 secret(哈希关系)
合约验证 π 且 nullifier 未被使用后,向新地址转出 ETH
// 简化版 Tornado 提款核心逻辑
contract Tornado {
    uint256 public constant LEVELS = 20;
    mapping(uint256 => bool) public nullifierHashes; // 防双花
    IHasher public immutable hasher;

    function withdraw(
        bytes calldata _proof,
        bytes32 _root,
        bytes32 _nullifierHash,
        address payable _recipient
    ) external {
        require(!nullifierHashes[_nullifierHash], "Already used");
        require(verifyProof(_proof, [uint256(_root), uint256(_nullifierHash)]), "Invalid proof");

        nullifierHashes[_nullifierHash] = true;          // Effect:先标记防双花
        _recipient.transfer(amount);                     // Interaction:后转账
    }
}

为什么匿名集有效

  • 每个面额(0.1 / 1 / 10 / 100 ETH)独立一棵树,匿名集 = 该树所有未取出的叶子
  • 取款地址与存款地址无链上关联,除非链下信息泄露
  • 0.1% 手续费 + 固定小费用于激励中继(Relayer,避免链上关联)

一句话:Tornado 的隐私来自"Merkle 包含证明"——只要证明在集合内,验证者无法定位是哪一片叶子。

七、zkRollup 与 zkEVM:扩容的另一半

原理

zkRollup 在 L2 执行交易,把整批交易的状态转换压缩成一条 SNARK/STARK 证明,提交到 L1:

L2 批量执行 1000 笔交易
        │
        ▼
生成有效性证明 π(证明:旧状态 + 这批交易 = 新状态)
        │
        ▼
L1 合约验证 π → 更新全局状态根(state root)
资产安全由密码学保证,无需乐观挑战期

主流 zkEVM 对比

项目证明系统EVM 兼容度特点
zkSync EraSNARK(编译级)高(LLVM 前端)Solidity 支持好,账户抽象内置
Polygon zkEVMSNARK(字节码级)全 EVM逐字节码验证,兼容最彻底
ScrollSNARK(字节码级)全 EVM社区驱动,兼容优先
StarknetSTARK非 EVM(Cairo)原生 Cairo 语言,性能强
LineaSNARK高ConsenSys 出品,工具链成熟

关键难点:哈希与证明成本

zkEVM 的核心工程挑战是把 EVM 的 Keccak-256 / MPT(Merkle Patricia Trie) 电路化——这两个操作在电路中极贵。解决思路:

  • 使用二进制字段 + Plookup 查表优化 Keccak 电路
  • 用递归证明把多个子证明折叠,控制 L1 验证 gas
  • 部分方案改用 Poseidon(ZK 友好哈希)重构状态树(如 Miden、Aztec)

一句话:zkRollup 把"计算"搬下链,把"证明"搬上链——链上只做一次配对验证,gas 与吞吐量同时优化。

八、身份与可验证凭证(VC)

选择性披露

传统身份验证泄露全部信息(如身份证号)。零知识让凭证变为可选择性披露:

可验证凭证(VC)由签发方签名,包含若干属性:
{ name, age, country, email, memberSince, ... }

持证者在需要时只披露"age ≥ 18"或"country = JP",
而不暴露其他属性——用 ZK 证明"我是该凭证的合法持有者"即可

链上 DID 架构

┌────────────┐    签发      ┌────────────┐
│  签发方     │ ───────────► │  持证者钱包  │
│ (Issuer)    │  签名 VC      │ (Holder)    │
└────────────┘              └─────┬──────┘
                                  │ 出示"可验证出示"(VP)
                                  │ 仅含 ZK 证明 + 必要属性
                                  ▼
                          ┌────────────┐
                          │  验证方     │
                          │ (Verifier) │  链上校验签发方公钥 + ZK 证明
                          └────────────┘
  • DID 方法:did:ethr、did:key、Polygon ID、Ceramic 等
  • 代表性项目:Anon Aadhaar(印度 Aadhaar 匿名化)、Semaphore(匿名投票/群签名)、zkLogin(Sui)

应用场景

场景传统方式ZK 方式
KYC提交身份证照片只证明"已通过 KYC"
年龄/权限出示证件原件证明"≥18 岁"
空投/会员校验链上快照证明"持有某凭证"且匿名
投票实名+黑名单匿名+防双投(Semaphore)

一句话:可验证凭证把"身份"从中心化数据库迁移到用户自持,ZK 则是隐私合规的桥梁。

九、工具链与工程实践

开发栈速览

工具用途备注
Circom 2电路语言约束即代码,生态最大
SnarkJS证明生成/验证Groth16/PLONK,全流程 CLI
arkworks / bellmanRust 证明库工程化 ZK 基础设施
halo2PLONK 系(Halo2)zcash 后续、Aztec 使用
RISC Zero / SP1zkVM用通用语言写"程序"而非电路
Noir高级电路语言Aztec 出品,类 Rust 语法

工程实践要点

  1. 只在必要时用 ZK:简单校验用 Merkle 证明,避免过度工程
  2. 可信设置管理:使用通用设置(PLONK/halo2)减少仪式风险
  3. 证明上链成本:Groth16 约 250k-500k gas,STARK 验证依赖 EIP-4844(blob)分摊
  4. 隐私≠匿名:隐私保护交易内容,匿名保护身份,二者可组合
  5. 审计重点:电路约束完备性、输入范围检查、Merkle 路径正确性、nullifier 防双花
# 常用 ZK 工程命令
circom circuit.circom --r1cs --wasm --sym   # 编译
snarkjs zkey export solidityverifier         # 导出 Solidity 验证器
snarkjs info build/circuit.r1cs              # 查看约束数量
npx snarkjs groth16 setup                     # Groth16 专用 setup

总结

维度要点
核心思想完备性 + 可靠性 + 零知识,Fiat-Shamir 变换实现非交互
证明系统Groth16(小体积)/ PLONK(通用设置)/ STARK(透明抗量子)
电路工程Circom 编写约束,SnarkJS 生成证明,Solidity Verifier 上链
隐私应用Tornado 混币 = Merkle 包含 + nullifier 防双花
扩容应用zkRollup 把计算下放、证明上链,zkEVM 兼容 EVM
身份应用可验证凭证 + 选择性披露,KYC 匿名化
趋势递归证明、zkVM、证明预计算让链上验证成本趋近零

零知识证明已从密码学论文走向生产环境:隐私币、zkRollup、隐私 DeFi、去中心化身份都在大规模使用。对开发者而言,掌握电路思维(约束即真相)、熟练 Circom/SnarkJS 工具链,并理解证明系统的信任假设,是进入 ZK 世界的三条必经之路。随着 Plonky3、STIR、zkVM 等新基建成熟,ZK 将像 Gas 一样成为公链的基础设施,而非锦上添花的特性。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「区块链 Web3」更多文章

  1. 非 EVM 生态:Solana 与 Move 系公链开发
  2. MEV 与区块构建市场:抢跑、三明治与 PBS
  3. NFT 市场合约开发:ERC-721/1155 深入与版税