Skip to main content

Poseidon Hash Function

Poseidon is the core hash function used throughout the Specter data protocol. It was chosen because it is an algebraic hash function designed specifically for efficient computation inside zero-knowledge circuits. Every commitment, nullifier, token identifier, access tag, and Merkle tree node in the protocol is computed using a variant of Poseidon.

Why Poseidon?

Traditional hash functions like SHA-256 and Keccak-256 are designed for hardware and software efficiency on standard processors. They use bitwise operations (XOR, AND, rotations, shifts) that are cheap on CPUs but expensive to represent in the arithmetic circuits used by ZK proof systems.

ZK circuits operate over finite fields — they represent computation as systems of polynomial equations over large prime numbers. Each bitwise operation in SHA-256 or Keccak must be decomposed into field arithmetic, which multiplies the constraint count dramatically.

Poseidon takes the opposite approach. It operates natively on finite field elements using only field multiplications and additions. Its round function is a simple power map (S-box: x^5) followed by a linear mixing layer — operations that translate directly into a small number of R1CS constraints.

Hash FunctionApproximate R1CS ConstraintsCircuit Efficiency
SHA-256~25,000 per hashBaseline
Keccak-256~150,000 per hash6x worse than SHA-256
Poseidon (t=3)~3,000 per hash~8x better than SHA-256

This ~8x improvement in constraint count translates directly to faster proof generation, smaller proof sizes, and lower verification gas costs. For a protocol that computes multiple hashes per proof (commitment hash, nullifier hash, 20 Merkle tree node hashes), the cumulative savings are substantial.

BN254 Scalar Field

All Poseidon computations in Specter operate over the BN254 scalar field:

p = 21888242871839275222246405745257275088548364400416034343698204186575808495617

This is a ~254-bit prime. Every input, output, and intermediate value in a Poseidon hash must be a valid element of this field — meaning it must be a non-negative integer less than p.

The BN254 curve was chosen because it is the curve supported by Ethereum's ecPairing precompile (0x08), which is required for on-chain Groth16 proof verification. Using the same field for both hashing and proving avoids costly field conversions.

When values from other domains (e.g., Keccak-256 hashes, which produce 256-bit outputs) need to enter the Poseidon field, they are reduced modulo p:

fieldValue = keccak256(data) % p

This reduction is performed identically on-chain (in Solidity) and off-chain (in circuits and client code) to ensure consistency.

Poseidon Variants

Specter uses three Poseidon configurations, differing in the number of inputs they accept. The naming convention uses "T" (for "t-value"), which equals the number of inputs plus one (the internal state width).

PoseidonT3 (2 inputs)

output = PoseidonT3(input0, input1)

The workhorse of the protocol. Used for all binary operations:

UseInput 0Input 1Output
Merkle tree nodesLeft child hashRight child hashParent node hash
Nullifier (inner)nullifierSecretcommitmentInner hash
Nullifier (outer)Inner hashleafIndexFinal nullifier
Token IDtokenAddress0tokenId
Access tagnullifierSecretsessionNonceaccessTag

On-chain deployment. PoseidonT3 is the only Poseidon variant deployed on-chain, as a Solidity library at approximately 55 KB of bytecode. This large size is why Specter increases the EVM's MaxCodeSize to 64 KB. The on-chain library costs approximately 30,000 gas per hash invocation and is used by the CommitmentTree for computing Merkle tree nodes during root verification.

PoseidonT5 (4 inputs)

output = PoseidonT5(input0, input1, input2, input3)

Used for generic data commitments — the data-agnostic commitment format:

UseInput 0Input 1Input 2Input 3Output
Data commitmentsecretnullifierSecretdataHashblindingcommitment

PoseidonT5 is computed off-chain only (in the browser, in the client application, or inside ZK circuits). It is never executed on-chain because:

  • On-chain deployment would require ~179,000+ gas per hash
  • The smart contracts only need to verify ZK proofs that attest to the commitment's correctness — they never recompute the commitment themselves

PoseidonT8 (7 inputs)

output = PoseidonT8(input0, input1, input2, input3, input4, input5, input6)

Used for token commitments — the full commitment format with token and policy binding:

UseInput 0Input 1Input 2Input 3Input 4Input 5Input 6Output
Token commitmentsecretnullifierSecrettokenIdamountblindingpolicyIdpolicyParamsHashcommitment

Like PoseidonT5, PoseidonT8 is computed off-chain only. The on-chain contracts receive only the final commitment hash and verify it through ZK proofs.

All Hashes in the Protocol

The following table catalogs every Poseidon hash computation in the Specter protocol:

Hash PurposeVariantInputsComputed Where
Token commitmentPoseidonT8 (7 inputs)secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHashOff-chain (client + circuit)
Data commitmentPoseidonT5 (4 inputs)secret, nullifierSecret, dataHash, blindingOff-chain (client + circuit)
Nullifier (inner hash)PoseidonT3 (2 inputs)nullifierSecret, commitmentOff-chain (circuit)
Nullifier (outer hash)PoseidonT3 (2 inputs)innerHash, leafIndexOff-chain (circuit)
Token IDPoseidonT3 (2 inputs)tokenAddress, 0Off-chain (client)
Access tagPoseidonT3 (2 inputs)nullifierSecret, sessionNonceOff-chain (circuit)
Merkle tree nodesPoseidonT3 (2 inputs)leftChild, rightChildOff-chain (indexer) + On-chain (tree contract)
Change commitmentPoseidonT8 (7 inputs)secret, nullifierSecret, tokenId, changeAmount, newBlinding, policyId, policyParamsHashOff-chain (circuit)

On-Chain vs. Off-Chain

The design principle is clear: minimize on-chain Poseidon computation. Only PoseidonT3 is deployed on-chain, and only for Merkle tree operations that the smart contracts must perform directly (root tracking, proof verification during root updates). All multi-input hashes (4-input and 7-input commitments) are computed off-chain and verified on-chain through ZK proofs — the chain never sees the hash inputs, only the final hash output and a proof that it was correctly computed.

Security Properties

Poseidon's security has been analyzed extensively in the academic literature. Key properties relevant to Specter:

Collision resistance. It is computationally infeasible to find two distinct inputs that produce the same Poseidon hash. This ensures that each commitment is unique and that Merkle tree nodes cannot be forged.

Preimage resistance. Given a Poseidon hash output, it is computationally infeasible to recover the inputs. This ensures that observing a commitment hash on-chain reveals nothing about the underlying data (secret, nullifierSecret, dataHash, blinding, etc.).

Algebraic soundness. Poseidon's round structure (full rounds + partial rounds with the x^5 S-box over a large prime field) provides security margins against algebraic attacks including Grobner basis attacks, interpolation attacks, and differential/linear cryptanalysis. The number of rounds is chosen to provide a security margin of at least 2x above the theoretical minimum.

Field-native operations. Because Poseidon operates directly on BN254 field elements, there are no bit-decomposition or domain-conversion steps that could introduce subtle vulnerabilities. Every operation is a native field multiplication or addition.