본문으로 건너뛰기
cryptographyMarch 2, 2026·7 min read

poseidon2-go: The First Standalone Poseidon2 in Go, Now Open Source

We open-sourced poseidon2-go, the first standalone, pure-Go implementation of Poseidon2 over BN254. Zero allocations, cross-verified against Noir and Solidity, and ready for your ZK backend.

@

@Arjay

NixProtocol

공유:

Why we built it

Every ZK privacy system needs a hash function. Not SHA-256, that's catastrophically expensive inside a circuit. You need an algebraic hash: one designed from the ground up to be cheap in arithmetic constraints. Poseidon2 is the current state of the art.

The problem: if you're building ZK infrastructure in Go, there was no standalone Poseidon2 library. Rust has multiple. TypeScript has one. Solidity has one. Go had nothing.

So we built poseidon2-go, a pure-Go implementation of Poseidon2 over the BN254 scalar field. Today we're releasing it under the Apache 2.0 license.

What it does

poseidon2-go gives you four functions:

// Hash arbitrary-length inputs
func Hash(inputs []fr.Element) fr.Element

// Optimized 2-element hash (Merkle trees, commitments)
func Hash2(a, b fr.Element) fr.Element

// Hash to 32-byte output
func HashToBytes(inputs []fr.Element) [32]byte

// Raw permutation on a 4-element state
func Permute(s *[4]fr.Element)

That's the entire public API. One import, no configuration, no options structs. You call Hash2 to build a Merkle tree. You call Hash when you have more than two inputs. You call Permute when you know what you're doing.

The parameters

ParameterValue
FieldBN254 Fr
State width (t)4
Full rounds8 (4 + 4)
Partial rounds56
S-boxx⁵
Rate3
Capacity1

These match Aztec's Barretenberg prover, which means circuits proved in Noir will accept hashes computed by this library. Same constants, same sponge construction, same outputs.

Cross-implementation compatibility

This was the hardest part. Getting the permutation right is straightforward. It's just matrix multiplications and exponentiations over a field. Getting the sponge construction right across implementations is where things break.

Different implementations encode the IV differently, absorb into different state indices, and handle padding differently. We matched Noir's FieldSponge mode exactly:

  • IV: len(inputs) << 64, placed in state[3] (the capacity element)
  • Absorption: into state[0..2] (rate = 3), permuting when the rate buffer fills
  • Squeeze: state[0] after the final permutation
  • No padding marker (fixed-length mode)

Every test vector is verified against three independent implementations:

  • Noir stdlib (Barretenberg): Aztec's ZK prover
  • @zkpassport/poseidon2: TypeScript reference
  • Poseidon2.sol: Ethereum Solidity on-chain implementation

If those three agree and we match, it's correct.

Performance

Zero heap allocations. The entire hot path operates on stack-allocated fr.Element values.

BenchmarkTimeAllocations
Permute12.1 µs0
Hash2 (Merkle node)9.7 µs0
Hash1039.8 µs0
Hash100395 µs0

Hash2 is faster than Permute because it skips the sponge loop. Two elements fit in the rate, so it's a single permutation with optimized state setup.

These numbers are from an Apple M1 Pro. Your mileage will vary, but the zero-allocation property holds everywhere. The garbage collector never sees this code.

Why Go matters for ZK infrastructure

Most ZK tooling is written in Rust. That makes sense for provers, where you need every cycle. But the ecosystem around a prover (indexers, relayers, backend services, chain integrations) is often written in Go.

If your relayer needs to verify a Merkle proof before submitting a transaction, it needs to recompute Poseidon2 hashes. If your indexer watches for note commitments, it needs Poseidon2. If your backend derives nullifiers for a wallet sync service, it needs Poseidon2.

Without a Go library, these services either shell out to a Rust binary, maintain FFI bindings, or reimplement the hash function from scratch. All three options are fragile. A native Go library eliminates the problem.

How we use it

Inside NIX Protocol, poseidon2-go powers:

  • Note commitment verification: every UTXO note is committed with Poseidon2
  • Merkle tree construction: the on-chain Merkle tree uses Poseidon2 for internal nodes
  • Nullifier derivation: spent notes produce nullifiers via Poseidon2
  • Backend services: relayers and indexers that interact with the privacy pool

The same hash function runs in the Noir circuit (Barretenberg), the Solidity contract (poseidon2-evm), and now the Go backend (poseidon2-go). Same inputs, same outputs, every time.

Get started

Install:

go get github.com/nixprotocol/poseidon2-go

Use it:

import (
    "github.com/consensys/gnark-crypto/ecc/bn254/fr"
    poseidon2 "github.com/nixprotocol/poseidon2-go"
)

var a, b fr.Element
a.SetUint64(1)
b.SetUint64(2)
digest := poseidon2.Hash2(a, b)

One dependency: gnark-crypto for BN254 field arithmetic. That's it.

Open source, open standard

We released poseidon2-go under the Apache 2.0 license. Use it in your project, fork it, vendor it, do whatever you need.

The ZK ecosystem moves faster when implementations are open and interoperable. If you're building privacy infrastructure, a rollup, a bridge, or anything that touches Poseidon2 on BN254, this library exists so you don't have to write it yourself.

GitHub: github.com/nixprotocol/poseidon2-go

Docs & integration support: nixprotocol.com/docs

계속 읽기

프라이버시 인프라와 영지식 증명에 대한 더 많은 연구 아티클을 탐색하세요.

모든 아티클 보기