Skip to content
This repository was archived by the owner on Jul 17, 2026. It is now read-only.

opaquecash/stellar

Repository files navigation

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.

Opaque

Private money and verifiable trust on Stellar, enforced by zero-knowledge proofs, not by promises.

npm License: GPLv3 Stellar Soroban Live on testnet

Live app · Demo video · Docs · SDK on npm · Contracts on-chain

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.

What to believe in three minutes

If you only take three things from this project, take these:

  1. It works. Real Soroban contracts on Stellar testnet, a real browser wallet, real Groth16 proofs. Not a slideshow. Verify any flow on stellar.expert.
  2. 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.
  3. It is honest. Every trust assumption in this MVP is written down in plain language below, not hidden.

Where the zero knowledge does the work

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.

Live demo: verify it on-chain

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.

What Opaque does

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.

The one flow that ties it together

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.

Build on it: the SDK

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/stellar
import { 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.

What works today

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.

Honest trade-offs

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 cases

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.

Try it

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 dev

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

Protocol services

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

Architecture

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.

On-chain contracts (Stellar testnet)

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

Security

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.

License

Opaque is licensed under GPLv3.

Every transaction deserves the right to be private.

About

Stellar Implementation of the Opaque Protocol

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors