Docs / Notes
Inside Ordeo
What a note is and how it stays private
A note is a hash commitment to an asset, a value and an owner. The pool stores the hash plus the secret parts in encrypted form; it never learns who owns a note or what it holds.
On this page
Structure
A note has four fields. Only the commitment, a hash of all four, goes on chain.
| Field | Meaning |
|---|---|
asset | 0 for ETH, 1 for ORDEO |
value | Wei for ETH; vault shares for ORDEO. At most 2^120 - 1. |
pk | The owner's note public key, Poseidon(sk) |
blinding | A random field element that hides the owner and makes every commitment unique |
Hashes
pk = Poseidon1(sk) ownerHash = Poseidon2(pk, blinding) commitment = Poseidon3(asset, value, ownerHash) nullifier = Poseidon3(commitment, leafIndex, sk)
Poseidon here is the circomlib-compatible instance defined on BN254's scalar field. The circuit, the contract libraries (poseidon-solidity) and the browser use the same constants and share test vectors, so all three compute identical hashes.
Positional nullifiers
Each spend reveals the note’s nullifier. Because it mixes in the leaf index and the spending key, the owner alone can derive it, it never varies for that note, and two notes with identical contents at different leaves still have different nullifiers.
The pool records each nullifier once and refuses a second spend. Nothing in a nullifier points back to the commitment it came from.
Filler notes
Each spend takes two inputs and produces two outputs, always. When you spend one note, the second input is a zero-value filler with a random blinding; the circuit skips its membership check because its value is zero. When there is only one real output, the second is a zero-value note to yourself. On chain, a payment, a merge, an unshield and a sale all look the same shape: two nullifiers and two commitments. Output order is shuffled at random.
Encryption
Each new note is encrypted to its owner’s x25519 viewing key so their app can find it:
- Generate a fresh ephemeral x25519 key and compute the shared secret with the viewing key.
- Derive a 32-byte key with HKDF-SHA256 (salt: ephemeral public key then viewing key; info:
ordeo-note). - Seal the 65-byte plaintext (asset, value, blinding) with ChaCha20-Poly1305.
The result is 114 bytes: a version byte, the 32-byte ephemeral key and the sealed box with its 16-byte tag. The pool accepts up to 512 bytes per output. Your app tries to open every new ciphertext with your viewing key; the ones that open are yours.
Shield notes: the pool sets the value
When you shield, the wallet only supplies ownerHash. The pool works out the note value from the amount it actually received, after the fee and, for ORDEO, the share conversion. The ciphertext of a shield note therefore carries value 0. Your app takes the real value from the Shielded event emitted in that transaction, which includes noteValue and leafIndex.
Opening one note: proof of payment
After a private send, the sender’s app can produce a link that opens the one note it created for the recipient: asset, value, recipient public key, blinding, leaf index and transaction. /verify recomputes the commitment, checks that it exists in the pool at that leaf in that transaction and, if the recipient registered their key, shows the address it is registered to.
The link keeps the sender hidden and cannot move funds. Spending still needs the recipient’s spending key, which the link does not contain.