Privacy Wallet · Technical Docs

Data structures

Representing value inside Void

Notes, commitments, nullifiers, and the Merkle tree encode all private balances. This page explains how they work together.

Summary

Plain notes never touch the chain. Void stores commitments and encrypted metadata; commitments become leaves in an append-only Merkle tree, while nullifiers mark notes as spent.

Notes & commitments

A note represents a claim to lamports. Its commitment binds the amount and a random nonce.

commitment = VoidHash(amount, noteNonce)
  • amount. 64-bit lamport value encoded into the field and range-checked inside the circuit.
  • noteNonce. Fresh randomness; changing it alters the commitment even if the amount stays the same.

Nullifiers

Nullifiers act as one-way spend tags derived from user secrets and a unique spend nonce.

nullifier = VoidHash(receiverViewPriv, spendNonce)
  • receiverViewPriv. Proves ownership; never leaves the client.
  • spendNonce. Unique per spend attempt, ensuring the nullifier cannot collide.

Output commitments

New notes created as outputs reuse the same commitment scheme with their own randomness.

outputCommitment = VoidHash(outAmount, outNoteNonce)

Merkle tree

Commitments are appended to a 26-level Merkle tree (67,108,864 leaves). The program stores the latest root plus a history buffer to validate proofs.

  • Depth. 26 levels.
  • Hash function. Internal nodes use VoidHash(left, right).
  • Insertion optimization. Filled-subtrees cache enables append operations in time proportional to depth.
  • Root history. A circular buffer stores the most recent ROOT_HISTORY roots.
                 merkleRoot
                 /                      h01          h23
             /   \        /             h0      h1    h2     h3
         / \     / \   / \    / \ 
        c0 c1   c2 c3 c4 c5  c6 c7  ...

- cX are note commitments
- internal nodes hX = VoidHash(left, right)
- leaf index + sibling hashes = Merkle path

Working with Merkle paths

Clients fetch paths from indexers or RPC. Each path contains sibling hashes plus left/right indicators, all of which the circuit validates against the stored root.