stellar/stellar-dev-skill

zk-proofs

Zero-knowledge proofs and privacy patterns on Stellar.

소스 보기
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

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

PrimitiveCAPStatus
BLS12-381 ops (G1/G2 add, mul, MSM, pairing check, hash-to-curve, Fr arithmetic)CAP-0059Available (Protocol 22+)
BN254 base ops (G1 add/mul, pairing check — crypto::bn254)CAP-0074Available (Protocol 25+)
BN254 G1 MSM, Fr arithmetic (fr_add/sub/mul/pow/inv), on-curve checksCAP-0080Available (Protocol 26+)
Poseidon/Poseidon2 permutation primitives (not complete hash functions)CAP-0075Available (Protocol 25+)

Before implementation, always confirm:

  1. CAP status in the preamble (Accepted/Implemented vs draft)
  2. Target network protocol version (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+).

ToolchainProof systemCurveOn-chain on Stellar
Circom + snarkjs (-p bls12381)Groth16BLS12-381✅ via CAP-0059 (Protocol 22+)
Circom + snarkjs (default bn128)Groth16BN254✅ via CAP-0074 (Protocol 25+)
Noir + BarretenbergUltraHonkBN254✅ via CAP-0074 + CAP-0080 (Protocol 26+) — needs the rs-soroban-ultrahonk verifier contract
RISC Zero (STARK → Groth16 wrap)Groth16BN254✅ via CAP-0074 (Protocol 25+)

The on-chain verifier (Groth16 over BLS12-381)

The official groth16_verifier example 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).

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).

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 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 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-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 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

같은 저장소의 Skills

더 많은 Skills

모든 Skills
stellar
커뮤니티

agentic-payments

Agentic and machine-to-machine payments on Stellar. Covers x402 (HTTP 402 paid APIs via OZ Channels facilitator, fee-sponsored clients) and MPP (Machine Payments Protocol) in both Charge mode (per-request SAC) and Session mode (channel-backed off-chain commits, high-frequency; formerly called Channel mode). Defaults to USDC (SEP-41 SAC) on stellar:testnet/stellar:pubnet (CAIP-2). Use when selling a paid API to AI agents, building an x402 client, or designing a payment-channel architecture for high-frequency agent traffic.

설치 수
2
GitHub Stars
51
업데이트
9월 5일
stellar
커뮤니티

assets

Stellar Assets (classic) + trustlines + Stellar Asset Contract (SAC) bridge to smart contracts. Covers asset issuance, distribution, authorization flags, clawback, regulated assets, trustline management, and the SAC interop layer that exposes classic assets as SEP-41 contract tokens. Use when tokenizing real-world assets, issuing stablecoins, managing trustlines, or bridging classic assets to smart contracts.

설치 수
2
GitHub Stars
51
업데이트
9월 5일
stellar
커뮤니티

cross-chain

Cross-chain interoperability for Stellar. Entry point with a rail-selection decision table and shared pitfalls, routing to three companion files — cctp.md (Circle CCTP V2, native USDC burn-and-mint between Stellar and EVM/Solana chains, domain 27, the CctpForwarder requirement for Stellar recipients), axelar.md (Axelar GMP for Soroban contracts calling contracts on other chains, and the Interchain Token Service for multichain tokens), and layerzero.md (LayerZero V2 OApp messaging with configurable DVN security, OFT omnichain tokens, and USDT0 — native USDT on Stellar). Also covers NEAR Intents (intent-based cross-chain swaps into XLM or Stellar USDC) at the routing level. Use when bridging USDC or USDT to or from Stellar, sending messages between a Stellar contract and another blockchain, making a token exist on multiple chains, or adding cross-chain swaps to an app.

설치 수
2
GitHub Stars
51
업데이트
9월 5일
stellar
커뮤니티

dapp

Stellar dApp / frontend development. Covers the JavaScript stellar-sdk (browser + Node.js), Freighter wallet, Stellar Wallets Kit (multi-wallet), Wallet Standard, smart accounts with passkeys, transaction building / signing / submission, smart contract invocation from the client, simulation, and error handling. Use when building a React/Next.js/Node.js app that talks to Stellar — classic operations or smart contracts.

설치 수
2
GitHub Stars
51
업데이트
9월 5일