Key Derivation
Nullmask derives all cryptographic keys deterministically from a single wallet signature. Since modern wallets use deterministic signatures (RFC 6979), all keys can be recovered by re-signing a fixed message.

Viewing Key
Definition 1: Viewing Key
A Nullmask Viewing Key is a tuple (pk,nk,ivk,ovk) where:
pk: Public Key — a secp256k1 point corresponding to the wallet account's public key
nk∈F: Nullifying Key — used for derivation of deterministic note nullifiers
ivk∈F: Incoming Viewing Key — used for trial decryption of incoming notes
ovk: Outgoing Viewing Key — an E scalar for encrypting receipts of sent notes
Definition 2: Export Viewing Key Message
The Nullmask EXPORT_VK_MESSAGE is a special protocol-fixed message. The signature of this message serves as an entropy source for all Nullmask keys. Current value: "Authorize view-only access to Nullmask shielded account."
Algorithm 1: Derive Viewing Key
Input: wallet connection
Output: A Viewing Key (pk,nk,ivk,ovk)
Request a
personal_signof the messageEXPORT_VK_MESSAGERecover pk from the signature σ
seed←Poseidon2T4(σ)
nk←Poseidon2T4(seed,1)
ivk←Poseidon2T4(seed,2)
ovk←Poseidon2T4(seed,3)
Return (pk,nk,ivk,ovk)
The viewing key grants view access to the account's transaction history. It remains stored in the proxy service and is never posted on-chain.
Receiving Key
Definition 3: Receiving Key
A Nullmask Receiving Key is a triple (pk,pnk,ek) where:
pk: Public Key — a secp256k1 point corresponding to the wallet account's public key
pnk∈F: Public Nullifying Key — the hash of nk
ek: Encryption Key — an E point used for encrypting notes destined for this recipient
Algorithm 2: Derive Receiving Key
Input: A Viewing Key (pk,nk,ivk,ovk)
Output: The corresponding Receiving Key (pk,pnk,ek)
pnk←Poseidon2T4(nk)
ek←ivk⋅G
Return (pk,pnk,ek)
The receiving key is posted on-chain in the key registry. It enables anyone to send shielded funds to the corresponding address.
Receiving Key Hash
Algorithm 3: Hash Receiving Key
Input: Receiving Key (pk,pnk,ek)
Output: Receiving Key hash rk_hash
address←keccak(pk)[12:]
buffer←pk(64 bytes)∣∣address(20 bytes)
Pad buffer with 9 zero bytes to length 93 bytes
Split buffer into 3 parts of 31 bytes each
Return Poseidon2T4(part1,part2,part3,pnk,ek.x,ek.y)
The receiving key hash is a single field element that identifies the note owner. It is computed identically in the Noir circuit and the Solidity contract, enabling on-chain verification of key registry membership.
On-Chain Registration
The receiving key is registered on-chain via registerReceivingKey(). The contract:
Recovers the Ethereum address from the public key components
Verifies
msg.sendermatches the derived addressComputes the receiving key hash using Poseidon2
Inserts the hash into the key registry Merkle tree
This registration is verified in the shielded transfer circuit via a Merkle inclusion proof, ensuring the proxy cannot tamper with address-to-key mappings.
Key Storage
Viewing Key
Proxy (local storage)
Decrypt incoming/outgoing notes, derive nullifiers
Receiving Key
On-chain (key registry)
Enable others to send shielded funds
Access Token
HTTP-only cookie
Authenticate proxy requests
Last updated