Files
zeekay e9e6598969 blake3/poseidon: Layer 1 (Go) with KAT against published spec
blake3: pure-Go body wrapping zeebo/blake3 (already in go.mod). Three modes
(hash/keyed/derive_key) plus XOF. KAT against the official BLAKE3
test_vectors.json (35 input lengths from 0 to 102400 bytes, 4 modes each
= 140 byte-equal assertions).

poseidon: extended Sum2 wrapper with Permutation2 (raw t=2 permutation) and
Compress2 (gnark-crypto Compress contract). KAT vectors generated from
gnark-crypto v0.20.1 (default params t=2, rF=6, rP=50, d=5) for 16
permutation cases.

Both layers assert byte-equal to the published spec, no oracle indirection.
2025-12-27 23:24:54 -08:00

85 lines
2.6 KiB
Go

// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poseidon
import (
"errors"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/consensys/gnark-crypto/ecc/bn254/fr/poseidon2"
)
// FieldSize is the size of a BN254 scalar field element in bytes.
const FieldSize = 32
// Poseidon2 default parameters for BN254 scalar field, t=2 (compression mode).
// Matches gnark-crypto's GetDefaultParameters().
const (
Width = 2
FullRounds = 6
PartialRounds = 50
SBoxDegree = 5
)
var ErrInvalidFieldSize = errors.New("poseidon: input must be a multiple of 32 bytes")
// Sum2 computes the Poseidon2 hash of `inputs` interpreted as a sequence of
// 32-byte BN254 scalar field elements via Merkle-Damgard. It returns a single
// 32-byte digest.
//
// inputs must have length divisible by 32. Empty input is permitted and
// returns the Poseidon2 hash of the empty preimage (well-defined per the
// gnark-crypto spec).
func Sum2(inputs []byte) ([FieldSize]byte, error) {
var out [FieldSize]byte
if len(inputs)%FieldSize != 0 {
return out, ErrInvalidFieldSize
}
h := poseidon2.NewMerkleDamgardHasher()
if _, err := h.Write(inputs); err != nil {
return out, err
}
digest := h.Sum(nil)
copy(out[:], digest)
return out, nil
}
// Permutation2 applies the raw t=2 Poseidon2 permutation in-place to the
// given two field elements (each 32 bytes, big-endian, canonical reduced).
// This is the layer that the C++ and Metal implementations are byte-equal to.
//
// Both `left` and `right` are read and overwritten with the permutation
// output. They must each be exactly 32 bytes and below the BN254 scalar
// modulus.
func Permutation2(left, right *[FieldSize]byte) error {
var s [Width]fr.Element
if err := s[0].SetBytesCanonical(left[:]); err != nil {
return err
}
if err := s[1].SetBytesCanonical(right[:]); err != nil {
return err
}
p := poseidon2.NewPermutation(Width, FullRounds, PartialRounds)
if err := p.Permutation(s[:]); err != nil {
return err
}
copy(left[:], s[0].Marshal())
copy(right[:], s[1].Marshal())
return nil
}
// Compress2 absorbs (left, right) through the t=2 Poseidon2 permutation and
// returns the canonical 32-byte field element output (equivalent to
// gnark-crypto's Compress: returns state[1]+right_input after permutation).
func Compress2(left, right *[FieldSize]byte) ([FieldSize]byte, error) {
var out [FieldSize]byte
p := poseidon2.NewPermutation(Width, FullRounds, PartialRounds)
res, err := p.Compress(left[:], right[:])
if err != nil {
return out, err
}
copy(out[:], res)
return out, nil
}