> For AI agents: the documentation index is at https://docs.umbraprivacy.com/llms.txt, and every page is available as Markdown at its URL with .md appended.

# Hash functions (/docs/cryptography/hashing)

Umbra hashes almost everything with Poseidon, a hash built from arithmetic in the BN254 scalar field, because it is cheap to prove inside a circuit and Solana can compute it with a syscall. Where the chain hashes raw bytes rather than field elements, such as the transaction digest the attesters sign, it uses Keccak-256.

## Why Poseidon

A zero-knowledge circuit is a system of equations over a prime field. A hash like SHA-256 or Keccak works on bits, and expressing every bit operation as a field equation makes each call very expensive to prove. Poseidon is defined directly as additions, multiplications and fifth powers in the field, so a call costs a small fraction of that. Every note, identity, tree and nullifier in Umbra is hashed many times per proof, which makes the difference decisive.

The Solana runtime provides Poseidon over BN254 as the `sol_poseidon` syscall, with the same parameters as the circomlib Poseidon the circuits use. Anything a circuit hashes that the program also needs to compute, such as a tree leaf or a proof's public input, is therefore hashed in a form the syscall reproduces exactly.

## Two forms of Poseidon

The circuits use Poseidon in two forms. These pages write them as follows.

- **`H_on(x1, …, xn)`, the syscall form.** Circomlib's `Poseidon(n)` for n from 1 to 12, identical to the `sol_poseidon` syscall. When the value needs a domain, the domain is the first input. The chain can recompute anything hashed this way. Tree nodes, the leaves of the note, fee, indexed and zone trees, mint identifiers and proof public inputs all use it.
- **`H_off[d](x1, …, xn)`, the circuit form.** Circomlib's `PoseidonEx` with up to 16 inputs, where the sponge's capacity element, which the syscall always sets to zero, starts at `n + d × 65536`. The domain and the number of inputs are separated for free, without spending an input on them. The chain never recomputes these hashes; they are used for objects only circuits and off-chain parties open, such as note and identity commitments.

A third construction keys the permutation itself: the sender record's keystream puts the per-transaction viewing key in the capacity. See [encryption](https://docs.umbraprivacy.com/docs/cryptography/encryption).

