证明"我知道"而不泄露"我知道什么"——密码学最优雅的发明。
从 zk-SNARK 到 zk-STARK,从隐私交易到 AI 推理可验证性。
1985 年,Goldwasser、Micali 和 Rackoff 在论文中定义了零知识证明 (Zero-Knowledge Proof, ZKP):一个证明者 (Prover) 可以向验证者 (Verifier) 证明某个声明为真,而不泄露任何超出声明本身的信息。
如果声明为真,诚实的证明者总是能说服诚实的验证者。即"真的假不了"——正确声明的证明一定能通过验证。
如果声明为假,作弊的证明者几乎不可能说服诚实的验证者。即"假的真不了"——错误声明的证明极大概率被拒绝(允许极小概率的误差,如 2-128)。
验证者在验证完成后,除了"声明为真"这一事实外,不获得任何额外信息。形式化:存在一个模拟器 (Simulator) 不需要知道秘密就能生成与真实证明不可区分的"假"证明。
最著名的 ZKP 直觉解释是 Jean-Jacques Quisquater 等人的"阿里巴巴洞穴"故事:
这是一个交互式零知识证明。现代 ZKP 系统(SNARK/STARK)是非交互式的——证明者生成一个证明,验证者可以离线验证。
| 维度 | 类型 A | 类型 B | 说明 |
|---|---|---|---|
| 交互性 | 交互式 (Interactive) | 非交互式 (Non-interactive) | NIZK 通过 Fiat-Shamir 变换或 CRS 实现 |
| 证明大小 | 简洁 (Succinct) | 非简洁 | 简洁证明通常 < 1KB,验证时间 < 10ms |
| 透明性 | 需要可信设置 (Trusted Setup) | 透明 (Transparent) | 可信设置是安全隐患——有毒废物 (Toxic Waste) |
| 后量子安全 | 非后量子 | 后量子安全 | 基于椭圆曲线的不抗量子,基于哈希的抗量子 |
zk-SNARK (Zero-Knowledge Succinct Non-interactive Argument of Knowledge) 是目前最广泛使用的 ZKP 方案。Zcash 最初使用它实现隐私交易。
| 特性 | zk-SNARK | zk-STARK |
|---|---|---|
| 全称 | Succinct Non-interactive ARgument of Knowledge | Scalable Transparent ARgument of Knowledge |
| 密码学假设 | 椭圆曲线配对 (Pairing),如 BN254/BLS12-381 | 抗碰撞哈希函数,如 SHA-256/Keccak |
| 可信设置 | ⚠️ 需要 (Ceremony/CRS) | ✅ 不需要 (Transparent) |
| 证明大小 | 极小 (~288 字节) | 较大 (~50-200 KB) |
| 验证时间 | 极快 (~1-10 ms) | 快 (~10-100 ms) |
| 证明时间 | 中等 (秒级) | 较慢 (秒~分钟级) |
| 后量子安全 | ❌ 不安全(依赖椭圆曲线) | ✅ 安全(仅依赖哈希) |
| 代表实现 | Groth16, PLONK, Marlin | STARK, Fractal, Winterfell |
| 代表应用 | Zcash, Filecoin, Tornado Cash | Ethereum EIP-4844, StarkNet, zkSync |
| 标准化 | ZKProof 社区推进 | ZKProof 社区推进 |
| 方案 | 设置类型 | 证明大小 | 特点 |
|---|---|---|---|
| Groth16 | 每个电路单独设置 | ~192 字节(最小) | 证明最小最快,但每个电路需新设置。Zcash 使用 |
| PLONK | 通用设置(一次) | ~400 字节 | 通用 CRS,不同电路可复用。灵活性高 |
| Marlin | 通用设置 | ~1 KB | PLONK 的改进版,更好的组合性 |
| Halo 2 | 无需设置(递归) | 可变 | Zcash 团队开发,支持递归证明,无需可信设置 |
| SP1 (Succinct) | 无需设置 | 可变 | Rust 生态,支持 RISC-V 程序证明,开发体验好 |
递归证明 (Recursive Proof) 是 ZKP 最强大的特性之一——一个 ZKP 可以验证另一个 ZKP。这意味着:
问题:AI 服务声称用了 GPT-4 跑推理,但用户无法验证是否偷偷用了更便宜的模型。
ZKP 方案:服务商用 ZKP 证明"我确实按照模型 M 执行了推理,输入是 x,输出是 y"——而不泄露模型权重。
状态:研究阶段,已有原型系统(EZKL、zkML)
问题:验证"我是成年人"需要出示身份证,泄露姓名、出生日期、地址等全部信息。
ZKP 方案:用 ZKP 证明"我的出生日期在 2000 年之前"——仅此一条信息,不泄露其他任何信息。
状态:已有商业部署(Polygon ID、zkPass)
问题:银行想证明其风控模型在特定人群上的公平性,但不能公开客户数据。
ZKP 方案:用 ZKP 证明"在加密数据集上,模型的公平性指标满足阈值"——不泄露任何客户数据。
状态:研究阶段
问题:GDPR 要求数据处理合规,但审计者直接查看数据又违反隐私。
ZKP 方案:用 ZKP 证明"数据处理流程符合 GDPR 规则"——审计者验证证明而不需要看数据。
状态:概念验证阶段
问题:如何证明某个 AI 模型是你的知识产权?
ZKP 方案:在模型中嵌入水印,用 ZKP 证明"这个模型包含我的水印"——不泄露水印的具体位置和内容。
状态:研究阶段
问题:去中心化市场中,如何验证算力提供者确实执行了计算?
ZKP 方案:算力提供者提交计算结果 + ZKP,市场合约自动验证证明后释放付款。
状态:早期实践(Gensyn、Ritual)
| 项目/产品 | ZKP 类型 | 用途 | 成熟度 |
|---|---|---|---|
| Zcash | Groth16 → Halo 2 | 隐私交易:隐藏发送者、接收者、金额 | 生产级 |
| Tornado Cash | zk-SNARK | Ethereum 交易混币器(被制裁但技术有效) | 生产级 |
| StarkNet | zk-STARK | Ethereum L2 扩容:证明批量交易有效性 | 生产级 |
| zkSync Era | zk-SNARK (Boojum) | Ethereum L2 扩容,SNARK 证明 EVM 执行 | 生产级 |
| Polygon Miden | zk-STARK | 隐私 L2,支持私有状态和公开状态 | 测试网 |
| Polygon ID | zk-SNARK (Circom) | 去中心化身份,选择性披露 | 可用 |
| Worldcoin | zk-SNARK (Semaphore) | 人格证明:证明是真人不泄露生物信息 | 生产级 |
| EZKL | zk-SNARK (Halo 2) | ML 模型推理的零知识证明 | 研究/原型 |
| zkPass | zk-TLS | 用 ZKP 证明 HTTPS 网站数据而不泄露凭证 | 可用 |
zkML (Zero-Knowledge Machine Learning) 是 ZKP 与 ML 的交叉领域——用零知识证明来验证机器学习推理过程。这是 AI 时代 ZKP 最有前途的应用方向之一。
证明:"我确实用模型 M 对输入 x 做了推理,输出 y"
保护:模型权重可以是私有的
用途:API 服务商向客户证明没有偷换模型
证明:"在私有输入 x 上,公开模型 M 的推理结果是 y"
保护:用户输入数据不泄露给验证者
用途:医疗诊断、金融风控等隐私敏感场景
证明:"在私有输入 x 上,私有模型 M 的推理结果是 y"
保护:输入和模型都不泄露
用途:模型 IP 保护 + 用户隐私的双重要求
| 层级 | 工具/框架 | 说明 |
|---|---|---|
| 模型导出 | ONNX → ZKP 电路 | 将 ML 模型导出为 ONNX 格式,再转译为 ZKP 电路 |
| ZKP 后端 | Halo 2, PLONK, Groth16 | 实际的证明系统 |
| ML-ZKP 框架 | EZKL, keras2circom, zkCNN | 将 ML 操作映射到 ZKP 算术电路 |
| 优化层 | 量化、算子融合、查找表 | 将浮点运算量化为整数,减少电路约束数量 |
| 部署 | 链上验证 / API 服务 | 在区块链或 API 中验证证明 |
| 模型 | 参数量 | 证明时间 | 验证时间 | 证明大小 |
|---|---|---|---|---|
| MNIST MLP | ~1K | ~1 秒 | ~10 ms | ~10 KB |
| 小型 CNN | ~10K | ~10 秒 | ~50 ms | ~50 KB |
| ResNet-18 | ~11M | ~分钟级 | ~秒级 | ~数 MB |
| GPT-2 Small | ~124M | 小时级 | 分钟级 | ~数 GB |
| Llama 7B | ~7B | 不现实 | 不现实 | 不现实 |
Circom 是最流行的 ZKP 电路语言,配合 snarkjs 使用:
// range_check.circom — 证明一个值在 [0, max) 范围内
// 实际应用:证明年龄 ≥ 18 而不泄露具体年龄
pragma circom 2.1.5;
template RangeCheck(n) {
// 证明 input 在 [0, 2^n) 范围内
signal input in;
signal output out;
// 使用位分解证明值在范围内
component bits[n];
for (var i = 0; i < n; i++) {
bits[i] = Num2Bits(1);
}
// 约束:重构的值必须等于输入
// 这是零知识证明的核心——通过约束保证计算的正确性
}
// 更实用的例子:证明 age >= 18 而不泄露具体年龄
template AgeVerification() {
signal input age; // 私有输入:实际年龄
signal input difference; // 私有输入:age - 18
signal output isValid; // 公开输出:是否 ≥ 18
// 约束:age - 18 = difference
// 如果 age < 18,difference 会溢出,约束失败
component isPositive = GreaterEqThan(8); // 8位足够表示年龄差
isPositive.in[0] <== age;
isPositive.in[1] <== 18;
isValid <== isPositive.out;
}
component main {public [isValid]} = AgeVerification();
# 1. 编译电路
circom age_verification.circom --r1cs --wasm --sym
# 2. 可信设置 (Powers of Tau Ceremony)
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau \
--name="First contribution" -v
# 3. 电路特定设置 (Phase 2)
snarkjs setup --r1cs age_verification.r1cs \
--pk verification_key.json \
--vk proving_key.json
# 4. 生成证明
# input.json: {"age": 25, "difference": 7}
snarkjs calculatewitness age_verification.wasm input.json
snarkjs proof proving_key.json witness.wtns proof.json public.json
# 5. 验证证明 — 验证者只需要 verification_key + proof + public 输出
# 验证者不知道 age = 25,只知道 isValid = 1
snarkjs verify verification_key.json public.json proof.json
# 输出: [INFO] snarkJS: OK!
# ⚡ 关键:验证者只知道"这个人 ≥ 18 岁",不知道具体年龄!
import random
import hashlib
"""
简化版交互式 ZKP:证明知道离散对数
场景:Peggy 知道 x 使得 g^x = y (mod p)
她想证明知道 x 而不泄露 x
"""
# 参数(实际中 p 是大素数,如 2048 位)
p = 2**127 - 1 # 梅森素数(仅演示,实际用安全素数)
g = 2 # 生成元
x = 42 # Peggy 的秘密
y = pow(g, x, p) # 公开值:g^x mod p
def peggy_commit():
"""Peggy 生成承诺:随机选 r,发送 g^r"""
r = random.randint(2, p - 1)
commitment = pow(g, r, p)
return r, commitment
def victor_challenge():
"""Victor 发送随机挑战 c"""
return random.randint(0, 1)
def peggy_respond(r, c, x):
"""Peggy 根据挑战计算响应"""
if c == 0:
return r # 响应 = r
else:
return (r + x) % (p - 1) # 响应 = r + x
def victor_verify(commitment, c, s, y, g, p):
"""Victor 验证响应"""
if c == 0:
return pow(g, s, p) == commitment
else:
return pow(g, s, p) == (commitment * y) % p
# 运行交互式协议 n 轮
n_rounds = 20
all_passed = True
for i in range(n_rounds):
r, commitment = peggy_commit()
c = victor_challenge()
s = peggy_respond(r, c, x)
if not victor_verify(commitment, c, s, y, g, p):
all_passed = False
break
if all_passed:
print(f"✅ {n_rounds} 轮全部通过 — 以 1-2^(-{n_rounds}) 概率确认 Peggy 知道 x")
print(f" Victor 仍然不知道 x 的值是多少!")
else:
print("❌ 验证失败")
# 安装 EZKL
# pip install ezkl
import ezkl
import json
import torch
import torch.nn as nn
# 定义简单模型(MNIST 分类器)
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = torch.relu(self.fc1(x))
return self.fc2(x)
model = SimpleModel()
model.eval()
# 导出 ONNX
dummy_input = torch.randn(1, 784)
torch.onnx.export(model, dummy_input, "model.onnx")
# 生成 EZKL 设置
ezkl.gen_settings("model.onnx", "settings.json")
# 生成电路
ezkl.calibrate_settings(
"settings.json",
"model.onnx",
"input.json", # 包含测试输入数据
"resources" # 资源限制
)
# 编译电路 → 生成证明 → 验证
ezkl.compile_circuit("model.onnx", "model.ezkl", "settings.json")
ezkl.setup("model.ezkl", "settings.json", "vk.key", "pk.key")
ezkl.prove("model.ezkl", "input.json", "witness.json",
"pk.key", "proof.json", "settings.json")
result = ezkl.verify("proof.json", "settings.json", "vk.key")
print(f"✅ ZKP 验证结果: {result}")
证明生成仍然是性能瓶颈。对于复杂计算(如 ML 推理),证明时间可能比直接计算慢 1000-10000 倍。硬件加速(GPU/FPGA/ASIC)正在改善。
编写 ZKP 电路需要密码学专业知识。Circom/Noir/etc 降低了门槛但仍不简单。AI 工程师很难直接上手。
SNARK 的可信设置是安全隐患。虽然多人 Ceremony 降低了风险,但"有毒废物"的存在仍让保守的安全团队担忧。
ZKP 电路是编译时确定的。模型更新后需要重新编译电路和生成新密钥,这增加了运维复杂度。
ZKP 电路在有限域上运算,而 ML 模型使用浮点数。量化误差是 zkML 的核心难题——精度损失可能影响模型准确率。
ZKP 电路的安全性审计远不如传统软件成熟。一个错误的约束可能导致安全漏洞——证明者可以伪造证明。