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
Relayer
checks its fee
simulates · submits
NoidPool
verifies the proof
spends · inserts
Receiver
finds the note
with their ivk
In your browser
- 1Looks up the recipient with registrationOf: their userCommitment and viewing key.
- 2Asks the relayer for its fee in this token — its real gas cost plus a small margin.
- 3Picks up to 4 of your unspent notes in that token, largest first, covering amount + fee. Empty slots get zero-value dummy notes.
- 4Builds 3 outputs: the receiver’s note (the amount), your change (the rest), and the relayer’s fee note — always last.
- 5Encrypts each output to its owner’s viewing key, and writes your outgoing record with your ovk.
- 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
- 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.
- 8Simulates the transaction: a bad proof, a spent note or a stale root fails here, for free.
- 9Submits transfer(calls) from its own address and pays the gas.
Inside NoidPool
- 10Checks the tree exists, the root is one of its last 100, and the 4 nullifiers are unspent and all different.
- 11Verifies the proof against [root, 4 nullifiers, 3 output commitments, relayer, extDataHash].
- 12Marks the 4 nullifiers spent, inserts the 3 new commitments, and emits NoteCreated ×3 and OutgoingRecord.
On the receiver’s side
- 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 input | Count | What it is |
|---|---|---|
| root | 1 | a recent root of the inputs’ tree |
| nullifiers | 4 | the inputs’ spent markers |
| outCommitments | 3 | receiver · change · relayer fee |
| relayer | 1 | the relayer’s userCommitment |
| extDataHash | 1 | the 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.
