mcpField
Z
zk-proofs

zk-proofs

github.com/stellar安全55322 次安装1 周前更新
代码仓库

关于 zk-proofs

面向在 Stellar 智能合约中实现零知识验证的开发者技能。内容按协议和 SDK 要求梳理密码学原语与证明系统,并提供实现路径及安全注意事项。

主要功能

  1. 01Stellar ZK 能力与版本检查
  2. 02Groth16 验证器实现指南
  3. 03Circom、Noir 和 RISC Zero 工作流
  4. 04BN254、BLS12-381 与 Poseidon 支持
  5. 05验证器架构与安全注意事项

适用场景

  1. 01选择证明原语前核查网络支持
  2. 02将 Circom 证明接入 Stellar 合约
  3. 03评估 Noir UltraHonk 验证限制
  4. 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+)

实现前,务必确认:

  1. 前言中的 CAP 状态(Accepted/Implemented,而非草案)
  2. 目标网络的协议版本(软件版本)
  3. 所需宿主函数是否受 soroban-sdk 发行版支持

自 Protocol 25 起,两条曲线都具备基础宿主函数——曲线不再是决定一切的因素。仍需关注的是证明系统:Groth16 在任一曲线上都能通过小型合约完成验证,而 UltraHonk 需要专用的合约内验证器(已有实现——见 Noir 操作指南;但它依赖 CAP-0080 宿主函数,且当前发行版基于 soroban-sdk 28 构建,因此需要 Protocol 28+)。

工具链证明系统曲线在 Stellar 上链
Circom + snarkjs(-p bls12381)Groth16BLS12-381✅ 通过 CAP-0059(Protocol 22+)
Circom + snarkjs(默认 bn128)Groth16BN254✅ 通过 CAP-0074(Protocol 25+)
Noir + BarretenbergUltraHonkBN254✅ 通过 CAP-0074 + CAP-0080(当前验证器发行版需要 Protocol 28+)——需要 rs-soroban-ultrahonk 验证器合约;仅限非 ZK 证明(见 Noir 操作指南)
RISC Zero(STARK → Groth16 封装)Groth16BN254✅ 通过 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 陈述放到链上的三种方式,按优先顺序排列:

  1. 链上 UltraHonk 验证(当前验证器版本需要 Protocol 28+;CAP-0074 + CAP-0080):部署或调用基于 rs-soroban-ultrahonk 的验证器,并直接提交 (proof, public inputs)——无需信任任何第三方,也无需 oracle。
  2. 为链上部分切换证明技术栈:将链上关键陈述表示为 Circom/Groth16 电路(见上方演练),并在链下部分保留 Noir——代价是需要维护第二套工具链,换来体积更小、经过实战检验的验证器。
  3. 证明 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)评估实际证明大小下的资源成本——配对检查成本高;在承诺逐笔交易验证之前先做好预算

参考资料

评论

支持 Markdown