Proof Center

Don’t trust us. Verify the proof.

Umbra generates a zero-knowledge proof in your browser and verifies it inside a Stellar smart contract. Every claim below is backed by code, copyable contract ids, live explorer links, and the exact transactions.

Live on mainnetBLS12-381 · Groth1614/14 contract testsProven from the browser

How a private payment is verified

Wallet

You connect a Stellar wallet. Your keys and note secrets never leave the browser.

Note

A private note is minted locally — a secret plus an amount. Nobody else can see it.

Commitment

The note is hashed to a Poseidon commitment — the only part that ever touches the chain.

Browser proof

snarkjs generates a Groth16 proof in a Web Worker. It proves membership without revealing which note — the secret is never sent anywhere.

Soroban verifier

The contract checks the proof on-chain (BLS12-381 host functions) before any funds move. No valid proof, no payout.

Pool transfer

Funds move; the commitment is inserted into the on-chain Merkle tree; a one-time nullifier prevents double-spend.

Explorer confirmation

The transaction is confirmed on Horizon — publicly checkable, yet unlinkable to your other notes.

The proof is generated client-side; money only moves if the contract verifies it on-chain.

Live on Stellar mainnet

UmbraPool contract (verifier + tree)

CBWIV33FQ27LOTA2LGM5SVL2WHAMBFLZTYOZXWKEMDBFCLU4BNIUQOLU

Asset (native SAC)

CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA

Deployer

GDGVKQBH5QUFULVRIMAHDYXZ3IJWPAZWKOCCHQSZJ3LPA64AHZMFMFE4

Deploy transaction

f3d0a294bef424da8dce0c6ce7e4294d673bd496b22a05a57f4214f7409f5f62

WASM hash

968eb0db459a2b610a32079b0b5c66b9fc768747474b704c0a8bf339cee79334

network: mainnet · Public Global Stellar Network ; September 2015 · Protocol 26. The Groth16 verifier is a Rust library compiled into the pool contract, so verification happens inside the same on-chain call.

The protocol, in facts

Network

Stellar mainnet · P26

Proof system

Groth16

Curve

BLS12-381

On-chain verify

CAP-0059 host fns

Hash

Poseidon

Circuits

Circom (shield · withdraw · transfer · claim)

Merkle depth

13 (8,192 notes)

Shield public inputs

[commitment, amount]

Withdraw public inputs

[root, nullifier, recipient, amount, change]

Transfer public inputs

[root, nullifier, out₁, out₂]

Two real, unlinkable transactions

A shield (deposit) and a confidential transfer — on-chain they share no linking data, and the transfer reveals no amount at all. Both confirmed on Horizon.

Shield · deposit Confirmed on Horizon
9970804ed373ac17f87ba92d1aaca7b6dc25abb7144438974a492619e40a7f80
View on stellar.expert
Confidential transfer · amount hidden Confirmed on Horizon
0a65cf2814985086c1f628a16a3ad9fdc3a6e5df8e4c98aa3849b3bc0cb62c08
View on stellar.expert
cannot be connected

What Stellar sees vs what Umbra hides

Be precise about what’s hidden. Shielding and cashing out reveal their amounts — that’s link privacy: the deposit↔withdrawal connection is broken. A confidential transfer reveals no amount at all, and every withdrawal keeps its change private in the pool.

Stellar sees (public)

  • A shield (deposit) amount
  • A withdrawal's public amount — but not its change
  • The pool contract address
  • A withdrawal's recipient address
  • The nullifier (an opaque one-time tag)
  • Transaction timing

Umbra hides (private)

  • Which deposit funded which withdrawal
  • A confidential transfer's amount — fully hidden (only a nullifier + two commitments touch the chain)
  • The change on every withdrawal — it stays a private note in the pool
  • Your note secret (never leaves the browser)
  • Your private balance — only your wallet can reconstruct it
  • Your local audit metadata, unless you choose to disclose it

Your private balance follows your wallet

Not an account. Not a server balance. A private note your wallet can rebuild from the chain on any device.

1

Connect your wallet

No account, no server, no custodian.

2

Derive a deterministic seed

From a signature your wallet produces — same wallet, same seed.

3

Scan the pool's events

DepositCreated / WithdrawalCompleted, straight from the chain.

4

Rebuild the Merkle tree

Reconstructed from on-chain commitments — correct withdrawal paths.

5

Recover your spendable notes

Re-derive your secrets and match them; your balance reappears, withdrawable.

Verified end-to-end: wipe local storage, reconnect the same wallet, and the shielded balance is rebuilt from chain — withdrawable from there. No centralized account required.

Under the hood

Circuits (Circom)

shield proves commitment = Poseidon(secret, amount). withdraw and transfer are join-splits: each proves Merkle inclusion, ownership, a one-time nullifier, value conservation, and a 64-bit range on every amount — so a withdrawal keeps private change and a transfer hides amounts entirely.

On-chain verifier

A BLS12-381 Groth16 verifier using Stellar’s native host functions (CAP-0059) — a G1 MSM + a 4-term pairing check in one host call (~40M of the 100M tx budget).

Poseidon Merkle tree

Commitments are inserted into an on-chain Poseidon tree; a recent-roots ring lets withdrawals prove inclusion against a known root. Poseidon is byte-identical across contract, circuit, and wallet.

Nullifiers

Each spend reveals a one-time nullifier = Poseidon(secret, leafIndex); the contract rejects any nullifier it has seen — no double-spend, no link to the deposit.

Browser proving

Groth16 proofs are generated in the browser via snarkjs in a Web Worker — off the main thread, ~2.5s for the 6.6 MB withdraw key. No server ever sees your secret.

The pool contract

shield() verifies + inserts + returns the leaf; withdraw() verifies + spends the nullifier + inserts the change note + pays the public amount out; transfer() spends one note into two new commitments with no token moving. Money cannot move without an on-chain-verified proof.

Verified

cargo test -p umbra-pool 14 / 14 — real Groth16 proofs vs the real BLS12-381 host
@umbra/crypto-bls 13 / 13 — Poseidon: Rust ≡ circuit ≡ TS
vitest (unit/component) 30 / 30
tsc --noEmit clean
next build 16 / 16 routes
browser → mainnet shield · transfer · unshield confirmed on Horizon

Private by default, accountable by choice

Selective disclosure (v1)

Encryption-based

Every action is recorded and encrypted locally under a viewing key only the user holds. They can export an audit packet and disclose it — to an accountant or auditor — by choice. No backdoor, no automatic access, and no ZK disclosure proof: v1 is honest, symmetric, user-consented encryption. Roadmap: auditor public keys, scoped viewing keys.

What is real today vs roadmap

Real, on-chain, today

  • Shield (deposit) — proof verified on-chain
  • Confidential transfer (private send) — shielded→shielded, amount hidden
  • Unshield / withdraw — any amount, private change, C1-bound
  • Private payment / donation / invoice links
  • In-browser proving + Freighter signing
  • Selective disclosure (encrypted audit packets)
  • Cross-device wallet-linked recovery

Roadmap (not faked)

  • Confidential amounts on shield + withdraw (public today; transfers already hide them)
  • Fee-privacy relayer · production indexer
  • MPC trusted-setup ceremony · independent audit
  • Rollup for millions of notes (8,192-note pool today)

See it move, privately.

Open the wallet, shield mainnet funds, and watch the proof verify on-chain — then check the hash here.