01
Overview
Shroud gives an agent, or a person, three things that work together.
Zero-knowledge proofs
Shielded pools break the link between the wallet that puts money in and the wallet that takes it out. The withdrawal carries a Groth16 proof, made on your own machine, that one of the deposits is yours without saying which. The program checks it on chain.
The proofA private network
Relays and gateways break the link between your IP and what you ask for: a relay sees who, a gateway sees what, and no single server sees both.
Relays and gatewaysAn interface for AI agents
One MCP server with seven tools. Any agent that speaks MCP can shield, send privately, swap from a fresh wallet and read the chain through the network, with no account and no API key.
For AI agents02
Pools and notes
Each pool takes one size only. Because every deposit in a pool is the same amount, one deposit cannot be told from another by its size.
- 0.1SOL
- 1SOL
- 10SOL
- 100USDC
- 1,000USDC
When you shield, your device makes a note. Only its hash goes on chain, as the next leaf of the pool's Merkle tree (depth 20, so 1,048,576 deposits per pool).
- Note
- Two random numbers made on your device: the nullifier and the secret. The note is the money.
- Commitment
- A hash of the note, and the only part of it that goes on chain.
- Nullifier hash
- Shown when you withdraw, so the same note can never be spent twice.

03
The proof
To withdraw, your device makes a Groth16 zero-knowledge proof (circom, Poseidon hashing) that says, without saying which deposit:
I know the note behind one of the commitments in this pool's tree.
rootThat commitment is also in the clean set the keeper published.
assocRootHere is its nullifier hash, so the same note can never be spent twice.
nullifierHashThe recipient, the gateway and its fee are exactly these.
extDataHash

The program verifies it on chain with Solana's alt_bn128 syscalls (about 116,000 compute units for a SOL withdrawal), records the nullifier, and pays out of the pool in the same transaction.
Making the proof takes about 1.5 seconds for an agent. A full private send from the browser took 3.8 seconds on a desktop and 4.0 seconds on a phone in our rehearsal.
Nobody can redirect it
The fourth input is a hash of the recipient, the gateway and its fee. Change any of them and the proof fails, so whoever carries it cannot redirect the money.

04
Relays and gateways
A withdrawal still has to reach the chain, and whoever sends it learns your IP. Shroud splits that job in two, following Oblivious HTTP (RFC 9458).
| Role | Sees | Never sees |
|---|---|---|
| Relay | Your IP, a sealed blob, which gateway it goes to | What is inside |
| Gateway | The request: a withdrawal, a balance check, a swap quote | Who sent it |

Your device seals each request to a gateway's HPKE key (X25519, HKDF-SHA256, AES-128-GCM) and hands it to a relay run by a different operator. Both are registered on chain with their endpoint, key and bond, so the list comes from the program, not from us.
The gateway also acts as the relayer: it pays for the withdrawal transaction and takes the fee bound into the proof, so the receiving wallet needs no SOL and never signs.
What a gateway answers
A fixed list of calls.
- Solana reads (
getProgramAccountsonly for the Shroud program) - Sending transactions
- Jupiter quotes and swaps
- The clean set
- Withdrawals
05
Clean set and ragequit
A pool that takes anyone's money ends up holding stolen money, and then everyone in it looks guilty. Shroud uses an association set, as in Privacy Pools: a keeper reviews deposits and publishes the root of the ones it approves. A withdrawal must prove membership in that set as well as in the pool.
- New deposits wait for a 30-minute review before they are approved.
- Deposits from wallets flagged for hacks or theft are left out. If one is flagged after approval, the keeper resets the set and every older root stops working.
- The keeper serves the approved list; your device checks it against the root on chain, so it cannot hand you a set it did not publish.

