stellar/stellar-dev-skill

zk-proofs

Zero-knowledge proofs and privacy patterns on Stellar.

Hot #2752 First seen May 12, 2026

Installation

$ npx skills add stellar/stellar-dev-skill --skill zk-proofs

Summary

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

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from stellar/stellar-dev-skill.

npx skills add stellar/stellar-dev-skill

Browse all from stellar/stellar-dev-skill

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 51
License LICENSE
Default branch main
Open issues 2
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 15,555 B
  • docs SUMMARY.md 391 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 271 installs

SKILL.md

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 Available (Protocol 22+)
BN254 base ops (G1 add/mul, pairing check — crypto::bn254) CAP-0074 Available (Protocol 25+)
BN254 G1 MSM, Fr arithmetic (fr_add/sub/mul/pow/inv), on-curve checks CAP-0080 Available (Protocol 26+)
Poseidon/Poseidon2 permutation primitives (not complete hash functions) CAP-0075 Available (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+).

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 verifier contract
RISC Zero (STARK → Groth16 wrap) Groth16 BN254 ✅ via CAP-0074 (Protocol 25+)

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

The official groth16verifier example is the canonical implementation — the full contract:

#![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.

# 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 / verificationkey.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 verifyproof. 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 verifier contract, whose host calls include env.crypto().bn254()'s g1msm, Fr arithmetic (fradd/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.

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

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

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