Warning
This repository has been archived and is no longer maintained.
Development of the Stellar Implementation of the Opaque Protocol has been paused. Maintenance will resume once a partnership and the necessary support are in place.
Every payment on a public ledger is also a permanent, searchable record of who paid whom: payroll, donations, suppliers, savings, memberships. Opaque adds a privacy layer to Stellar where the right to transact privately is enforced by zero-knowledge proofs verified inside Soroban contracts, not by a trusted operator you have to believe.
Receive without exposing your wallet. Pool and withdraw unlinkably. Prove a credential without revealing your identity. It is live on Stellar testnet today, and it ships as both a working wallet and an installable developer SDK.
Tip
The fastest way to understand Opaque is to watch the 3-minute demo or open the live app with Freighter on Stellar testnet.
If you only take three things from this project, take these:
- It works. Real Soroban contracts on Stellar testnet, a real browser wallet, real Groth16 proofs. Not a slideshow. Verify any flow on stellar.expert.
- The ZK is load-bearing. A zero-knowledge proof is the gate that releases funds and answers credential checks. Remove the proof and the action does not happen, because the contract reverts.
- It is honest. Every trust assumption in this MVP is written down in plain language below, not hidden.
This is the heart of the project. Two independent Groth16 circuits (BN254 / alt_bn128) are proven in the browser and verified inside Soroban contracts on Stellar testnet, using the Protocol 25/26 BN254 host functions that make on-chain proof verification practical. Without a valid proof, a user cannot withdraw privately or prove a trait. The proof itself is the access control.
1. Private withdrawals (privacy pool). The browser proves "I own one unspent deposit inside the approved set, and this exact withdrawal is valid" without revealing which deposit. The pool contract verifies the proof on-chain, rejects reused nullifiers, and enforces a custody invariant so a published root can never release unbacked funds. The proof binds recipient, amount, fee, and relayer, so no one in the path can redirect the money.
2. Private reputation. A depth-20 Merkle circuit (Poseidon) proves "I control a stealth identity that holds attestation X" and emits an action-scoped nullifier, without revealing the identity or linking it to the wallet that holds funds. The reputation verifier contract checks the proof on-chain and returns a yes or no.
| What is proven | On-chain verifier (Stellar testnet) |
|---|---|
| Unlinkable pool withdrawal | Groth16 verifier CBWH…IDFC, gated by the privacy pool |
| Private credential / trait | Groth16 verifier CAWX…BS2T, gated by the reputation verifier |
Important
If a judge remembers one frame from the demo, make it the proof being verified on-chain. That contract invocation succeeding on testnet is the whole thesis: privacy enforced by math, checked by Stellar.
Every step below is a real transaction from the demo, on the deployed privacy pool CCXN…7J3J. Open any hash on stellar.expert, this is the whole rail, end to end, on Stellar testnet.
| # | What happened | Transaction |
|---|---|---|
| 1 | A sender announces a one-time stealth address (DKSAP, scheme 1) — the recipient's main wallet is never named | 6718…a6ed |
| 2 | XLM deposited into the privacy pool's commitment set | 569c…f902 |
| 3 | The ASP publishes the approved-set root that withdrawal proofs are checked against | 4104…80fd |
| 4 | The pool state-tree root is published for proof verification | 10af…55e9 |
| 5 | Unlinkable withdrawal: a Groth16 proof is verified on-chain and paid out, signed by the staked relayer (GC3A…P3OH), not the recipient's wallet |
6cbf…9421 |
Step 5 is the unlinkability claim made concrete: the withdrawal transaction is signed and fee-paid by the relayer's account, so the recipient never appears in the path. Reproduce the same flow yourself in the live app, these hashes are testnet history, not a fixture.
Five protocol pieces compose into one private rail.
| Piece | What it gives the user |
|---|---|
| Stealth payments | A sender pays a fresh one-time Stellar account derived from the recipient's published meta-address. The recipient's main wallet is never named on-chain. |
| Privacy pool | Deposit XLM into a commitment set, then withdraw later with a Groth16 proof that hides which deposit funded the withdrawal. |
| Association set | An Association Set Provider publishes the approved set root used by withdrawal proofs, so operators can define which deposits are eligible. |
| Relayer market | A staked relayer submits the withdrawal for the user, so the final transaction is not sent from the user's wallet. |
| ZK reputation | Prove a credential or trait on Soroban without linking it to the wallet that received or holds funds. |
A recipient discovers a stealth payment, sends it straight into the privacy pool from the stealth account itself (the connected wallet never signs and is never linked), then withdraws it unlinkably through a relayer with an on-chain-verified proof. Two privacy primitives become one continuous private payment rail, with zero knowledge as the gate at the end.
Opaque is not only an app. The entire protocol is published as a typed, framework-free TypeScript package, so any Stellar developer can add private payments and on-chain ZK reputation in a few lines.
npm install @opaquecash/stellarimport { OpaqueClient, keypairSigner } from "@opaquecash/stellar";
const opaque = new OpaqueClient({ network: "testnet", signer: keypairSigner(secret) });
// stealth payment
await opaque.payments.send({ to: recipientMetaAddress, amountXlm: "10" });
// privacy pool: deposit, prove, withdraw unlinkably
const { note } = await opaque.pool.deposit({ amountXlm: "5" });
const proof = await opaque.pool.proveWithdraw({ note, recipient });
await opaque.pool.withdraw({ proof, recipient, noteCommitment: note.commitment });
// on-chain ZK reputation
await opaque.reputation.proveAndVerify({ attestationId, stealthPrivKey, externalNullifier: 42n });ESM and CommonJS, full type declarations, tree-shakeable subpaths, and a clean-room install gate in CI so a broken package can never ship. Full guides and a generated API reference for every method live at soroban.opaque.cash.
Opaque is live on Stellar testnet for the MVP path.
| Surface | Status |
|---|---|
| Private payments | Register, send, scan, and sweep are wired across testnet contracts and the browser wallet. |
| Privacy pool | Deposit, root publication, in-browser proof generation, withdrawal, and nullifier-replay rejection are implemented. |
| Relayer market | A staked relayer registry plus a live gateway submit blind withdrawals on the user's behalf. |
| ZK reputation | Groth16 reputation proofs verify through Soroban contracts. |
| Developer SDK | Published to npm with docs, examples, a Node matrix CI, and a tag-driven release pipeline. |
Warning
This is experimental software on Stellar testnet. Do not use real funds. Mainnet is blocked until the security register is signed off. Read DISCLAIMER.md first.
Privacy systems live or die by their assumptions, so here are Opaque's, stated plainly.
Note
- The demo Association Set Provider approves all deposits. It provides liveness and root publication for the MVP, not selective screening.
- Privacy-pool Merkle roots are published off-chain by a trusted publisher, because on-chain Poseidon over a depth-20 tree exceeds Soroban's per-transaction CPU budget. An on-chain custody invariant prevents a bad root from releasing unbacked funds.
- The deposit is linked on-chain to the inbound stealth payment. Unlinkability comes from the withdrawal, not the deposit, and its strength is bounded by the pool's anonymity set. Equal-size, chunked deposits widen that set.
- The relayer's job-funding transaction is public and signed by the connected wallet, but the relayer cannot change the recipient, amount, or proof.
What is always enforced on-chain: proof verification, nullifier-replay protection, and the custody invariant.
| Use case | Why Opaque helps |
|---|---|
| Private creator and fan payments | Fans pay without linking the recipient's main wallet across every payment. |
| Payroll and contractor payouts | Workers receive XLM without making their salary history trivial to inspect. |
| DAO contributor rewards | Members prove eligibility while separating payouts from public identity. |
| Consumer wallet privacy | Wallets offer one-time receive accounts and pool withdrawals with no custodial infrastructure. |
| Credential-gated access | Apps verify reputation or attestations without learning the user's wallet graph. |
| Compliance-aware privacy | Association sets let operators define allowed deposits while preserving withdrawal unlinkability. |
Tip
No build required: open stellar.opaque.cash, connect Freighter on Stellar testnet, and initialize your Opaque keys.
To run the wallet locally:
git clone https://github.com/opaquecash/stellar.git
cd stellar
npm ci
npm run build:scanner
npm run fetch:circuits
cd frontend
npm ci
npm run devOpen http://localhost:5173, connect Freighter on Stellar testnet, and initialize your keys. The frontend reads contract IDs from deployments/v1/testnet.json, so you do not need to redeploy anything.
The pool and reputation verifiers need published roots, and relayed withdrawals need a gateway. A testnet ASP (approve-all) and a relayer are already running for the demo; operators should run their own before relying on the system outside demo use.
| Service | Command | Guide |
|---|---|---|
| ASP indexer | cd asp && npm run indexer |
Guide |
| Reputation publisher | cd publisher && npm run serve |
Readme |
| Relayer hub + node | cd relayer && npm run hub / npm run relayer |
Guide |
| Path | Purpose |
|---|---|
| frontend/ | React wallet: private receive, scan, sweep, pool deposit/withdraw, and reputation proofs. |
| sdk/ | @opaquecash/stellar, the typed protocol SDK published to npm, with docs and examples. |
| contracts/ | Soroban contracts: registries, announcer, attestations, Groth16 verifiers, privacy pool, relayer registry. |
| scanner/ | Rust DKSAP scanner compiled to WASM for browser-side receive detection. |
| circuits/ | Circom Groth16 circuits, fixtures, and regression tests. |
| asp/ | Association Set Provider and pool state-root publisher. |
| publisher/ | Reputation leaf collector and Merkle-root publisher. |
| relayer/ | Relayer gateway, hub, node engine, and market tests. |
| deployments/ | Versioned on-chain address book and manifests. |
| docs/ | Operator guides, protocol internals, and security notes. |
The canonical address book is deployments/v1/testnet.json. Key contracts, linked for inspection on stellar.expert:
| Contract | Explorer |
|---|---|
| Privacy pool | CCXN…7J3J |
| Pool Groth16 verifier | CBWH…IDFC |
| Reputation verifier | CAFV…BSVR |
| Reputation Groth16 verifier | CAWX…BS2T |
| Attestation engine (V2) | CB6K…SDPX |
| Schema registry | CA5X…7QCP |
| Stealth registry | CAIX…5VXW |
| Stealth announcer | CB2Y…QGCS |
| Relayer registry | CBTH…Q3ND |
Caution
Report vulnerabilities through SECURITY.md, not public issues. The browser key-storage threat model is in docs/GHOST_THREAT_MODEL.md. Mainnet use is blocked until the security register is signed off.
Opaque is licensed under GPLv3.
Every transaction deserves the right to be private.