Occlude Docs
Occlude is a privacy Protocol Layer anchored to Solana. It adds a shielded pool where SOL and SPL tokens move privately. Validators verify Groth16 ZK proofs in under 10 ms on commodity hardware. Settlement finality equals Solana finality — the Anchor program on-chain holds the canonical merkle root and nullifier set.
Devnet is live. Connect with @occlude/sdk and the devnet RPC. Mainnet opens after MPC ceremony, external audit, and output-note compute benchmarks.
What Occlude is not
Not a sidechain. Not a new L1. Occlude does not issue a gas token or run its own consensus chain. Proof verification and state transitions happen offchain; the on-chain Anchor program acts as the settlement layer.
Architecture
Occlude combines an offchain prover network with an on-chain Anchor program. The prover network batches shielded transactions and generates Groth16 proofs. The Anchor program verifies each proof and updates the merkle root and nullifier set on Solana.
Anchor program
The on-chain program holds two data structures:
- Merkle tree — Poseidon-hashed commitment tree. Each deposit adds a leaf. Depth 20 supports ~1M simultaneous notes.
- Nullifier set — Every spent note emits a nullifier. The program rejects duplicate nullifiers, making double-spend structurally impossible.
Proof system
Groth16 with BLS12-381 curve and Poseidon hash. A proof encodes validity of a state transition without revealing inputs. Proof size: ~192 bytes. Verification time: ~8–12 ms on a single CPU core. Any laptop qualifies as a verifier node.
Validator network
10 validators run BFT consensus with 7-of-10 threshold. Each validator independently verifies incoming proofs, signs state updates, and broadcasts to peers. The Anchor program accepts a state update once it carries ≥7 valid signatures.
Data flow
User submits tx → Validator pool receives note + proof
→ 7-of-10 validators verify Groth16 proof
→ Threshold signature produced
→ Anchor program verifies signature + proof on Solana
→ Merkle root updated, nullifier recorded on-chain
→ Solana finality ≈ settlement finality
Primitives
Occlude exposes three shielded operations. All three produce Groth16 proofs verified by the validator set before the Anchor program updates state.
| Primitive | Input | Output | On-chain effect |
|---|---|---|---|
| deposit | SOL / SPL amount + shielded pubkey | Encrypted note | New leaf in merkle tree |
| transfer | Input note(s) + recipient shielded address | New encrypted note(s) | Nullifier added, new leaf committed |
| withdraw | Note + Solana destination address | SOL / SPL to destination | Nullifier added, funds released |
Notes
A note commits to four fields: (value, token_mint, pubkey, blinding_factor). Notes are encrypted to the recipient's shielded key and stored off-chain. Only the note commitment (Poseidon hash) lives on-chain.
Nullifiers
Spending a note produces a nullifier: nullifier = Poseidon(note_preimage, spend_key). The nullifier is published on-chain. Attempting to reuse a note fails at the Anchor program — no validator cooperation required to prevent double-spend.
Privacy model
On-chain, only three things are public per transaction: a nullifier, a new commitment leaf, and the Groth16 proof. Sender, receiver, amount, and token type are all hidden inside the ZK circuit.
What the proof encodes
- The spending party knows the note preimage (value, pubkey, blinding factor).
- The commitment in the merkle tree matches the note.
- The nullifier is correctly derived from the note and spend key.
- The output commitment correctly encodes the transferred value.
- No value is created or destroyed (conservation constraint in-circuit).
What validators see
Validators receive the proof, the public inputs (nullifier, new commitment, root), and the encrypted note for the recipient. They verify the proof is valid. They cannot infer sender, receiver, or amount from these inputs.
Trusted setup
Groth16 requires a one-time trusted setup (MPC ceremony) to generate proving and verification keys. This is Mainnet Gate 1. The ceremony requires ≥100 independent participants — a single honest participant is sufficient to destroy toxic waste and make the setup sound.
Validators
Validators are the BFT verification layer between users and the Anchor program. They verify proofs, cosign state updates, and submit batches on-chain.
Admission
New validators earn a rank on the public testnet before admission to the mainnet set. Rank is derived from uptime, correct verification history, and latency. This gates Sybil attacks without a permissioned whitelist.
Consensus
7-of-10 BFT threshold. Each validator signs a state update independently. The Anchor program verifies the threshold signature on-chain before accepting any state change. A minority coalition of up to 3 validators can delay but not corrupt state.
Slashing
Two slashable conditions:
- Equivocation — signing conflicting state updates at the same height.
- Persistent unavailability — missing more than a configurable fraction of rounds.
Slashing is executed by the Anchor program. Stake is locked at registration and reduced on proven violation.
Fee model
Protocol fees accumulate in the Anchor program's fee account. Validators call claim_rewards proportionally to their participation weight. No founder allocation. No protocol treasury cut.
Developer guide
Occlude ships an SDK for Node.js and browser environments. The devnet endpoint is available now. No API key needed for devnet.
Install
npm install @occlude/sdk
Connect to devnet
import { OccludeClient } from '@occlude/sdk';
const client = new OccludeClient({
network: 'devnet',
// rpcUrl: 'https://api.devnet.solana.com', // optional override
});
Generate a shielded keypair
import { generateShieldedKeypair } from '@occlude/sdk';
const { publicKey, secretKey } = generateShieldedKeypair();
// Store secretKey securely — it controls all notes created for publicKey
Deposit
import { Connection, Keypair } from '@solana/web3.js';
const solanaWallet = Keypair.generate(); // your Solana wallet
const { note, txSignature } = await client.deposit({
wallet: solanaWallet,
amount: 1_000_000, // lamports
token: 'SOL',
to: publicKey, // shielded recipient
});
console.log('Note commitment:', note.commitment);
console.log('Solana tx:', txSignature);
Transfer
const { newNote, txSignature } = await client.transfer({
note, // input note from deposit (or prior transfer)
secretKey, // spend authority
to: recipientPublicKey,
amount: 500_000, // must be ≤ note.value
});
Withdraw
const { txSignature } = await client.withdraw({
note,
secretKey,
to: solanaDestinationAddress, // standard base58 pubkey
});
console.log('Withdrawn:', txSignature);
Note storage. Notes are not stored on-chain. You are responsible for persisting note data. Losing a note means losing the funds it represents. Use note.encrypt(publicKey) to store an encrypted blob.
SDK reference
OccludeClient
| Method | Parameters | Returns |
|---|---|---|
deposit(opts) |
wallet, amount, token, to |
{ note, txSignature } |
transfer(opts) |
note, secretKey, to, amount |
{ newNote, txSignature } |
withdraw(opts) |
note, secretKey, to |
{ txSignature } |
getMerkleRoot() |
— | Buffer (32 bytes) |
getNullifierSet() |
— | Set<string> |
isSpent(nullifier) |
nullifier: Buffer |
boolean |
Note
| Field / Method | Type | Description |
|---|---|---|
note.commitment | Buffer | Poseidon hash of note preimage |
note.value | bigint | Amount in base units |
note.token | string | Token mint address or 'SOL' |
note.encrypt(pubkey) | Uint8Array | Encrypt note to storage |
Note.decrypt(blob, sk) | Note | Restore note from encrypted blob |
Utilities
import { generateShieldedKeypair, poseidonHash, groth16Verify } from '@occlude/sdk';
// Verify a proof locally without submitting
const valid = groth16Verify(proof, publicInputs, verificationKey);
// Compute a commitment manually
const commitment = poseidonHash([value, pubkeyScalar, blindingFactor]);
Security
Threat model
Occlude assumes an honest majority of validators (7-of-10). A minority coalition can delay state updates but cannot:
- Steal funds — the Anchor program enforces nullifier uniqueness on-chain.
- Forge proofs — Groth16 soundness holds under the DLOG hardness assumption.
- Reverse finality — once the Anchor program accepts a root update, Solana's consensus guarantees it.
Known limitations (devnet)
- Trusted setup not yet complete. Devnet uses a local ceremony for testing only.
- Note storage is client-side. No recovery service exists yet.
- Front-running at the validator layer is possible in adversarial conditions (timing-based). Mitigated on mainnet by encrypted mempool.
Audit scope
Mainnet Gate 2 requires an external audit covering the Anchor program, the ZK circuit, and the validator consensus logic. Audit firm TBD — selection announced on @paraloomlabs.
Bug reports
Open an issue at github.com/paraloom-labs/paraloom-core. Critical vulnerabilities: email directly before public disclosure.
Roadmap
Mainnet requires three gates to pass in sequence. Each gate has an objective completion criterion — no date targets, no soft promises.
Groth16 trusted setup with ≥100 independent participants. A single honest participant destroys toxic waste. Open participation — anyone can join.
Independent security audit of Anchor program, ZK circuit, and validator consensus. Full report published before mainnet.
Final performance benchmarks for output note generation at production scale. Throughput target: ≥50 private transfers/sec on the validator set.
Currently on devnet
- Full Groth16 circuit (deposit, transfer, withdraw)
- 10-validator BFT set (permissioned devnet)
- Anchor program v0.1 deployed on Solana devnet
@occlude/sdkv0.1 (Node.js + browser)- Proof verification in ~10 ms (commodity hardware)
After mainnet
- SPL token support (ERC-20 equivalents on Solana)
- Encrypted mempool (validator-side front-run protection)
- Cross-program invocation from Solana programs into the shielded pool
- Mobile SDK
Follow progress at github.com/paraloom-labs/paraloom-core. All development is open source, MIT licensed.