Protocol

The transact circuit

What a proof of a private transaction shows.

Copy Markdown

One circuit covers every private action: a transfer, an exit, or both. It is written in Noir and proved with UltraHonk.

Notes

text
stub = H(opk, rho, r)
cm   = H(1, stub, assetId, amount)
nf   = H(2, nk, cm, i)
opk  = H(3, nk)

H is Poseidon2. nk is the owner's secret nullifier key. rho and r are random values per note. i is the input position, which defends against a duplicate-note attack.

Public inputs

The pool passes eight values to the verifier, in this order.

#InputMeaning
0rootA known root of the note tree
1nullifier0First spent note
2nullifier1Second spent note
3commitment0First new note
4commitment1Second new note
5assetIdAsset of every note in the transaction
6exitAmountValue leaving the pool
7extDataHashBinds recipient, relayer, fee and ciphertexts

extDataHash is keccak256(recipient, caller, fee, keccak(data), keccak(ciphertext0), keccak(ciphertext1)) reduced to the field. Anyone who relays the transaction cannot change what it binds.

What the proof shows

  • Each real input note is in the tree at root.
  • The nullifiers are derived correctly, and they differ from each other.
  • Every input and output note carries assetId, which sits inside the commitment, so a note cannot be spent as another asset.
  • Inputs equal outputs plus exitAmount. Value cannot be created.
  • Every amount fits in 120 bits.

An input with zero amount is a dummy. It is not checked against the tree but still produces a nullifier.