关于 zk-proofs
面向在 Stellar 智能合约中实现零知识验证的开发者技能。内容按协议和 SDK 要求梳理密码学原语与证明系统,并提供实现路径及安全注意事项。
主要功能
- 01Stellar ZK 能力与版本检查
- 02Groth16 验证器实现指南
- 03Circom、Noir 和 RISC Zero 工作流
- 04BN254、BLS12-381 与 Poseidon 支持
- 05验证器架构与安全注意事项
适用场景
- 01选择证明原语前核查网络支持
- 02将 Circom 证明接入 Stellar 合约
- 03评估 Noir UltraHonk 验证限制
- 04设计隐私池与防重放机制
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ stellar/ stellar-dev-skill --skill zk-proofs安装后在 Claude Code 中输入/zk-proofs确认已加载。
详细介绍
零知识证明与隐私
Stellar 上的 ZK 验证。功能取决于协议和 SDK 版本——依赖某项原语前,务必先确认 CAP 状态、网络版本以及 soroban-sdk 对宿主函数的支持情况。
最后一次根据 Protocol 29 核验(主网,2026 年 10 月)。CAP-0074/0075 已在 Protocol 25「X-Ray」中发布,CAP-0080(BN254 MSM、Fr 算术、曲线点检查)已在 Protocol 26 中发布——如果某篇文档说 BN254 或 Poseidon 仍是「提议中」,那它发布于此之前。
何时使用此技能
- 将 Groth16(或其他 SNARK)验证器实现为 Stellar 智能合约
- 将 Circom、Noir 或 RISC Zero 的输出接入链上验证
- 构建隐私池、机密代币或基于 Merkle 树的承诺
- 使用 BN254 / Poseidon 宿主函数(Protocol 25+)
相关技能
- 合约模式与部署 →
../smart-contracts/development.md - 验证器安全审查 →
../smart-contracts/security.md - 此处引用的 CAP →
../standards/SKILL.md
可用功能——构建前请先核验
| 原语 | CAP | 状态 |
|---|---|---|
| BLS12-381 运算(G1/G2 加法、乘法、MSM、配对检查、哈希到曲线、Fr 算术) | CAP-0059 | 可用(Protocol 22+) |
BN254 基础运算(G1 加法/乘法、配对检查——crypto::bn254) | CAP-0074 | 可用(Protocol 25+) |
BN254 G1 MSM、Fr 算术(fr_add/sub/mul/pow/inv)、曲线点检查 | CAP-0080 | 可用(Protocol 26+) |
| Poseidon/Poseidon2 置换原语(不是完整的哈希函数) | CAP-0075 | 可用(Protocol 25+) |
实现前,务必确认:
- 前言中的 CAP 状态(
Accepted/Implemented,而非草案) - 目标网络的协议版本(软件版本)
- 所需宿主函数是否受
soroban-sdk发行版支持
自 Protocol 25 起,两条曲线都具备基础宿主函数——曲线不再是决定一切的因素。仍需关注的是证明系统:Groth16 在任一曲线上都能通过小型合约完成验证,而 UltraHonk 需要专用的合约内验证器(已有实现——见 Noir 操作指南;但它依赖 CAP-0080 宿主函数,且当前发行版基于 soroban-sdk 28 构建,因此需要 Protocol 28+)。
| 工具链 | 证明系统 | 曲线 | 在 Stellar 上链 |
|---|---|---|---|
Circom + snarkjs(-p bls12381) | Groth16 | BLS12-381 | ✅ 通过 CAP-0059(Protocol 22+) |
Circom + snarkjs(默认 bn128) | Groth16 | BN254 | ✅ 通过 CAP-0074(Protocol 25+) |
| Noir + Barretenberg | UltraHonk | BN254 | ✅ 通过 CAP-0074 + CAP-0080(当前验证器发行版需要 Protocol 28+)——需要 rs-soroban-ultrahonk 验证器合约;仅限非 ZK 证明(见 Noir 操作指南) |
| RISC Zero(STARK → Groth16 封装) | Groth16 | BN254 | ✅ 通过 CAP-0074(Protocol 25+) |
链上验证器(基于 BLS12-381 的 Groth16)
官方 groth16_verifier 示例 是规范实现——完整合约如下:
#![no_std]
use soroban_sdk::{
contract, contracterror, contractimpl, contracttype,
crypto::bls12_381::{Fr, G1Affine, G2Affine},
vec, Env, Vec,
};
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Groth16Error {
MalformedVerifyingKey = 0,
}
#[derive(Clone)]
#[contracttype]
pub struct VerificationKey {
pub alpha: G1Affine,
pub beta: G2Affine,
pub gamma: G2Affine,
pub delta: G2Affine,
pub ic: Vec<G1Affine>,
}
#[derive(Clone)]
#[contracttype]
pub struct Proof {
pub a: G1Affine,
pub b: G2Affine,
pub c: G1Affine,
}
#[contract]
pub struct Groth16Verifier;
#[contractimpl]
impl Groth16Verifier {
pub fn verify_proof(
env: Env,
vk: VerificationKey,
proof: Proof,
pub_signals: Vec<Fr>,
) -> Result<bool, Groth16Error> {
let bls = env.crypto().bls12_381();
// vk_x = ic[0] + sum(pub_signals[i] * ic[i+1])
if pub_signals.len() + 1 != vk.ic.len() {
return Err(Groth16Error::MalformedVerifyingKey);
}
let mut vk_x = vk.ic.get(0).unwrap();
for (s, v) in pub_signals.iter().zip(vk.ic.iter().skip(1)) {
let prod = bls.g1_mul(&v, &s);
vk_x = bls.g1_add(&vk_x, &prod);
}
// e(-A, B) * e(alpha, beta) * e(vk_x, gamma) * e(C, delta) == 1
let neg_a = -proof.a;
let vp1 = vec![&env, neg_a, vk.alpha, vk_x, proof.c];
let vp2 = vec![&env, proof.b, vk.beta, vk.gamma, vk.delta];
Ok(bls.pairing_check(vp1, vp2))
}
}
点编码采用未压缩的大端格式:G1Affine 封装 96 字节,G2Affine 封装 192 字节,Fr 封装 32 字节。示例的测试套件展示了从 arkworks 类型(ark-bls12-381 + ark-serialize)进行转换的确切方法——使用你的证明工具链 JSON 输出构建测试数据时,可直接复用。
在生产环境中,应使用应用逻辑封装此验证器:在部署时(构造函数中)固定 VerificationKey,而不是将其作为调用参数传入;并将证明绑定到上下文(参见注意事项)。
演练:Circom → 链上验证
Circom 可针对任一曲线进行编译。本演练使用 BLS12-381,以匹配上文的规范验证器合约;从 Protocol 25 开始,默认的 bn128(BN254)输出也可通过 crypto::bn254 进行验证——只需确保电路曲线与验证器合约一致。
# 1. Circuit
cat > multiplier.circom <<'EOF'
pragma circom 2.1.6;
template Multiplier() {
signal input a;
signal input b;
signal output c;
c <== a * b;
}
component main = Multiplier();
EOF
# 2. Compile for BLS12-381 to match the verifier contract above
# (the default bn128 also verifies on-chain since Protocol 25 — via a BN254 verifier)
circom multiplier.circom --r1cs --wasm -p bls12381
# 3. Trusted setup (powers of tau on bls12-381, then circuit-specific phase 2)
snarkjs powersoftau new bls12-381 12 pot12_0000.ptau
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="contrib" -e="random"
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau
snarkjs groth16 setup multiplier.r1cs pot12_final.ptau multiplier.zkey
snarkjs zkey export verificationkey multiplier.zkey verification_key.json
# 4. Witness + proof
echo '{"a": 3, "b": 11}' > input.json
node multiplier_js/generate_witness.js multiplier_js/multiplier.wasm input.json witness.wtns
snarkjs groth16 prove multiplier.zkey witness.wtns proof.json public.json
# 5. Sanity-check off-chain before going on-chain
snarkjs groth16 verify verification_key.json public.json proof.json
然后,将 proof.json / verification_key.json(坐标为十进制字符串)转换为合约类型——将每个点序列化为未压缩的大端字节格式,按 96/192 字节布局存储,例如可像示例测试那样使用 arkworks——并调用 verify_proof。公开信号(public.json)会成为 Vec<Fr> 参数;合约还必须验证这些信号的含义(参见注意事项)。
对于实际应用,每个证明的流程如下:客户端在本地生成证明(使用 WASM prover 或原生程序),在合约调用中提交 (proof, public_signals),合约验证证明、应用策略并更新状态。
演练:Noir(UltraHonk,可在链上验证,适用于 Protocol 28+)
Noir 的标准后端(Barretenberg)会生成基于 BN254 的 UltraHonk 证明。自 Protocol 25(CAP-0074)起,该曲线的基本运算已成为原生功能;UltraHonk 验证则通过 Nethermind 的 rs-soroban-ultrahonk 验证器合约在链上运行,其宿主调用包括 env.crypto().bn254() 的 g1_msm、Fr 算术运算(fr_add/sub/mul/pow/inv)和 pairing_check——MSM 和 Fr 函数来自 CAP-0080(Protocol 26)。当前版本 ultrahonk_soroban_verifier 0.1.0 依赖 soroban-sdk ^28,其 README 指出,使用该版本构建的合约需要 Protocol 28+。OpenZeppelin 的 Confidential Tokens 开发者预览版(testnet)正是基于此技术栈运行。OpenZeppelin 审计了较早的提交版本(报告日期为 2026-09-18,没有 Critical/High/Medium 级别的问题,所有问题均已解决);该版本后续升级了 SDK,因此经过审计的修订版并非当前发布版本。部署到主网前,请审查这一差异和审计报告。
仅非 ZK。 rs-soroban-ultrahonk 验证器实现的是 Barretenberg 的非零知识变体(
UltraKeccakFlavor)。其 README 指出,证明不提供隐藏见证的保证:私有电路输入并不会仅仅因为不是公开输入就成为秘密。在下面的示例中,证明不会保护age。请使用此验证器来证明某个陈述成立,而不是隐藏数据。若要在当前链上隐藏输入,请使用 Circom 等 Groth16 技术栈。ZK 验证正在 PR #38 中开发。
验证器 README 将 Nargo 1.0.0-beta.9 + Barretenberg 0.87.0 与 Keccak oracle 列为兼容的证明工具(noirup -v 1.0.0-beta.9、bbup -v 0.87.0)。bb 默认使用 Poseidon2 oracle,而该验证器会拒绝此类证明,因此每条 bb 命令都要传入 --oracle_hash keccak。不要传入 --zk:它会选择验证器不支持的 ZK 变体。请使用 0.87.0:从 bb 1.0.0 起,默认变体为 ZK,且 --disable_zk 会取代 --zk,因此这些命令生成的证明会被验证器拒绝。
# Local proving workflow (Nargo 1.0.0-beta.9, Barretenberg 0.87.0)
nargo new age_check && cd age_check
cat > src/main.nr <<'EOF'
fn main(age: u64, threshold: pub u64) {
assert(age >= threshold);
}
EOF
cat > Prover.toml <<'EOF'
age = "30"
threshold = "18"
EOF
nargo execute witness # compiles target/age_check.json, writes target/witness.gz
# -o is an output directory: prove writes target/proof + target/public_inputs, write_vk writes target/vk
bb prove --scheme ultra_honk --oracle_hash keccak -b target/age_check.json -w target/witness.gz -o target
bb write_vk --scheme ultra_honk --oracle_hash keccak -b target/age_check.json -o target
bb verify --scheme ultra_honk --oracle_hash keccak -k target/vk -p target/proof -i target/public_inputs # off-chain check
将 Noir 陈述放到链上的三种方式,按优先顺序排列:
- 链上 UltraHonk 验证(当前验证器版本需要 Protocol 28+;CAP-0074 + CAP-0080):部署或调用基于 rs-soroban-ultrahonk 的验证器,并直接提交
(proof, public inputs)——无需信任任何第三方,也无需 oracle。 - 为链上部分切换证明技术栈:将链上关键陈述表示为 Circom/Groth16 电路(见上方演练),并在链下部分保留 Noir——代价是需要维护第二套工具链,换来体积更小、经过实战检验的验证器。
- 证明 oracle(适用于较旧的协议目标或对验证器审计有顾虑的情况):某个服务在链下运行
bb verify并提交签名证明;合约对证明者调用require_auth()并执行策略。必须明确说明并记录信任假设(证明者)——这不是无需信任的 ZK,而是一个可验证计算 oracle。
演练:RISC Zero(Groth16 封装,自 Protocol 25 起可在链上验证)
RISC Zero 可证明任意 Rust 执行(zkVM),并能将其 STARK 收据封装为基于 BN254 的 Groth16 证明(“stark-to-snark”)——证明足够小,可在支持 BN254 的链上进行验证。
// Guest (runs inside the zkVM): the computation being proven
use risc0_zkvm::guest::env;
fn main() {
let input: u64 = env::read();
let result = expensive_check(input);
env::commit(&result); // becomes part of the public journal
}
// Host: produce and verify a receipt locally
let receipt = prover.prove(env, ELF)?.receipt;
receipt.verify(IMAGE_ID)?; // off-chain verification
自 Protocol 25(CAP-0074)起,经过 Groth16 封装的收据可原生在链上验证:使用一个仿照上文 BLS12-381 验证器的 BN254 验证器合约。其公开输入不是直接使用 IMAGE_ID 和日志摘要——RISC Zero 的 Groth16 验证器需要五个域元素,用于编码控制根(拆分为两半)、收据声明摘要(也拆分为两部分)以及 BN254 控制 ID。图像 ID 和日志是链下(或合约代码中)重建该收据声明的输入,然后再进行配对检查;若绑定不正确,就会拒绝有效的密封数据,或证明错误的陈述。封装工作流和声明摘要的构造方式,请参阅 RISC Zero 文档。对于仅包含 STARK 收据(未进行 Groth16 封装)或目标网络早于 Protocol 25 的情况,证明模式仍可作为后备方案。
架构模式
- 验证网关:将密码学检查隔离在专用验证器合约/模块中——规范化输入、执行验证,并发出明确的成功/失败事件。这样审计范围更小,升级也更简洁。
- 策略与证明分离:
Verifier(密码学有效性)→Policy(业务/合规规则)→Application(状态转换)。各部分可独立测试和升级。 - 能力门控:仅在确认所需原语可用的情况下启用 ZK 工作流;保留确定性后备方案,并记录支持的网络/协议矩阵。
对于 Merkle 树承诺(隐私池、允许列表):CAP-0075(Protocol 25+)提供 Poseidon/Poseidon2 置换,而非现成的哈希函数。若要在链上重新计算 Poseidon Merkle 根和承诺,必须在 guest 代码中构建 sponge/压缩结构,并传入电路使用的相同域、状态大小 t、S 盒次数 d、轮数、MDS/对角矩阵和轮常数——此外还要匹配填充和域分离设置。参数不匹配会悄然产生与电路不同的根,而临时构造也可能不安全。对于早于 Protocol 25 的目标网络,应将树设计为合约只需比较根,并在 SNARK 内验证成员证明。
常见问题
- 验证了证明,却没有验证陈述。 有效证明只能表明某个见证满足电路要求。合约必须验证公开输入的语义:此证明对应谁、使用哪个 Merkle 根、执行什么操作、涉及多少金额。
- 未绑定防重放信息。 有效证明可能被重放。将 nonce/会话/操作绑定到公开输入,并在链上持久化防重放标记(nullifier 集合)。
- 曲线与验证器不匹配。 证明所用的曲线必须与验证器合约的宿主函数匹配:规范示例验证器使用 BLS12-381,因此默认使用
bn128的 Circom 证明无法通过它(对 BN254 验证器来说则相反)。自 Protocol 25 起,两种曲线都可用——只要确保电路和验证器使用同一种曲线即可。 - 误以为证明会隐藏其私有输入。 只有零知识证明才会隐藏。链上的 UltraHonk 验证器仅接受非零知识证明,因此在其支持零知识版本之前,不要将其用于涉及隐私的电路。
- 可信设置卫生。 Groth16 需要针对特定电路进行第二阶段设置;用于生产时应采用真正的多方仪式,而非单一贡献者的开发设置。
- 将协议假设硬编码。 使用能力门控;不要假设草案 CAP 已在目标网络上线。
测试
- 单元测试:输入域验证、重放保护、事件正确性、拒绝格式错误或遭篡改的证明(负向路径最重要)
- 集成测试:在本地网络上完成完整的证明 → 提交 → 验证 → 状态转换流程
- 运维测试:通过模拟(
--send=no)评估实际证明大小下的资源成本——配对检查成本高;在承诺逐笔交易验证之前先做好预算
参考资料
- groth16_verifier 示例 — 标准验证器 + arkworks 测试样例
- soroban-examples
- Stellar 上的 ZK 证明 · Stellar 上的隐私 — 官方文档
- rs-soroban-ultrahonk — UltraHonk/BN254 验证器合约
- BLS12-381 SDK 文档 · BN254 SDK 文档
- Circom 文档 · snarkjs · Noir 文档 · RISC Zero 文档