Privacy Wallet · Technical Docs

Circuit design

Circuit overview

The join–split circuit proves correct movement of value between notes, accepting inputs and outputs while enforcing Merkle membership and value conservation.

Summary

The join–split circuit validates that up to six input notes are spent correctly into up to six output notes. It proves knowledge of Merkle paths, correct nullifier derivation, and that the total input value matches the total output value.

Template parameters

The circuit is instantiated with fixed parameters for the deployed version:

  • nIns = 6. Maximum number of input notes per transaction.
  • nOuts = 6. Maximum number of output notes per transaction.
  • depth = 26. Merkle tree depth, matching the on-chain tree capacity.

Witness (private) inputs

These inputs remain private and are never revealed on-chain. They encode note contents and Merkle paths.

signal input inBalance[6];          // Input note amounts
signal input inSpendNonce[6];       // Per-note spend nonces
signal input inNoteNonce[6];        // Commitment nonces for inputs
signal input receiverViewPriv;      // Secret key used to derive nullifiers
signal input inPathElements[6][26]; // Merkle sibling hashes for inputs
signal input inPathIndex[6][26];    // Merkle path directions (0 = left, 1 = right)
signal input outAmount[6];          // Output note amounts
signal input outNoteNonce[6];       // Commitment nonces for outputs

Public inputs

These inputs are provided to the on-chain verifier to validate the proof against the current state.

1.      merkleRoot              // Global Merkle root
2-7.    inputNullifier[6]       // Nullifiers of spent notes
8-11.   destLimbs[4]            // Recipient address, as 4 × u64 limbs
12-17.  outputCommitment[6]     // Commitments of new output notes
18.     publicAmount            // Publicly withdrawn amount
19.     extAmountIn             // Publicly deposited amount

Address packing

Solana addresses are 32 bytes. The circuit packs them into four 64-bit limbs to simplify range checks and avoid variable-length byte processing inside the field arithmetic.