Pure-Go implementation of the Poseidon2 hash function over the BN254 scalar field.
This is the first standalone Poseidon2 implementation in Go. It produces identical outputs to Noir's standard library (Barretenberg), @zkpassport/poseidon2, and poseidon2-evm.
| Parameter | Value |
|---|---|
| Field | BN254 Fr (p = 21888...95617) |
| State width (t) | 4 |
| Full rounds (rf) | 8 (4 + 4) |
| Partial rounds (rp) | 56 |
| S-box | x^5 |
| Rate | 3 |
| Capacity | 1 |
go get github.com/nixprotocol/poseidon2-gopackage main
import (
"fmt"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
poseidon2 "github.com/nixprotocol/poseidon2-go"
)
func main() {
// Hash two field elements (Merkle tree node, commitment, etc.)
var a, b fr.Element
a.SetUint64(1)
b.SetUint64(2)
digest := poseidon2.Hash2(a, b)
fmt.Printf("Hash2(1, 2) = %s\n", digest.String())
// Hash arbitrary number of field elements
inputs := []fr.Element{a, b}
digest = poseidon2.Hash(inputs)
// Get 32-byte output
bytes := poseidon2.HashToBytes(inputs)
fmt.Printf("bytes = %x\n", bytes)
}// Hash computes Poseidon2 sponge hash over arbitrary-length inputs.
func Hash(inputs []fr.Element) fr.Element
// Hash2 hashes exactly 2 field elements.
func Hash2(a, b fr.Element) fr.Element
// HashToBytes computes Hash and returns the result as 32 bytes (big-endian).
func HashToBytes(inputs []fr.Element) [32]byte
// Permute applies the raw Poseidon2 permutation on a 4-element state.
func Permute(s *[4]fr.Element)All inputs and outputs are BN254 scalar field elements, valid in the range [0, p) where:
p = 21888242871839275222246405745257275088548364400416034343698204186575808495617
This is approximately 254 bits (not a full 256-bit integer range). Values set via SetBytes() or SetBigInt() that exceed p are automatically reduced modulo p by gnark-crypto. This is standard behavior across all Poseidon2 implementations in every ZK ecosystem (Noir, Solidity, Rust, TypeScript).
The sponge uses the FieldSponge mode matching Noir/Barretenberg:
- IV:
len(inputs) << 64, placed instate[3](capacity element) - Absorption: into
state[0..2](rate = 3), permuting when full - Squeeze:
state[0]after final permutation - No padding marker (fixed-length mode)
All test vectors are verified against:
- Noir stdlib (Barretenberg) - Aztec's ZK prover
- @zkpassport/poseidon2 - TypeScript reference
- Poseidon2.sol - Ethereum Solidity implementation
Round constants are sourced from poseidon2-evm, which uses the same constants as Aztec's Barretenberg prover. These are the standard BN254 Poseidon2 constants used across the ZK ecosystem.
Only one external dependency:
github.com/consensys/gnark-crypto- BN254 field arithmetic (fr.Element)
Run benchmarks:
go test -bench=. -benchmemApache 2.0 - See LICENSE.