Skip to content

Notes and Nullifiers

The pool keeps its books the way Zcash’s Orchard does: notes and nullifiers. The hashes are different. Cheshire’s proofs work over the M31 field (numbers modulo 2³¹ − 1), so it uses Poseidon2, a hash built to be cheap inside such proofs. Below, H(a, b, …) means one Poseidon2 hash of the inputs, with a domain tag in front so that hashes for different purposes never collide.

A wallet has one secret, sk: 8 field elements (about 248 bits), derived from the wallet’s twelve words. Its public key is pk = H(sk). A wallet’s address (its payment code, tchs1… on test networks) carries pk and a second key used to find payments (see payment codes).

A note is four values: (amount, pk, rho, r).

  • amount is in satoshis, below 2²⁷ (about 1.34 BTC). A larger balance is several notes.
  • pk is the owner’s public key.
  • rho and r are random-looking values that make every note unique and hide it.

The note’s key and commitment are

K = H(pk, rho, r)
C = H(amount, K)

C is what goes into the tree. Nobody can tell from C what the amount is or whose it is. The owner, who knows (amount, rho, r), can recompute C and find it.

The notes tree is a Merkle tree of depth 48. It only grows: each new note goes in at the next free position, and that position is part of the pool’s state. There is room for 2⁴⁸ notes, so block space, not the tree, is the limit.

Spending the note at position i publishes its nullifier:

nf = H(sk, rho, i)
  • Only the owner can compute it, because it needs sk.
  • It reveals nothing about which note it is.
  • It is the same every time for the same note, so a second spend is caught.

The position is in the hash for a reason. The payer chooses rho. Without the position, a malicious payer could send you two notes with the same rho; spending one would make the other unspendable. Zcash calls this “faerie gold”. Positions are unique, so nullifiers are.

The nullifier tree is an indexed Merkle tree: a sorted linked list in Merkle form. Each leaf holds a nullifier key and the next larger one. To add nf, the proof shows the leaf just below nf, whose “next” is just above it. That proves nf is not there yet. Then it updates that leaf and appends the new one. The key stored is the first 3 field elements of nf, about 93 bits.

Every contribution has the same shape: it spends up to 2 notes and creates exactly 2.

  • A payment spends one or two of your notes, and creates one note for the payee and one change note for you.
  • A deposit spends nothing and creates your new note.
  • A withdrawal spends your notes and pays out to a Bitcoin address, with change.

Unused input slots spend a dummy (a nullifier that marks nothing real), and unused outputs are zero-value notes. So an observer cannot tell a one-input payment from a two-input one. This is count hiding. Orchard shows how many actions a transaction has; Cheshire does not.

Your wallet’s proof for a contribution says, without revealing any of the values:

  • each real input note is in the notes tree under a recent root (an anchor);
  • you know its sk;
  • its nullifier is correctly formed;
  • inputs plus any deposit equal outputs plus any payout plus the fee;
  • every amount is in range, so sums cannot wrap around the field.

That is the whole statement. The pool checks it through the recursion described in the proof.

Your wallet proves against the notes root it last saw. By the time the contribution settles, other settlements may have moved the root. So the pool accepts a contribution anchored to any of the last 16 settlement roots. A contribution older than that must be proved again against a fresh root.

tree depth 48 (2⁴⁸ positions per tree)
amount per note below 2²⁷ sat
nullifier key 3 field elements, ~93 bits
anchor window 16 settlements

A nullifier-key collision would make the later note unspendable: a freeze, not new money. By calculation, the chance of an accidental one is about 5·10⁻⁵ after 10¹² nullifiers, and forcing one on purpose costs about 2⁹³ work. These are calculations, not measurements.