Private SOL transfers using a Noir circuit for proof generation and a Pinocchio-based on-chain program for deposit/withdraw logic. Proofs are verified on-chain via a Sunspot Groth16 verifier program.
Shielded Pool (repo)
├─ noir_circuit/
│ ├─ nargo execute -> witness (.gz)
│ └─ sunspot prove -> proof (.proof) + public witness (.pw)
├─ verifier program (Sunspot Groth16)
│ └─ verifies proof + public witness
└─ shielded_pool_program/
├─ initialize/deposit/withdraw
├─ checks root/nullifier/recipient/amount
└─ CPI to verifier program
DISCLAIMER: This repository has not been audited. Use at your own risk.
- Initialize: relayer creates state + vault PDAs (fee payer = relayer).
- Deposit: sender transfers SOL into the vault and updates the Merkle root.
- Withdraw: relayer submits proof, program verifies the proof, consumes the nullifier, and releases SOL to the recipient.
Privacy comes from the ZK proof: the withdraw does not require the sender to sign, and the nullifier prevents double spend.
This is not a full anonymity system for SOL transfers on its own. Practical privacy depends on:
- Pool size and volume (more deposits/withdrawals = larger anonymity set)
- Transaction frequency and timing correlation
- Unique wallet count interacting with the pool
- Delay between deposit and withdraw
- Fixed denominations (UI/UX should encourage standardized amounts)
- Nargo
1.0.0-beta.13 - Sunspot (Go 1.24+)
- Solana CLI
- Node.js 18+ (for the client)
Example setup:
# Noir
noirup -v 1.0.0-beta.13
# Sunspot
git clone https://github.com/reilabs/sunspot.git ~/sunspot
cd ~/sunspot/go && go build -o sunspot .
export PATH="$HOME/sunspot/go:$PATH"
export GNARK_VERIFIER_BIN="$HOME/sunspot/gnark-solana/crates/verifier-bin".
├── noir_circuit/ # Noir circuit + proving artifacts
│ ├── src/main.nr
│ ├── Prover.toml
│ └── target/ # .json/.ccs/.pk/.vk/.proof/.pw
├── shielded_pool_program/ # Pinocchio program (initialize/deposit/withdraw)
├── client/ # TS integration test + helper scripts
└── keypair/ # Local keypairs (ignored by git)
Create the sender and relayer keypairs:
solana-keygen new --outfile keypair/sender.json --no-bip39-passphrase -s
solana-keygen new --outfile keypair/relayer.json --no-bip39-passphrase -s
solana airdrop 2 $(solana address -k keypair/sender.json) --url devnet
solana airdrop 2 $(solana address -k keypair/relayer.json) --url devnetIf you modify the circuit or clone this repository for the first time, you need to generate the artifacts:
cd noir_circuit
nargo compile
nargo execute
sunspot compile target/shielded_pool_verifier.json
sunspot setup target/shielded_pool_verifier.ccs
sunspot prove target/shielded_pool_verifier.json target/shielded_pool_verifier.gz target/shielded_pool_verifier.ccs target/shielded_pool_verifier.pk
sunspot deploy target/shielded_pool_verifier.vksunspot deploy outputs a verifier program .so you can deploy to Solana.
solana program deploy path/to/verifier.so --url devnetBefore building, update the verifier program ID in
shielded_pool_program/src/instructions/withdraw.rs to match the verifier you just deployed.
cargo build-sbf --manifest-path shielded_pool_program/Cargo.toml
solana program deploy shielded_pool_program/target/deploy/shielded_pool_pinocchio.so --url devnetThe client requires program IDs via env vars.
RPC_URL=https://api.devnet.solana.com \
ZK_VERIFIER_PROGRAM_ID=<verifier_program_id> \
SHIELDED_POOL_PROGRAM_ID=<shielded_pool_program_id> \
pnpm --dir client run test-shielded-pool- Fee payer: the relayer pays transaction fees for initialize/withdraw.
- Sender privacy: the sender signs only the deposit. Withdraw uses proof verification and nullifier checks instead of a sender signature.
- Proof size: current proofs are 388 bytes, plus a 140-byte public witness.