Skip to content

The Pool Coin

All private value sits in one Bitcoin output: a pay-to-taproot (P2TR) output whose internal key is a “nothing up my sleeve” point that nobody can sign for. So there is no key path, and no admin key. The only way to spend the pool coin is to satisfy one of its script leaves, and every leaf is a version 0xc2 tapscript, the leaf version Great Script Restoration adds.

A script cannot remember anything. So the state rides along in the transactions:

  • Each settlement writes a hash of the new state into a small output called the caboose: an OP_RETURN wrapped in a P2WSH script, 330 sat.
  • The next settlement’s leaf rebuilds the previous transaction from data in its witness, checks that it hashes to the coin it is spending, and so reads the old state, the pool’s value and the epoch counter.

The state is the pool’s roots:

roots = H(notes_root, nullifier_root, next_note, next_nullifier)
with the window of the last 16 roots and the payroll queue folded in
roots_h = SHA256(roots)

(The window lets a wallet prove against a slightly old root. The payroll queue belongs to the payroll layer.)

The pool’s value V is an ordinary amount on an ordinary output. Anyone can read it. The settlement leaf requires

V' = V + deposits − payouts − fees

where the three totals come from the proof. fees here is what the miner receives: there is no fee output, so the difference between inputs and outputs pays the miner, as in any Bitcoin transaction. An overdraw fails in script.

Bitcoin’s own rule, that a transaction cannot create value, is the outer wall. Whatever a proof says, the pool can never pay out more than V.

Every leaf checks that the coin it spends descends, through a chain of pool transactions, from one genesis transaction. A coin someone sends to the pool’s address from anywhere else is not the pool and cannot act as it. This matters because deposits come in through an inbox that pays to the pool’s address: without the check, a fake pool coin at the same address could try to claim them.

Each settlement adds one to an epoch counter. Every sixth epoch is a drain: that settlement must also spend the exit inbox and process every queued exit request (see exits). The leaf checks this with OP_MOD, so a settlement that skips a drain is invalid. A drain happens even when no request is waiting. Since 2026-11-06 a drain can also take the deposit inbox, so waiting deposits’ fees pay for it.

Script has no loops. So the pool has one leaf per transaction shape:

  • how many records the settlement publishes (from 1 to the pool’s bound, fixed when the pool is made: 1,750 by default, the prover’s cap);
  • what the previous transaction was (the genesis, a settlement or a solo exit);
  • whether a deposit inbox is spent;
  • whether it is a drain, with or without the deposit inbox;
  • whether it migrates notes to a successor pool.

At the default bound a pool has about 21,000 leaves (12 shapes × 1,750 record counts, plus the exits). A node builds them once when it starts, in parallel (57 s on four shared cores, measured 2026-11-09), and keeps only their hashes. Only the leaf a settlement uses is revealed on chain. The leaves sit in a balanced tree, so the proof that a leaf belongs to the pool grows by one 32-byte hash each time the leaf count doubles: 21,000 leaves make the tree 15 levels deep where a 64-record pool’s 1,687 make it 11, so the full bound costs about 32 vB more per settlement (four hashes, at the witness discount). Every leaf is checked for forbidden opcodes by the repository’s own BIP-440/441 interpreter as it is built. The GSR authors’ gsr check tool is run at a 64-record bound, since a leaf for more records repeats the same per-record block and adds no new opcode sequences. Its last recorded run, on 2026-11-03, passed 3,364 distinct leaves with no errors, warnings or lints. Retired pool kinds were removed after that, and the same bound now gives 1,687 distinct leaves; no run on that set is recorded yet.

The pool’s address is a hash of all its leaves. Change one byte of one leaf and you get a different pool. That is why a pool can never be changed in place: new rules mean a new pool, and moving to it is opt-in (see upgrades). It is also why the repository’s refactors are checked to keep every leaf byte-identical.