Cryptographic Primitives
Poseidon2 T4
Nullmask uses the Poseidon2 hash function with state width 4 (T4) throughout the protocol. Poseidon2 is a zk-SNARK-friendly hash function designed for minimal constraint count in arithmetic circuits.
Properties
State width: 4 field elements
Rate: 3 field elements per permutation
Output: 1 field element (extracted from state[0])
Field: BN254 scalar field
Usage
Key derivation
(seed, discriminator)
Derive nk, ivk, ovk from signature
Note commitment
(rk_hash, trapdoor), (value, coin_id, trapdoor)
Create note commitments
Note nullifier
(commitment, nk)
Derive spend nullifiers
Transaction nullifier
(nk, nonce*2^32 + chainId, pk_hash)
Prevent replay
Nullifiers hash
(nf1, nf2, nf3, nf4, nf5, nf6)
Bind notes to actions
Merkle tree
(left, right)
LeanIMT node hashing
Encryption mask
(shared_secret, counter)
Note encryption
Receiving key hash
(part1, part2, part3, pnk, ek.x, ek.y)
Key identity
Implementations
Noir:
circuits/lib/src/hash.nr— Generic N-input hasherSolidity:
Poseidon2T4Unrolled.sol— Gas-optimized Yul implementationTypeScript:
@zkpassport/poseidon2— JS library for off-chain computation
All three implementations produce identical outputs for identical inputs.
LeanIMT (Lean Incremental Merkle Tree)
The Nullmask contract uses a Lean Incremental Merkle Tree for storing note commitments and receiving key hashes.
Properties
Maximum depth: 16 (supports up to 65,536 leaves)
Hash function: Poseidon2 T4
Root history: Circular buffer of 64 historical roots
How It Differs from Standard Merkle Trees
In a standard Merkle tree, missing nodes are filled with a "zero hash" value. In a LeanIMT:
When a node has no right sibling, the parent equals the left child (no dummy hash needed)
The tree depth grows dynamically as leaves are added
Proof length equals the current tree depth (not the maximum depth)
Root Verification
The contract maintains a circular buffer of the last 64 Merkle roots. When a shielded action is submitted, the proof includes a root that must match one of these historical roots. This allows transactions to be valid even if new notes were added between proof generation and submission.
Grumpkin
Nullmask uses the Grumpkin embedded curve for elliptic curve operations within ZK circuits. Grumpkin's base field equals the BN254 scalar field, meaning all curve point coordinates are native field elements — making in-circuit EC operations efficient.
Usage
Key derivation: ek=ivk⋅G (fixed-base scalar multiplication)
Note encryption: dhek=epk⋅G and shared=epk⋅ek (Diffie-Hellman key exchange)
Why Grumpkin?
Grumpkin is the embedded curve used by Barretenberg. Its base field is the BN254 scalar field, so all coordinates are native circuit field elements — no expensive non-native arithmetic is needed.
secp256k1 ECDSA
Standard Ethereum transaction signatures are verified inside the ZK circuit.
Usage
Transaction authorization: The circuit verifies the ECDSA signature of the EIP-1559 transaction
Key recovery: The sender's public key is recovered from the signature
Address derivation: The Ethereum address is derived from the public key via
keccak256(pk)[12:]
Constraint Cost
ECDSA verification is the most expensive operation in the circuit (~42,000 gates). The UltraHonk prover handles this efficiently, enabling proof generation in ~2 seconds on consumer hardware.
UltraHonk
Nullmask uses the UltraHonk proof system (via the Barretenberg prover) for generating and verifying ZK proofs.
Properties
Proof system: UltraPlonk/UltraHonk
Prover: Barretenberg (
bb)Verification: On-chain Solidity verifiers (generated from circuit compilation)
Proving time: ~2 seconds on consumer hardware for transfer circuits
Proof Verification
On-chain verification is performed by generated Solidity verifier contracts. Each circuit type (transfer, withdrawal, swap) has its own verifier. The verifier interface is:
Last updated