● Live on Stellar testnet Stellar Hacks: Real-World ZK Protocol 27 Β· BN254 Soroban

Provable clean-funds compliance,
without revealing your counterparty graph.

A wallet proves β€” in zero knowledge β€” that its counterparties belong to a screened ASP allow-set. A Soroban contract verifies the RISC Zero Groth16 seal on-chain and gates the wallet's stablecoin transfers on that proof. Privacy preserved. Compliance provable.

105,224
user cycles Β· 1 segment
~12M
Soroban instr Β· on-chain verify
109 B
journal Β· env::commit_slice
14
enforced error codes
Public Stellar testnet
checking Soroban RPC…
registry β†—

How it works

The proof is a reusable attestation that gates any SEP-41 transfer β€” the same clearance unlocks USDC, EURC, any token β€” rather than a one-shot shielded pool.

1 Β· PROVE (off-chain)

RISC Zero zkVM guest

A Rust no_std guest takes the wallet's private counterparty graph + an ASP allow-set Merkle root and proves every one of K counterparties is a member β€” without revealing them. Commits a 109-byte journal, emits a selector-prefixed Groth16 seal.

assert!(k > 0)
nullifier = SHA256("aegis_null"β€–walletβ€–secretβ€–rootβ€–block)
2 Β· VERIFY (on-chain)

Soroban ComplianceRegistry

Hashes the journal, calls the Nethermind stellar-risc0-verifier to verify the seal against (image_id, journal_digest) via BN254 host functions, then enforces: image_id, allow_set_root, wallet binding, pass==1, nullifier non-replay.

verify_proof β†’ try_invoke
Ok(Ok(_)) β‡’ genuine pass
3 Β· GATE (on-chain)

transfer_if_cleared

