Protocol
Proofs and circuits

Escape-hatch circuit

The proof that lets one note leave the pool without any attester, the eleven note values it discloses, what it proves and does not, and what the escape_hatch instruction computes itself.

The escape-hatch proof takes one note out of the pool to a public destination with no attester involved: no guardian node, screener, fee enclave, data endorser, zone authority or relayer. The price is that the note's contents become public.

The sequence below shows the whole operation. Only your device and the shielded pool program take part.

sequenceDiagram
participant U as Your device
participant P as Shielded pool
participant D as Destination
U->>U: prove the note and disclose it
U->>P: escape_hatch with the proof
P->>P: check roots, slot, proof
P->>P: stage the nullifier
P->>P: hold amount less the fee in escrow
Note over P,D: 48 hours later, anyone releases it
P->>D: amount less the fee

What it proves

The circuit is EscapeHatch(10, 8, 1, 4, 11, 8, 1, 8), with a note tree of 10 levels of 8 children and one level of 4 (2³² leaves), and a nullifier tree of 11 levels of 8 children and one level of 8 (2³⁶ leaves). It has 17,337 non-linear constraints. For the one note being spent it proves the following.

The note is real. Its kind is identity-owned or address-owned, and its amount is not zero. A dummy note cannot be escaped.

The prover owns it. For an identity-owned note, the owner is the identity commitment (sometimes called the user commitment) rebuilt from the prover's spending key, nullifier key and outgoing viewing key with their blinders and the other five slot leaves. For an address-owned note, the owner is the program owner commitment of the spender authority address in the public values, which the handler takes from an account that must sign. That address must be zero for an identity-owned note.

The token matches. The token address's two halves hash to the note's token identifier, so the program pays out the token the note actually holds.

The note exists. Its leaf, the note commitment wrapped with its insertion slot and position, is in the note tree at the given root.

The note is within its validity window at the execution slot, exactly as in a value transaction.

The nullifier is correct for the note, its owner's nullifier key and the domain of its owner kind, chosen exactly as a value transaction chooses it. A note therefore has the same nullifier whether it is spent in a value transaction or escaped, so it cannot be spent once each way.

The nullifier is unspent. It falls strictly between a leaf of the nullifier tree and its successor at the given nullifier root. This check is inside the escape-hatch proof; there is no separate freshness proof, because there is no attester signature for one to sit outside of.

The note's blinding factors are non-zero.

Values are in range. The slot is 48-bit, and every address half is 128-bit, including the token's.

For an identity-owned note the circuit also opens the platform wallet slot of the owner's identity and publishes the platform wallet address.

The 24 public values

The handler rebuilds the fold of 24 values from its own accounts and the payload. They fall into these groups.

Roots and the clock (slots 0 to 2). The note-tree root comes from the spend tree's root history, by sequence number. The nullifier-tree root comes from the nullifier staging buffer's active roots, also by sequence number. The execution slot comes from the payload and is checked against the clock and the process policy's proof-age window.

Context digest (slots 3 and 4). Keccak-256 of the payload before the proof and of every account address the instruction names, the escaper's first, in two halves.

Nullifier (slot 5). From the payload.

Token address (slots 6 and 7). From the mint account. The handler refuses a mint whose token program is not supported, and Token-2022's native mint.

Destination (slots 8 and 9). The address of the escape's one destination account, the account the release pays: a wallet for SOL, and for any other token a token account, which the handler first checks is an initialized token account of the note's mint. Because the account's own address is folded, a proof made for one account fails for any other, even another token account with the same owner.

Withdrawal rate (slot 10). The process policy's default withdraw rate. The circuit does not use it; it is folded so that the proof commits to the rate the handler will charge.

The disclosed note (slots 11 to 21). Eleven values from the payload: the note's kind, zone, amount, valid-after slot, valid-before slot, owner commitment, memo hash, insertion slot, insertion position, and the platform wallet address in two halves (zero for an address-owned note). The note's version is not among them. It stays a private input to the note commitment, so a wrong version still fails the membership check, but it is not published.

Spender authority (slots 22 and 23). The address of a signing account for an address-owned note, or zero.

What an escape reveals

Every one of those values is public. Together with the token and the destination, anyone watching the chain learns which note left, how much it held, which identity or address owned it, and which platform wallet that identity registered. The view below shows what a chain observer can read.

Escape hatch
Everything a chain observer can read
Program
Shielded pool, escape_hatch
Escaper
The signing account that pays
Token
The mint
Destination
The receiving address
Amount
The whole note
Owner
Identity commitment or address commitment
Platform wallet
For an identity-owned note
Note position
Insertion slot and position
Zone, memo hash, validity window
As stored in the note
Keys
PrivateNever leave your device
An escape keeps your keys private and discloses the rest of the note.

What it does not prove

The escape hatch skips everything that depends on someone other than the note's owner.

The owner is in the identity tree. Nothing checks it. Ownership rests on the note instead: the rebuilt identity commitment must be the owner of a note in the note tree, and only a registered identity could have received one.

The destination is acceptable. The circuit does not check it. The destination is any address the prover chooses, bound into the fold so no one else can change it. The handler checks only that it is writable and, for a token, that it is a token account of the note's mint.

The fee. The circuit does not compute it. The handler computes it from the disclosed amount and the withdraw rate.

Screening, zone approval and the token allowlist. None of them apply to an escape. A note in a zone can be escaped without its zone authority's signature.

What the handler does

escape_hatch checks and settles the escape in this order.

Check the token and the pool token account

The token must use a supported token program and must not be the refused native mint. The pool token account must be the canonical one for that token, and for SOL it must be the pool's lamport account.

Resolve the roots and the slot

The note-tree root and the nullifier root must both still be active, and the execution slot must be within the proof-age window.

Compute the fee and the payout

The fee is the disclosed amount times the withdraw rate over 10,000, rounded up, and the payout is the amount less the fee. The pool token account must hold at least the payout, and for SOL the pool's lamport account must hold at least the payout above its reserve.

Check the destination

The destination must be writable in the escape's transaction, which refuses the System Program, the sysvars and the other built-in accounts the runtime keeps read-only, since no release could pay one. For a token other than SOL, it must also be an initialized token account of the note's mint under its token program. For SOL it is any other address.

Verify the proof

The handler folds the 24 values and verifies the proof under the escape-hatch key compiled into the program.

Stage the nullifier

The nullifier is staged in the nullifier staging buffer (the code calls it the turnstile), which refuses one it already holds.

Hold the payout and credit the fee

The payout moves from the pool token account into the escape's escrow, which records the destination and pays it there 864,000 slots later, and the fee is credited to the token's escape-hatch fee accrual. SOL is held in lamports, moved from the pool's lamport account.

The escaper signs and pays the transaction fee. The context digest covers every account address, the escaper's included, so only the escaper the proof was made for can submit it, and no account can be swapped after proving.

Reference

Last updated

On this page