V0.5 Testnet · Noid → Noid
Send (transfer)
A private send moves value between Noid users entirely inside the pool — no coins enter or leave, and neither side is revealed. You paste an ordinary address; the wallet finds its Noid identity on-chain.
Sent by
The relayer
Proof
transfer (19 signals)
Fee
A private note
Notes
up to 4 in → 3 out
Menoid wallet
gathers notes
proves the transfer
Relayer
validates locally
submits, pays gas
NoidPool
verifies · nullifies
inserts new notes
Receiver
finds the note
by trial decryption
In the wallet
- 1Resolves the receiver’s address to their userCommitment and encryption key, from the chain.
- 2Gathers unspent notes until they cover amount + fee.
- 3Batches them into groups of at most 4 — the circuit’s fixed input width.
- 4Builds up to 3 outputs per batch: C1 to the receiver, C2 your change, C3 the relayer’s fee.
- 5For each input, fetches its Merkle path and computes its nullifier. Unused slots are switched off with the enabled[] mask.
- 6Proves each batch and hands the Transfer Calls — public signals, nullifiers, proof, output commitments, encrypted notes — to the relayer.
At the relayer
gate 1 — the relayer protecting itself
- 7Checks each call, validates the proofs off-chain, and checks the fee is worth it. Invalid or unprofitable → rejected, before any gas is spent.
- 8Submits transfer(calls) and pays the gas.
Inside NoidPool
gate 2 — the real one
- 9enabled[] is boolean, each pool exists, each root is among its pool’s last 10, and each nullifier is unspent and unique within the call.
- 10Rejects duplicate output commitments, builds the 19 public signals — injecting its own relayerCommitment — and verifies each proof.
- 11Marks every nullifier spent, inserts the new commitments, and emits NoteCreated for each.
On the receiver’s side
- 12Their wallet finds the new note by trial decryption. Nothing announces the payment — they simply find it.
Batches that build on each other
Several Transfer Calls run atomically in one transaction, and a later call may spend a note that an earlier call created in that same transaction. That is note aggregation: fold 4 notes into 1, feed it into the next call with 3 more, and so on — large payments without ever widening the circuit. And since a 1-input and a 4-input transfer produce identically shaped proofs, the proof never leaks how many notes you spent.
What the proof proves
- You own the notes: sk → spendPk → userCommitment with your address, and every enabled input is built from it.
- They exist: each enabled input’s Merkle path leads to a root the pool accepts.
- The nullifiers are right: each is Poseidon(2, commitment, r, sk) — only the owner can make one.
- The outputs are well-formed and locked to the intended owners.
- Nothing is created: every amount is range-checked below 2¹²⁸, and the inputs sum to the outputs.
- The relayer is paid: the third output must belong to the relayer’s commitment.
Why the range checks matter
Go deeperThe 19 public signals
| Index | Signal | Count |
|---|---|---|
| 0 | relayerCommitment | 1 |
| 1–4 | enabled[] | 4 |
| 5–8 | roots[] | 4 |
| 9–12 | nullifiers[] | 4 |
| 13–15 | output_enabled[] | 3 |
| 16–18 | c_outs[] | 3 |
Everything else — sk, your address, the input commitments, every amount and random number, the Merkle paths, and the receivers — is private.
On-chain, anyone can see
- That a transfer happened
- Nullifiers and new commitments
- The relayer as the sender
Stays private
- Sender and receiver
- The amounts
- Which notes were spent
