Skip to content

Commit e55bdc2

Browse files
Tabaieivokub
andauthored
feat: merkle damgard and poseidon2 (#1407)
Co-authored-by: Arya Tabaie <[email protected]> Co-authored-by: Ivo Kubjas <[email protected]>
1 parent 9388128 commit e55bdc2

File tree

7 files changed

+353
-198
lines changed

7 files changed

+353
-198
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/blang/semver/v4 v4.0.0
1010
github.com/consensys/bavard v0.1.27
1111
github.com/consensys/compress v0.2.5
12-
github.com/consensys/gnark-crypto v0.15.0
12+
github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca
1313
github.com/fxamacker/cbor/v2 v2.7.0
1414
github.com/google/go-cmp v0.6.0
1515
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAh
6161
github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
6262
github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk=
6363
github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk=
64-
github.com/consensys/gnark-crypto v0.15.0 h1:OXsWnhheHV59eXIzhL5OIexa/vqTK8wtRYQCtwfMDtY=
65-
github.com/consensys/gnark-crypto v0.15.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
64+
github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca h1:u6iXwMBfbXODF+hDSwKSTBg6yfD3+eMX6o3PILAK474=
65+
github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
6666
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
6767
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
6868
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=

std/hash/hash.go

+47-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
package hash
66

77
import (
8-
"errors"
8+
"fmt"
99
"sync"
1010

1111
"github.com/consensys/gnark/frontend"
@@ -58,7 +58,7 @@ func GetFieldHasher(name string, api frontend.API) (FieldHasher, error) {
5858
defer lock.RUnlock()
5959
builder, ok := builderRegistry[name]
6060
if !ok {
61-
return nil, errors.New("hash function not found")
61+
return nil, fmt.Errorf("hash function \"%s\" not registered", name)
6262
}
6363
return builder(api)
6464
}
@@ -87,3 +87,48 @@ type BinaryFixedLengthHasher interface {
8787
// FixedLengthSum returns digest of the first length bytes.
8888
FixedLengthSum(length frontend.Variable) []uints.U8
8989
}
90+
91+
// Compressor is a 2-1 one-way function. It takes two inputs and compresses
92+
// them into one output.
93+
//
94+
// NB! This is lossy compression, meaning that the output is not guaranteed to
95+
// be unique for different inputs. The output is guaranteed to be the same for
96+
// the same inputs.
97+
//
98+
// The Compressor is used in the Merkle-Damgard construction to build a hash
99+
// function.
100+
type Compressor interface {
101+
Compress(frontend.Variable, frontend.Variable) frontend.Variable
102+
}
103+
104+
type merkleDamgardHasher struct {
105+
state frontend.Variable
106+
iv frontend.Variable
107+
f Compressor
108+
api frontend.API
109+
}
110+
111+
// NewMerkleDamgardHasher transforms a 2-1 one-way function into a hash
112+
// initialState is a value whose preimage is not known
113+
func NewMerkleDamgardHasher(api frontend.API, f Compressor, initialState frontend.Variable) FieldHasher {
114+
return &merkleDamgardHasher{
115+
state: initialState,
116+
iv: initialState,
117+
f: f,
118+
api: api,
119+
}
120+
}
121+
122+
func (h *merkleDamgardHasher) Reset() {
123+
h.state = h.iv
124+
}
125+
126+
func (h *merkleDamgardHasher) Write(data ...frontend.Variable) {
127+
for _, d := range data {
128+
h.state = h.f.Compress(h.state, d)
129+
}
130+
}
131+
132+
func (h *merkleDamgardHasher) Sum() frontend.Variable {
133+
return h.state
134+
}

std/hash/poseidon2/poseidon2.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package poseidon2
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/consensys/gnark/frontend"
7+
"github.com/consensys/gnark/std/hash"
8+
poseidon2 "github.com/consensys/gnark/std/permutation/poseidon2"
9+
)
10+
11+
// NewMerkleDamgardHasher returns a Poseidon2 hasher using the Merkle-Damgard
12+
// construction with the default parameters.
13+
func NewMerkleDamgardHasher(api frontend.API) (hash.FieldHasher, error) {
14+
f, err := poseidon2.NewPoseidon2(api)
15+
if err != nil {
16+
return nil, fmt.Errorf("could not create poseidon2 hasher: %w", err)
17+
}
18+
return hash.NewMerkleDamgardHasher(api, f, 0), nil
19+
}

std/hash/poseidon2/poseidon2_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package poseidon2
2+
3+
import (
4+
"testing"
5+
6+
"github.com/consensys/gnark-crypto/ecc"
7+
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr/poseidon2"
8+
"github.com/consensys/gnark/frontend"
9+
"github.com/consensys/gnark/test"
10+
)
11+
12+
type Poseidon2Circuit struct {
13+
Input []frontend.Variable
14+
Expected frontend.Variable `gnark:",public"`
15+
}
16+
17+
func (c *Poseidon2Circuit) Define(api frontend.API) error {
18+
hsh, err := NewMerkleDamgardHasher(api)
19+
if err != nil {
20+
return err
21+
}
22+
hsh.Write(c.Input...)
23+
api.AssertIsEqual(hsh.Sum(), c.Expected)
24+
return nil
25+
}
26+
27+
func TestPoseidon2Hash(t *testing.T) {
28+
assert := test.NewAssert(t)
29+
30+
const nbInputs = 5
31+
// prepare expected output
32+
h := poseidon2.NewMerkleDamgardHasher()
33+
circInput := make([]frontend.Variable, nbInputs)
34+
for i := range nbInputs {
35+
_, err := h.Write([]byte{byte(i)})
36+
assert.NoError(err)
37+
circInput[i] = i
38+
}
39+
res := h.Sum(nil)
40+
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
41+
}

0 commit comments

Comments
 (0)