The nullifier is the circuit form over the nullifier key, the note's insertion position and its nullifier blinding factor, under a domain that depends on the input's kind: 68 for an identity-owned input, 69 for a program-owned input, 70 for a fee note and 71 for a dummy input. Separate domains keep a dummy input, whose position and blinding factor are free, from reproducing the nullifier of a note whose key its sender shares. See [nullifiers](https://docs.umbraprivacy.com/docs/state/nullifiers).

## Folding many values into one

The syscall accepts at most 12 inputs, and a transaction has 53 public values. Umbra folds them with a chained hash, written `Agg`: the first 12 values are hashed together, then each further call hashes the previous result with up to 11 new values. The circuit template accepts up to 144 values. The diagram below shows the fold for the transaction proof's 53 values.

1. Call 1: values 1 to 12
2. Call 2: result 1, values 13 to 23
3. Call 3: result 2, values 24 to 34
4. Call 4: result 3, values 35 to 45
5. Call 5: result 4, values 46 to 53
6. Public input: the result of call 5

*The chained fold over the transaction proof's 53 public values: five syscall-form Poseidon calls, each taking the previous result first.*

Because each call takes the previous result as its first input, changing any one value changes every result after it, and the last result commits to all 53 values in order. Folds of other sizes work the same way:

- The **transaction proof's** single public input is `Agg` over its 53 values, the five calls above.
- The **registration proof's** is `Agg` over 11 values, one call.
- The **privacy artifacts root** is `Agg` over 43 values: the 19 sender-record ciphertexts, their 19 lane commitments, the three viewing-key share leaves and the two disclosure sections.
- The **nullifier-freshness proof's** public input is a single `H_on` over its three nullifiers and the nullifier tree root.

The program recomputes each public input from its own state with the same fold, so a proof only verifies against exactly the values the chain holds. [Zero-knowledge proofs](https://docs.umbraprivacy.com/docs/cryptography/zero-knowledge-proofs) explains why every proof the program verifies has just one public input.

## Keccak-256

Keccak-256 hashes byte strings, and the Solana runtime provides it as the `sol_keccak256` syscall.

- **The context digest.** Keccak-256 of the value instruction's payload, up to the combined attester signature, followed by the addresses of all 39 of its accounts, the submitter's first. An escape's and a registration's digests are built the same way, over the payload before the proof or the signature and every account the instruction names. Every attester signs this digest. It also enters the transaction proof as two public values, its first 16 bytes and its last 16, so the proof and the signature are bound to the same transaction.
- **The memo digest.** Keccak-256 of a fixed 127-byte preimage that starts with the domain string `umbra/tx-memo/v4`, names the program and instruction, and ends with a root over the three encrypted notes and a root over any auxiliary records. The program never reads it; it sits inside the payload the context digest covers, and the data endorser recomputes it from the encrypted notes it archives.
- **Attestation.** An enclave's measurement is Keccak-256 of its first three platform configuration registers, and the attestation marker's address commits to the Keccak-256 of the attested user data, measurement and timestamp.
- **Account addresses.** The seeds of the programs' own accounts begin with Keccak-256 of the account type's name.

The empty leaf of the note tree is also Keccak-derived: Keccak-512 of the string `UmbraPrivacy`, reduced into the field.

## Domain separation

Two objects hashed from the same inputs must never collide, or a value built for one purpose could be passed off as another: a fee commitment accepted as a note commitment, say, or a leaf accepted as a tree node. Umbra prevents this by giving each kind of object its own domain number, listed in the circuits' domain registry (`circuits/zk/hash/domains.circom`), which runs from 0 to 71. A syscall-form hash takes its domain as its first input. A circuit-form hash puts it in the sponge's capacity element instead, where it costs no input.

Several numbers in the registry are reserved or retired rather than reused, because renumbering would change every hash that uses the numbers above them. The groups below explain what each domain-tagged object is and what goes into it. A lookup table of every number follows them.

### Notes and their leaves

A **note commitment** is a circuit-form hash under domain 4 of the note's 13 fields: its version, discriminator, zone, amount, validity window (two fields), blinding factor, nullifier blinding factor, owner commitment, mint identifier, the two funding commitments that record who funded it, and its memo hash. The blinding factor is what keeps the commitment from being guessed by hashing likely values.

When the program appends a note, it does not store the commitment alone. The **note tree leaf** is a syscall-form hash under domain 36 of the commitment, the slot in which it was inserted and its position in the tree. The program computes this leaf itself when it appends the note, which ties every note to where and when it entered the tree; [note commitments](https://docs.umbraprivacy.com/docs/state/commitments) explains why that matters for nullifiers.

### Identities and owners

Your identity commitment is built in two layers. Each of its eight **identity slot leaves** is a circuit-form hash under its own domain, 42 to 49, of the slot's value and that slot's blinder. A slot that holds a 256-bit value, such as your platform wallet's address or the hash of your ML-KEM public key, enters as two halves plus the blinder. Giving every slot its own domain means a value cannot be moved from one slot to another and still produce the same leaf.

The **identity commitment** is a circuit-form hash under domain 50 of the eight slot leaves. A note owned by a Solana address rather than an identity has a **program owner commitment** instead: a circuit-form hash under domain 34 of the address's two halves.

### Fees

A transaction's fees are committed separately from its notes. Each **fee commitment** is a circuit-form hash under domain 10 of the mint identifier, the fee total for that token, a nullifier blinding and a blinding. The fee enclave opens these three commitments to check the fees.

The **fee tree leaf** is the fee commitment's counterpart of the note tree leaf: a syscall-form hash under domain 38 of the fee commitment, its insertion slot, its insertion position and the treasury owner commitment that the program reads from the fee enclave's record when it appends the leaf. Fee collection proves against these leaves.

The **fee voucher commitment** under domain 11 covers a fee discount. It hashes the transaction's three nullifiers, the digest of the voucher table (itself hashed under domain 35) and a blinding. The **fee-rate tree leaf** under domain 40 is a syscall-form hash of a mint identifier and its packed fee rates; governance publishes the tree's root.

### Viewing keys and the sender record

The **per-transaction viewing key** is a circuit-form hash under domain 5 of your outgoing viewing key, the transaction's three nullifiers and randomness that the transaction publishes. Because the nullifiers are fresh in every transaction, so is the key. The **keystream blinding key** hashes exactly the same inputs under domain 24, which gives a second, independent key from the same material.

The sender record is encrypted lane by lane, and each lane has a **lane commitment**, a circuit-form hash under domain 26 of the lane's keystream and its blinding lane.

The per-transaction viewing key is shared among the three guardian nodes. Each node's **share leaf** is a hash of its share and blinding share, tagged in the capacity with domain 21 and the share's evaluation point, so each leaf names the node it belongs to. The **sharing root** under domain 22 hashes the three share leaves.

### Disclosure

A transaction also commits to a disclosure tree, from which a holder of the right keys can open individual fields to a zone authority. Each **disclosure field leaf** is a circuit-form hash of one field's masked value and masked blinding, under a domain that names the field: 13 for the zone, 14 for the owner, 15 for the amount, 16 for the mint, 17 for the slot, 27 for the creator and 28 for the discriminator. The mask is zero for a dummy note, so a dummy contributes nothing that can be opened.

Each note's field leaves are folded under domain 33, and the folds are gathered into two **sections**: the source section under domain 29 and the destination section under domain 30, each starting with a leaf for its zone.

### Zones

A zone's signing key is recorded in the zone signer set as a **zone signer-set leaf**, a syscall-form hash under domain 39 of the key's two coordinates and the zone number. Because the zone number is inside the leaf, a key's leaf names the one zone it may approve for.

A zone authority approves a transaction by signing a **zone approval message**. The circuit first hashes the two halves of the transaction's context digest together, then hashes that result under domain 63 for an approval of notes leaving the zone or 64 for notes entering it. The two domains keep an approval to release notes from being reused as an approval to accept them.

### Tree nodes and mint identifiers

**Tree nodes** and **mint identifiers** carry no domain. A tree node always has 8 inputs, or 4 at the top level, and a mint identifier always has 2: the mint address's two halves. Every leaf that sits under a node is itself domain-tagged, which keeps leaves and nodes from being confused. The leaves of the indexed trees (the nullifier, registration and fee-nullifier trees) are syscall-form hashes under domain 37 of a value, the index of the next leaf and the next value.

### Every domain at a glance

| Object                        | Form            | Domain                                                             |
| ----------------------------- | --------------- | ------------------------------------------------------------------ |
| Note commitment               | `H_off`         | 4                                                                  |
| Nullifier                     | `H_off`         | 68 (identity-owned), 69 (address-owned), 70 (fee note), 71 (dummy) |
| Per-transaction viewing key   | `H_off`         | 5                                                                  |
| Fee commitment                | `H_off`         | 10                                                                 |
| Fee voucher commitment        | `H_off`         | 11                                                                 |
| Disclosure field leaves       | `H_off`         | 13 to 17, 27, 28                                                   |
| Viewing-key share leaf        | Capacity-tagged | 21, with the share's evaluation point                              |
| Sharing root                  | `H_off`         | 22                                                                 |
| Keystream blinding key        | `H_off`         | 24                                                                 |
| Sender record lane commitment | `H_off`         | 26                                                                 |
| Disclosure sections           | `H_off`         | 29 (source), 30 (destination)                                      |
| Disclosure note fold          | `H_off`         | 33                                                                 |
| Program owner commitment      | `H_off`         | 34                                                                 |
| Voucher table digest          | `H_off`         | 35                                                                 |
| Note tree leaf                | `H_on`          | 36                                                                 |
| Indexed tree leaf             | `H_on`          | 37                                                                 |
| Fee tree leaf                 | `H_on`          | 38                                                                 |
| Zone signer-set leaf          | `H_on`          | 39                                                                 |
| Fee-rate tree leaf            | `H_on`          | 40                                                                 |
| Identity slot leaves          | `H_off`         | 42 to 49, one per slot                                             |
| Identity commitment           | `H_off`         | 50                                                                 |
| Zone approval message         | `H_on`          | 63 (source), 64 (destination)                                      |
| Tree node, mint identifier    | `H_on`          | None                                                               |

## How values enter a hash

Every hash input is a field element, written as 32 bytes little-endian, and the syscall is called in little-endian mode. The program refuses a nullifier, commitment or other field value that is at or above the field modulus, so no value has two encodings. A 32-byte Solana address does not fit in the field, so addresses and mints enter as two 128-bit halves, the first 16 bytes and the last 16, each read little-endian. A mint inside a note is its mint identifier, `H_on` of those two halves.

## Related

- [Commitments](https://docs.umbraprivacy.com/docs/cryptography/commitments): What these hashes hide and bind.
- [Merkle trees](https://docs.umbraprivacy.com/docs/cryptography/merkle-trees): Why the trees are eight wide.
- [Zero-knowledge proofs](https://docs.umbraprivacy.com/docs/cryptography/zero-knowledge-proofs): The single public input each proof carries.
- [Note commitments](https://docs.umbraprivacy.com/docs/state/commitments): From a note's fields to its tree leaf.


Last updated 12 October 2026
