Skip to content

Commit

Permalink
feat: merkle damgard and poseidon2 (#1407)
Browse files Browse the repository at this point in the history
Co-authored-by: Arya Tabaie <15056835+Tabaie@users.noreply.github.com>
Co-authored-by: Ivo Kubjas <ivo.kubjas@consensys.net>
  • Loading branch information
3 people authored Feb 17, 2025
1 parent 9388128 commit e55bdc2
Show file tree
Hide file tree
Showing 7 changed files with 353 additions and 198 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/consensys/bavard v0.1.27
github.com/consensys/compress v0.2.5
github.com/consensys/gnark-crypto v0.15.0
github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca
github.com/fxamacker/cbor/v2 v2.7.0
github.com/google/go-cmp v0.6.0
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAh
github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk=
github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk=
github.com/consensys/gnark-crypto v0.15.0 h1:OXsWnhheHV59eXIzhL5OIexa/vqTK8wtRYQCtwfMDtY=
github.com/consensys/gnark-crypto v0.15.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca h1:u6iXwMBfbXODF+hDSwKSTBg6yfD3+eMX6o3PILAK474=
github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
Expand Down
49 changes: 47 additions & 2 deletions std/hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package hash

import (
"errors"
"fmt"
"sync"

"github.com/consensys/gnark/frontend"
Expand Down Expand Up @@ -58,7 +58,7 @@ func GetFieldHasher(name string, api frontend.API) (FieldHasher, error) {
defer lock.RUnlock()
builder, ok := builderRegistry[name]
if !ok {
return nil, errors.New("hash function not found")
return nil, fmt.Errorf("hash function \"%s\" not registered", name)
}
return builder(api)
}
Expand Down Expand Up @@ -87,3 +87,48 @@ type BinaryFixedLengthHasher interface {
// FixedLengthSum returns digest of the first length bytes.
FixedLengthSum(length frontend.Variable) []uints.U8
}

// Compressor is a 2-1 one-way function. It takes two inputs and compresses
// them into one output.
//
// NB! This is lossy compression, meaning that the output is not guaranteed to
// be unique for different inputs. The output is guaranteed to be the same for
// the same inputs.
//
// The Compressor is used in the Merkle-Damgard construction to build a hash
// function.
type Compressor interface {
Compress(frontend.Variable, frontend.Variable) frontend.Variable
}

type merkleDamgardHasher struct {
state frontend.Variable
iv frontend.Variable
f Compressor
api frontend.API
}

// NewMerkleDamgardHasher transforms a 2-1 one-way function into a hash
// initialState is a value whose preimage is not known
func NewMerkleDamgardHasher(api frontend.API, f Compressor, initialState frontend.Variable) FieldHasher {
return &merkleDamgardHasher{
state: initialState,
iv: initialState,
f: f,
api: api,
}
}

func (h *merkleDamgardHasher) Reset() {
h.state = h.iv
}

func (h *merkleDamgardHasher) Write(data ...frontend.Variable) {
for _, d := range data {
h.state = h.f.Compress(h.state, d)
}
}

func (h *merkleDamgardHasher) Sum() frontend.Variable {
return h.state
}
19 changes: 19 additions & 0 deletions std/hash/poseidon2/poseidon2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package poseidon2

import (
"fmt"

"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/hash"
poseidon2 "github.com/consensys/gnark/std/permutation/poseidon2"
)

// NewMerkleDamgardHasher returns a Poseidon2 hasher using the Merkle-Damgard
// construction with the default parameters.
func NewMerkleDamgardHasher(api frontend.API) (hash.FieldHasher, error) {
f, err := poseidon2.NewPoseidon2(api)
if err != nil {
return nil, fmt.Errorf("could not create poseidon2 hasher: %w", err)
}
return hash.NewMerkleDamgardHasher(api, f, 0), nil
}
41 changes: 41 additions & 0 deletions std/hash/poseidon2/poseidon2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package poseidon2

import (
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr/poseidon2"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/test"
)

type Poseidon2Circuit struct {
Input []frontend.Variable
Expected frontend.Variable `gnark:",public"`
}

func (c *Poseidon2Circuit) Define(api frontend.API) error {
hsh, err := NewMerkleDamgardHasher(api)
if err != nil {
return err
}
hsh.Write(c.Input...)
api.AssertIsEqual(hsh.Sum(), c.Expected)
return nil
}

func TestPoseidon2Hash(t *testing.T) {
assert := test.NewAssert(t)

const nbInputs = 5
// prepare expected output
h := poseidon2.NewMerkleDamgardHasher()
circInput := make([]frontend.Variable, nbInputs)
for i := range nbInputs {
_, err := h.Write([]byte{byte(i)})
assert.NoError(err)
circInput[i] = i
}
res := h.Sum(nil)
assert.CheckCircuit(&Poseidon2Circuit{Input: make([]frontend.Variable, nbInputs)}, test.WithValidAssignment(&Poseidon2Circuit{Input: circInput, Expected: res}), test.WithCurves(ecc.BLS12_377)) // we have parametrized currently only for BLS12-377
}
Loading

0 comments on commit e55bdc2

Please sign in to comment.