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.
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
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.
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.
Connect your wallet
No account, no server, no custodian.
Derive a deterministic seed
From a signature your wallet produces — same wallet, same seed.
Scan the pool's events
DepositCreated / WithdrawalCompleted, straight from the chain.
Rebuild the Merkle tree
Reconstructed from on-chain commitments — correct withdrawal paths.
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
Private by default, accountable by choice
Selective disclosure (v1)
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)