> For the complete documentation index, see [llms.txt](https://parad0xlabs.gitbook.io/parad0xlabs-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://parad0xlabs.gitbook.io/parad0xlabs-docs/dark-null-protocol-zk-privacy-and-identity/shielded-pool.md).

# Shielded Pool

**For someone new to crypto:** every payment on a public blockchain is like writing a cheque in permanent ink that anyone in the world can read forever — they can see your wallet, the other wallet, and the exact amount. A *shielded pool* is a way to break that link.

Imagine a coat-check room. You hand over a coat and get a numbered ticket. Later, someone walks up with a matching ticket and collects *a* coat. Nobody watching can tell whose coat went to whom, or that the two visits were even related. A shielded pool does the same thing with money: you put value **in** under a secret, and later you (or anyone you give the secret to) take value **out** — and there is no public link between the deposit and the withdrawal. The watcher learns that money entered the pool and money left the pool, but **not who paid whom, and not how much**.

The one piece that *is* public, on purpose, is a small "spent" marker called a **nullifier**. It proves a note was used exactly once — so nobody can spend the same deposit twice — without revealing which deposit it was.

**For the technically inclined:** the pool is a Groth16 zk-SNARK over the BN254 curve. A **deposit** records a *note* — a Poseidon commitment — as a leaf in an on-chain Merkle tree (the anonymity set). A **withdrawal** submits a Groth16 proof of the statement *"I know the secret behind some unspent note in this tree"*, together with a **nullifier** deterministically derived from that secret. The program checks the proof against the current Merkle root, and if the nullifier has not been seen, records it and releases the funds. The proof reveals nothing about *which* leaf was opened, so deposit and withdrawal are cryptographically unlinkable, and the amount is hidden inside fixed-denomination notes.

> **Status:** this pool is a **devnet prototype**, not a live mainnet system. The on-chain program ships in a deliberately **fail-closed** state (deposits rejected, withdrawals denied) until the verifying key, circuit, and curve dependency below are finalized. The **Status** section at the bottom marks the line between what runs and what is still design. Public Beta, non-custodial, not yet audited.

## What it hides, and what it does not

| Visible on-chain                          | Hidden                                                  |
| ----------------------------------------- | ------------------------------------------------------- |
| That a deposit happened, into which pool  | **Which** note a withdrawal spends                      |
| That a withdrawal happened                | The **link** between any deposit and any withdrawal     |
| The nullifier (a one-time "spent" marker) | **Who** the original depositor was                      |
| The fixed denomination of the pool        | The **amount** an individual paid (fixed notes blur it) |

The privacy is *statistical*: a withdrawal is hidden among all the un-spent notes in the tree. **The more deposits sit in the pool, the larger the anonymity set, and the stronger the privacy.** A near-empty pool hides almost nothing; a busy one hides a lot. This is an inherent property — not a knob we can turn.

## How a payment flows

```mermaid
sequenceDiagram
    actor Payer
    participant Pool as Shielded Pool (on-chain tree)
    participant Null as Nullifier Registry
    actor Recipient
    Payer->>Pool: Deposit value as a note = Poseidon commitment
    Note over Pool: note added as a leaf; Merkle root updates
    Note over Payer,Recipient: secret handed to whoever will withdraw (off-chain)
    Recipient->>Pool: Withdraw + Groth16 proof + nullifier
    Pool->>Pool: verify proof against current Merkle root
    Pool->>Null: record nullifier (reject if already spent)
    Pool->>Recipient: release funds — no link to the deposit
```

The secret behind a note never touches the chain. It is chosen client-side at deposit time and is the only thing that can produce a valid withdrawal proof. There is **no on-chain edge** drawn between the deposit transaction and the withdrawal transaction.

## The pieces of the construction

| Piece               | Role                                                          | What makes it private                                                                                         |
| ------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Note commitment** | `Poseidon(domain, secret, leaf_index)` recorded on deposit    | Hides the secret; reveals only an opaque field element                                                        |
| **Merkle tree**     | Holds every note commitment; its root is public               | A proof opens *some* leaf without saying which                                                                |
| **Groth16 proof**   | 256-byte BN254 proof of note knowledge + tree membership      | Zero-knowledge: proves the statement, leaks nothing else                                                      |
| **Nullifier**       | `Poseidon(domain, secret, pool_key)`, published on withdrawal | Deterministic from the secret, so double-spend is impossible; unlinkable to the commitment without the secret |

To stop a valid proof sitting in the mempool from being front-run and redirected, the withdrawal circuit binds the **recipient** and **pool id** into the proof's public inputs alongside the nullifier and Merkle root. A proof is therefore only valid for the destination it was built for.

## What "proven on devnet" actually means

On devnet we demonstrated the **end-to-end unlinkability property**: generating notes, building the Merkle tree, producing real Groth16 withdrawal proofs, and confirming that the resulting deposit/withdrawal pair carries **no on-chain link** between sender and recipient, with the amount masked by fixed denominations. That is the heart of the design, and it works in the prototype.

It is **not** a live payment rail. Here is the gap:

* The on-chain pool program is gated `IS_STUB = true` / `MAINNET_READY = false`. **Deposits are rejected and withdrawals fail closed** — this is intentional, so funds can never enter a pool they could not safely leave.
* The Groth16 **verifying key is a placeholder**. A real key requires a trusted-setup ceremony (see below).
* The Merkle-root construction in the deployed program is a placeholder; the production incremental Poseidon tree is the next build step.

## Caveats

**1. Single-party trusted setup.** Groth16 needs a one-time *trusted setup* to generate the verifying key. Until a proper **multi-party ceremony** finalizes, any key is **single-party** — whoever ran the setup could, in principle, forge withdrawals. We therefore keep the verifier fail-closed and will not enable real withdrawals on a single-party key. The ceremony is in-design, with no date.

**2. The mainnet verifier waits on BN254 native curve ops.** On-chain Groth16 verification needs Solana's native BN254 (`alt_bn128`) pairing syscalls — the heavier curve operations covered by the in-flight `alt-bn128` / SIMD-0302 work. This is the **same dependency** that the heavier Dark Passport circuits are waiting on. The verifier algorithm is real BN254 Groth16 today; it is the on-chain settlement of that proof on mainnet that is blocked on this curve support landing.

## Where it fits in Dark NULL

The shielded pool is the **sender-and-amount** privacy leg. It composes with the rest of the stack rather than standing alone:

| Pairs with                                                                     | Gives you                                                                                         |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| **Stealth addresses** (recipient privacy)                                      | Full unlinkability — sender, amount, *and* recipient all hidden                                   |
| **`dark_nullifier_record`** (shared anti-replay registry, **live on mainnet**) | One canonical place that enforces single-use across the protocol, so a note cannot be spent twice |

The pool hides the *sender* and *amount*; stealth addresses hide the *recipient*. Used together they close the loop. The nullifier registry they both lean on is the one component here that is already live on mainnet — the pool itself is the devnet prototype that will plug into it.

## Status

* **Live on mainnet:** the shared nullifier registry (`dark_nullifier_record`) used for single-use / anti-replay. Stealth addresses are the complementary recipient-privacy leg (documented separately).
* **Devnet (prototype):** end-to-end shielded-pool flow — note commitments, Merkle membership, and real Groth16 withdrawal proofs — demonstrating **sender-and-amount unlinkability**. The off-chain proof path and BN254 Groth16 verification algorithm are real.
* **In-design / not yet live:** the on-chain pool program is **fail-closed** (no deposits or withdrawals on mainnet); production incremental Poseidon Merkle tree; a **multi-party trusted-setup ceremony** (current verifying key is single-party); and on-chain mainnet verification, which is blocked on BN254 native curve ops (the `alt-bn128` / SIMD-0302 dependency, shared with Dark Passport).
* **Not claimed:** no mainnet shielded payments, no audit, no completion date.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://parad0xlabs.gitbook.io/parad0xlabs-docs/dark-null-protocol-zk-privacy-and-identity/shielded-pool.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
