From 4ce782d62ab36b778d3988c69a359f9aa9824cdf Mon Sep 17 00:00:00 2001 From: AlexandreBelling Date: Thu, 24 Oct 2024 18:42:06 +0200 Subject: [PATCH 1/3] feat(mimc): adds a State and SetState functionality --- std/hash/mimc/mimc.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/std/hash/mimc/mimc.go b/std/hash/mimc/mimc.go index 1d6fa4d35c..f0887c1756 100644 --- a/std/hash/mimc/mimc.go +++ b/std/hash/mimc/mimc.go @@ -61,6 +61,29 @@ func (h *MiMC) Reset() { h.h = 0 } +// SetState manually sets the state of the hasher to the provided value. In the +// case of MiMC only a single frontend variable is expected to represent the +// state. +func (h *MiMC) SetState(newState []frontend.Variable) error { + + if len(h.data) > 0 { + return errors.New("the hasher is not in an initial state") + } + + if len(newState) != 1 { + return errors.New("the MiMC hasher expects a single field element to represent the state") + } + + h.h = newState[0] + return nil +} + +// State returns the inner-state of the hasher. In the context of MiMC only a +// single field element is returned. +func (h *MiMC) State() []frontend.Variable { + return []frontend.Variable{h.h} +} + // Sum hash using [Miyaguchi–Preneel] where the XOR operation is replaced by // field addition. // From 88f16ef6750df0e58be287ac87796862b46b5f0c Mon Sep 17 00:00:00 2001 From: AlexandreBelling Date: Thu, 24 Oct 2024 18:43:29 +0200 Subject: [PATCH 2/3] fix: ensures the state is flushed --- std/hash/mimc/mimc.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/std/hash/mimc/mimc.go b/std/hash/mimc/mimc.go index f0887c1756..1d8013ab13 100644 --- a/std/hash/mimc/mimc.go +++ b/std/hash/mimc/mimc.go @@ -75,12 +75,14 @@ func (h *MiMC) SetState(newState []frontend.Variable) error { } h.h = newState[0] + h.data = nil return nil } // State returns the inner-state of the hasher. In the context of MiMC only a // single field element is returned. func (h *MiMC) State() []frontend.Variable { + h.Sum() // this flushes the unsummed data return []frontend.Variable{h.h} } From 33fb4755517dee9a44e3439e92e6737c08aab7b1 Mon Sep 17 00:00:00 2001 From: AlexandreBelling Date: Thu, 12 Dec 2024 00:16:57 +0100 Subject: [PATCH 3/3] change the implementation to match the one of gnark crypto and adds a test --- std/hash/hash.go | 15 ++++++++ std/hash/mimc/mimc_test.go | 72 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/std/hash/hash.go b/std/hash/hash.go index 24854c577f..6fc8752aa7 100644 --- a/std/hash/hash.go +++ b/std/hash/hash.go @@ -40,6 +40,21 @@ type FieldHasher interface { Reset() } +// StateStorer allows to store and retrieve the state of a hash function. +type StateStorer interface { + FieldHasher + // State retrieves the current state of the hash function. Calling this + // method should not destroy the current state and allow continue the use of + // the current hasher. The number of returned Variable is implementation + // dependent. + State() []frontend.Variable + // SetState sets the state of the hash function from a previously stored + // state retrieved using [StateStorer.State] method. The implementation + // returns an error if the number of supplied Variable does not match the + // number of Variable expected. + SetState(state []frontend.Variable) error +} + var ( builderRegistry = make(map[string]func(api frontend.API) (FieldHasher, error)) lock sync.RWMutex diff --git a/std/hash/mimc/mimc_test.go b/std/hash/mimc/mimc_test.go index 6739be9359..a3e48112f8 100644 --- a/std/hash/mimc/mimc_test.go +++ b/std/hash/mimc/mimc_test.go @@ -17,6 +17,8 @@ limitations under the License. package mimc import ( + "errors" + "fmt" "math/big" "testing" @@ -93,3 +95,73 @@ func TestMimcAll(t *testing.T) { } } + +// stateStoreCircuit checks that SetState works as expected. The circuit, however +// does not check the correctness of the hashes returned by the MiMC function +// as there is another test already testing this property. +type stateStoreTestCircuit struct { + X frontend.Variable +} + +func (s *stateStoreTestCircuit) Define(api frontend.API) error { + + hsh1, err1 := NewMiMC(api) + hsh2, err2 := NewMiMC(api) + + if err1 != nil || err2 != nil { + return fmt.Errorf("could not instantiate the MIMC hasher: %w", errors.Join(err1, err2)) + } + + // This pre-shuffle the hasher state so that the test does not start from + // a zero state. + hsh1.Write(s.X) + + state := hsh1.State() + hsh2.SetState(state) + + hsh1.Write(s.X) + hsh2.Write(s.X) + + var ( + dig1 = hsh1.Sum() + dig2 = hsh2.Sum() + newState1 = hsh1.State() + newState2 = hsh2.State() + ) + + api.AssertIsEqual(dig1, dig2) + + for i := range newState1 { + api.AssertIsEqual(newState1[i], newState2[i]) + } + + return nil +} + +func TestStateStoreMiMC(t *testing.T) { + + assert := test.NewAssert(t) + + curves := map[ecc.ID]hash.Hash{ + ecc.BN254: hash.MIMC_BN254, + ecc.BLS12_381: hash.MIMC_BLS12_381, + ecc.BLS12_377: hash.MIMC_BLS12_377, + ecc.BW6_761: hash.MIMC_BW6_761, + ecc.BW6_633: hash.MIMC_BW6_633, + ecc.BLS24_315: hash.MIMC_BLS24_315, + ecc.BLS24_317: hash.MIMC_BLS24_317, + } + + for curve := range curves { + + // minimal cs res = hash(data) + var ( + circuit = &stateStoreTestCircuit{} + assignment = &stateStoreTestCircuit{X: 2} + ) + + assert.CheckCircuit(circuit, + test.WithValidAssignment(assignment), + test.WithCurves(curve)) + } +}