09

V1 Mainnet · Compliance-friendly by design

Viewing keys

Privacy by default, transparency by choice. A viewing key lets someone you choose — an auditor, an exchange, an accountant — see your private activity. It never lets them spend.

KeyHoldsThey can seeSpend?
Incoming viewing keyincomingSecret, userCommitmentNotes you received, every epoch — not what you spent or who you paidNo
Epoch viewing keyivk + ovk of one epoch, nk, userCommitmentEverything, but only for that viewing epochNo
Full viewing keyincomingSecret, outgoingSecret, nk, userCommitmentEverything — received, spent, who you paid, your balance — every epochNo
Payment receiptNo key: one note’s opening (token, amount, receiver uc, r)One payment — anyone rebuilds its commitment and finds it on-chainNo

The protocol supports all four; the app shares the full viewing key.

How a viewing key reads the pool

In the viewer’s browser

the same public data for everyone

  1. 1Downloads every NoteCreated, NullifierSpent, OutgoingRecord and Swapped event. Everyone downloads everything, so nothing reveals whose key is being used.
  2. 2Derives ivk and ovk for every epoch, from 0 to the current one.
  3. 3Tries to decrypt every note with each ivk. A swap’s bought note takes its amount from the Swapped event.
  4. 4Keeps a note only if Poseidon(4, token, amount, Poseidon(3, userCommitment, r)) equals the commitment on-chain — anyone can encrypt something to you, but only real notes rebuild. Zero-value dummies are skipped.
  5. 5Computes each note’s nullifier with nk: if it appears in NullifierSpent, the note is spent — and the viewer sees when.
  6. 6Opens the outgoing records with ovk: who was paid, how much, and the fee.
  7. 7Result: the balance of every token and the full history — masked, received, sent, swapped, unmasked.

The full viewing key, as a string

Format
menoid-vk1:<base64url(json)>.<checksum>
 
json     = { kind: "full", address, network, keyIndex,
             userCommitment, incomingSecret, outgoingSecret, nullifierKey }
checksum = first 4 bytes of keccak256(payload)   // catches a mistyped key

In the app

  • Share yours: click your address to open your account, then Copy viewing key.
  • Read someone’s: at the bottom of the account box, View someone’s private activity, then paste their key. It is decrypted in your browser and never sent anywhere — and the app checks the key really matches their on-chain registration.

Why it can’t spend

Spending needs a proof, and a proof needs sk — the circuit rebuilds spendPk and your userCommitment from it. A viewing key holds nk = Poseidon(2, sk): enough to recognise spent notes, but Poseidon can’t be run backwards to get sk.

Sharing can’t be undone

Whoever holds your full viewing key can read everything it covers — including future activity under the same key index. To stop that, move to a new key index with rotateKeys: a new identity they can’t see. updateViewingKey only changes the epoch: it shuts out holders of an epoch key, not holders of a full or an incoming key, which cover every epoch.