Encryption & Viewing Keys

Every participant in the pool has a viewing keypair:

Key Symbol Who has it Used for
Private viewing key k The user only Decrypting notes, deriving channel keys, nullifiers
Public viewing key K On-chain Letting others encrypt notes and channels to you

K = k·G on the STARK curve. The public key is registered once via the SetViewingKey action and treated as immutable - every channel ever opened to you is derived from it, so it cannot change without breaking discovery.

Symmetric masking

Inside a channel, data is hidden with cheap "hash-and-add" masking rather than heavyweight ciphers. Each field gets its own domain-separated Poseidon hash plus a per-use salt, so no mask is ever reused:

enc_amount = (h(ENC_AMOUNT_TAG, channel_key, token, index, 0, salt) + amount) mod 2^128
enc_token  =  h(ENC_TOKEN_TAG,  channel_key, index, 0, salt) + token

Anyone holding the channel key recomputes the mask and subtracts it off. Anyone without it sees values indistinguishable from random field elements. The domain tags (ENC_AMOUNT_TAG:V1, ENC_TOKEN_TAG:V1, …) guarantee that a mask derived for one purpose can never be replayed in another context.

Asymmetric encryption: ECDH with ephemeral keys

Symmetric masking needs a shared secret - the channel key. Establishing it uses ECDH on the STARK curve with a fresh ephemeral key per channel:

sender picks random r
publishes   rG            (ephemeral public key)
computes    shared = r·K  (recipient's public viewing key)

enc_channel_key = h(ENC_CHANNEL_KEY_TAG, shared.x) + channel_key
enc_sender_addr = h(ENC_SENDER_ADDR_TAG, shared.x) + sender_addr

The recipient computes the same secret from the other side: k·(rG) = r·K. An observer sees only rG and two masked values - they learn that a channel was opened, not by whom or to whom.

The auditor copy

At registration, the user's private viewing key k is also encrypted - with the same ephemeral ECDH pattern - to the auditor's public key and stored on-chain. This single escrowed ciphertext is what enables compliance: an auditor under lawful process can recover k and decrypt that user's history, while everyone else's data stays private. See Compliance & Auditing.

Why open notes skip masking

An open note carries its amount in plaintext, using the protocol-reserved salt. This is deliberate: when a DeFi interaction (say, an AMM swap) produces an output amount that is only known at execution time, the client cannot mask it in advance - the mask is part of the proven transaction, but the amount isn't decided until the anonymizer contract runs on-chain. Open notes trade amount privacy for that late binding; ownership and subsequent spends remain private.

Rule of thumb

  • Everything derivable from k + public on-chain data → readable by you.
  • Everything else → opaque Poseidon outputs and masked field elements.

Next: where encrypted notes are filed and how recipients find them - Channels & Note Discovery.