Solana specifics
How Umbra fits Solana's transaction size, account and compute limits, and what that costs.
Umbra is built around Solana's limits: how large a transaction can be, how many accounts it may name, and how much computation it may use. This page covers the ones that shape the protocol, and what each costs.
Transactions
One value instruction
Every deposit, private transfer and withdrawal is one instruction, process_3x3x2x2. Its payload is a fixed 798 bytes and it names a fixed 39 accounts, whatever the transaction does. Unused lanes and token slots are still present, filled with placeholder values, so every value transaction has the same shape on chain. The exact sizes of this and every other instruction are in the reference below.
Fixed size has a cost: a transaction that only deposits still carries the space for three spent notes, two withdrawal lanes and three tokens, and pays for it in transaction size.
Version 1 transactions
The client sends Solana's version 1 transactions by default. A version 1 transaction can carry up to 4,096 bytes inline and takes no address lookup tables, and its compute limit and priority fee travel as fields of the message rather than as separate instructions. That is enough room for a deposit and the transfer that funds it to travel together in one transaction, signed by your main wallet, which pays the fee, and by your platform wallet as the depositor. Both land or neither does.
The client can also send version 0 transactions. Those are capped at 1,232 bytes, and a deposit with every account written out measures 1,687 bytes. A deposit fits only by looking up most of its accounts from an address lookup table holding the pool's constant accounts, which brings it to 1,163 bytes. Signers, invoked programs and a few per-transaction accounts can never come from a table, so at version 0 the funding transfer cannot fit beside the deposit and goes in a transaction of its own.
Compute
A deposit uses about 432,000 compute units by the client's own measure. Most of that is three pairing checks over Solana's BN254 syscalls: two Groth16 proof verifications (the transaction proof and the nullifier-freshness proof) and one check of the combined attester signature. The rest is the Poseidon hashing that rebuilds each proof's public input from the transaction, and the tree updates. Because Solana's default limit is 200,000 units per instruction, the client always sets its own limit. It simulates the transaction at the maximum, reads how many units it used, and sends it with a limit derived from that.
Cryptography on Solana
Pairings
Groth16 proofs and BLS signatures are both checked with pairings, through Solana's alt_bn128 syscalls. Each Groth16 proof is one pairing check over four pairs. The combined attester signature is one check over two pairs, however many attesters signed, because the six signatures and the six keys are each added into one before the check.
Poseidon
Poseidon, the hash function Umbra's commitments, nullifiers and trees use, runs through Solana's Poseidon syscall, which takes at most 12 inputs per call. Every hash the chain recomputes has to fit that limit. A tree node hashes at most eight children. The transaction proof's public input is a fold of 53 values, hashed in blocks: the first twelve, then the previous result with the next eleven, and so on until all are folded.
P-384
AWS signs Nitro attestation documents with ECDSA on the P-384 curve, and Solana has no syscall for it. The P-384 signature verifier program checks each signature in software, spread across several transactions, and leaves a result marker that the Nitro attestation verifier reads. See on-chain programs.
Accounts
Trees
A commitment tree is a 19,249-byte account. It keeps only the tree's frontier, the right edge needed to append new leaves and compute the root, and its 512 most recent roots. That is more than one instruction can create, so like the staging buffers below it is created small, grown and then finalized. The leaves themselves are written to the program's logs, and anyone who needs a Merkle path rebuilds it from those logs, which is what the indexer does. A whole tree of 2^32 notes therefore costs one small account.
Staging buffers
A nullifier staging buffer, which the code calls the turnstile, is a 2,464,893-byte account. It holds two buffers of 30,000 nullifiers, each with a Bloom filter for quick lookups, plus a ring of 512 recent nullifier-tree roots. Solana lets an account grow by at most 10,240 bytes per instruction, so a staging buffer is created small and grown step by step until it reaches full size, then finalized. Anyone may pay for those steps.
Reference
| Tag | Instruction | Version | Payload bytes | Accounts |
|---|---|---|---|---|
| 0 | register | v0 | 306 | 6 |
| 24 | process_3x3x2x2 | v0 | 798 | 39 |
| 25 | escape_hatch | v0 | 329 | 15 |
contracts/solana/tools/umbra-manifest/manifest/shielded-pool.json.Related
Last updated