Ragequit
The wallet that made a deposit can always take it back, in full, to itself, in public. Nobody's money is stuck, and the private exit stays closed to stolen funds.
The admin can pause new deposits and node sign-ups. Withdrawals and ragequits have no pause in the program.
06
Fees
A private withdrawal pays two fees. Ragequit has no fee, and a deposit costs only the Solana transaction and a small deposit record.
- Gateway fee
- Set by each gateway and bound into your proof. Defaults: 0.002 SOL in SOL pools (covers the nullifier account and the transaction), 0.6 USDC in USDC pools (also covers opening the recipient's token account).
- Protocol fee
- 0.3% of the pool size, paid in the same transaction to the buyback address. Every hour that address spends what it holds (keeping a little SOL for its own fees) on $SHROUD through Jupiter and burns all of it, starting when the token launches. The program caps the fee at 1%.
Buyback address. Every swap and every burn is a public transaction from it
8Pna6caENb2RF9Zqjqy9v8AkXk7PBAkYMC2FSTtiDTgRWhat arrives
For each pool, with the default gateway fee.
| Pool | Gateway fee | Protocol fee, 0.3% | Arrives at the new wallet |
|---|---|---|---|
| 0.1 SOL | 0.002 SOL | 0.0003 SOL | 0.0977 SOL |
| 1 SOL | 0.002 SOL | 0.003 SOL | 0.995 SOL |
| 10 SOL | 0.002 SOL | 0.03 SOL | 9.968 SOL |
| 100 USDC | 0.6 USDC | 0.3 USDC | 99.1 USDC |
| 1,000 USDC | 0.6 USDC | 3 USDC | 996.4 USDC |

07
For AI agents (MCP)
The Shroud CLI is the npm package shroud-sol and includes an MCP server. Nothing to install: point it at the agent's wallet keypair and add it to any MCP client.
claude mcp add shroud -e SHROUD_WALLET=~/agent.json -- npx -y shroud-sol mcp{
"command": "npx",
"args": ["-y", "shroud-sol", "mcp"],
"env": {
"SHROUD_WALLET": "/path/agent.json"
}
}The zero-knowledge proofs are made on the agent's machine, in about 1.5 seconds, with the proving key shipped in the package. There is no account and no API key.
Seven tools
- shroud_pools
- Pools, sizes, deposits, withdrawals.
- shroud_shield
- Deposit from the agent wallet; the note goes to ~/.shroud/notes.json (0600).
- shroud_notes
- Each stored note: in the pool, in the clean set, spent.
- private_send
- Withdraw a note to any address through relay → gateway.
- private_swap
- Withdraw a SOL note into a new wallet and buy a token from it via Jupiter, every call through the network. The new wallet’s key is stored with the note.
- private_rpc
- Any read-only Solana call through the network.
- shroud_ragequit
- Take a deposit back to the agent wallet, in public.

08
Run a node
Anyone can run a relay, a gateway or both. Registration locks a bond: SOL until $SHROUD exists (0.5 SOL today), then $SHROUD staked into an account the node owns. Leaving starts a seven-day cooldown, after which the whole bond comes back.
shroud register --operator op.json --hpke-seed hpke.key \
--endpoint https://node.example.com --roles relay,gatewayshroud node --operator op.json --hpke-seed hpke.key --port 7401 \
--upstream <your RPC> --asp <clean-set keeper URL>- A gateway
- Earns the fee of every withdrawal it sends and pays its transaction costs from the operator wallet. It keeps no logs of requests.
- A relay
- Earns nothing directly today; it is the half of the network that keeps gateways from seeing IPs. Running both roles under one operator gives no extra privacy, so clients always pick a relay and a gateway from different operators.
- The admin
- Can switch a misbehaving node off. Its bond stays its own.
09
What you trust
- The trusted setup
- Groth16 needs a one-time ceremony. If every participant colluded, they could forge withdrawals. Mainnet keys come from a public ceremony on top of a public powers-of-tau; one honest participant is enough. Read the transcript
- The program and whoever can upgrade it
- Until the upgrade authority is given up, its holder can change the code. Check the authority on any explorer before you deposit size.
- The keeper
- Decides which deposits are in the clean set. It can delay or exclude your deposit, never take it: ragequit always works.
- Two operators not colluding
- A relay and a gateway that share their logs could link an IP to a request. Clients pick them from different operator keys. At launch the first relay and gateway are run by the team, so this protection becomes real as independent operators join; the node list on chain shows who runs what.
10
Trusted setup
The proving key on mainnet comes from a phase-2 ceremony on top of the ppot_0080_15.ptau powers of tau. Each contribution below added randomness that was thrown away, and a beacon from a Solana blockhash closed it; the key is safe if any one contributor really threw theirs away.

The transcript
- Powers of tauppot_0080_15.ptauSHA-256
4a8eb5a754ed4710d6ad603c2e1a89c72325a448e4474f8694d2c71c7c272f3dChecked in full (every contribution) with snarkjs on 2026-11-11.
- Shroud team, contribution 1Contribution hash
f556dba7 9eccee4b 9adfd7fd 11cf130a 401744ef 40b3a4d1 46c1aa5d af0785db e1aff5b2 11998d99 59a8c9b8 1e028b4d 8f925eb5 e291c044 76b4be77 8fa18bbb - Shroud team, contribution 2Contribution hash
17ac393d ece8e378 7ebc81f4 2a1f2290 739cbb21 00c4a02c b075d6d3 d9055bf3 4fc3acb3 8dba8d57 b5f5612f d9de191e bfe5d828 837a6d1e 60c8f37f 871c4f97 - BeaconValue
cd26d81cb4d18a46ec8fe6b703eed75d623976bd6cb8dd7c5be43024ec8aef7aThe blockhash Eopw4NmiT2drJTUZnPuFwVFBcbPv37hMUKCWXvUFrMXw of Solana mainnet slot 467990330 (picked at 2026-11-11T04:32:04Z, when the finalized tip was slot 467990180). 10 iterations.
- Verifying keySHA-256
45942b7602f11c1031eb7c5c2d6cf79afcc5199ce353aa02b18c5cff60d6f4b1The key the program was built with: /zk/verification_key.json
Check it yourself
This replays every step against the circuit and the public powers of tau.
node scripts/ceremony.mjs verify11
What it does not hide
Shielded pools break the link between a deposit and a withdrawal. These stay visible, with Shroud or without.
- That you deposited
- From which wallet, and when.
- Timing
- Depositing and withdrawing a minute apart into a quiet pool narrows it down to a few people. Wait, and let deposits pile up between.
- Habits
- Withdrawing to a wallet you already use, or always the same amount right after you deposit, links them by behaviour.
- Your IP, when you deposit
- Not on chain, but your own wallet's RPC sees it when you deposit through it. Withdrawals and reads through the network do not reveal it.
12
Program
- Program id
- D2XCVLhRjztzr7cJVEXV2taMGUyCCemcTSpyZzvsnoCV
- Deployed
- Slot 467992659, 2026-11-11
- Executable hash
- c9112f851a3a3f28e57f858546f86d3f143771b18a236ea027c73c8e33b8f656
- Verifier
- Groth16 over BN254, 4 public inputs
- Tree
- Poseidon, depth 20, 30 recent roots
- Clean-set roots kept
- 16
Instructions
- deposit
- withdraw
- ragequit
- set_assoc_root
- reset_assoc_roots
- register_node (SOL or staked)
- update_node
- leave_node
- reclaim_node
- admin config