The problem
Private balances live on ristretto255, encrypted by Token-2022. Shielded notes live inside a Groth16 proof on BN254. Two curves, two proof systems, and no native way to show that a number on one equals a number on the other. Shielded pools usually reveal the amount at the door. Zero doesn’t.
1. Keep the amount inside the proof
The hidden-amount circuit takes the amount a and a random blinding ρ as inputs that are never posted. The program verifies the Groth16 proof against a single point in their place:
P = a·IC₂ + ρ·IC₃ built from the verifying key's own points
P is a commitment to the amount on BN254, and the Groth16 proof holds for it without anyone learning a.
2. Prove it matches the transfer
A confidential transfer commits to its amount on ristretto255. A cross-curve equality proof shows that commitment and P hide the same integer. Zero implements Πdleq from Beyond the circuit (Orrù, Kadianakis, Maller and Zaverucha, IACR Communications in Cryptology, 2025): one 104-byte proof with a 128-bit challenge, checked on-chain in about 24,000 compute units.
C_transfer = x·G + r·H on ristretto255 (range-proven by Token-2022)
P = x·g + ρ·h on BN254
same x, proven without revealing itThe same proof also shows knowledge of P’s opening, which is exactly what makes a Groth16 proof with a committed input sound.
3. Open it with the pool's key
Token-2022 never exposes a transfer’s blinding r. Zero doesn’t need it. The pool’s ElGamal key is PK = s⁻¹·H, and the transfer carries a decrypt handle D = r·PK for it, so:
s·D = r·H ⇒ C = x·G + s·D
The same commitment, opened by the pool’s own key through its handle. It stays binding because nobody knows the discrete log of H.
4. Credit exactly once
A hidden deposit is two instructions in one transaction: your confidential transfer to the pool, then the pool’s finalize. The program reads the instruction right before its own through the instructions sysvar and checks it is exactly that transfer, from your account, into the pool, with the proof account it checked when the deposit was staged. Then it records the notes’ nullifiers. The transfer and the credit land together or not at all.
The whole flow
- 01Prove
Your browser builds the private transfer to the pool and the hidden-amount Groth16 proof.
- 02Stage
The relayer verifies the transfer proofs into accounts, checks everything independently, and the pool program verifies both proofs and records the deposit.
- 03Sign once
Your wallet signs one transaction under 1 KB: the transfer and the finalize.
- 04Done
Your notes are in the tree. The chain saw a private transfer and a proof, and no amount.
Withdrawing to Private runs the same bridge in reverse: the pool program checks your proof and the bridge proof, then signs the confidential transfer out itself.
Two independent locks
| Lock | What it checks |
|---|---|
| On-chain | The Groth16 proof with the committed amount, and the cross-curve proof tying it to the transfer. |
| The relayer | The Groth16 proof again, with the amount as an ordinary input, and the transfer decrypted with the pool key and compared. |