dApp Docs/零知识证明(ZKP)集成指南
Development reference. Not independently verified for production.

零知识证明(ZKP)集成指南 —— MSG Chain

数据来源:MSG Chain 代码库核实

主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。


目录

  1. 概述
  2. ZKP 基础
  3. 电路构建
  4. 链上验证合约
  5. zkKYC 集成
  6. 私有交易
  7. 前端集成
  8. 完整示例

1. 概述

1.1 什么是零知识证明

零知识证明(Zero-Knowledge Proof, ZKP)是一种密码学协议,允许一方(证明者 Prover)向另一方(验证者 Verifier)证明某个陈述为真,而无需披露该陈述之外的任何信息。

核心三性:

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 层面提供了对以下椭圆曲线库的支持:

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 接受:

电路的输出是公开的约束满足性(Constraint Satisfaction)。

2.1.3 QAP(Quadratic Arithmetic Program)

Groth16 等 SNARK 系统将算术电路转化为 QAP 形式:

  1. 电路 -> R1CS(Rank-1 Constraint System)
  2. R1CS -> QAP(通过拉格朗日插值)
  3. 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 安全注意事项

  1. 可信设置风险:Groth16 的可信设置需要多方参与,任何一方作恶可伪造证明。建议使用 Ceremony 多方计算。
  2. 归零攻击:验证时必须确保公开输入不包含退化值(如全零)。
  3. 重放攻击:在公开输入中加入 nonce 或时间戳,防止证明重复使用。
  4. 曲线兼容性:确保电路编译使用的曲线与链上验证合约的曲线一致。
  5. 算术化陷阱:有限域溢出、负数处理、整数除法的模拟等需谨慎。

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();

关键语法规则:

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 电路优化技巧

  1. 减少约束数:使用 MiMC 而非 SHA256(减少 100x 约束)
  2. 并行化:大电路可拆分为多个子电路并行生成证明
  3. 预计算:将固定计算在设置阶段完成
  4. 查找表:使用 PLONK 的查找参数减少约束
  5. 递归证明:将多个小证明聚合为一个
# 基准测试:不同哈希函数的约束数
# 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 优化建议

  1. 预计算:将验证密钥预先部署到合约存储,避免每次反序列化
  2. 批量验证:使用批量配对检查,多个证明共用一次配对运算
  3. 懒验证:仅在必要时执行完整配对(先检查轻量级约束)
  4. 缓存:缓存已验证的公开输入结果
  5. 聚合证明:使用递归证明将多个证明聚合为一个

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 的问题:

zkKYC 的优势:

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 集成检查清单


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, &note_commitment) {
        return Err(ContractError::DuplicateCommitment {});
    }
    COMMITMENTS.save(deps.storage, &note_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, &note_commitment) {
        return Err(ContractError::DuplicateCommitment {});
    }
    COMMITMENTS.save(deps.storage, &note_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">&#10004;</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">&#10008;</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 开发团队