零知识证明(ZKP)集成指南 —— MSG Chain
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
目录
1. 概述
1.1 什么是零知识证明
零知识证明(Zero-Knowledge Proof, ZKP)是一种密码学协议,允许一方(证明者 Prover)向另一方(验证者 Verifier)证明某个陈述为真,而无需披露该陈述之外的任何信息。
核心三性:
- 完备性(Completeness):若陈述为真,诚实的证明者总能说服诚实的验证者。
- 可靠性(Soundness):若陈述为假,作弊的证明者几乎不可能说服诚实的验证者。
- 零知识性(Zero-Knowledge):验证者除了"陈述为真"这一事实外,学不到任何额外信息。
1.2 ZK-SNARKs vs ZK-STARKs
| 特性 | ZK-SNARKs | ZK-STARKs |
|---|---|---|
| 全称 | Zero-Knowledge Succinct Non-Interactive Argument of Knowledge | Zero-Knowledge Scalable Transparent Argument of Knowledge |
| 证明大小 | ~200–300 字节 | ~45–200 KB |
| 验证时间 | 毫秒级(常数) | 毫秒级(对数) |
| 可信设置 | 需要(Groth16) | 不需要(透明) |
| 量子安全 | 否(基于椭圆曲线) | 是(基于哈希) |
| 链上 Gas 成本 | 低 | 较高 |
| MSG Chain 推荐 | ✅ 首选 Groth16(BN254) | 适用于高安全性场景 |
1.3 为什么在 MSG Chain 上使用 ZKP
MSG Chain 基于 Cosmos SDK + CosmWasm,支持 Rust 编写的智能合约。将 ZKP 集成到 MSG Chain 可以实现:
| 场景 | 描述 | 收益 |
|---|---|---|
| zkKYC | 用户证明已通过 KYC 但不出示证件 | 合规 + 隐私保护 |
| 私有交易 | 隐藏转账的发送方、接收方、金额 | 金融隐私 |
| zkRollup | 在链下批量处理交易,链上验证有效性证明 | 扩容 |
| zkOracle | 证明某个链下数据源的数据是真实且未被篡改的 | 数据可信 |
| 匿名投票 | 证明投票权且不暴露投票身份 | DAO 隐私治理 |
| 身份聚合 | 用一个零知识身份关联多个链上账户 | 跨链身份 |
1.4 MSG Chain 密码学原语支持
MSG Chain 在 CosmWasm 层面提供了对以下椭圆曲线库的支持:
cosmwasm_crypto::bls12_381—— BLS12-381 曲线配对运算cosmwasm_crypto::bn254—— BN254(alt_bn128)曲线配对运算arkworks系列库 —— 通用曲线与 ZKP 基础设施
1.5 架构总览
+-----------------+ +----------------+ +-----------------+
| 用户/Prover | | 前端浏览器 | | MSG Chain |
| (隐私数据) |----->| (生成证明) |----->| (验证证明) |
+-----------------+ +----------------+ +-----------------+
| |
+-----v------+ +-----v------+
| WASM 证明器 | | 验证合约 |
| (Circom) | | (CosmWasm) |
+------------+ +------------+
1.6 本指南的学习路径
ZK 理论 --> 电路设计 --> 证明生成 --> 链上验证 --> 前端集成 --> 完整 dApp
| | | | | |
$2 $3 $3.4 $4 $7 $8
1.7 开发环境前置要求
# 安装 Circom
git clone https://github.com/iden3/circom.git
cd circom && cargo build --release && cargo install --path circom
# 安装 snarkjs
npm install -g snarkjs
# 安装 Rust 工具链(wasm 目标)
rustup target add wasm32-unknown-unknown
# 安装 CosmWasm 开发工具
cargo install cosmwasm-check
cargo install cargo-generate --features vendored-openssl
1.8 项目仓库结构
zkp-msg-chain/
+-- circuits/ # Circom 电路
| +-- age_verify.circom
| +-- membership.circom
| +-- range_proof.circom
+-- contract/ # CosmWasm 验证合约
| +-- src/
| | +-- contract.rs
| | +-- zk_verifier.rs
| | +-- types.rs
| +-- Cargo.toml
+-- frontend/ # React 前端
| +-- src/
| | +-- zkProver.ts
| | +-- components/
| +-- package.json
+-- scripts/ # 部署与测试脚本
| +-- deploy.ts
| +-- test_proof.ts
+-- README.md
2. ZKP 基础
2.1 密码学基础概念
2.1.1 椭圆曲线配对(Elliptic Curve Pairing)
配对运算是 ZK-SNARKs 的数学基础。给定三个群 G1、G2、GT,配对函数 e 满足双线性:
e(a.xB7g, b.xB7h) = e(g, h)^(a.xB7b)
MSG Chain 支持的主流曲线:
| 曲线 | 配对类型 | 安全级别 | 适用场景 |
|---|---|---|---|
| BN254 | Ate Pairing | ~128 bit | EVM 兼容、Groth16 |
| BLS12-381 | Ate Pairing | ~128 bit | 更高安全性、PLONK |
| BLS12-377 | Ate Pairing | ~128 bit | 与 BLS 签名协同 |
2.1.2 算术电路(Arithmetic Circuit)
ZKP 将计算语句转化为算术电路,电路由加法门和乘法门组成,在有限域 Fp 上运算。
一个电路 C 接受:
- 公开输入(public inputs):验证者可查看的值
- 私有输入(private inputs / witness):仅证明者知道的值
电路的输出是公开的约束满足性(Constraint Satisfaction)。
2.1.3 QAP(Quadratic Arithmetic Program)
Groth16 等 SNARK 系统将算术电路转化为 QAP 形式:
- 电路 -> R1CS(Rank-1 Constraint System)
- R1CS -> QAP(通过拉格朗日插值)
- QAP -> 证明密钥 & 验证密钥
2.2 Groth16 协议
Groth16 是当前使用最广泛的 ZK-SNARK 协议,其证明大小仅 3 个群元素(约 128-192 字节),验证仅需一次配对运算。
2.2.1 可信设置(Trusted Setup)
Groth16 需要一次针对特定电路的可信设置,分为两个阶段:
阶段 1(Powers of Tau): 通用设置,不依赖具体电路
# 初始化 powers of tau(2^11 表示最多支持 2048 个约束)
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
# 贡献随机熵(需多人参与)
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
# 完成阶段 1
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v
阶段 2(Circuit-specific): 针对具体电路
# 编译电路,生成 R1CS
circom my_circuit.circom --r1cs --wasm --sym
# 生成阶段 2 设置
snarkjs groth16 setup my_circuit.r1cs pot12_final.ptau circuit_0000.zkey
# 贡献
snarkjs zkey contribute circuit_0000.zkey circuit_final.zkey --name="Contributor" -v
# 导出验证密钥
snarkjs zkey export verificationkey circuit_final.zkey verification_key.json
2.2.2 证明和验证过程
证明者生成:
证明密钥(pk) + 公开输入(pub) + 私有输入(witness) -> 证明 蟺
验证者验证:
验证密钥(vk) + 公开输入(pub) + 证明 蟺 -> true/false
配对方程:
e(A, B) = e(伪, 尾) . e(pub.xB7纬^-1, 纬) . e(C, 汛)
2.3 PLONK 协议
PLONK(Permutations over Lagrange-bases for Oecumenical Non-interactive arguments of Knowledge)是 Groth16 的替代方案。
| 特性 | Groth16 | PLONK |
|---|---|---|
| 可信设置 | 每个电路一次 | 通用设置(通用 CRS) |
| 证明大小 | ~200 B | ~1-2 KB |
| 验证时间 | ~1ms | ~3ms |
| 电路复杂度 | O(n) | O(n) 但常数更大 |
PLONK 的核心思想是用置换检查(Permutation Check)和查表(Lookup Argument)替代 R1CS。
2.4 Bulletproofs
Bulletproofs 是一种无需可信设置的零知识证明方案,特别适合范围证明(Range Proofs)。
| 特性 | 值 |
|---|---|
| 可信设置 | 不需要 |
| 证明大小 | O(log n) ~ 1.5 KB(64 位范围) |
| 验证时间 | O(n) |
| 典型应用 | 保密交易中的金额范围证明 |
2.5 电路构建框架对比
| 框架 | 语言 | 适用曲线 | 证明系统 | 生态系统 |
|---|---|---|---|---|
| Circom 2 | Circom | BN254, BLS12-381 | Groth16, PLONK | 最大,工具链成熟 |
| ZoKrates | ZoKrates DSL | BN254, BLS12-377 | Groth16, PGHR13 | 易用,EVM 集成好 |
| Arkworks | Rust | 任意 Arkworks 曲线 | Groth16, PLONK, Marlin | 灵活,功能最全 |
| Bellman | Rust | BLS12-381 | Groth16 | Filecoin 生态常用 |
| Lurk | Lisp | BN254 | Nova | 递归证明 |
对于 MSG Chain,推荐组合:Circom(电路设计)+ Arkworks(链上验证)
2.6 有限域与标量场
MSG Chain 上 ZKP 使用的曲线参数:
BN254:
基域: Fp = 21888242871839275222246405745257275088696311157297823662689037894645226208583
标量域: Fr = 21888242871839275222246405745257275088548364400416034343698204186575808495617
嵌入度: k = 12
BLS12-381:
基域: Fp = 52435875175126190479447740508185965837690552500527637822603658699938581184513
标量域: Fr = 52435875175126190479447740508185965837690552500527637822603658699938581184513
嵌入度: k = 12
2.7 证明大小与 Gas 消耗对比
| 方案 | 证明大小 | 链上验证 Gas(估计) | 推荐场景 |
|---|---|---|---|
| Groth16 (BN254) | ~128 B | ~200k-300k | 通用 |
| Groth16 (BLS12-381) | ~192 B | ~300k-500k | 高安全 |
| PLONK (BN254) | ~1.2 KB | ~1M-2M | 通用 CRS |
| Bulletproofs | ~1.5 KB | ~500k-800k | 范围证明 |
| STARK | ~50 KB | ~5M+ | 高频验证 |
2.8 安全注意事项
- 可信设置风险:Groth16 的可信设置需要多方参与,任何一方作恶可伪造证明。建议使用 Ceremony 多方计算。
- 归零攻击:验证时必须确保公开输入不包含退化值(如全零)。
- 重放攻击:在公开输入中加入 nonce 或时间戳,防止证明重复使用。
- 曲线兼容性:确保电路编译使用的曲线与链上验证合约的曲线一致。
- 算术化陷阱:有限域溢出、负数处理、整数除法的模拟等需谨慎。
3. 电路构建
3.1 Circom 入门
Circom 是一种用于构建算术电路的领域特定语言(DSL),编译产生 R1CS 约束系统。
3.1.1 安装 Circom 2
# 从源码编译
git clone https://github.com/iden3/circom.git
cd circom
cargo build --release
cargo install --path circom
# 验证安装
circom --version
# 安装 snarkjs
npm install -g snarkjs
3.1.2 基本语法
// 每个 Circom 文件定义模板(template)
// 模板实例化后成为电路
template IsZero() {
signal input in;
signal output out;
signal inv;
// 约束:in * inv == 1 - out
inv <-- in != 0 ? 1 / in : 0;
out <== 1 - (in * inv);
in * out === 0; // 确保 in=0 时 out=1
}
// 顶层组件
component main = IsZero();
关键语法规则:
signal input- 输入信号(公开或私有)signal output- 输出信号signal- 中间信号<==- 赋值并增加约束===- 仅增加约束<--- 仅赋值(无约束,需要自行约束)component- 子组件实例化
3.1.3 信号类型
pragma circom 2.1.0;
template SignalExample() {
// 公开输入(默认)
signal input public_input;
// 私有输入(仅证明者知道)
signal input private_input;
// 输出(始终公开)
signal output result;
// 中间信号
signal intermediate;
intermediate <== public_input * private_input;
result <== intermediate + 1;
}
3.2 年龄验证电路(Age Verification)
证明用户年龄大于某个阈值而不泄露具体年龄。
// circuits/age_verify.circom
pragma circom 2.1.0;
include "circomlib/comparators.circom";
include "circomlib/bitify.circom";
// 年龄验证电路
// 公开输入: min_age
// 私有输入: age
// 输出: valid (1 表示通过)
template AgeVerification(min_age_bits) {
signal input min_age;
signal input age;
signal output valid;
// 使用 LessThan 组件比较
component lt = LessThan(min_age_bits);
lt.in[0] <== min_age;
lt.in[1] <== age;
// 如果 age >= min_age,则 lt.out = 0,valid = 1
valid <== 1 - lt.out;
}
// 增强版:带生日哈希验证
// 用户证明:(1) 当前年龄 >= 18 (2) 生日哈希匹配 KYC 记录
template AgeVerificationWithHash() {
// 公开输入
signal input min_age;
signal input birthday_hash;
signal input commitment_root;
// 私有输入
signal input age;
signal input birthday;
signal input merkle_path[10];
signal input merkle_index;
// 输出
signal output valid;
// 子组件
component age_check = AgeVerification(32);
component hash_check = HashMerkleProof(10);
// 年龄验证
age_check.min_age <== min_age;
age_check.age <== age;
// 计算生日哈希并验证 Merkle 证明
component hash = Keccak256(1);
hash.inputs[0] <== birthday;
hash.out[0] ==> hash_check.leaf;
hash_check.root <== commitment_root;
hash_check.path <== merkle_path;
hash_check.index <== merkle_index;
// 两个条件同时满足
valid <== age_check.valid * hash_check.valid;
}
3.2.1 年龄验证的完整流程
用户(Prover):
1. 输入生日(私有)
2. 输入年龄(私有)
3. 输入 min_age = 18(公开)
4. 生成证明 proof
dApp / 合约(Verifier):
接收 proof + min_age
验证通过 -> 用户可访问 18+ 内容
验证失败 -> 拒绝访问
3.3 成员资格证明(Membership Proof)
证明某个值属于一个已知集合而不暴露是哪一个值。
// circuits/membership.circom
pragma circom 2.1.0;
include "circomlib/mimc.circom";
// 基于 Merkle 树的成员资格证明
template MembershipProof(tree_depth) {
// 公开输入
signal input merkle_root;
signal input leaf_count;
// 私有输入
signal input leaf;
signal input merkle_path[tree_depth];
signal input merkle_index;
// 输出
signal output valid;
// 1. 验证 leaf 在 Merkle 树中
component merkle_proof = MerkleTreeProof(tree_depth);
merkle_proof.leaf <== leaf;
merkle_proof.root <== merkle_root;
merkle_proof.path <== merkle_path;
merkle_proof.index <== merkle_index;
// 2. 验证 index 在 [0, leaf_count) 范围内
component lt = LessThan(tree_depth == 32 ? 32 : tree_depth);
lt.in[0] <== merkle_index;
lt.in[1] <== leaf_count;
// valid = merkle_valid AND index_valid
valid <== merkle_proof.valid * lt.out;
}
// MiMC Merkle 树证明
template MerkleTreeProof(depth) {
signal input leaf;
signal input root;
signal input path[depth];
signal input index;
signal output valid;
signal computed_root;
signal intermediate[depth + 1];
intermediate[0] <== leaf;
component hashers[depth];
component index_bits = Num2Bits(depth);
index_bits.in <== index;
for (var i = 0; i < depth; i++) {
hashers[i] = MiMC7(2);
hashers[i].inputs[0] <== index_bits.out[i] == 0
? intermediate[i]
: path[i];
hashers[i].inputs[1] <== index_bits.out[i] == 0
? path[i]
: intermediate[i];
intermediate[i + 1] <== hashers[i].out;
}
computed_root <== intermediate[depth];
component eq = IsEqual();
eq.in[0] <== computed_root;
eq.in[1] <== root;
valid <== eq.out;
}
3.4 范围证明(Range Proof)
证明某个值在指定区间内。
// circuits/range_proof.circom
pragma circom 2.1.0;
include "circomlib/comparators.circom";
include "circomlib/bitify.circom";
// 基础范围证明:值在 [lower, upper] 之间
template RangeCheck(n_bits) {
signal input value;
signal input lower_bound;
signal input upper_bound;
signal output valid;
// value >= lower_bound
component ge_lower = GreaterEqThan(n_bits);
ge_lower.in[0] <== value;
ge_lower.in[1] <== lower_bound;
// value <= upper_bound
component le_upper = LessEqThan(n_bits);
le_upper.in[0] <== value;
le_upper.in[1] <== upper_bound;
valid <== ge_lower.out * le_upper.out;
}
// 二进制展开范围证明
// 将 value 分解为 bit 位,确保 value in [0, 2^n)
template BitRangeProof(n_bits) {
signal input value;
signal output valid;
component bits = Num2Bits(n_bits);
bits.in <== value;
// bits.out 的每一位自动约束为 0 或 1
// 因此 value in [0, 2^n_bits - 1]
signal sum;
sum <== 0;
for (var i = 0; i < n_bits; i++) {
sum <== sum + bits.out[i] * (1 << i);
}
valid <== 1;
}
// 金额范围证明(用于私有交易)
// 证明 shielded_amount in [0, MAX_SUPPLY]
template AmountRangeProof() {
signal input amount;
signal output valid;
component bit_check = BitRangeProof(128);
bit_check.value <== amount;
component non_negative = GreaterEqThan(128);
non_negative.in[0] <== amount;
non_negative.in[1] <== 0;
valid <== bit_check.valid * non_negative.out;
}
3.5 私有交易电路
// circuits/shielded_transfer.circom
pragma circom 2.1.0;
include "circomlib/mimc.circom";
include "circomlib/bitify.circom";
include "circomlib/comparators.circom";
// 保密转移电路
// 证明:
// 1. sender 拥有某个 note 的所有权
// 2. 新 note 的金额 = 旧 note 金额 - 转账金额
// 3. 转账金额 > 0
// 4. 余额不溢出
template ShieldedTransfer(note_depth) {
// -- 公开输入 --
signal input old_note_commitment;
signal input new_note_commitment;
signal input nullifier;
signal input merkle_root;
// -- 私有输入 --
signal input old_note_amount;
signal input old_note_owner;
signal input old_note_random;
signal input new_note_amount;
signal input new_note_owner;
signal input new_note_random;
signal input transfer_amount;
signal input merkle_path[note_depth];
signal input merkle_index;
signal input owner_secret;
// -- 输出 --
signal output valid;
// 1. 验证旧 note 承诺
component old_comm = MiMC7(3);
old_comm.inputs[0] <== old_note_amount;
old_comm.inputs[1] <== old_note_owner;
old_comm.inputs[2] <== old_note_random;
component old_eq = IsEqual();
old_eq.in[0] <== old_comm.out;
old_eq.in[1] <== old_note_commitment;
// 2. 验证 nullifier 正确派生
component nullifier_hash = MiMC7(2);
nullifier_hash.inputs[0] <== old_note_commitment;
nullifier_hash.inputs[1] <== owner_secret;
component null_eq = IsEqual();
null_eq.in[0] <== nullifier_hash.out;
null_eq.in[1] <== nullifier;
// 3. 验证 Merkle 包含性
component merkle_proof = MerkleTreeProof(note_depth);
merkle_proof.leaf <== old_note_commitment;
merkle_proof.root <== merkle_root;
merkle_proof.path <== merkle_path;
merkle_proof.index <== merkle_index;
// 4. 验证转移金额 > 0
component positive = GreaterThan(128);
positive.in[0] <== transfer_amount;
positive.in[1] <== 0;
// 5. 验证旧金额 = 新金额 + 转移金额
component sum_check = Sum();
sum_check.a <== new_note_amount;
sum_check.b <== transfer_amount;
component sum_eq = IsEqual();
sum_eq.in[0] <== sum_check.out;
sum_eq.in[1] <== old_note_amount;
// 6. 验证新 note 承诺
component new_comm = MiMC7(3);
new_comm.inputs[0] <== new_note_amount;
new_comm.inputs[1] <== new_note_owner;
new_comm.inputs[2] <== new_note_random;
component new_eq = IsEqual();
new_eq.in[0] <== new_comm.out;
new_eq.in[1] <== new_note_commitment;
// 全部条件相与
valid <== old_eq.out *
null_eq.out *
merkle_proof.valid *
positive.out *
sum_eq.out *
new_eq.out;
}
template Sum() {
signal input a;
signal input b;
signal output out;
out <== a + b;
}
3.6 电路编译与见证生成
3.6.1 编译电路
# 1. 编译 Circom -> R1CS + WASM
circom age_verify.circom \
--r1cs \
--wasm \
--sym \
--output ./build
# 输出文件:
# build/age_verify.r1cs # R1CS 约束系统
# build/age_verify.wasm # WASM 见证生成器
# build/age_verify.sym # 符号文件
# 查看约束数
snarkjs r1cs info build/age_verify.r1cs
# 输出示例:
# [INFO] snarkJS: Curve: bn-128
# [INFO] snarkJS: # of Wires: 128
# [INFO] snarkJS: # of Constraints: 47
# [INFO] snarkJS: # of Private Inputs: 2
# [INFO] snarkJS: # of Public Inputs: 1
# [INFO] snarkJS: # of Outputs: 1
3.6.2 生成见证(Witness)
// scripts/generate_witness.js
const { readFileSync, writeFileSync } = require('fs');
const { execSync } = require('child_process');
async function main() {
execSync(`
snarkjs wtns calculate \
build/age_verify.wasm \
input.json \
build/witness.wtns
`, { stdio: 'inherit' });
execSync(`
snarkjs wtns export json \
build/witness.wtns \
build/witness.json
`, { stdio: 'inherit' });
}
const sampleInput = {
"min_age": "18",
"age": "25"
};
writeFileSync('input.json', JSON.stringify(sampleInput, null, 2));
main().catch(console.error);
3.6.3 可信设置与密钥生成
# 完成 Powers of Tau(假设已有 pot12_final.ptau)
# 生成阶段 2 密钥
snarkjs groth16 setup \
build/age_verify.r1cs \
pot12_final.ptau \
build/circuit_final.zkey
# 生成验证密钥
snarkjs zkey export verificationkey \
build/circuit_final.zkey \
build/verification_key.json
# 生成 Solidity 验证合约(参考,CosmWasm 需自定义)
snarkjs zkey export solidityverifier \
build/circuit_final.zkey \
build/verifier.sol
3.6.4 生成最终证明
snarkjs groth16 prove \
build/circuit_final.zkey \
build/witness.wtns \
build/proof.json \
build/public.json
# proof.json 包含 { pi_a, pi_b, pi_c } 三个群元素
# public.json 包含公开输入值
proof.json 结构:
{
"pi_a": ["143...", "256...", "1"],
"pi_b": [
["937...", "612..."],
["431...", "188..."],
["1", "0"]
],
"pi_c": ["571...", "803...", "1"],
"protocol": "groth16",
"curve": "bn128"
}
3.7 回路测试与调试
// tests/circuit_test.js
const { expect } = require('chai');
const { readFileSync } = require('fs');
describe('AgeVerification Circuit', () => {
let circuit;
before(async () => {
const circom = require('circom_tester');
circuit = await circom.WasmTester('circuits/age_verify.circom');
});
it('should verify age >= 18 when age is 20', async () => {
const witness = await circuit.calculateWitness({
"min_age": 18,
"age": 20
});
await circuit.checkConstraints(witness);
await circuit.assertOut(witness, { "valid": "1" });
});
it('should reject age < 18 when age is 15', async () => {
const witness = await circuit.calculateWitness({
"min_age": 18,
"age": 15
});
await circuit.checkConstraints(witness);
await circuit.assertOut(witness, { "valid": "0" });
});
it('should handle edge case: age == min_age', async () => {
const witness = await circuit.calculateWitness({
"min_age": 18,
"age": 18
});
await circuit.checkConstraints(witness);
await circuit.assertOut(witness, { "valid": "1" });
});
});
3.8 电路优化技巧
- 减少约束数:使用 MiMC 而非 SHA256(减少 100x 约束)
- 并行化:大电路可拆分为多个子电路并行生成证明
- 预计算:将固定计算在设置阶段完成
- 查找表:使用 PLONK 的查找参数减少约束
- 递归证明:将多个小证明聚合为一个
# 基准测试:不同哈希函数的约束数
# SHA256(256位): ~27,000 约束
# Poseidon(2输入): ~50 约束
# MiMC7(2输入): ~10 约束
# Pedersen Hash: ~100 约束
4. 链上验证合约
4.1 CosmWasm 验证合约架构
contract/
+-- src/
| +-- contract.rs # 合约入口
| +-- state.rs # 链上状态
| +-- zk_verifier.rs # ZKP 验证逻辑
| +-- types.rs # 类型定义
| +-- pairing.rs # 配对运算
| +-- error.rs # 错误类型
| +-- msg.rs # 消息定义
+-- Cargo.toml
+-- examples/
+-- integration_test.rs
4.2 Cargo.toml 依赖配置
[package]
name = "zk-verifier"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["library"]
library = []
[dependencies]
cosmwasm-std = { version = "1.5", features = ["staking"] }
cosmwasm-storage = "1.5"
cosmwasm-crypto = "1.5"
cw-storage-plus = "1.2"
cw2 = "1.2"
schemars = "0.8"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
# ZKP 相关
ark-bn254 = "0.4"
ark-bls12-381 = "0.4"
ark-groth16 = "0.4"
ark-relations = "0.4"
ark-serialize = "0.4"
ark-ff = "0.4"
ark-ec = "0.4"
4.3 类型定义
// src/types.rs
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Binary;
/// Groth16 证明的三个群元素
#[cw_serde]
pub struct Groth16Proof {
/// 蟺_A in G1
pub pi_a: [u8; 64],
/// 蟺_B in G2
pub pi_b: [u8; 128],
/// 蟺_C in G1
pub pi_c: [u8; 64],
}
/// BN254 G1 点
#[cw_serde]
pub struct G1Point {
pub x: [u8; 32],
pub y: [u8; 32],
}
/// BN254 G2 点
#[cw_serde]
pub struct G2Point {
pub x: [u8; 64],
pub y: [u8; 64],
}
/// 验证密钥(Verification Key)
#[cw_serde]
pub struct VerifyingKey {
pub alpha_g1: G1Point,
pub beta_g2: G2Point,
pub gamma_g2: G2Point,
pub delta_g2: G2Point,
pub gamma_abc_g1: Vec<G1Point>,
}
/// 零知识证明相关的消息
#[cw_serde]
pub struct ZKProof {
pub proof_type: ProofType,
pub proof: Binary,
pub public_inputs: Vec<Binary>,
pub verifying_key_id: String,
}
#[cw_serde]
pub enum ProofType {
Groth16Bn254,
Groth16Bls12381,
Plonk,
Bulletproof,
}
/// 验证结果
#[cw_serde]
pub struct VerificationResult {
pub verified: bool,
pub prover_address: Option<String>,
pub timestamp: u64,
}
/// KYC 状态
#[cw_serde]
pub struct KYCStatus {
pub address: String,
pub kyc_level: u8,
pub verified_at: u64,
pub expires_at: u64,
pub proof_hash: String,
}
4.4 消息定义
// src/msg.rs
use cosmwasm_schema::{cw_serde, QueryResponses};
use crate::types::*;
#[cw_serde]
pub struct InstantiateMsg {
pub admin: String,
pub verifying_keys: Vec<VerifyingKey>,
}
#[cw_serde]
pub enum ExecuteMsg {
UpdateVerifyingKey {
key_id: String,
key: VerifyingKey,
},
VerifyGroth16 {
proof: Groth16Proof,
public_inputs: Vec<Binary>,
key_id: String,
},
VerifyKYC {
proof: Groth16Proof,
public_inputs: Vec<Binary>,
key_id: String,
kyc_level: u8,
},
RevokeKYC {},
}
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(KYCStatusResponse)]
GetKYCStatus { address: String },
#[returns(ConfigResponse)]
GetConfig {},
#[returns(VerificationResult)]
VerifyProof {
proof: Groth16Proof,
public_inputs: Vec<Binary>,
key_id: String,
},
}
#[cw_serde]
pub struct KYCStatusResponse {
pub status: KYCStatus,
}
#[cw_serde]
pub struct ConfigResponse {
pub admin: String,
pub key_count: u32,
}
4.5 核心验证逻辑:BN254 Groth16 验证器
// src/zk_verifier.rs
use cosmwasm_std::{StdResult, StdError, Binary};
use crate::types::*;
/// 验证 Groth16 证明(BN254 曲线)
///
/// 配对检查方程:
/// e(蟺_A, 蟺_B) = e(伪, 尾) . e(危(pub_i . 纬_abc_i), 纬) . e(蟺_C, 汛)
pub fn verify_groth16(
proof: &Groth16Proof,
public_inputs: &[Binary],
vk: &VerifyingKey,
) -> StdResult<bool> {
// 验证输入数量匹配
if public_inputs.len() != vk.gamma_abc_g1.len() - 1 {
return Err(StdError::generic_err(
"Public inputs count mismatch"
));
}
// 1. 反序列化证明元素
let pi_a = g1_from_bytes(&proof.pi_a)?;
let pi_b = g2_from_bytes(&proof.pi_b)?;
let pi_c = g1_from_bytes(&proof.pi_c)?;
// 2. 反序列化验证密钥元素
let alpha_g1 = g1_from_point(&vk.alpha_g1)?;
let beta_g2 = g2_from_point(&vk.beta_g2)?;
let gamma_g2 = g2_from_point(&vk.gamma_g2)?;
let delta_g2 = g2_from_point(&vk.delta_g2)?;
// 3. 计算公开输入的组合
let mut accumulated_g1 = g1_from_point(&vk.gamma_abc_g1[0])?;
for (i, input) in public_inputs.iter().enumerate() {
let scalar = fr_from_bytes(input)?;
let gamma_i = g1_from_point(&vk.gamma_abc_g1[i + 1])?;
accumulated_g1 = g1_add(&accumulated_g1, &g1_scalar_mul(&gamma_i, &scalar));
}
// 4. 执行配对检查
let pairing_left = pairing(&pi_a, &pi_b);
let pairing_right_1 = pairing(&alpha_g1, &beta_g2);
let pairing_right_2 = pairing(&accumulated_g1, &gamma_g2);
let pairing_right_3 = pairing(&pi_c, &delta_g2);
let pairing_right = gt_mul(&pairing_right_1, &pairing_right_2);
let pairing_right = gt_mul(&pairing_right, &pairing_right_3);
Ok(gt_eq(&pairing_left, &pairing_right))
}
/// 验证器封装:包含日志和事件
pub fn verify_and_emit(
deps: DepsMut,
info: MessageInfo,
proof: Groth16Proof,
public_inputs: Vec<Binary>,
key_id: String,
) -> Result<bool, ContractError> {
let config = CONFIG.load(deps.storage)?;
let vk = config
.verifying_keys
.get(&key_id)
.ok_or(ContractError::KeyNotFound { key_id: key_id.clone() })?;
let valid = verify_groth16(&proof, &public_inputs, vk)?;
if valid {
deps.api.debug(format!(
"ZK proof verified successfully for key_id: {}", key_id
));
let event = Event::new("zk-proof-verified")
.add_attribute("key_id", &key_id)
.add_attribute("verifier", info.sender)
.add_attribute("valid", "true");
deps.events.push(event);
} else {
return Err(ContractError::VerificationFailed {});
}
Ok(true)
}
4.6 BN254 曲线配对运算
// src/pairing.rs
use cosmwasm_std::{StdResult, StdError};
use crate::types::*;
/// BN254 曲线的阶
const BN254_ORDER: [u8; 32] = hex_literal::hex!(
"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"
);
/// Fp 域的模
const BN254_FP_MODULUS: [u8; 32] = hex_literal::hex!(
"30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47"
);
/// G1 标量乘法(使用 Double-and-Add)
fn g1_scalar_mul(point: &G1PointAffine, scalar: &[u8; 32]) -> G1PointAffine {
let mut result = G1PointAffine::infinity();
let mut base = point.clone();
for i in (0..256).rev() {
result = g1_add(&result, &result);
let byte_idx = i / 8;
let bit_idx = i % 8;
if (scalar[byte_idx] >> (7 - bit_idx)) & 1 == 1 {
result = g1_add(&result, &base);
}
}
result
}
/// Fp 域运算(模 BN254 素域)
fn fp_add(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
let (sum, carry) = u256_add(a, b);
if carry || u256_ge(&sum, &BN254_FP_MODULUS) {
u256_sub(&sum, &BN254_FP_MODULUS)
} else {
sum
}
}
fn fp_sub(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
if u256_ge(a, b) {
u256_sub(a, b)
} else {
u256_add(&u256_sub(&BN254_FP_MODULUS, b), a)
}
}
fn fp_mul(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
let product = u256_mul(a, b);
barrett_reduce(&product)
}
fn fp_square(a: &[u8; 32]) -> [u8; 32] {
fp_mul(a, a)
}
/// 模除法:a / b = a * b^(p-2)
fn fp_div(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
let inv = fp_pow(b, &BN254_FP_MODULUS);
fp_mul(a, &inv)
}
fn fp_pow(base: &[u8; 32], exp: &[u8; 32]) -> [u8; 32] {
let mut result = [1u8; 32];
let mut b = base.clone();
let mut e = exp.clone();
let mut i = 255;
loop {
if u256_is_odd(&e) {
result = fp_mul(&result, &b);
}
e = u256_shr(&e);
if i == 0 { break; }
b = fp_square(&b);
i -= 1;
}
result
}
/// 完整的配对运算:e(P, Q)
fn pairing(p: &G1PointAffine, q: &G2PointAffine) -> Fp12 {
miller_loop(q, p)
}
fn gt_eq(a: &Fp12, b: &Fp12) -> bool {
fp6_eq(&a.c0, &b.c0) && fp6_eq(&a.c1, &b.c1)
}
fn gt_mul(a: &Fp12, b: &Fp12) -> Fp12 {
fp12_mul(a, b)
}
/// 从 64 字节反序列化 G1 压缩点
fn g1_from_bytes(bytes: &[u8; 64]) -> StdResult<G1PointAffine> {
let mut x = [0u8; 32];
let mut y = [0u8; 32];
x.copy_from_slice(&bytes[0..32]);
y.copy_from_slice(&bytes[32..64]);
if !is_on_curve_g1(&x, &y) {
return Err(StdError::generic_err("Point not on G1 curve"));
}
Ok(G1PointAffine { x, y })
}
fn g1_from_point(point: &G1Point) -> StdResult<G1PointAffine> {
if !is_on_curve_g1(&point.x, &point.y) {
return Err(StdError::generic_err("Point not on G1 curve"));
}
Ok(G1PointAffine {
x: point.x,
y: point.y,
})
}
fn g2_from_point(point: &G2Point) -> StdResult<G2PointAffine> {
let x = Fp2 {
c0: point.x[..32].try_into().unwrap(),
c1: point.x[32..].try_into().unwrap(),
};
let y = Fp2 {
c0: point.y[..32].try_into().unwrap(),
c1: point.y[32..].try_into().unwrap(),
};
if !is_on_curve_g2(&x, &y) {
return Err(StdError::generic_err("Point not on G2 curve"));
}
Ok(G2PointAffine { x, y })
}
/// 检查 G1 点是否在 BN254 曲线上
fn is_on_curve_g1(x: &[u8; 32], y: &[u8; 32]) -> bool {
let lhs = fp_square(y);
let rhs = fp_add(&fp_mul(&fp_square(x), x), &[3u8; 32]);
fp_eq(&lhs, &rhs)
}
4.7 合约主入口
// src/contract.rs
use cosmwasm_std::{
entry_point, Binary, Deps, DepsMut, Env, Event,
MessageInfo, Response, StdResult,
to_binary,
};
use crate::error::ContractError;
use crate::msg::*;
use crate::state::*;
use crate::zk_verifier::{verify_groth16, verify_and_emit};
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
let admin = deps.api.addr_validate(&msg.admin)?;
let config = Config {
admin: admin.clone(),
};
CONFIG.save(deps.storage, &config)?;
for key in &msg.verifying_keys {
let key_id = generate_key_id(key);
VERIFYING_KEYS.save(deps.storage, &key_id, key)?;
}
Ok(Response::new()
.add_attribute("action", "instantiate")
.add_attribute("admin", admin))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::UpdateVerifyingKey { key_id, key } => {
execute_update_key(deps, info, key_id, key)
}
ExecuteMsg::VerifyGroth16 { proof, public_inputs, key_id } => {
execute_verify_proof(deps, env, info, proof, public_inputs, key_id)
}
ExecuteMsg::VerifyKYC { proof, public_inputs, key_id, kyc_level } => {
execute_verify_kyc(deps, env, info, proof, public_inputs, key_id, kyc_level)
}
ExecuteMsg::RevokeKYC {} => {
execute_revoke_kyc(deps, env, info)
}
}
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetKYCStatus { address } => {
to_binary(&query_kyc_status(deps, address)?)
}
QueryMsg::GetConfig {} => {
to_binary(&query_config(deps)?)
}
QueryMsg::VerifyProof { proof, public_inputs, key_id } => {
to_binary(&query_verify_proof(deps, proof, public_inputs, key_id)?)
}
}
}
fn execute_update_key(
deps: DepsMut,
info: MessageInfo,
key_id: String,
key: VerifyingKey,
) -> Result<Response, ContractError> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(ContractError::Unauthorized {});
}
VERIFYING_KEYS.save(deps.storage, &key_id, &key)?;
Ok(Response::new()
.add_attribute("action", "update_key")
.add_attribute("key_id", key_id))
}
fn execute_verify_proof(
deps: DepsMut,
_env: Env,
info: MessageInfo,
proof: Groth16Proof,
public_inputs: Vec<Binary>,
key_id: String,
) -> Result<Response, ContractError> {
let valid = verify_and_emit(deps, info, proof, public_inputs, key_id)?;
Ok(Response::new()
.add_attribute("action", "verify_proof")
.add_attribute("result", if valid { "valid" } else { "invalid" }))
}
fn execute_verify_kyc(
deps: DepsMut,
env: Env,
info: MessageInfo,
proof: Groth16Proof,
public_inputs: Vec<Binary>,
key_id: String,
kyc_level: u8,
) -> Result<Response, ContractError> {
let valid = verify_and_emit(deps.branch(), info.clone(), proof, public_inputs.clone(), key_id)?;
if !valid {
return Err(ContractError::VerificationFailed {});
}
let user_address = public_inputs
.first()
.ok_or(ContractError::InvalidInput {})?;
let user_addr = deps.api.addr_validate(
&String::from_utf8(user_address.to_vec())
.map_err(|_| ContractError::InvalidInput {})?
)?;
let kyc_status = KYCStatus {
address: user_addr.to_string(),
kyc_level,
verified_at: env.block.time.seconds(),
expires_at: env.block.time.seconds() + 365 * 24 * 3600,
proof_hash: hex::encode(&public_inputs[0].to_vec()),
};
KYC_STATUS.save(deps.storage, &user_addr, &kyc_status)?;
Ok(Response::new()
.add_attribute("action", "verify_kyc")
.add_attribute("address", user_addr)
.add_attribute("kyc_level", kyc_level.to_string())
.add_attribute("expires_at", kyc_status.expires_at.to_string()))
}
fn execute_revoke_kyc(
deps: DepsMut,
_env: Env,
info: MessageInfo,
) -> Result<Response, ContractError> {
KYC_STATUS.remove(deps.storage, &info.sender);
Ok(Response::new()
.add_attribute("action", "revoke_kyc")
.add_attribute("address", info.sender))
}
fn query_kyc_status(deps: Deps, address: String) -> StdResult<KYCStatusResponse> {
let addr = deps.api.addr_validate(&address)?;
let status = KYC_STATUS
.load(deps.storage, &addr)
.unwrap_or(KYCStatus {
address,
kyc_level: 0,
verified_at: 0,
expires_at: 0,
proof_hash: String::new(),
});
Ok(KYCStatusResponse { status })
}
fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
let config = CONFIG.load(deps.storage)?;
let key_count = VERIFYING_KEYS
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.count() as u32;
Ok(ConfigResponse {
admin: config.admin.to_string(),
key_count,
})
}
fn query_verify_proof(
deps: Deps,
proof: Groth16Proof,
public_inputs: Vec<Binary>,
key_id: String,
) -> StdResult<VerificationResult> {
let config = CONFIG.load(deps.storage)?;
let vk = VERIFYING_KEYS
.load(deps.storage, &key_id)
.map_err(|_| StdError::generic_err("Key not found"))?;
let valid = verify_groth16(&proof, &public_inputs, &vk)?;
Ok(VerificationResult {
verified: valid,
prover_address: None,
timestamp: 0,
})
}
4.8 状态管理
// src/state.rs
use cosmwasm_std::Addr;
use cw_storage_plus::{Item, Map};
use crate::types::*;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Schema)]
pub struct Config {
pub admin: Addr,
}
pub const CONFIG: Item<Config> = Item::new("config");
pub const VERIFYING_KEYS: Map<&str, VerifyingKey> = Map::new("vk");
pub const KYC_STATUS: Map<&Addr, KYCStatus> = Map::new("kyc");
4.9 错误类型
// src/error.rs
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Unauthorized")]
Unauthorized {},
#[error("Verification key not found: {key_id}")]
KeyNotFound { key_id: String },
#[error("Proof verification failed")]
VerificationFailed {},
#[error("Invalid public input")]
InvalidInput {},
#[error("KYC already verified")]
KYCAlreadyVerified {},
#[error("KYC expired")]
KYCExpired {},
#[error("Invalid curve point")]
InvalidPoint {},
#[error("Pairing check failed")]
PairingFailed {},
}
4.10 使用 Arkworks 库的验证器
// src/verifier_ark.rs
use ark_bn254::{Bn254, Fr};
use ark_groth16::{Groth16, Proof, VerifyingKey};
use ark_serialize::{CanonicalDeserialize, Compress};
use ark_ec::pairing::Pairing;
type E = Bn254;
pub fn verify_with_arkworks(
proof_bytes: &[u8],
public_inputs: &[Fr],
vk_bytes: &[u8],
) -> Result<bool, VerifierError> {
let vk = VerifyingKey::<E>::deserialize_with_mode(
vk_bytes,
Compress::No,
ark_serialize::Validate::Yes,
).map_err(|_| VerifierError::DeserializationFailed)?;
let proof = Proof::<E>::deserialize_with_mode(
proof_bytes,
Compress::No,
ark_serialize::Validate::Yes,
).map_err(|_| VerifierError::DeserializationFailed)?;
let valid = Groth16::<E>::verify(&vk, &public_inputs, &proof)
.map_err(|_| VerifierError::VerificationFailed)?;
Ok(valid)
}
#[derive(Debug)]
pub enum VerifierError {
DeserializationFailed,
VerificationFailed,
}
4.11 Gas 优化建议
- 预计算:将验证密钥预先部署到合约存储,避免每次反序列化
- 批量验证:使用批量配对检查,多个证明共用一次配对运算
- 懒验证:仅在必要时执行完整配对(先检查轻量级约束)
- 缓存:缓存已验证的公开输入结果
- 聚合证明:使用递归证明将多个证明聚合为一个
4.12 合约测试
// tests/integration_test.rs
#[cfg(test)]
mod tests {
use cosmwasm_std::testing::{
mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{from_binary, Binary};
use crate::contract::{instantiate, execute, query};
use crate::msg::*;
use crate::types::*;
fn setup_test() -> (DepsMut, Env, MessageInfo) {
let mut deps = mock_dependencies();
let env = mock_env();
let info = mock_info("creator", &[]);
let vk = VerifyingKey {
alpha_g1: G1Point { x: [0u8; 32], y: [0u8; 32] },
beta_g2: G2Point {
x: [0u8; 64],
y: [0u8; 64],
},
gamma_g2: G2Point {
x: [0u8; 64],
y: [0u8; 64],
},
delta_g2: G2Point {
x: [0u8; 64],
y: [0u8; 64],
},
gamma_abc_g1: Vec::new(),
};
let msg = InstantiateMsg {
admin: "creator".to_string(),
verifying_keys: vec![vk],
};
instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
(deps.as_mut(), env, info)
}
#[test]
fn test_instantiate() {
let (deps, _, _) = setup_test();
let config: ConfigResponse = from_binary(
&query(
deps.as_ref(),
mock_env(),
QueryMsg::GetConfig {},
).unwrap()
).unwrap();
assert_eq!(config.admin, "creator");
assert_eq!(config.key_count, 1);
}
#[test]
fn test_unauthorized_key_update() {
let (mut deps, env, _info) = setup_test();
let attacker = mock_info("attacker", &[]);
let result = execute(
deps.as_mut(),
env,
attacker,
ExecuteMsg::UpdateVerifyingKey {
key_id: "default".to_string(),
key: VerifyingKey {
alpha_g1: G1Point { x: [0u8; 32], y: [0u8; 32] },
beta_g2: G2Point {
x: [0u8; 64],
y: [0u8; 64],
},
gamma_g2: G2Point {
x: [0u8; 64],
y: [0u8; 64],
},
delta_g2: G2Point {
x: [0u8; 64],
y: [0u8; 64],
},
gamma_abc_g1: Vec::new(),
},
},
);
assert!(result.is_err());
}
}
4.13 部署脚本
#!/bin/bash
# scripts/deploy_verifier.sh
CHAIN_ID="msg-chain-1"
RPC="https://rpc.msgchain.org"
ADMIN="msg1..."
LABEL="zk-verifier-v1"
echo "Building contract..."
cd contract
RUSTFLAGS='-C link-arg=-s' cargo wasm
cosmwasm-check target/wasm32-unknown-unknown/release/zk_verifier.wasm
echo "Optimizing..."
wasm-opt -O3 \
target/wasm32-unknown-unknown/release/zk_verifier.wasm \
-o target/wasm32-unknown-unknown/release/zk_verifier_opt.wasm
echo "Uploading contract..."
RES=$(msgd tx wasm store \
target/wasm32-unknown-unknown/release/zk_verifier_opt.wasm \
--from $ADMIN \
--chain-id $CHAIN_ID \
--gas auto --gas-adjustment 1.4 \
--fees 1000umsg \
--node $RPC \
--output json -y)
CODE_ID=$(echo $RES | jq -r '.logs[0].events[] | select(.type == "store_code") | .attributes[] | select(.key == "code_id") | .value')
echo "Code ID: $CODE_ID"
echo "Instantiating contract..."
msgd tx wasm instantiate $CODE_ID '{"admin":"'$ADMIN'","verifying_keys":[]}' \
--from $ADMIN \
--label $LABEL \
--chain-id $CHAIN_ID \
--gas auto --gas-adjustment 1.4 \
--fees 2000umsg \
--node $RPC \
--output json -y
CONTRACT_ADDR=$(msgd query wasm list-contract-by-code $CODE_ID --output json | jq -r '.contracts[-1]')
echo "Contract deployed at: $CONTRACT_ADDR"
5. zkKYC 集成
5.1 什么是 zkKYC
zkKYC(Zero-Knowledge Know Your Customer)允许用户向 dApp 证明他们已经完成了 KYC 验证,而无需透露具体身份信息(姓名、身份证号、住址等)。
传统 KYC 的问题:
- 用户隐私暴露:提交身份证、护照等敏感信息
- 数据泄露风险:中心化数据库成为黑客目标
- 重复验证:每个 dApp 都要重新做 KYC
zkKYC 的优势:
- 用户控制数据:只披露"已通过 KYC"的事实
- 可选择性披露:可控制暴露哪些信息
- 一次 KYC,多处使用:一个合规提供商验证后即可在多个 dApp 使用
- 可撤销性:监管机构可在必要时撤销证明
5.2 系统架构
+-------------------+
| KYC 提供商 |
| (合规机构) |
+--------+----------+
| 验证身份 -> 签发签名
v
+-------------------+
| KYC 数据源 |
| (链下数据库) |
+--------+----------+
| Merkle 承诺
v
+-------------------+
| KYC 承诺合约 |
| (MSG Chain) |
| Merkle Root |
+--------+----------+
|
+-------------------+-------------------+
v v v
+------------+ +------------+ +------------+
| 用户 A | | 用户 B | | 用户 C |
| 生成 ZK 证明 | | 生成 ZK 证明 | | 生成 ZK 证明 |
+-----+------+ +-----+------+ +-----+------+
| | |
+-------------------+-------------------+
v
+-------------------+
| dApp 验证合约 |
| (MSG Chain) |
+-------------------+
5.3 KYC 提供商注册用户
# scripts/kyc_provider/register_user.py
import hashlib
import json
from typing import List
from pymerkle import MerkleTree
class KYCProvider:
def __init__(self, tree_file: str = "kyc_merkle.json"):
self.tree = MerkleTree(hash_type="sha256")
self.tree_file = tree_file
self.users: dict[str, dict] = {}
def verify_identity(self, user_address: str, identity_docs: dict) -> bool:
print(f"Verifying identity for {user_address}...")
checks = {
"id_document_valid": True,
"face_match": True,
"aml_check": True,
"pep_check": True,
"sanctions_check": True,
}
if all(checks.values()):
print("Identity verified successfully")
return True
print("Identity verification failed")
return False
def add_user(self, user_address: str, kyc_level: int) -> bytes:
import os
salt = os.urandom(32).hex()
leaf_data = f"{user_address}:{kyc_level}:{salt}".encode()
leaf = hashlib.sha256(leaf_data).digest()
self.tree.append(leaf)
self.users[user_address] = {
"leaf": leaf.hex(),
"kyc_level": kyc_level,
"salt": salt,
"index": self.tree.length - 1,
}
print(f"User {user_address} added at index {self.tree.length - 1}")
self._save_state()
return leaf
def get_merkle_root(self) -> str:
return self.tree.root_hash.hex()
def get_merkle_path(self, user_address: str) -> List[str]:
if user_address not in self.users:
raise ValueError(f"User {user_address} not found")
index = self.users[user_address]["index"]
proof = self.tree.proof(index)
return [node.encode().hex() for node in proof]
def get_user_salt(self, user_address: str) -> str:
if user_address not in self.users:
raise ValueError(f"User {user_address} not found")
return self.users[user_address]["salt"]
def upload_root_to_chain(self, rpc_url: str, contract_addr: str, admin_key: str):
root = self.get_merkle_root()
print(f"Uploading Merkle root: {root} to {contract_addr}")
def _save_state(self):
with open(self.tree_file, "w") as f:
json.dump({
"tree": self.tree.to_dict(),
"users": self.users,
}, f, indent=2)
if __name__ == "__main__":
provider = KYCProvider()
provider.verify_identity("msg1qyp...", {"id_number": "ID123456"})
provider.add_user("msg1qyp...", kyc_level=2)
provider.verify_identity("msg1qz9...", {"id_number": "ID789012"})
provider.add_user("msg1qz9...", kyc_level=1)
print(f"Merkle Root: {provider.get_merkle_root()}")
5.4 用户生成 ZK 证明(TypeScript)
// scripts/zk_proof/generate_kyc_proof.ts
import * as snarkjs from "snarkjs";
import * as fs from "fs/promises";
interface KYCWitness {
userAddress: bigint;
kycLevel: number;
timestamp: bigint;
merkleRoot: string;
salt: string;
merklePath: string[];
merkleIndex: number;
}
interface ZKProofResult {
proof: object;
publicSignals: string[];
}
class KYCProver {
private wasmPath: string;
private zkeyPath: string;
constructor(wasmPath: string, zkeyPath: string) {
this.wasmPath = wasmPath;
this.zkeyPath = zkeyPath;
}
private addressToFr(address: string): bigint {
const { words } = require("bech32").decode(address);
const data = require("bech32").fromWords(words.slice(1));
const hex = Buffer.from(data).toString("hex");
return BigInt("0x" + hex);
}
private async prepareInputs(
userAddress: string,
kycLevel: number,
salt: string,
merklePath: string[],
merkleIndex: number,
merkleRoot: string,
): Promise<Record<string, any>> {
const addrFr = this.addressToFr(userAddress);
return {
user_address: addrFr.toString(),
kyc_level: kycLevel.toString(),
merkle_root: BigInt("0x" + merkleRoot).toString(),
timestamp: Math.floor(Date.now() / 1000).toString(),
salt: salt,
merkle_path: merklePath.map(
(node) => BigInt("0x" + node).toString()
),
merkle_index: merkleIndex.toString(),
};
}
async generateProof(witness: KYCWitness): Promise<ZKProofResult> {
console.log("Generating zkKYC proof...");
const inputs = await this.prepareInputs(
"msg1qyp...", 1, "salt", [], 0, "root"
);
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
inputs,
this.wasmPath,
this.zkeyPath,
);
console.log("Proof generated successfully");
return { proof, publicSignals };
}
formatProofForChain(proof: object): {
pi_a: string[];
pi_b: string[][];
pi_c: string[];
} {
const p = proof as any;
return {
pi_a: p.pi_a,
pi_b: p.pi_b,
pi_c: p.pi_c,
};
}
}
async function main() {
const prover = new KYCProver(
"./circuits/kyc_verify.wasm",
"./circuits/circuit_final.zkey",
);
const result = await prover.generateProof({
userAddress: BigInt(0),
kycLevel: 2,
timestamp: BigInt(Math.floor(Date.now() / 1000)),
merkleRoot: "abcdef...",
salt: "user-secret-salt",
merklePath: ["a1b2c3...", "d4e5f6..."],
merkleIndex: 42,
});
await fs.writeFile("proof.json", JSON.stringify(result.proof, null, 2));
await fs.writeFile("public.json", JSON.stringify(result.publicSignals, null, 2));
}
main().catch(console.error);
5.5 提交到链上
// scripts/submit_kyc.ts
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { GasPrice } from "@cosmjs/stargate";
import * as fs from "fs/promises";
class KYCSubmitter {
private client: SigningCosmWasmClient;
private senderAddress: string;
constructor(client: SigningCosmWasmClient, senderAddress: string) {
this.client = client;
this.senderAddress = senderAddress;
}
async submitProof(
contractAddress: string,
proof: any,
publicSignals: string[],
): Promise<string> {
console.log("Submitting zkKYC proof to chain...");
const executeMsg = {
verify_kyc: {
proof: {
pi_a: this.encodeG1Point(proof.pi_a),
pi_b: this.encodeG2Point(proof.pi_b),
pi_c: this.encodeG1Point(proof.pi_c),
},
public_inputs: publicSignals.map(s => this.toBinary(s)),
key_id: "kyc_v1",
kyc_level: parseInt(publicSignals[1]),
},
};
const result = await this.client.execute(
this.senderAddress,
contractAddress,
executeMsg,
"auto",
"zkKYC proof submission",
);
console.log(`Proof submitted in tx: ${result.transactionHash}`);
return result.transactionHash;
}
async queryKYCStatus(contractAddress: string, address: string): Promise<any> {
const result = await this.client.queryContractSmart(
contractAddress,
{ get_kyc_status: { address } },
);
return result.status;
}
private encodeG1Point(g1: string[]): Uint8Array {
const x = BigInt(g1[0]);
const y = BigInt(g1[1]);
return new Uint8Array([
...this.padTo32(this.bigIntToBytes(x)),
...this.padTo32(this.bigIntToBytes(y)),
]);
}
private encodeG2Point(g2: string[][]): Uint8Array {
const x0 = BigInt(g2[0][0]);
const x1 = BigInt(g2[0][1]);
const y0 = BigInt(g2[1][0]);
const y1 = BigInt(g2[1][1]);
return new Uint8Array([
...this.padTo32(this.bigIntToBytes(x0)),
...this.padTo32(this.bigIntToBytes(x1)),
...this.padTo32(this.bigIntToBytes(y0)),
...this.padTo32(this.bigIntToBytes(y1)),
]);
}
private bigIntToBytes(n: bigint): number[] {
const hex = n.toString(16).padStart(64, "0");
const bytes: number[] = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.slice(i, i + 2), 16));
}
return bytes;
}
private padTo32(bytes: number[]): number[] {
while (bytes.length < 32) bytes.unshift(0);
return bytes.slice(-32);
}
private toBinary(value: string): string {
const n = BigInt(value);
return "0x" + n.toString(16).padStart(64, "0");
}
}
async function main() {
const mnemonic = process.env.MSG_MNEMONIC || "";
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: "msg",
});
const [account] = await wallet.getAccounts();
const client = await SigningCosmWasmClient.connectWithSigner(
"https://rpc.msgchain.org",
wallet,
{ gasPrice: GasPrice.fromString("1000000000umsg") },
);
const submitter = new KYCSubmitter(client, account.address);
const status = await submitter.queryKYCStatus(
"msg1contractaddress...",
account.address,
);
console.log("KYC Status:", status);
}
main().catch(console.error);
5.6 zkKYC 集成检查清单
- [ ] KYC 提供商完成用户身份验证
- [ ] 用户哈希被添加到 Merkle 树
- [ ] Merkle 根已上传到 MSG Chain
- [ ] 用户从前端获取 Merkle 路径和 salt
- [ ] 浏览器使用 .wasm 生成 ZK 证明
- [ ] 证明提交到链上验证合约
- [ ] 链上合约验证配对方程
- [ ] 验证通过后更新链上 KYC 状态
- [ ] dApp 查询 KYC 状态并授予访问权限
6. 私有交易
6.1 概念
私有交易(Private/Shielded Transactions)使用 ZKP 隐藏交易的敏感信息:
| 属性 | 公开交易 | 私有交易 |
|---|---|---|
| 发送方 | 公开 | 隐藏(使用 Note) |
| 接收方 | 公开 | 隐藏(使用 Note) |
| 金额 | 公开 | 隐藏(承诺) |
| 资产类型 | 公开 | 可选隐藏 |
| 交易历史 | 公开可追溯 | 零知识 |
6.2 核心数据结构
// contracts/zkasset/src/types.rs
use cosmwasm_schema::cw_serde;
#[cw_serde]
pub struct NoteCommitment(pub [u8; 32]);
#[cw_serde]
pub struct Nullifier(pub [u8; 32]);
#[cw_serde]
pub struct Note {
pub amount: u128,
pub owner: [u8; 20],
pub randomness: [u8; 32],
}
impl Note {
pub fn commitment(&self) -> NoteCommitment {
let hash = mimc_hash(&[
fr_from_u128(self.amount),
fr_from_bytes(&self.owner),
fr_from_bytes(&self.randomness),
]);
NoteCommitment(hash)
}
pub fn nullifier(&self, owner_secret: &[u8; 32]) -> Nullifier {
let hash = mimc_hash(&[
fr_from_bytes(&self.commitment().0),
fr_from_bytes(owner_secret),
]);
Nullifier(hash)
}
}
#[cw_serde]
pub struct TransferPublicInputs {
pub old_note_commitment: [u8; 32],
pub new_note_commitment: [u8; 32],
pub nullifier: [u8; 32],
pub merkle_root: [u8; 32],
}
6.3 隐私资产合约
// contracts/zkasset/src/contract.rs
use cosmwasm_std::{
entry_point, Binary, Deps, DepsMut, Env, Event,
MessageInfo, Response, StdResult, Uint128,
to_binary,
};
use crate::error::ContractError;
use crate::msg::*;
use crate::state::*;
use crate::types::*;
use crate::zk_verifier::verify_groth16;
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
let admin = deps.api.addr_validate(&msg.admin)?;
let config = Config {
admin,
asset_name: msg.asset_name,
asset_symbol: msg.asset_symbol,
total_supply: Uint128::zero(),
mint_authority: admin.clone(),
};
CONFIG.save(deps.storage, &config)?;
if let Some(vk) = msg.transfer_vk {
VERIFYING_KEYS.save(deps.storage, "transfer", &vk)?;
}
Ok(Response::new()
.add_attribute("action", "instantiate")
.add_attribute("asset", &config.asset_symbol))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Mint { note_commitment, amount } => {
execute_mint(deps, info, note_commitment, amount)
}
ExecuteMsg::Transfer { proof, public_inputs } => {
execute_transfer(deps, env, info, proof, public_inputs)
}
ExecuteMsg::Burn { proof, public_inputs, recipient } => {
execute_burn(deps, env, info, proof, public_inputs, recipient)
}
}
}
fn execute_mint(
deps: DepsMut,
info: MessageInfo,
note_commitment: NoteCommitment,
amount: Uint128,
) -> Result<Response, ContractError> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.mint_authority {
return Err(ContractError::Unauthorized {});
}
if COMMITMENTS.has(deps.storage, ¬e_commitment) {
return Err(ContractError::DuplicateCommitment {});
}
COMMITMENTS.save(deps.storage, ¬e_commitment, &true)?;
CONFIG.update(deps.storage, |mut c| -> StdResult<_> {
c.total_supply = c.total_supply.checked_add(amount)
.map_err(|_| StdError::generic_err("Supply overflow"))?;
Ok(c)
})?;
Ok(Response::new()
.add_attribute("action", "mint")
.add_attribute("amount", amount.to_string()))
}
fn execute_transfer(
deps: DepsMut,
env: Env,
info: MessageInfo,
proof: Groth16Proof,
public_inputs: TransferPublicInputs,
) -> Result<Response, ContractError> {
if NULLIFIERS.has(deps.storage, &public_inputs.nullifier) {
return Err(ContractError::NullifierAlreadyUsed {});
}
if !COMMITMENTS.has(deps.storage, &NoteCommitment(public_inputs.old_note_commitment)) {
return Err(ContractError::CommitmentNotFound {});
}
let vk = VERIFYING_KEYS
.load(deps.storage, "transfer")
.map_err(|_| ContractError::KeyNotFound {
key_id: "transfer".to_string(),
})?;
let pub_inputs_bin = vec![
Binary::from(public_inputs.old_note_commitment.to_vec()),
Binary::from(public_inputs.new_note_commitment.to_vec()),
Binary::from(public_inputs.nullifier.to_vec()),
Binary::from(public_inputs.merkle_root.to_vec()),
];
let valid = verify_groth16(&proof, &pub_inputs_bin, &vk)?;
if !valid {
return Err(ContractError::VerificationFailed {});
}
NULLIFIERS.save(deps.storage, &public_inputs.nullifier, &true)?;
COMMITMENTS.save(
deps.storage,
&NoteCommitment(public_inputs.new_note_commitment),
&true,
)?;
Ok(Response::new()
.add_attribute("action", "transfer")
.add_attribute("nullifier", hex::encode(&public_inputs.nullifier.0)))
}
6.4 私有交易状态管理
// contracts/zkasset/src/state.rs
use cosmwasm_std::Addr;
use cw_storage_plus::{Item, Map};
use cw_storage_plus::Uint128;
use crate::types::*;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Schema)]
pub struct Config {
pub admin: Addr,
pub asset_name: String,
pub asset_symbol: String,
pub total_supply: Uint128,
pub mint_authority: Addr,
}
pub const CONFIG: Item<Config> = Item::new("config");
pub const COMMITMENTS: Map<&NoteCommitment, bool> = Map::new("cm");
pub const NULLIFIERS: Map<&Nullifier, bool> = Map::new("nf");
pub const MERKLE_ROOT: Item<NoteCommitment> = Item::new("root");
pub const VERIFYING_KEYS: Map<&str, VerifyingKey> = Map::new("vk");
6.5 Note 管理器(客户端 TypeScript)
// frontend/src/note_manager.ts
import * as crypto from "crypto";
interface NoteData {
amount: bigint;
owner: Uint8Array;
randomness: Uint8Array;
}
interface StoredNote {
note: NoteData;
commitment: string;
spent: boolean;
index: bigint;
}
export class NoteManager {
private notes: Map<string, StoredNote> = new Map();
private viewingKey: Uint8Array;
constructor(viewingKey: Uint8Array) {
this.viewingKey = viewingKey;
}
createNote(amount: bigint, ownerAddress: Uint8Array): NoteData {
const randomness = crypto.randomBytes(32);
return { amount, owner: ownerAddress, randomness };
}
computeCommitment(note: NoteData): string {
return crypto.createHash("sha256")
.update(Buffer.concat([
this.bigintToBuf(note.amount),
note.owner,
note.randomness,
]))
.digest("hex");
}
addNote(note: NoteData, index: bigint): void {
const commitment = this.computeCommitment(note);
this.notes.set(commitment, { note, commitment, spent: false, index });
}
getUnspentNotes(): StoredNote[] {
return Array.from(this.notes.values()).filter((n) => !n.spent);
}
getBalance(): bigint {
return this.getUnspentNotes()
.reduce((sum, n) => sum + n.note.amount, BigInt(0));
}
private bigintToBuf(n: bigint): Buffer {
let hex = n.toString(16);
if (hex.length % 2) hex = "0" + hex;
return Buffer.from(hex, "hex");
}
}
6.6 公开资产与私有资产的桥接
// contracts/zkasset/src/bridge.rs
/// 将公开代币转换为私有 Note
pub fn execute_deposit(
deps: DepsMut,
env: Env,
info: MessageInfo,
amount: Uint128,
note_commitment: NoteCommitment,
) -> Result<Response, ContractError> {
let coin = info
.funds
.iter()
.find(|c| c.denom == "umsg")
.ok_or(ContractError::InsufficientFunds {})?;
if coin.amount < amount {
return Err(ContractError::InsufficientFunds {});
}
if COMMITMENTS.has(deps.storage, ¬e_commitment) {
return Err(ContractError::DuplicateCommitment {});
}
COMMITMENTS.save(deps.storage, ¬e_commitment, &true)?;
Ok(Response::new()
.add_attribute("action", "deposit")
.add_attribute("amount", amount.to_string()))
}
/// 将私有 Note 兑换为公开代币
pub fn execute_withdraw(
deps: DepsMut,
env: Env,
info: MessageInfo,
proof: Groth16Proof,
public_inputs: WithdrawPublicInputs,
recipient: String,
amount: Uint128,
) -> Result<Response, ContractError> {
let vk = VERIFYING_KEYS
.load(deps.storage, "withdraw")
.map_err(|_| ContractError::KeyNotFound {
key_id: "withdraw".to_string(),
})?;
let pub_inputs_bin = vec![
Binary::from(public_inputs.note_commitment.to_vec()),
Binary::from(public_inputs.nullifier.to_vec()),
];
let valid = verify_groth16(&proof, &pub_inputs_bin, &vk)?;
if !valid {
return Err(ContractError::VerificationFailed {});
}
NULLIFIERS.save(deps.storage, &public_inputs.nullifier, &true)?;
let bank_msg = cosmwasm_std::BankMsg::Send {
to_address: recipient.clone(),
amount: vec![cosmwasm_std::Coin {
denom: "umsg".to_string(),
amount,
}],
};
Ok(Response::new()
.add_message(bank_msg)
.add_attribute("action", "withdraw")
.add_attribute("recipient", recipient)
.add_attribute("amount", amount.to_string()))
}
7. 前端集成
7.1 架构概览
+-------------------+ +-------------------+ +-------------------+
| 用户浏览器 | | WASM 证明器 | | MSG Chain |
| | | | | |
| 1. 输入私有数据 | | 2. 加载 .wasm | | 4. 提交交易 |
| 2. 调用生成证明 | ---> | 3. 生成 ZK 证明 | ---> | 5. 验证证明 |
| 3. 显示结果 | | | | 6. 返回结果 |
+-------------------+ +-------------------+ +-------------------+
7.2 WASM 证明集成
// frontend/src/zkProver.ts
/**
* 浏览器端 ZK 证明生成器。
* 使用 snarkjs 在浏览器中通过 WASM 生成 Groth16 证明。
*/
import { groth16 } from "snarkjs";
import { Buffer } from "buffer";
export interface ZKProof {
pi_a: string[];
pi_b: string[][];
pi_c: string[];
protocol: string;
curve: string;
}
export interface ProofResult {
proof: ZKProof;
publicSignals: string[];
}
/**
* 浏览器端 ZK 证明生成器
*/
export class ZKProver {
private wasmUrl: string;
private zkeyUrl: string;
private wasmCache: ArrayBuffer | null = null;
private zkeyCache: ArrayBuffer | null = null;
constructor(wasmUrl: string, zkeyUrl: string) {
this.wasmUrl = wasmUrl;
this.zkeyUrl = zkeyUrl;
}
/**
* 预加载 WASM 和 ZKEY 文件
*/
async load(): Promise<void> {
console.log("Loading WASM proving assets...");
const [wasmResponse, zkeyResponse] = await Promise.all([
fetch(this.wasmUrl),
fetch(this.zkeyUrl),
]);
this.wasmCache = await wasmResponse.arrayBuffer();
this.zkeyCache = await zkeyResponse.arrayBuffer();
console.log(`WASM: ${(this.wasmCache.byteLength / 1024).toFixed(1)} KB`);
console.log(`ZKEY: ${(this.zkeyCache.byteLength / 1024).toFixed(1)} KB`);
}
/**
* 生成 ZK 证明
*/
async generateProof(inputs: Record<string, any>): Promise<ProofResult> {
if (!this.wasmCache || !this.zkeyCache) {
throw new Error("Prover not loaded. Call load() first.");
}
console.log("Generating ZK proof...");
const startTime = performance.now();
const { proof, publicSignals } = await groth16.fullProve(
inputs,
new Uint8Array(this.wasmCache),
new Uint8Array(this.zkeyCache),
);
const endTime = performance.now();
console.log(`Proof generated in ${(endTime - startTime).toFixed(0)}ms`);
console.log(`Public signals: ${publicSignals.length}`);
return {
proof: proof as ZKProof,
publicSignals,
};
}
/**
* 在本地验证证明(可选,通常由链上验证)
*/
async verifyLocally(
proof: ZKProof,
publicSignals: string[],
verificationKey: any,
): Promise<boolean> {
return await groth16.verify(verificationKey, publicSignals, proof);
}
}
7.3 React 组件
// frontend/src/components/ZKProofGenerator.tsx
import React, { useState, useCallback, useEffect } from "react";
import { ZKProver, ProofResult } from "../zkProver";
interface ZKProofGeneratorProps {
wasmUrl: string;
zkeyUrl: string;
verificationKeyUrl: string;
onProofGenerated: (result: ProofResult) => void;
onError: (error: Error) => void;
}
/**
* ZK 证明生成器 React 组件
*/
export const ZKProofGenerator: React.FC<ZKProofGeneratorProps> = ({
wasmUrl,
zkeyUrl,
verificationKeyUrl,
onProofGenerated,
onError,
}) => {
const [prover, setProver] = useState<ZKProver | null>(null);
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState<string>("");
const [proofResult, setProofResult] = useState<ProofResult | null>(null);
const [age, setAge] = useState<string>("");
const [minAge, setMinAge] = useState<number>(18);
useEffect(() => {
const initProver = async () => {
try {
setProgress("Loading proving assets...");
const p = new ZKProver(wasmUrl, zkeyUrl);
await p.load();
setProver(p);
setProgress("Ready");
} catch (err) {
onError(err as Error);
setProgress("Failed to load");
}
};
initProver();
}, [wasmUrl, zkeyUrl]);
const handleGenerateProof = useCallback(async () => {
if (!prover || !age) return;
setLoading(true);
setProgress("Generating ZK proof...");
try {
const inputs = {
age: age,
min_age: minAge.toString(),
};
const result = await prover.generateProof(inputs);
setProofResult(result);
onProofGenerated(result);
setProgress("Proof generated successfully");
} catch (err) {
onError(err as Error);
setProgress("Proof generation failed");
} finally {
setLoading(false);
}
}, [prover, age, minAge, onProofGenerated, onError]);
const handleSubmitToChain = useCallback(async () => {
if (!proofResult) return;
// 交由父组件处理链上提交
}, [proofResult]);
return (
<div className="zk-proof-generator">
<h2>Zero-Knowledge Proof Generator</h2>
<div className="status">
Status: <span className={progress === "Ready" ? "ready" : "loading"}>
{progress}
</span>
</div>
<div className="input-group">
<label>
Age (private input):
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
disabled={loading}
placeholder="Enter your age"
/>
</label>
</div>
<div className="input-group">
<label>
Minimum age (public input):
<input
type="number"
value={minAge}
onChange={(e) => setMinAge(Number(e.target.value))}
disabled={loading}
/>
</label>
</div>
<button
onClick={handleGenerateProof}
disabled={loading || !prover || !age}
className="generate-btn"
>
{loading ? "Generating..." : "Generate ZK Proof"}
</button>
{proofResult && (
<div className="proof-result">
<h3>Proof Generated</h3>
<div className="proof-details">
<p>Public signals: {proofResult.publicSignals.join(", ")}</p>
<p>Proof size: {JSON.stringify(proofResult.proof).length} bytes</p>
</div>
<button onClick={handleSubmitToChain} className="submit-btn">
Submit to MSG Chain
</button>
</div>
)}
{loading && (
<div className="loading-spinner">
<div className="spinner"></div>
<p>Generating proof... (may take 10-30 seconds)</p>
</div>
)}
</div>
);
};
7.4 提交交易组件
// frontend/src/components/ProofSubmitter.tsx
import React, { useState } from "react";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { GasPrice } from "@cosmjs/stargate";
interface ProofSubmitterProps {
client: SigningCosmWasmClient;
senderAddress: string;
contractAddress: string;
}
export const ProofSubmitter: React.FC<ProofSubmitterProps> = ({
client,
senderAddress,
contractAddress,
}) => {
const [txHash, setTxHash] = useState<string>("");
const [submitting, setSubmitting] = useState(false);
const submitProof = async (proof: any, publicSignals: string[]) => {
setSubmitting(true);
try {
const result = await client.execute(
senderAddress,
contractAddress,
{
verify_kyc: {
proof: {
pi_a: proof.pi_a,
pi_b: proof.pi_b,
pi_c: proof.pi_c,
},
public_inputs: publicSignals,
key_id: "kyc_v1",
kyc_level: 1,
},
},
"auto",
"zkKYC verification",
);
setTxHash(result.transactionHash);
} catch (err) {
console.error("Submission failed:", err);
} finally {
setSubmitting(false);
}
};
return (
<div className="proof-submitter">
{txHash && (
<div className="tx-result">
<p>Transaction submitted: {txHash}</p>
<a
href={`https://explorer.msgchain.org/tx/${txHash}`}
target="_blank"
rel="noopener noreferrer"
>
View on Explorer
</a>
</div>
)}
</div>
);
};
7.5 验证结果显示
// frontend/src/components/VerificationResult.tsx
import React from "react";
interface VerificationResultProps {
verified: boolean;
kycLevel: number;
expiresAt: number;
onRetry: () => void;
}
export const VerificationResult: React.FC<VerificationResultProps> = ({
verified,
kycLevel,
expiresAt,
onRetry,
}) => {
const isExpired = Date.now() > expiresAt * 1000;
return (
<div className={`verification-result ${verified ? "success" : "failure"}`}>
{verified ? (
<>
<div className="icon">✔</div>
<h3>KYC Verified</h3>
<p>Level: {kycLevel}</p>
<p>
Expires: {new Date(expiresAt * 1000).toLocaleDateString()}
</p>
{isExpired && (
<p className="warning">KYC has expired. Please renew.</p>
)}
</>
) : (
<>
<div className="icon">✘</div>
<h3>Verification Failed</h3>
<p>Your proof could not be verified.</p>
<button onClick={onRetry}>Retry</button>
</>
)}
</div>
);
};
7.6 完整页面示例
// frontend/src/pages/KYCVerificationPage.tsx
import React, { useState, useCallback } from "react";
import { ZKProofGenerator } from "../components/ZKProofGenerator";
import { ProofSubmitter } from "../components/ProofSubmitter";
import { VerificationResult } from "../components/VerificationResult";
import { ProofResult } from "../zkProver";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
export const KYCVerificationPage: React.FC = () => {
const [proofResult, setProofResult] = useState<ProofResult | null>(null);
const [verificationStatus, setVerificationStatus] = useState<{
verified: boolean;
kycLevel: number;
expiresAt: number;
} | null>(null);
const getClient = useCallback(async () => {
if (!window.keplr) throw new Error("Please install Keplr wallet");
await window.keplr.enable("msg-chain-1");
const offlineSigner = window.keplr.getOfflineSigner("msg-chain-1");
return await SigningCosmWasmClient.connectWithSigner(
"https://rpc.msgchain.org",
offlineSigner,
{ gasPrice: GasPrice.fromString("1000000000umsg") },
);
}, []);
const handleProofGenerated = useCallback(async (result: ProofResult) => {
setProofResult(result);
// 自动提交到链上
try {
const client = await getClient();
const [account] = await client.getAccounts();
const txResult = await client.execute(
account.address,
process.env.NEXT_PUBLIC_KYC_CONTRACT!,
{
verify_kyc: {
proof: result.proof,
public_inputs: result.publicSignals,
key_id: "kyc_v1",
kyc_level: 1,
},
},
"auto",
);
// 查询验证结果
const status = await client.queryContractSmart(
process.env.NEXT_PUBLIC_KYC_CONTRACT!,
{ get_kyc_status: { address: account.address } },
);
setVerificationStatus({
verified: status.status.kyc_level > 0,
kycLevel: status.status.kyc_level,
expiresAt: status.status.expires_at,
});
} catch (err) {
console.error("Verification failed:", err);
setVerificationStatus({
verified: false,
kycLevel: 0,
expiresAt: 0,
});
}
}, [getClient]);
const handleError = useCallback((error: Error) => {
console.error("ZK Error:", error);
alert("Error: " + error.message);
}, []);
return (
<div className="kyc-page">
<h1>zkKYC Verification</h1>
<p>Prove your identity without revealing personal data</p>
<ZKProofGenerator
wasmUrl="/circuits/kyc_verify.wasm"
zkeyUrl="/circuits/circuit_final.zkey"
verificationKeyUrl="/circuits/verification_key.json"
onProofGenerated={handleProofGenerated}
onError={handleError}
/>
{verificationStatus && (
<VerificationResult
verified={verificationStatus.verified}
kycLevel={verificationStatus.kycLevel}
expiresAt={verificationStatus.expiresAt}
onRetry={() => {
setProofResult(null);
setVerificationStatus(null);
}}
/>
)}
</div>
);
};
8. 完整示例
8.1 zkKYC 完整流程
本节将展示从用户生成证明到链上验证的完整端到端流程。
8.2 流程概览
1. 用户 -> 前端: 输入年龄(私有数据)
2. 前端 -> WASM: 调用 snarkjs 生成证明
3. 前端 -> MSG Chain: 提交证明到验证合约
4. 合约 -> 验证: 执行 Groth16 配对检查
5. 合约 -> 状态: 更新 KYC 验证状态
6. 前端 -> 查询: 获取 KYC 状态并展示
7. 用户 -> dApp: 通过 KYC 检查,访问受限内容
8.3 完整集成代码
// examples/complete-zkkyc-flow.ts
import { groth16 } from "snarkjs";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { GasPrice } from "@cosmjs/stargate";
import * as fs from "fs/promises";
/**
* zkKYC 完整流程集成类
*/
class ZKKYCIntegration {
private client!: SigningCosmWasmClient;
private senderAddress!: string;
private contractAddress: string;
constructor(
contractAddress: string,
private rpcUrl: string = "https://rpc.msgchain.org",
private mnemonic?: string,
) {
this.contractAddress = contractAddress;
}
/**
* 初始化:连接钱包和客户端
*/
async initialize(): Promise<void> {
const mnemonic = this.mnemonic || process.env.MSG_MNEMONIC;
if (!mnemonic) throw new Error("Mnemonic not provided");
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: "msg",
});
const [account] = await wallet.getAccounts();
this.senderAddress = account.address;
this.client = await SigningCosmWasmClient.connectWithSigner(
this.rpcUrl,
wallet,
{ gasPrice: GasPrice.fromString("1000000000umsg") },
);
console.log(`Connected as: ${this.senderAddress}`);
}
/**
* 步骤 1: 加载电路并生成证明
*/
async generateProof(
age: number,
minAge: number,
wasmPath: string,
zkeyPath: string,
): Promise<{ proof: any; publicSignals: string[] }> {
console.log("Step 1: Generating ZK proof...");
console.log(` Age: ${age}, Min Age: ${minAge}`);
const inputs = {
age: age.toString(),
min_age: minAge.toString(),
};
const startTime = Date.now();
const { proof, publicSignals } = await groth16.fullProve(
inputs,
wasmPath,
zkeyPath,
);
const duration = Date.now() - startTime;
console.log(` Proof generated in ${duration}ms`);
console.log(` Public signals: ${publicSignals.join(", ")}`);
return { proof, publicSignals };
}
/**
* 步骤 2: 提交证明到 MSG Chain
*/
async submitProof(
proof: any,
publicSignals: string[],
kycLevel: number = 1,
): Promise<string> {
console.log("Step 2: Submitting proof to MSG Chain...");
const executeMsg = {
verify_kyc: {
proof: {
pi_a: proof.pi_a,
pi_b: proof.pi_b,
pi_c: proof.pi_c,
},
public_inputs: publicSignals,
key_id: "kyc_v1",
kyc_level: kycLevel,
},
};
const result = await this.client.execute(
this.senderAddress,
this.contractAddress,
executeMsg,
"auto",
"zkKYC verification",
);
console.log(` Transaction: ${result.transactionHash}`);
return result.transactionHash;
}
/**
* 步骤 3: 查询 KYC 状态
*/
async queryKYCStatus(): Promise<{
verified: boolean;
level: number;
expiresAt: number;
}> {
console.log("Step 3: Querying KYC status...");
const status = await this.client.queryContractSmart(
this.contractAddress,
{ get_kyc_status: { address: this.senderAddress } },
);
const verified = status.status.kyc_level > 0;
console.log(` Verified: ${verified}`);
console.log(` Level: ${status.status.kyc_level}`);
console.log(` Expires: ${new Date(status.status.expires_at * 1000).toISOString()}`);
return {
verified,
level: status.status.kyc_level,
expiresAt: status.status.expires_at,
};
}
/**
* 步骤 4: 验证 dApp 访问权限
*/
async checkAccess(requiredLevel: number): Promise<boolean> {
console.log("Step 4: Checking dApp access...");
const access = await this.client.queryContractSmart(
this.contractAddress,
{
check_access: {
address: this.senderAddress,
required_level: requiredLevel,
},
},
);
console.log(` Access granted: ${access.granted}`);
return access.granted;
}
/**
* 运行完整的 zkKYC 流程
*/
async runCompleteFlow(
age: number,
minAge: number,
kycLevel: number,
wasmPath: string,
zkeyPath: string,
): Promise<boolean> {
console.log("\n=== zkKYC Complete Flow ===\n");
// 初始化
await this.initialize();
// 生成证明
const { proof, publicSignals } = await this.generateProof(
age, minAge, wasmPath, zkeyPath,
);
// 提交证明
await this.submitProof(proof, publicSignals, kycLevel);
// 查询状态
const status = await this.queryKYCStatus();
// 检查访问
const access = await this.checkAccess(kycLevel);
console.log("\n=== Flow Complete ===");
console.log(`Result: ${access ? "SUCCESS" : "FAILED"}`);
return access;
}
}
// 运行示例
async function main() {
const integration = new ZKKYCIntegration(
"msg1contractaddress...",
"https://rpc.msgchain.org",
);
const success = await integration.runCompleteFlow(
25, // age (private)
18, // min_age (public)
1, // kyc_level
"./circuits/age_verify.wasm",
"./circuits/circuit_final.zkey",
);
process.exit(success ? 0 : 1);
}
main().catch(console.error);
8.4 ZK dApp 访问控制
// examples/zk-access-control.ts
/**
* 基于 zkKYC 的 dApp 访问控制中间件。
*/
interface AccessRequirement {
minKycLevel: number;
minAge?: number;
allowedCountries?: string[];
expirationDays?: number;
}
class ZKAccessController {
private contractAddress: string;
private client: SigningCosmWasmClient;
constructor(contractAddress: string, client: SigningCosmWasmClient) {
this.contractAddress = contractAddress;
this.client = client;
}
/**
* 检查用户是否可以访问指定功能
*/
async checkAccess(
userAddress: string,
requirement: AccessRequirement,
): Promise<{
granted: boolean;
reason?: string;
}> {
try {
const status = await this.client.queryContractSmart(
this.contractAddress,
{ get_kyc_status: { address: userAddress } },
);
const kycLevel = status.status.kyc_level;
if (kycLevel === 0) {
return { granted: false, reason: "KYC not completed" };
}
if (kycLevel < requirement.minKycLevel) {
return {
granted: false,
reason: `Insufficient KYC level (need ${requirement.minKycLevel}, have ${kycLevel})`,
};
}
if (status.status.expires_at < Math.floor(Date.now() / 1000)) {
return { granted: false, reason: "KYC expired" };
}
return { granted: true };
} catch (error) {
return { granted: false, reason: "Error checking access" };
}
}
/**
* 创建访问控制高阶函数
*/
createAccessGuard(requirement: AccessRequirement) {
return async (userAddress: string) => {
const result = await this.checkAccess(userAddress, requirement);
if (!result.granted) {
throw new Error(`Access denied: ${result.reason}`);
}
return true;
};
}
}
// 使用示例
// const guard = new ZKAccessController(contractAddr, client);
// const requireKYC = guard.createAccessGuard({ minKycLevel: 2 });
// await requireKYC("msg1qyp...");
// 在 dApp 路由中使用
// router.get("/sensitive-data", async (req, res) => {
// const guard = createAccessGuard({ minKycLevel: 1 });
// try {
// await guard(req.user.address);
// res.json(sensitiveData);
// } catch (e) {
// res.status(403).json({ error: e.message });
// }
// });
8.5 附录:常用命令速查
# Circom 编译
circom circuit.circom --r1cs --wasm --sym -o build/
# snarkjs 可信设置
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau
# 电路密钥生成
snarkjs groth16 setup circuit.r1cs pot12_final.ptau circuit.zkey
snarkjs zkey contribute circuit.zkey circuit_final.zkey
snarkjs zkey export verificationkey circuit_final.zkey vk.json
# 证明生成
snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json
# CosmWasm 合约部署
cargo wasm
cosmwasm-check target/wasm32-unknown-unknown/release/contract.wasm
wasm-opt -O3 contract.wasm -o contract_opt.wasm
msgd tx wasm store contract_opt.wasm --from admin ...
# 查询
msgd query wasm contract-state smart CONTRACT_ADDR '{"get_config":{}}'
8.6 常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
| 证明生成失败 | 输入格式错误 | 确保所有输入为十进制字符串 |
| 链上验证返回 false | 曲线不匹配 | 检查电路使用 bn128,验证器使用 BN254 |
| 交易 Gas 不足 | 配对计算消耗高 | 设置 gas 为 auto,adjustment >= 1.5 |
| WASM 加载失败 | MIME 类型错误 | 配置服务器返回 application/wasm |
| 公开输入数量不匹配 | 电路更新后未重新部署 | 重新生成验证密钥并部署合约 |
| 证明过大 | 电路约束太多 | 优化电路,使用更高效的哈希函数 |
| KYC 过期 | 超过 1 年有效期 | 重新验证 KYC |
文档版本: 1.0.0
维护者: MSG Chain 开发团队
