06

V1 Mainnet · Private → private

Send

A private send moves value from your notes to someone else’s, inside the pool. Your browser makes a zero-knowledge proof; a relayer submits it. The chain learns that some notes were spent and some were created — not who, not how much, not even which token.

Sent by

The relayer

Proof

transfer (Groth16)

Fee

A private note, same token

Notes

up to 4 in → 3 out

Menoid app

picks your notes

makes the proof

proof + encrypted notes

Relayer

checks its fee

simulates · submits

transfer()

NoidPool

verifies the proof

spends · inserts

NoteCreated

Receiver

finds the note

with their ivk

Your address never appears on the transaction: the relayer sends it and pays the gas.

In your browser

  1. 1Looks up the recipient with registrationOf: their userCommitment and viewing key.
  2. 2Asks the relayer for its fee in this token — its real gas cost plus a small margin.
  3. 3Picks up to 4 of your unspent notes in that token, largest first, covering amount + fee. Empty slots get zero-value dummy notes.
  4. 4Builds 3 outputs: the receiver’s note (the amount), your change (the rest), and the relayer’s fee note — always last.
  5. 5Encrypts each output to its owner’s viewing key, and writes your outgoing record with your ovk.
  6. 6Computes extDataHash over the encrypted outputs and the record, then makes the proof with the transfer circuit.

At the relayer

a courier — it can’t read or change anything

  1. 7Opens its fee note with its own viewing key and checks it rebuilds the commitment in the proof, is in the token being spent, and covers the fee.
  2. 8Simulates the transaction: a bad proof, a spent note or a stale root fails here, for free.
  3. 9Submits transfer(calls) from its own address and pays the gas.

Inside NoidPool

  1. 10Checks the tree exists, the root is one of its last 100, and the 4 nullifiers are unspent and all different.
  2. 11Verifies the proof against [root, 4 nullifiers, 3 output commitments, relayer, extDataHash].
  3. 12Marks the 4 nullifiers spent, inserts the 3 new commitments, and emits NoteCreated ×3 and OutgoingRecord.

On the receiver’s side

  1. 13Their app decrypts the new note with their ivk, rebuilds its commitment, and shows it in their private balance.

What the proof proves

  • You own the inputs. From sk the circuit rebuilds spendPk, then your userCommitment with your address — and every input must be built from it.
  • They exist. Each real input’s Merkle path leads to the public root. Zero-value dummies skip this check; they can’t carry value.
  • The nullifiers are theirs. Each equals Poseidon(5, nk, commitment, r), with nk rebuilt from sk.
  • Nothing is created. Every amount is below 2¹²⁸, and the inputs add up exactly to the outputs.
  • The relayer gets the last output — its owner must be the relayer named in the public inputs.
  • The extra data is locked. extDataHash is a public input, so the encrypted notes can’t be swapped out.
  • One token throughout. Inputs and outputs share one token — and it stays private.

More than 4 notes

One proof spends at most 4 notes. A bigger payment is split into up to 4 proofs, relayed together in one transaction, each paying its own fee note. Every proof has the same shape — 4 nullifiers in, 3 notes out — so nobody can tell how many real notes you spent.

On-chain, anyone can see

  • That a private send happened, and when
  • 4 nullifiers and 3 new commitments
  • The relayer’s address as the sender

Stays private

  • Who sent it, and who received it
  • The amount
  • The token
  • Which notes were spent
Go deeperThe transfer circuit, input by input
Public inputCountWhat it is
root1a recent root of the inputs’ tree
nullifiers4the inputs’ spent markers
outCommitments3receiver · change · relayer fee
relayer1the relayer’s userCommitment
extDataHash1the encrypted outputs and outgoing record

Private inputs: sk, your address, the token, and each input’s amount, r and Merkle path; each output’s amount, r and owner. The circuit has 26,856 constraints; its proving key (about 18 MB) downloads once and is cached.