Reverts NotCleared (#9) unless from is currently cleared. ZK is load-bearing β€” remove the proof and the gate cannot be satisfied; there is no other path to "cleared".

clearance TTL window
SEP-41 transfer on success
flow: prove β†’ verify β†’ clearance β†’ gated transfer
🧠
Aegis guest
RISC Zero zkVM
β†’
πŸ”
Groth16 seal
selector-prefixed
β†’
πŸ“œ
ComplianceRegistry
Soroban
β†’
βœ…
clearance
TTL window
β†’
πŸ’Έ
SEP-41 transfer
USDC / EURC / any

Live on public Stellar testnet

Every contract and transaction is judge-verifiable on stellar.expert. Persistent entries are bumped to ~100k ledgers, so the deploy stays live for the judging window.

Groth16Verifier Β· Nethermind
BN254 pairing check
CBZAX43T4YNSWNWM2GCIHUSNWUAQYMOD5RJZVLNNC3UDIG6Z6IOQUZNB
stellar.expert β†—
ComplianceRegistry Β· Aegis
Seal verify + claims + gate
CAI3XYL2KRM7BCJYN46DODKGIIKMFFNFWYPNKRRWYXVE3ZIXBTGQCERB
stellar.expert β†—
MockToken Β· demo SEP-41
Token the gate transfers
CDQDBN2HA64U4M3MCIDHHRQCL5XOXE5CVSZNFGBAKQ6LD5GFVNE67AMK
stellar.expert β†—
Key transactions (testnet)
init
986b3da8… β†—
register_compliance (on-chain Groth16 verify)
25f9e655… β†—
transfer_if_cleared
1a942cf1… β†—
Reproducible guest constants
Image ID
eddc2223c80408e93512ec945c1832e450e78c334fb482c318c17c929d2d4b6f
Demo allow-set root (depth-2, 4 leaves)
48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de

End-to-end demo

Run on Protocol 27 β€” public testnet + local standalone. Real seal, image_id, journal, balances.

βœ“

Clean path Β· wallet alice (counterparties in allow-set)

  1. 1
    register_compliance(alice, journal, seal, image_id) β†’ on-chain Groth16 verify passes, clearance stored.
  2. 2
    is_cleared(alice) β†’ true; k=2, as_of_block=12345, nullifier 0x6831a291…
  3. 3
    transfer_if_cleared(alice β†’ bob, 1000) β†’ succeeds
  4. 4
    Balances: alice 999_999_000, bob 1_000
βœ•

Blocked path Β· wallet bob (no proof)

  1. 1
    is_cleared(bob) β†’ false
  2. 2
    transfer_if_cleared(bob β†’ alice, 1) β†’ reverts Error(Contract, #9) = NotCleared
  3. 3
    The gate holds. ZK is load-bearing β€” no other path to "cleared".

Soundness β€” negative tests (on-chain verifier binding)

TAMPERED SEAL

Flip one byte β†’ #10 ProofVerificationFailed. Verifier traps in bn254_multi_pairing_check ("point not on curve"). A forged seal cannot produce a clearance.

WRONG IMAGE_ID

Seal from a different guest β†’ #11 BadImageId, caught before the verifier call.

K = 0

Guest assert!(k>0) makes the proof un-generatable; registry also reverts #13 ZeroK. The "all compliant" claim cannot be vacuously true.

Why RISC Zero for compliance

Aegis is the only RISC Zero zkVM compliance coprocessor in the field β€” other RISC Zero projects target proof-of-reserves, receipts, or settlement, while the remaining compliance submissions use Noir or Circom hand-written circuits.

zkVM over hand-circuits

Compliance logic is branching and graph-shaped (membership today, deny-set + graph reachability tomorrow). A Rust program compiled by the zkVM is cheaper to get right and to audit than an equivalent hand-rolled Noir/Circom circuit, and composes with standard crates.

SDF-aligned infrastructure

RISC Zero on Stellar is one of SDF's named ZK stacks, with an official Nethermind on-chain verifier (stellar-risc0-verifier) that Aegis forks β€” built on promoted primitives, not a bespoke circuit.

Groth16 β€” the right trade for a gate

A compliance gate verifies proofs frequently (every transfer). Groth16 seals are ~260 B and verify in a single BN254 pairing_check (~12M instr); STARKs would be ~100 KB and blow the 100M-instruction WASM budget. vs Noir/UltraHonk (~35M instr), Groth16 is cheaper for this access pattern.

Coprocessor, not a mixer

The proof is a reusable attestation that gates any SEP-41 transfer β€” the same clearance unlocks USDC, EURC, any token β€” rather than a one-shot shielded pool. This is the "proof layer under SDF's Confidential Tokens policy engine" framing.

Aegis vs typical compliance-pool submissions

Property Aegis (RISC Zero) Typical Noir/Circom pool
ZK backendzkVM (Rust program)hand-written circuit
On-chain verify cost~12M instr (single pairing)~35M instr (UltraHonk)
Proof scopereusable attestation (gates N transfers)one-shot withdraw
Tokens gatedany SEP-41 (USDC/EURC/…)single pool token
Nullifier bindingwallet β€– secret β€– root β€– ledgernote-secret only (typical)
Audit surfaceRust program + standard cratescustom circuit constraints

Security model

What is cryptographically enforced vs what is not. Stated honestly β€” this is a hackathon prototype, not audited.

Enforced by ZK / on-chain

  • β€’ Membership β€” every K counterparty is a leaf of the allow-set Merkle root
  • β€’ Nullifier non-replay (wallet β€– secret β€– root β€– ledger bound)
  • β€’ image_id match β€” seal from a different guest rejected
  • β€’ allow_set_root match β€” proof against a stale root rejected
  • β€’ wallet match via Address::to_payload
  • β€’ pass==1 and k>0 β€” vacuous truth impossible
  • β€’ Admin-gated init + require_auth on all mutating fns
  • β€’ Persistent-entry TTL bumped (100k ledgers) β€” no silent eviction

Not enforced (production extensions)

  • β€’ Counterparty-set completeness β€” guest proves "all K I disclosed are clean", not "I disclosed all". Bind to real on-chain graph via ASP-countersigned commitment (main extension).
  • β€’ Gate is opt-in β€” transfer_if_cleared is a compliant path; direct token.transfer is not blocked. Production: SAC controlled-token or transfer proxy.
  • β€’ Membership only in MVP β€” deny-set + graph reachability architected, not in demo guest.
  • β€’ SHA-256 demo tree β€” production swaps Poseidon2 (Stellar host fn).
  • β€’ Local proving for demo β€” production shape is a relayer proving service.
Honest limitations & future work β–Ύ