Privacy design
Profile A — Shielded UTXO pool
The full-privacy value model: notes as Poseidon commitments, a 2-in/2-out join-split transaction circuit, value conservation in the field, and double-spend protection by nullifier.
Profile A follows the UTXO model (after Tornado Nova): arbitrary amounts, join-split transfers. A single transaction circuit covers deposit, private transfer, and withdraw via a signed external amount.
Notes & keys
| Name | Definition |
|---|---|
privKey | random F_p |
pubKey | H(privKey) |
note commitment | H(amount, pubKey, blinding) |
signature | H(privKey, commitment, merklePathIndices) |
nullifier | H(commitment, merklePathIndices, signature) |
blinding is a random F_p per note. amount is constrained to 248 bits to prevent field overflow during summation.
The transaction circuit
shielded/transaction.circom, instantiated 2-in / 2-out. Public signals, in order:
[ root, publicAmount, extDataHash,
inputNullifier[nIns], outputCommitment[nOuts] ]Private inputs: per-input inAmount, inPrivateKey, inBlinding, inPathIndices, inPathElements[LEVELS]; per-output outAmount, outPubkey, outBlinding.
Constraints
- For each input: recompute
commitment,signature,nullifier; requirenullifier === inputNullifier[i]. - For each input with non-zero amount:
MerkleProof(commitment, …) === root(zero-amount inputs are dummies and skip the root check). - For each output: recompute
commitment; require=== outputCommitment[i];amountfits in 248 bits. - All input nullifiers are pairwise distinct.
- Value conservation:
Σ inAmount + publicAmount === Σ outAmount(mod p). extDataHashis bound (squared) — committing to recipient, relayer, fee, and the encrypted output ciphertexts the contract emits for note discovery.
publicAmount — one circuit, three actions
The contract computes publicAmount = (extAmount − fee) mod p. The sign of extAmount selects the action:
extAmount | Action | Effect |
|---|---|---|
> 0 | Deposit | caller sends extAmount |
< 0 | Withdrawal | contract pays −extAmount to recipient |
= 0 | Private transfer | value stays inside the shielded set |
Spending & double-spend protection
On transact, the contract verifies the proof against a known root, checks each inputNullifier is unused and marks it spent, inserts each outputCommitment into the tree, emits the encrypted outputs, and settles publicAmount against the bridge/escrow.
Build these proofs with the SDK — see SDK: Shielded UTXO.