Architecture
The Solana program, the wallet, the prover, QFire, the verifier nodes, and the read services.
Sombra is one Solana program and a handful of services around it. The program owns the money. Everything else helps you prove you may move it.
The Solana program
One program owns the shielded state. For each pool it holds:
- The commitment tree. Every note's commitment, as a leaf. The program keeps a ring of recent roots so a proof built a moment ago still verifies.
- Nullifier markers. One small account per spent note. Its existence is what makes a second spend fail.
- Vaults. A token account per mint, holding the public tokens backing the notes.
- The verifier registry. Who may vote, and how many votes an action needs.
- Pending actions. An action that has been opened but not yet finalised.
What the program does with a spend:
- Accepts the action, with its root, nullifiers, and output commitments.
- Counts verifier votes until the action reaches quorum.
- On finalisation, records every nullifier, appends the output leaves, pays any withdraw and any fee, and closes the action.
The program never sees an amount, an owner, or a recipient. It sees commitments, nullifiers, and votes.
The program does not verify the proof
This is the most important thing to understand about Sombra, so it gets its own heading.
The Solana runtime cannot verify a RISC Zero receipt. There is no syscall for it and the proof is far too large to post. So the proof is verified off-chain, by a registered set of verifier nodes, which then vote on-chain. The program counts the votes.
The verifier set is a trust assumption of the design. A quorum that colluded, or that ran a modified verifier, could vote for an action no honest verifier would accept. Nothing on-chain would catch it. This is the real cost of putting a STARK behind a Solana program today, and it is stated here rather than buried.
What limits the damage: every node checks the receipt against a pinned image ID, the hash of the exact proving program. A receipt from any other program fails. The pinned ID is part of the release, so a change to the proving logic is a visible change to the software every node runs.
The wallet
The wallet holds your recovery phrase and derives every key from it. It runs on your device and does three things:
- Finds your notes. It reads the published payloads and tries to decrypt them. The ones that open are yours.
- Keeps your balance locally. Your notes, decrypted, on your machine.
- Builds spends. Picks input notes, builds the outputs, gets a proof, opens the action, and uploads the payloads your recipient needs.
Keys never leave the device. No signature ever authorises a spend.
Builders use the same logic through the SDK: @sombra/client for the flow, @sombra/crypto for the primitives, @sombra/keyring for key handling. The client talks to the chain, QFire, and a prover through small interfaces you supply, so you can host your own.
The prover
Proving a spend is heavy work. The prover takes a witness and returns a verified receipt.
Your wallet sends a witness to a prover we run, and gets back a verified receipt. Proving on your own machine is not available in V2 at the moment.
The witness holds everything the proof needs, and that includes your spend key. For as long as the proof takes, the prover is handling the key that moves your money.
So it runs inside a hardware trusted execution environment, on NVIDIA Confidential Computing GPUs. The witness sits in encrypted memory the host cannot read — not the operator, not us, not anyone with root on the machine. That is enforced by the silicon rather than by how we promise to behave.
We will publish remote attestations shortly, so you can confirm that the prover you reached is genuine and is running the code we published.
The proof never goes on-chain.
QFire
QFire is the door. The indexer has no public address of its own, so every request to it — yours and every node's — arrives through QFire, which also fronts the Solana RPC and streams program events.
Your wallet needs no key for it: the wallet lane is open, the same public reads and the same public upload as before, just at one address instead of two. Nodes get their own lane, authenticated with post-quantum ML-DSA-65 keys, so node traffic stays separate from everyone else's and a node's view of the chain cannot be quietly starved.
One door is easier to defend than two. It is also the only place where rate limits, bad requests and abuse have to be handled, instead of in the service holding the data.
The verifier nodes
A node does one job: verify a receipt and vote.
- The set is a registered list of operators. The admin registers and deactivates seats.
- Each node checks the receipt against the pinned image ID, then checks that the journal matches the action that was opened on-chain.
- A node votes with a Solana key that rotates on every vote, derived by hash, so its votes are not trivially linkable to each other.
- When enough nodes have voted, the action can be finalised. That step has no gatekeeper: anyone can send it.
Nodes keep no state of their own. They re-read the chain on boot and pick up any action still waiting.
The read services
The indexer follows the program and stores the leaves, the actions, the encrypted payloads and the published weekly keys. Your wallet syncs from it, through QFire, instead of replaying the chain.
It is also where you upload. The receipt and the encrypted payloads go there, with no account and no key — the upload is public data either way. That makes it part of delivery, not only of reading: until the payload is there, the nodes have nothing to verify and your recipient cannot find their note. Nothing is lost if it misbehaves, though. A payload it refuses to serve can be uploaded again, and everything it holds can be rebuilt from the chain.
The relayer submits transactions for you. It pays Solana's gas and takes the relayer fee you set, which can be zero.
It is how you should send everything except a deposit. Submitting a transaction yourself means paying for it from your own public Solana account, which puts your public identity next to the action for anyone watching. When the relayer submits, none of your keys touch the transaction. You also need no SOL at all. See Fees and the relayer.
Neither service can move your money. The indexer serves public data, and the relayer only forwards what your wallet already signed off on.
How a transfer runs
In order:
- Your wallet picks the input notes and builds the outputs.
- It sends the witness to a prover and gets a receipt back, then checks the receipt itself.
- The relayer opens the action on-chain for you: the root, the nullifiers, and the output commitments. (You can submit this yourself, at the cost of attaching your public account to it.)
- The receipt and the encrypted payloads go up through QFire to the indexer, so the nodes can read them and your recipient can find their note.
- The nodes read the bundle back through QFire too, verify it, and vote. A node signs its own vote, and anyone can pay to put that vote on chain.
- At quorum, anyone can finalise: the nullifiers are recorded, the leaves appended, and any withdrawal or fee paid.
- Your wallet sees the new leaf and your change note.
From step 3 onward nothing needs you. Steps 5 and 6 have no gatekeeper, so the payment finishes whether or not your wallet is still open.
Every step is stored before the next one starts, so a spend that is interrupted resumes where it stopped rather than stranding your notes.
Not in Phase 1
- Deposit privacy. Moving public tokens in reveals the link between your public wallet and your first note. Spending inside the pool is private. The way in is not.
- A permissionless verifier set. Today the set is registered by the admin. Staking and open participation are not built.
- Mainnet. Devnet only.
Next: Security — the full threat model, including what each of these means for you.