使用 Next.js 构建零知识证明应用
概述
零知识证明(Zero-Knowledge Proof, ZKP)是一种革命性的密码学技术,允许一方(证明者)向另一方(验证者)证明某个陈述的真实性,而无需透露除该陈述真实性以外的任何信息。
在医疗健康应用中,零知识证明架构可以实现:
- 完全隐私保护:服务器永远无法访问用户的原始健康数据
- 可验证性:用户可以证明符合条件而不泄露敏感信息
- 去中心化信任:无需信任第三方即可验证数据真实性
技术架构
1. 零知识证明类型选择
zk-SNARKs(零知识简洁非交互式知识论证)
code
// lib/crypto/zk-snarks.ts
import { groth16 } from 'snarkjs';
import { buildPoseidon } from 'circomlibjs';
export class ZKSnarksProcessor {
private poseidon: any;
async init() {
this.poseidon = await buildPoseidon();
}
// 生成证明
async generateProof(input: any, wasmPath: string, zkeyPath: string) {
const { proof, publicSignals } = await groth16.fullProve(
input,
wasmPath,
zkeyPath
);
return { proof, publicSignals };
}
// 验证证明
async verifyProof(
proof: any,
publicSignals: string[],
vkeyPath: string
): Promise<boolean> {
const vKey = await fetch(vkeyPath).then(r => r.json());
return await groth16.verify(vKey, publicSignals, proof);
}
// Poseidon 哈希(用于电路中的哈希操作)
hash(inputs: bigint[]): bigint {
return this.poseidon(inputs);
}
}
Code collapsed
zk-STARKs(零知识可扩展透明知识论证)
code
// lib/crypto/zk-starks.ts
import { StarkNet } from 'starknet';
export class ZKStarksProcessor {
// STARKs 适用于大规模数据证明
// 无需可信设置,但证明体积较大
async generateStarkProof(witness: any[]): Promise<string> {
// 实现略 - 使用 starknet 库或类似工具
return '';
}
async verifyStarkProof(proof: string): Promise<boolean> {
// 实现略
return true;
}
}
Code collapsed
2. 客户端加密实现
端到端加密工具类
code
// lib/crypto/client-encryption.ts
import { encrypt, decrypt } from '@metamask/eth-sig-util';
import { randomBytes, scryptSync } from 'crypto';
export class ClientEncryption {
// 生成加密密钥对
static async generateKeyPair(): Promise<CryptoKeyPair> {
return await crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
true,
['deriveKey']
);
}
// 从密码派生密钥
static deriveKey(password: string, salt: Buffer): Buffer {
return scryptSync(password, salt, 32);
}
// 加密敏感数据
static async encryptData(
data: string,
publicKey: string
): Promise<{ encrypted: string; iv: string }> {
const iv = randomBytes(16);
const key = await this.importKey(publicKey);
const encrypted = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv,
},
key,
new TextEncoder().encode(data)
);
return {
encrypted: Buffer.from(encrypted).toString('base64'),
iv: iv.toString('base64'),
};
}
// 解密数据
static async decryptData(
encryptedData: string,
iv: string,
privateKey: CryptoKey
): Promise<string> {
const decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: Buffer.from(iv, 'base64'),
},
privateKey,
Buffer.from(encryptedData, 'base64')
);
return new TextDecoder().decode(decrypted);
}
static async importKey(keyData: string): Promise<CryptoKey> {
return await crypto.subtle.importKey(
'raw',
Buffer.from(keyData, 'base64'),
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
}
}
Code collapsed
3. Next.js API 路由实现
零知识验证端点
code
// app/api/verify/zk-proof/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ZKSnarksProcessor } from '@/lib/crypto/zk-snarks';
const zkProcessor = new ZKSnarksProcessor();
await zkProcessor.init();
export async function POST(req: NextRequest) {
try {
const { proof, publicSignals } = await req.json();
// 验证零知识证明
const vkeyPath = '/keys/verification_key.json';
const isValid = await zkProcessor.verifyProof(
proof,
publicSignals,
vkeyPath
);
if (!isValid) {
return NextResponse.json(
{ error: '无效的零知识证明' },
{ status: 400 }
);
}
// 服务器只存储证明和公开信号,不存储原始数据
return NextResponse.json({
verified: true,
timestamp: Date.now(),
});
} catch (error) {
return NextResponse.json(
{ error: '证明验证失败' },
{ status: 500 }
);
}
}
Code collapsed
盲签名端点
code
// app/api/crypto/blind-signature/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { blindSign } from '@/lib/crypto/blind-signature';
export async function POST(req: NextRequest) {
try {
const { blindedMessage } = await req.json();
// 服务器对盲化消息进行签名
const signature = await blindSign(blindedMessage);
// 服务器无法看到原始消息内容
return NextResponse.json({ signature });
} catch (error) {
return NextResponse.json(
{ error: '盲签名失败' },
{ status: 500 }
);
}
}
Code collapsed
4. 前端组件实现
加密数据存储 Hook
code
// hooks/useEncryptedStorage.ts
import { useState, useEffect } from 'react';
import { ClientEncryption } from '@/lib/crypto/client-encryption';
export function useEncryptedStorage<T>(key: string, password: string) {
const [data, setData] = useState<T | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function loadData() {
try {
const stored = localStorage.getItem(key);
if (!stored) {
setIsLoading(false);
return;
}
const { encrypted, iv } = JSON.parse(stored);
// 从密码派生密钥
const salt = Buffer.from(encrypted, 'base64').slice(0, 16);
const derivedKey = ClientEncryption.deriveKey(password, salt);
// 解密数据
const decrypted = await ClientEncryption.decryptData(
encrypted,
iv,
await ClientEncryption.importKey(derivedKey.toString('base64'))
);
setData(JSON.parse(decrypted));
} catch (err) {
setError(err as Error);
} finally {
setIsLoading(false);
}
}
loadData();
}, [key, password]);
const saveData = async (newData: T) => {
const publicKey = await getPublicKey();
const { encrypted, iv } = await ClientEncryption.encryptData(
JSON.stringify(newData),
publicKey
);
localStorage.setItem(key, JSON.stringify({ encrypted, iv }));
setData(newData);
};
return { data, isLoading, error, saveData };
}
Code collapsed
零知识证明提交组件
code
// components/zk/ZKProofSubmit.tsx
'use client';
import { useState } from 'react';
import { ZKSnarksProcessor } from '@/lib/crypto/zk-snarks';
export function ZKProofSubmit() {
const [isGenerating, setIsGenerating] = useState(false);
const [proofResult, setProofResult] = useState(null);
const generateAndSubmitProof = async (privateInput: any) => {
setIsGenerating(true);
try {
const zkProcessor = new ZKSnarksProcessor();
await zkProcessor.init();
// 在客户端生成证明
const { proof, publicSignals } = await zkProcessor.generateProof(
privateInput,
'/circules/health.wasm',
'/circules/health.zkey'
);
// 提交证明到服务器
const response = await fetch('/api/verify/zk-proof', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ proof, publicSignals }),
});
const result = await response.json();
setProofResult(result);
} catch (error) {
console.error('证明生成失败:', error);
} finally {
setIsGenerating(false);
}
};
return (
<div className="p-6 border rounded-lg">
<h3>零知识证明验证</h3>
<button
onClick={() => generateAndSubmitProof({ /* 私有输入 */ })}
disabled={isGenerating}
>
{isGenerating ? '生成证明中...' : '生成并提交证明'}
</button>
{proofResult && (
<div className="mt-4 p-4 bg-green-50 rounded">
证明验证成功!
</div>
)}
</div>
);
}
Code collapsed
5. Circom 电路设计
健康数据验证电路
code
// circuits/health-eligibility.circom
pragma circom 2.0.0;
include "../node_modules/circomlib/circuits/poseidon.circom";
// 证明用户年龄符合要求而不透露实际年龄
template AgeProof() {
signal input birthYear; // 私有输入
signal input currentYear; // 公开输入
signal input minAge; // 公开输入(最低年龄要求)
signal output isEligible; // 公开输出
// 计算年龄
signal age;
age <== currentYear - birthYear;
// 比较年龄
signal diff;
diff <== age - minAge;
// 如果 diff >= 0,isEligible = 1
isEligible <== DiffieHellman.isZero(diff);
}
// 证明健康数据符合标准而不透露具体数值
template HealthRangeProof() {
signal input value; // 私有输入
signal input min; // 公开输入
signal input max; // 公开输入
signal output inRange; // 公开输出
// 检查 value >= min
signal aboveMin;
aboveMin <== DiffieHellman.isZero(min - value);
// 检查 value <= max
signal belowMax;
belowMax <== DiffieHellman.isZero(value - max);
// 两个条件都满足
inRange <== AND(aboveMin, belowMax);
}
component main {public [currentYear, minAge]} = AgeProof();
Code collapsed
6. 同态加密集成
code
// lib/crypto/homomorphic.ts
import * as tfhe from 'node-tfhe';
export class HomomorphicEncryption {
private client: any;
constructor() {
this.client = new tfhe.TFHEClient();
}
// 加密数值
encryptInt(value: number): string {
return this.client.encryptInt(value);
}
// 在加密状态下进行计算
addEncrypted(a: string, b: string): string {
return this.client.add(a, b);
}
// 解密结果
decryptInt(encrypted: string): number {
return this.client.decryptInt(encrypted);
}
// 聚合加密数据
aggregateEncryptedSum(values: string[]): string {
return values.reduce((acc, val) => this.addEncrypted(acc, val), this.encryptInt(0));
}
}
Code collapsed
合规性要求
HIPAA 合规
code
// lib/compliance/hipaa.ts
export const HIPAARequirements = {
// 最小必要原则
minimumNecessary: {
description: '仅收集和使用必要的最小量健康信息',
implementation: '零知识证明天然符合此原则',
},
// 安全保障
safeguards: {
administrative: [
'访问控制策略',
'员工培训记录',
'应急响应计划',
],
physical: [
'服务器物理安全',
'设备管理政策',
],
technical: [
'端到端加密',
'访问日志审计',
'完整性验证',
],
},
// 病人权利
patientRights: [
'访问自己的健康信息',
'要求更正错误信息',
'获取数据使用记录',
'要求删除数据(GDPR 风格)',
],
};
Code collapsed
GDPR 合规
code
// lib/compliance/gdpr.ts
export const GDPRRequirements = {
// 数据最小化
dataMinimization: {
description: '零知识架构:服务器不存储个人数据',
compliant: true,
},
// 被遗忘权
rightToErasure: {
description: '可删除本地加密密钥实现数据擦除',
implementation: 'app/api/user/delete/route.ts',
},
// 数据可携权
dataPortability: {
description: '用户可导出自己的加密数据',
implementation: 'app/api/user/export/route.ts',
},
// 透明性
transparency: {
description: '明确告知数据如何被证明和验证',
privacyPolicy: '/privacy#zero-knowledge',
},
};
Code collapsed
安全检查清单
开发阶段
- 使用可信的密码学库(snarkjs、circom、tfhe)
- 电路代码经过形式化验证
- 可信设置在安全环境下进行
- 密钥生成使用安全的随机数生成器
- 加密密钥与签名密钥分离
- 实现密钥轮换机制
部署阶段
- 所有 API 使用 HTTPS
- 启用 HTTP 严格传输安全(HSTS)
- 实现证书固定(Certificate Pinning)
- 验证文件使用内容寻址存储(IPFS)
- 密钥不存储在应用代码中
- 使用环境变量或密钥管理服务
运行阶段
- 定期审计密码学库版本
- 监控异常证明模式
- 记录所有验证操作(不含敏感数据)
- 实现速率限制防止暴力攻击
- 定期进行渗透测试
- 建立安全事件响应流程
用户通信
- 隐私政策明确说明零知识架构
- 用户界面清晰显示加密状态
- 提供数据使用透明度报告
- 教育用户如何保护密钥
- 明确告知哪些数据是加密的
性能优化
1. 证明生成优化
code
// 使用 Web Worker 避免阻塞主线程
// workers/zk-proof-generator.ts
import { expose } from 'threads/worker';
import { ZKSnarksProcessor } from '@/lib/crypto/zk-snarks';
expose(async function generateProof(input: any) {
const processor = new ZKSnarksProcessor();
await processor.init();
return await processor.generateProof(
input,
'/circuits/health.wasm',
'/circuits/health.zkey'
);
});
Code collapsed
2. 证明缓存
code
// lib/cache/proof-cache.ts
import { LRUCache } from 'lru-cache';
export const proofCache = new LRUCache<string, any>({
max: 500,
ttl: 1000 * 60 * 60, // 1小时
});
export function getCachedProof(inputHash: string): any | undefined {
return proofCache.get(inputHash);
}
export function setCachedProof(inputHash: string, proof: any): void {
proofCache.set(inputHash, proof);
}
Code collapsed
3. 批量验证
code
// lib/crypto/batch-verify.ts
export async function batchVerifyProofs(
proofs: Array<{ proof: any; publicSignals: string[] }>
): Promise<boolean[]> {
return Promise.all(
proofs.map(({ proof, publicSignals }) =>
zkProcessor.verifyProof(proof, publicSignals, vkeyPath)
)
);
}
Code collapsed
参考资料
免责声明:本文提供的代码示例仅供学习参考。在生产环境中使用零知识证明系统前,请务必进行安全审计,并咨询密码学和法律专家。