Skip to main content

zk-proofs

Zero-knowledge proofs and privacy patterns on Stellar. Covers Groth16 verification in smart contracts via BLS12-381 host functions (CAP-59), the BN254 + Poseidon host functions (CAP-74/75), and concrete toolchain walkthroughs for Circom, Noir, and RISC Zero. Use when building privacy-preserving applications, ZK-verifier contracts, or wiring a proving toolchain to Stellar.

소스 정보

저장소
stellar/stellar-dev-skill
최근 소스 활동
2026년 8월 12일 20:13
감지된 SKILL.md 언어
영어
스타
53
포크
64

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
zk-proofs
description
Zero-knowledge proofs and privacy patterns on Stellar. Covers Groth16 verification in smart contracts via BLS12-381 host functions (CAP-59), the BN254 + Poseidon host functions (CAP-74/75), and concrete toolchain walkthroughs for Circom, Noir, and RISC Zero. Use when building privacy-preserving applications, ZK-verifier contracts, or wiring a proving toolchain to Stellar.
user-invocable
true
argument-hint
[zk task]
# Zero-Knowledge Proofs & Privacy ZK verification on Stellar. Capability is protocol- and SDK-version dependent — always verify CAP status, network version, and `soroban-sdk` host-function support before relying on a primitive. > **Last verified against Protocol 27** (mainnet, August 2026). CAP-0074/0075 shipped in Protocol 25 "X-Ray" and CAP-0080 (BN254 MSM, Fr arithmetic, on-curve checks) in Protocol 26 — if a doc tells you BN254 or Poseidon is "proposed", it predates that. ## When to use this skill - Implementing a Groth16 (or other SNARK) verifier as a Stellar smart contract - Wiring Circom, Noir, or RISC Zero output to on-chain verification - Building privacy pools, confidential tokens, or Merkle-tree-backed commitments - Using the BN254 / Poseidon host functions (Protocol 25+) ## Related skills - Contract patterns and deployment → `../smart-contracts/development.md` - Verifier security review → `../smart-contracts/security.md` - CAPs referenced here → `../standards/SKILL.md` ## What's available — verify before building | Primitive | CAP | Status | |-----------|-----|--------| | BLS12-381 ops (G1/G2 add, mul, MSM, pairing check, hash-to-curve, Fr arithmetic) | [CAP-0059](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0059.md) | **Available** (Protocol 22+) | | BN254 base ops (G1 add/mul, pairing check — `crypto::bn254`) | [CAP-0074](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0074.md) | **Available** (Protocol 25+) | | BN254 G1 MSM, Fr arithmetic (`fr_add/sub/mul/pow/inv`), on-curve checks | [CAP-0080](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0080.md) | **Available** (Protocol 26+) | | Poseidon/Poseidon2 **permutation** primitives (not complete hash functions) | [CAP-0075](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0075.md) | **Available** (Protocol 25+) | Before implementation, always confirm: 1. CAP status in the preamble (`Accepted`/`Implemented` vs draft) 2. Target network protocol version ([software versions](https://developers.stellar.org/docs/networks/software-versions)) 3. `soroban-sdk` release support for the host functions you need **Both curves have base host functions since Protocol 25** — the curve no longer decides everything. What still matters is the proof *system*: Groth16 verifies with a small contract on either curve, while UltraHonk needs a dedicated in-contract verifier (one exists — see the Noir walkthrough — but it relies on CAP-0080 host functions, so Protocol 26+). | Toolchain | Proof system | Curve | On-chain on Stellar | |-----------|--------------|-------|---------------------| | Circom + snarkjs (`-p bls12381`) | Groth16 | BLS12-381 | ✅ via CAP-0059 (Protocol 22+) | | Circom + snarkjs (default `bn128`) | Groth16 | BN254 | ✅ via CAP-0074 (Protocol 25+) | | Noir + Barretenberg | UltraHonk | BN254 | ✅ via CAP-0074 + CAP-0080 (**Protocol 26+**) — needs the [rs-soroban-ultrahonk](https://github.com/NethermindEth/rs-soroban-ultrahonk) verifier contract | | RISC Zero (STARK → Groth16 wrap) | Groth16 | BN254 | ✅ via CAP-0074 (Protocol 25+) | ## The on-chain verifier (Groth16 over BLS12-381) The official [groth16_verifier example](https://github.com/stellar/soroban-examples/tree/main/groth16_verifier) is the canonical implementation — the full contract: ```rust #![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)) } } ``` Point encodings are uncompressed big-endian: `G1Affine` wraps 96 bytes, `G2Affine` 192 bytes, `Fr` 32 bytes. The example's test suite shows the exact conversion from arkworks types (`ark-bls12-381` + `ark-serialize`) — reuse it when building fixtures from your proving toolchain's JSON output. In production, wrap this verifier with application logic: fix the `VerificationKey` at deploy time (constructor) instead of taking it as a call argument, and bind proofs to context (see [Pitfalls](#pitfalls)). ## Walkthrough: Circom → on-chain verification Circom compiles for either curve. This walkthrough targets BLS12-381 to match the canonical verifier contract above; since Protocol 25 the default `bn128` (BN254) output is equally verifiable via `crypto::bn254` — just make sure circuit curve and verifier contract agree. ```bash # 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 ``` Then convert `proof.json` / `verification_key.json` (decimal-string coordinates) into the contract's types — serialize each point uncompressed big-endian into the 96/192-byte layouts, e.g. via arkworks as in the example's tests — and invoke `verify_proof`. Public signals (`public.json`) become the `Vec<Fr>` argument; the contract must also validate what those signals *mean* (see [Pitfalls](#pitfalls)). For real applications the per-proof flow is: client proves locally (WASM prover or native), submits `(proof, public_signals)` in a contract invocation, contract verifies + applies policy + updates state. ## Walkthrough: Noir (UltraHonk, on-chain verifiable since Protocol 26) Noir's standard backend (Barretenberg) produces UltraHonk proofs over BN254. The curve's base operations are native since Protocol 25 (CAP-0074), and UltraHonk verification runs on-chain through [Nethermind's rs-soroban-ultrahonk](https://github.com/NethermindEth/rs-soroban-ultrahonk) verifier contract, whose host calls include `env.crypto().bn254()`'s `g1_msm`, Fr arithmetic (`fr_add/sub/mul/pow/inv`) and `pairing_check` — the MSM and Fr functions come from CAP-0080, so this verifier needs **Protocol 26+** and will fail to link on a Protocol-25 network. This is the stack OpenZeppelin's [Confidential Tokens developer preview](https://stellar.org/blog/developers/developer-preview-confidential-tokens-on-stellar) runs on (testnet). It is a young, community-maintained verifier: check its audit status and maturity before mainnet use. ```bash # Local proving workflow nargo new age_check && cd age_check cat > src/main.nr <<'EOF' fn main(age: u64, threshold: pub u64) { assert(age >= threshold); } EOF nargo check nargo execute witness # writes the witness from Prover.toml inputs bb prove -b target/age_check.json -w target/witness.gz -o target/proof bb verify -k target/vk -p target/proof # off-chain verification ``` Three ways to get a Noir statement on-chain, in order of preference: 1. **On-chain UltraHonk verification** (Protocol 26+, CAP-0074 + CAP-0080): deploy or call an [rs-soroban-ultrahonk](https://github.com/NethermindEth/rs-soroban-ultrahonk)-based verifier and submit `(proof, public inputs)` directly — trustless, no oracle. 2. **Switch the proving stack for on-chain parts**: express the on-chain-critical statement as a Circom/Groth16 circuit (walkthrough above) and keep Noir for off-chain components — smaller, battle-tested verifier at the cost of a second toolchain. 3. **Attestation oracle** (fallback for older protocol targets or unaudited-verifier concerns): a service runs `bb verify` off-chain and submits a signed attestation; the contract `require_auth()`s the attester and applies policy. The trust assumption (the attester) must be explicit and documented — this is *not* trustless ZK, it's a verifiable-computation oracle. ## Walkthrough: RISC Zero (Groth16 wrap, on-chain verifiable since Protocol 25) RISC Zero proves arbitrary Rust execution (zkVM) and can wrap its STARK receipts into a Groth16 proof over BN254 ("stark-to-snark") — small enough for on-chain verification where BN254 is supported. ```rust // 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 } ``` ```rust // Host: produce and verify a receipt locally let receipt = prover.prove(env, ELF)?.receipt; receipt.verify(IMAGE_ID)?; // off-chain verification ``` Since Protocol 25 (CAP-0074), the Groth16-wrapped receipt verifies **natively on-chain**: a BN254 verifier contract mirroring the BLS12-381 one above. Its public inputs are *not* the `IMAGE_ID` and journal digest directly — RISC Zero's Groth16 verifier takes five field elements encoding the control root (split into two halves), the receipt-claim digest (also split in two), and the BN254 control ID. The image ID and journal are inputs to reconstructing that receipt claim off-chain (or in contract code) before the pairing check; binding them incorrectly rejects valid seals or proves the wrong statement. See the [RISC Zero docs](https://dev.risczero.com/api) for the wrapping workflow and claim-digest construction. The attestation pattern remains a fallback for STARK-only receipts (no Groth16 wrap) or pre-Protocol-25 targets. ## Architecture patterns - **Verification gateway**: isolate cryptographic checks in a dedicated verifier contract/module — normalize inputs, verify, emit explicit success/failure events. Smaller audit surface, cleaner upgrades. - **Policy-and-proof split**: `Verifier` (cryptographic validity) → `Policy` (business/compliance rules) → `Application` (state transition). Each independently testable and upgradeable. - **Capability gating**: enable ZK flows only where required primitives are confirmed available; keep deterministic fallbacks and document the supported network/protocol matrix. For Merkle-tree commitments (privacy pools, allowlists): CAP-0075 (Protocol 25+) exposes Poseidon/Poseidon2 **permutations**, not ready-made hash functions. To recompute Poseidon Merkle roots and commitments on-chain you must build the sponge/compression construction in guest code and pass the same field, state size `t`, S-box degree `d`, round counts, MDS/diagonal matrix, and round constants your circuit uses — plus matching padding and domain separation. Mismatched parameters silently produce roots that differ from the circuit's, and an ad-hoc construction can be insecure. On pre-Protocol-25 targets, design trees so the contract only needs root comparisons and membership proofs verified inside the SNARK. ## Pitfalls - **Verifying the proof but not the statement.** A valid proof only shows *some* witness satisfies the circuit. The contract must validate the public inputs' semantics: who is this proof for, which Merkle root, which action, which amount. - **Missing anti-replay binding.** Valid proofs can be replayed. Bind a nonce/session/action into the public inputs and persist a replay guard (nullifier set) on-chain. - **Curve/verifier mismatch.** The proof's curve must match the verifier contract's host functions: the canonical example verifier is BLS12-381, so a default-`bn128` Circom proof fails against it (and vice versa for a BN254 verifier). Since Protocol 25 both curves work — just keep circuit and verifier on the same one. - **Trusted-setup hygiene.** Groth16 needs a circuit-specific phase-2 setup; for production use a real multi-party ceremony, not a single-contributor dev setup. - **Hardcoded protocol assumptions.** Capability-gate; don't assume draft CAPs are live on the target network. ## Testing - Unit: input domain validation, replay protection, event correctness, malformed/tampered proof rejection (negative paths are the important ones) - Integration: full prove → submit → verify → state-transition flow against a local network - Operational: resource costs for realistic proof sizes via simulation (`--send=no`) — pairing checks are expensive; budget before committing to per-transaction verification ## References - [groth16_verifier example](https://github.com/stellar/soroban-examples/tree/main/groth16_verifier) — canonical verifier + arkworks test fixtures - [soroban-examples](https://github.com/stellar/soroban-examples) - [ZK Proofs on Stellar](https://developers.stellar.org/docs/build/apps/zk) · [Privacy on Stellar](https://developers.stellar.org/docs/build/apps/privacy) — official docs - [rs-soroban-ultrahonk](https://github.com/NethermindEth/rs-soroban-ultrahonk) — UltraHonk/BN254 verifier contract - [BLS12-381 SDK docs](https://docs.rs/soroban-sdk/latest/soroban_sdk/crypto/bls12_381/index.html) · [BN254 SDK docs](https://docs.rs/soroban-sdk/latest/soroban_sdk/crypto/bn254/index.html) - [Circom docs](https://docs.circom.io) · [snarkjs](https://github.com/iden3/snarkjs) · [Noir docs](https://noir-lang.org/docs) · [RISC Zero docs](https://dev.risczero.com)
GitHub에서 보기