null.
Concepts

Zero-knowledge proofs

What the Groth16 2-in / 2-out join-split proves and hides — Poseidon Merkle membership over BN254.

Every pool operation carries a Groth16 zero-knowledge proof over the BN254 curve. The proof convinces the chain that a transfer is valid without revealing the inputs that make it valid.

The join-split circuit

The circuit is a 2-in / 2-out join-split (modelled on Tornado-Nova / Zcash-Sapling). It takes up to two input notes and produces two output notes, and proves — in zero knowledge — that:

  1. each input commitment == Poseidon(amount, Poseidon(privKey), blinding);
  2. each real input commitment is a member of the tree at the public root (a Poseidon Merkle proof; the membership check is gated off for amount == 0 dummy inputs, so 1-input transfers and shields are expressible);
  3. each nullifier == Poseidon(commitment, leafIndex, privKey), where leafIndex is derived inside the circuit from the Merkle path bits — so it is guaranteed consistent with the membership proof;
  4. each output commitment == Poseidon(amount, pubkey, blinding);
  5. value conservation holds: Σ inAmount + publicAmountIn == Σ outAmount + publicAmountOut + fee, with a 64-bit range check on every amount and public scalar;
  6. ownership — (2) and (3) both bind to the privKey behind the note's pubKey, so you cannot spend a note you do not own.

What it hides vs. proves

The proof hides the input notes (amount, blinding, spending key), their tree positions, and the output note owners. It proves the six statements above and publishes only opaque outputs.

Public signals

The proof commits to 12 public signals, in this fixed order (the chain reconstructs them positionally and verifies against them):

[nullifier0, nullifier1, outCommitment0, outCommitment1,
 root, publicAmountIn, publicAmountOut, fee,
 recipientHi, recipientLo, relayerHi, relayerLo]

The last four bind the withdrawal destination. A 32-byte address exceeds the ~254-bit BN254 scalar field, so recipient and relayer are each split into two 16-byte limbs (hi = bytes 0..16, lo = bytes 16..32) for an injective, aliasing-free binding. Each limb is forced into the constraint system with a square so the pairing check depends on it — a proof is valid only for the exact recipient and relayer it was made with.

Circuit size and proving cost

Curve           BN254
Constraints     25,736
Public signals  12  (8 inputs + 4 outputs)
Proof size      ~256 B packed (2×G1 + 1×G2)

Small circuit → sub-second proving. Measured proof generation is ~310 ms in Node, and ~340–850 ms in-browser via the SDK's Web Worker (see the SDK guide). The proving key is ~10.9 MB and must be fetched and cached by the client once.

The trusted setup that produced the proving/verifying keys is a dev-grade, single-contributor ceremony. It is not safe for real value — see Trusted setup.

On this page