Shared Library
The shared library (circuits/lib/) contains all core cryptographic logic used by the three shielded action circuits.
Modules
hash.nr — Poseidon2 Hashing
Generic N-input Poseidon2 hasher with state width 4.
Key functions:
poseidon2<N>(inputs: [Field; N]) -> Field— Generic hasherExported variants:
poseidon2_2,poseidon2_3,poseidon2_4,poseidon2_5,poseidon2_6
Algorithm:
Initialize state:
[N << 64, 0, 0, 0]Absorb inputs in blocks of 3 (applying permutation when buffer is full)
Final permutation → extract
state[0]
Compatible with Poseidon2T4Unrolled.sol (Solidity) and @zkpassport/poseidon2 (TypeScript).
keys.nr — Key Derivation
Derives cryptographic keys from ECDSA signatures.
Structures:
ViewingKey { public_key, nullifying_key, decryption_key, outgoing_viewing_key }ReceivingKey { public_key, key_data, pnk, encryption_key }RegisteredReceivingKey { key, index, merkle_path }— with Merkle proof of registration
Derivation:
Verify ECDSA signature against sender's secp256k1 public key
seed = poseidon2(pack_bytes(signature))nk = poseidon2([1, seed]),dk = poseidon2([2, seed]),ovk = poseidon2([3, seed])encryption_key = dk * G(fixed-base scalar multiplication on Grumpkin)pnk = poseidon2([nk])Derive Ethereum address from public key: last 20 bytes of
keccak256(pk_x || pk_y)
Exported functions: derive_viewing_key(), derive_receiving_key(), derive_keys(), receiving_key_hash()
note.nr — UTXO Model
Defines the note structure, commitment, encryption, and decryption.
Structures:
NotePlaintext { owner_hash, value, coin_id, value_commitment_trapdoor, receiving_key_trapdoor }NoteCiphertext { epk, encrypted_secret, tag, ciphertext }SpendableNote { note, index, merkle_path, nullifiers_hash }
Note Commitment: Three-layer Poseidon2 hash:
Note Nullifier: poseidon2([commitment, nullifying_key])
Note Encryption: DH key exchange with Poseidon2-based mask generation. Produces 9-field ciphertext (header + 5 encrypted fields).
Trial Decryption: Three strategies (deposit, incoming/DH, outgoing/OVK). Returns Option<(NotePlaintext, tag)> where tag indicates note origin type.
Exported functions: commit_note(), note_nullifier(), encrypt_note(), try_decrypt_note()
merkle.nr — Merkle Tree Verification
LeanIMT verification with dynamic depth.
Constants: MAX_DEPTH = 16 (supports ~65K leaves)
LeanIMT Properties:
When a node has no right sibling, parent = left child (no dummy hash)
Dynamic depth based on actual tree size
Proof: (leaf, index as bit path, siblings array)
Key functions: compute_root(), verify_inclusion()
shielding.nr — Core Action Logic
Orchestrates the full shielded action proof.
Shared logic (create_action_core):
Verify ECDSA signature of EIP-1559 transaction
Compute transaction nullifier from
(nk, chainId, nonce, pk_hash)Spend all 6 funding notes (verify Merkle paths, compute nullifiers, check ownership)
Verify balance equation (inputs ≥ outputs + fees)
Create change notes (action asset + fee asset)
Encrypt change notes for sender
Compute nullifiers hash from all 6 nullifiers
Transfer-specific (shield_transaction):
Verify recipient key in key registry via Merkle proof
Create and encrypt output note for recipient
Withdrawal-specific (shielded_withdrawal):
Include public recipient address, value, and token
No recipient key verification
Swap-specific (shield_swap):
Parse Uniswap V2 router call data
Assert swap recipient == sender
Create half-ciphertext (ownership only, value determined on-chain)
Create
receiving_key_commitmentfor on-chain note finalization
rlp.nr / rlp_encode.nr — Transaction Parsing
EIP-1559 transaction RLP encoding and decoding for signature verification.
Buffer sizes:
Transfer: 143 bytes
Swap: 401 bytes
Parses: chain_id, nonce, gas fields, recipient, value, data, v/r/s signature components.
Last updated