Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: merkle damgard and poseidon2 #1407

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is IV=0 temporary or it indeed doesn't have preimage?

}
28 changes: 28 additions & 0 deletions std/hash/poseidon2/poseidon2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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"
"github.com/stretchr/testify/require"
)

func TestPoseidon2Hash(t *testing.T) {
// prepare expected output
h := poseidon2.NewMerkleDamgardHasher()
for i := range 5 {
_, err := h.Write([]byte{byte(i)})
require.NoError(t, err)
}
res := h.Sum(nil)

test.SingleFunction(ecc.BLS12_377, func(api frontend.API) []frontend.Variable {
hsh, err := NewMerkleDamgardHasher(api)
require.NoError(t, err)
hsh.Write(0, 1, 2, 3, 4)
return []frontend.Variable{hsh.Sum()}
}, res)(t)
}
Loading
Loading