diff --git a/ecc/bls12-377/fr/mimc/mimc.go b/ecc/bls12-377/fr/mimc/mimc.go index 2855e3ab8e..43728abd36 100644 --- a/ecc/bls12-377/fr/mimc/mimc.go +++ b/ecc/bls12-377/fr/mimc/mimc.go @@ -7,14 +7,22 @@ package mimc import ( "errors" - "hash" + stdhash "hash" + "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark-crypto/hash" + "golang.org/x/crypto/sha3" - "math/big" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_BLS12_377, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( mimcNbRounds = 62 seed = "seed" // seed to derive the constants @@ -45,8 +53,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -193,3 +201,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != 32 { + return errors.New("the mimc state expects a state of 32 bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} diff --git a/ecc/bls12-377/fr/mimc/mimc_test.go b/ecc/bls12-377/fr/mimc/mimc_test.go new file mode 100644 index 0000000000..b018112257 --- /dev/null +++ b/ecc/bls12-377/fr/mimc/mimc_test.go @@ -0,0 +1,120 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package mimc_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/mimc" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiMCFiatShamir(t *testing.T) { + fs := fiatshamir.NewTranscript(mimc.NewMiMC(), "c0") + zero := make([]byte, mimc.BlockSize) + err := fs.Bind("c0", zero) + assert.NoError(t, err) + _, err = fs.ComputeChallenge("c0") + assert.NoError(t, err) +} + +func TestByteOrder(t *testing.T) { + assert := require.New(t) + + var buf [fr.Bytes]byte + // if the 31 first bytes are FF, it's a valid FF in little endian, but not in big endian + for i := 0; i < fr.Bytes-1; i++ { + buf[i] = 0xFF + } + _, err := fr.BigEndian.Element(&buf) + assert.Error(err) + _, err = fr.LittleEndian.Element(&buf) + assert.NoError(err) + + { + // hashing buf with big endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } + + { + // hashing buf with little endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + buf = [fr.Bytes]byte{} + // if the 31 bytes are FF, it's a valid FF in big endian, but not in little endian + for i := 1; i < fr.Bytes; i++ { + buf[i] = 0xFF + } + _, err = fr.BigEndian.Element(&buf) + assert.NoError(err) + _, err = fr.LittleEndian.Element(&buf) + assert.Error(err) + + { + // hashing buf with big endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + { + // hashing buf with little endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + + storedStates := make([][]byte, len(randInputs)) + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } +} diff --git a/ecc/bls12-377/twistededwards/eddsa/eddsa_test.go b/ecc/bls12-377/twistededwards/eddsa/eddsa_test.go index 09413696d2..4f79ebdf65 100644 --- a/ecc/bls12-377/twistededwards/eddsa/eddsa_test.go +++ b/ecc/bls12-377/twistededwards/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bls12-377/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BLS12_377.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/ecc/bls12-381/bandersnatch/eddsa/eddsa_test.go b/ecc/bls12-381/bandersnatch/eddsa/eddsa_test.go index cccc545ff7..c53bfc6cc8 100644 --- a/ecc/bls12-381/bandersnatch/eddsa/eddsa_test.go +++ b/ecc/bls12-381/bandersnatch/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bls12-381/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BLS12_381.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/ecc/bls12-381/fr/mimc/mimc.go b/ecc/bls12-381/fr/mimc/mimc.go index 59899b3e92..6db9581b75 100644 --- a/ecc/bls12-381/fr/mimc/mimc.go +++ b/ecc/bls12-381/fr/mimc/mimc.go @@ -7,14 +7,22 @@ package mimc import ( "errors" - "hash" + stdhash "hash" + "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/hash" + "golang.org/x/crypto/sha3" - "math/big" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_BLS12_381, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( mimcNbRounds = 111 seed = "seed" // seed to derive the constants @@ -45,8 +53,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -191,3 +199,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != 32 { + return errors.New("the mimc state expects a state of 32 bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} diff --git a/ecc/bls12-381/fr/mimc/mimc_test.go b/ecc/bls12-381/fr/mimc/mimc_test.go new file mode 100644 index 0000000000..b8b94cc1ea --- /dev/null +++ b/ecc/bls12-381/fr/mimc/mimc_test.go @@ -0,0 +1,120 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package mimc_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiMCFiatShamir(t *testing.T) { + fs := fiatshamir.NewTranscript(mimc.NewMiMC(), "c0") + zero := make([]byte, mimc.BlockSize) + err := fs.Bind("c0", zero) + assert.NoError(t, err) + _, err = fs.ComputeChallenge("c0") + assert.NoError(t, err) +} + +func TestByteOrder(t *testing.T) { + assert := require.New(t) + + var buf [fr.Bytes]byte + // if the 31 first bytes are FF, it's a valid FF in little endian, but not in big endian + for i := 0; i < fr.Bytes-1; i++ { + buf[i] = 0xFF + } + _, err := fr.BigEndian.Element(&buf) + assert.Error(err) + _, err = fr.LittleEndian.Element(&buf) + assert.NoError(err) + + { + // hashing buf with big endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } + + { + // hashing buf with little endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + buf = [fr.Bytes]byte{} + // if the 31 bytes are FF, it's a valid FF in big endian, but not in little endian + for i := 1; i < fr.Bytes; i++ { + buf[i] = 0xFF + } + _, err = fr.BigEndian.Element(&buf) + assert.NoError(err) + _, err = fr.LittleEndian.Element(&buf) + assert.Error(err) + + { + // hashing buf with big endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + { + // hashing buf with little endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + + storedStates := make([][]byte, len(randInputs)) + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } +} diff --git a/ecc/bls12-381/twistededwards/eddsa/eddsa_test.go b/ecc/bls12-381/twistededwards/eddsa/eddsa_test.go index cccc545ff7..c53bfc6cc8 100644 --- a/ecc/bls12-381/twistededwards/eddsa/eddsa_test.go +++ b/ecc/bls12-381/twistededwards/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bls12-381/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BLS12_381.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/ecc/bls24-315/fr/mimc/mimc.go b/ecc/bls24-315/fr/mimc/mimc.go index c91ead0218..7f1ab7c7e8 100644 --- a/ecc/bls24-315/fr/mimc/mimc.go +++ b/ecc/bls24-315/fr/mimc/mimc.go @@ -7,14 +7,22 @@ package mimc import ( "errors" - "hash" + stdhash "hash" + "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" + "github.com/consensys/gnark-crypto/hash" + "golang.org/x/crypto/sha3" - "math/big" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_BLS24_315, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( mimcNbRounds = 109 seed = "seed" // seed to derive the constants @@ -45,8 +53,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -191,3 +199,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != 32 { + return errors.New("the mimc state expects a state of 32 bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} diff --git a/ecc/bls24-315/fr/mimc/mimc_test.go b/ecc/bls24-315/fr/mimc/mimc_test.go new file mode 100644 index 0000000000..58aa9449fe --- /dev/null +++ b/ecc/bls24-315/fr/mimc/mimc_test.go @@ -0,0 +1,120 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package mimc_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/mimc" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiMCFiatShamir(t *testing.T) { + fs := fiatshamir.NewTranscript(mimc.NewMiMC(), "c0") + zero := make([]byte, mimc.BlockSize) + err := fs.Bind("c0", zero) + assert.NoError(t, err) + _, err = fs.ComputeChallenge("c0") + assert.NoError(t, err) +} + +func TestByteOrder(t *testing.T) { + assert := require.New(t) + + var buf [fr.Bytes]byte + // if the 31 first bytes are FF, it's a valid FF in little endian, but not in big endian + for i := 0; i < fr.Bytes-1; i++ { + buf[i] = 0xFF + } + _, err := fr.BigEndian.Element(&buf) + assert.Error(err) + _, err = fr.LittleEndian.Element(&buf) + assert.NoError(err) + + { + // hashing buf with big endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } + + { + // hashing buf with little endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + buf = [fr.Bytes]byte{} + // if the 31 bytes are FF, it's a valid FF in big endian, but not in little endian + for i := 1; i < fr.Bytes; i++ { + buf[i] = 0xFF + } + _, err = fr.BigEndian.Element(&buf) + assert.NoError(err) + _, err = fr.LittleEndian.Element(&buf) + assert.Error(err) + + { + // hashing buf with big endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + { + // hashing buf with little endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + + storedStates := make([][]byte, len(randInputs)) + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } +} diff --git a/ecc/bls24-315/twistededwards/eddsa/eddsa_test.go b/ecc/bls24-315/twistededwards/eddsa/eddsa_test.go index a08863e866..3954c0a991 100644 --- a/ecc/bls24-315/twistededwards/eddsa/eddsa_test.go +++ b/ecc/bls24-315/twistededwards/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bls24-315/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BLS24_315.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/ecc/bls24-317/fr/mimc/mimc.go b/ecc/bls24-317/fr/mimc/mimc.go index 4e5a82ccf7..c4ca3df7c1 100644 --- a/ecc/bls24-317/fr/mimc/mimc.go +++ b/ecc/bls24-317/fr/mimc/mimc.go @@ -7,14 +7,22 @@ package mimc import ( "errors" - "hash" + stdhash "hash" + "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" + "github.com/consensys/gnark-crypto/hash" + "golang.org/x/crypto/sha3" - "math/big" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_BLS24_317, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( mimcNbRounds = 91 seed = "seed" // seed to derive the constants @@ -45,8 +53,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -193,3 +201,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != 32 { + return errors.New("the mimc state expects a state of 32 bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} diff --git a/ecc/bls24-317/fr/mimc/mimc_test.go b/ecc/bls24-317/fr/mimc/mimc_test.go new file mode 100644 index 0000000000..54f4ce08aa --- /dev/null +++ b/ecc/bls24-317/fr/mimc/mimc_test.go @@ -0,0 +1,120 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package mimc_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/mimc" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiMCFiatShamir(t *testing.T) { + fs := fiatshamir.NewTranscript(mimc.NewMiMC(), "c0") + zero := make([]byte, mimc.BlockSize) + err := fs.Bind("c0", zero) + assert.NoError(t, err) + _, err = fs.ComputeChallenge("c0") + assert.NoError(t, err) +} + +func TestByteOrder(t *testing.T) { + assert := require.New(t) + + var buf [fr.Bytes]byte + // if the 31 first bytes are FF, it's a valid FF in little endian, but not in big endian + for i := 0; i < fr.Bytes-1; i++ { + buf[i] = 0xFF + } + _, err := fr.BigEndian.Element(&buf) + assert.Error(err) + _, err = fr.LittleEndian.Element(&buf) + assert.NoError(err) + + { + // hashing buf with big endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } + + { + // hashing buf with little endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + buf = [fr.Bytes]byte{} + // if the 31 bytes are FF, it's a valid FF in big endian, but not in little endian + for i := 1; i < fr.Bytes; i++ { + buf[i] = 0xFF + } + _, err = fr.BigEndian.Element(&buf) + assert.NoError(err) + _, err = fr.LittleEndian.Element(&buf) + assert.Error(err) + + { + // hashing buf with big endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + { + // hashing buf with little endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + + storedStates := make([][]byte, len(randInputs)) + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } +} diff --git a/ecc/bls24-317/twistededwards/eddsa/eddsa_test.go b/ecc/bls24-317/twistededwards/eddsa/eddsa_test.go index a045167a35..dc56916881 100644 --- a/ecc/bls24-317/twistededwards/eddsa/eddsa_test.go +++ b/ecc/bls24-317/twistededwards/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bls24-317/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BLS24_317.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/ecc/bn254/fr/mimc/mimc.go b/ecc/bn254/fr/mimc/mimc.go index f58fc6ef3b..f7fde2955e 100644 --- a/ecc/bn254/fr/mimc/mimc.go +++ b/ecc/bn254/fr/mimc/mimc.go @@ -7,14 +7,22 @@ package mimc import ( "errors" - "hash" + stdhash "hash" + "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/hash" + "golang.org/x/crypto/sha3" - "math/big" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_BN254, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( mimcNbRounds = 110 seed = "seed" // seed to derive the constants @@ -45,8 +53,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -191,3 +199,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != 32 { + return errors.New("the mimc state expects a state of 32 bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} diff --git a/ecc/bn254/fr/mimc/mimc_test.go b/ecc/bn254/fr/mimc/mimc_test.go index 73042f6c31..1c0a7e4c5f 100644 --- a/ecc/bn254/fr/mimc/mimc_test.go +++ b/ecc/bn254/fr/mimc/mimc_test.go @@ -1,11 +1,18 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + package mimc_test import ( + "bytes" "testing" "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -69,5 +76,45 @@ func TestByteOrder(t *testing.T) { _, err := mimcHash.Write(buf[:]) assert.Error(err) } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + storedStates := make([][]byte, len(randInputs)) + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } } diff --git a/ecc/bn254/twistededwards/eddsa/eddsa_test.go b/ecc/bn254/twistededwards/eddsa/eddsa_test.go index 16f6511f46..f817340864 100644 --- a/ecc/bn254/twistededwards/eddsa/eddsa_test.go +++ b/ecc/bn254/twistededwards/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bn254/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BN254.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/ecc/bw6-633/fr/mimc/mimc.go b/ecc/bw6-633/fr/mimc/mimc.go index 3ed4a8a714..f9665cf347 100644 --- a/ecc/bw6-633/fr/mimc/mimc.go +++ b/ecc/bw6-633/fr/mimc/mimc.go @@ -7,14 +7,22 @@ package mimc import ( "errors" - "hash" + stdhash "hash" + "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" + "github.com/consensys/gnark-crypto/hash" + "golang.org/x/crypto/sha3" - "math/big" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_BW6_633, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( mimcNbRounds = 136 seed = "seed" // seed to derive the constants @@ -45,8 +53,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -191,3 +199,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != 40 { + return errors.New("the mimc state expects a state of 40 bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} diff --git a/ecc/bw6-633/fr/mimc/mimc_test.go b/ecc/bw6-633/fr/mimc/mimc_test.go new file mode 100644 index 0000000000..f896dd8e3b --- /dev/null +++ b/ecc/bw6-633/fr/mimc/mimc_test.go @@ -0,0 +1,120 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package mimc_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/mimc" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiMCFiatShamir(t *testing.T) { + fs := fiatshamir.NewTranscript(mimc.NewMiMC(), "c0") + zero := make([]byte, mimc.BlockSize) + err := fs.Bind("c0", zero) + assert.NoError(t, err) + _, err = fs.ComputeChallenge("c0") + assert.NoError(t, err) +} + +func TestByteOrder(t *testing.T) { + assert := require.New(t) + + var buf [fr.Bytes]byte + // if the 39 first bytes are FF, it's a valid FF in little endian, but not in big endian + for i := 0; i < fr.Bytes-1; i++ { + buf[i] = 0xFF + } + _, err := fr.BigEndian.Element(&buf) + assert.Error(err) + _, err = fr.LittleEndian.Element(&buf) + assert.NoError(err) + + { + // hashing buf with big endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } + + { + // hashing buf with little endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + buf = [fr.Bytes]byte{} + // if the 39 bytes are FF, it's a valid FF in big endian, but not in little endian + for i := 1; i < fr.Bytes; i++ { + buf[i] = 0xFF + } + _, err = fr.BigEndian.Element(&buf) + assert.NoError(err) + _, err = fr.LittleEndian.Element(&buf) + assert.Error(err) + + { + // hashing buf with big endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + { + // hashing buf with little endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + + storedStates := make([][]byte, len(randInputs)) + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } +} diff --git a/ecc/bw6-633/twistededwards/eddsa/eddsa_test.go b/ecc/bw6-633/twistededwards/eddsa/eddsa_test.go index 11dd10da30..6ffff698d8 100644 --- a/ecc/bw6-633/twistededwards/eddsa/eddsa_test.go +++ b/ecc/bw6-633/twistededwards/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bw6-633/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BW6_633.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/ecc/bw6-761/fr/mimc/mimc.go b/ecc/bw6-761/fr/mimc/mimc.go index 2a0a323955..c77a4a9698 100644 --- a/ecc/bw6-761/fr/mimc/mimc.go +++ b/ecc/bw6-761/fr/mimc/mimc.go @@ -7,14 +7,22 @@ package mimc import ( "errors" - "hash" + stdhash "hash" + "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + "github.com/consensys/gnark-crypto/hash" + "golang.org/x/crypto/sha3" - "math/big" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_BW6_761, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( mimcNbRounds = 163 seed = "seed" // seed to derive the constants @@ -45,8 +53,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -191,3 +199,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != 48 { + return errors.New("the mimc state expects a state of 48 bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} diff --git a/ecc/bw6-761/fr/mimc/mimc_test.go b/ecc/bw6-761/fr/mimc/mimc_test.go new file mode 100644 index 0000000000..f40411068f --- /dev/null +++ b/ecc/bw6-761/fr/mimc/mimc_test.go @@ -0,0 +1,120 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package mimc_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/mimc" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiMCFiatShamir(t *testing.T) { + fs := fiatshamir.NewTranscript(mimc.NewMiMC(), "c0") + zero := make([]byte, mimc.BlockSize) + err := fs.Bind("c0", zero) + assert.NoError(t, err) + _, err = fs.ComputeChallenge("c0") + assert.NoError(t, err) +} + +func TestByteOrder(t *testing.T) { + assert := require.New(t) + + var buf [fr.Bytes]byte + // if the 47 first bytes are FF, it's a valid FF in little endian, but not in big endian + for i := 0; i < fr.Bytes-1; i++ { + buf[i] = 0xFF + } + _, err := fr.BigEndian.Element(&buf) + assert.Error(err) + _, err = fr.LittleEndian.Element(&buf) + assert.NoError(err) + + { + // hashing buf with big endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } + + { + // hashing buf with little endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + buf = [fr.Bytes]byte{} + // if the 47 bytes are FF, it's a valid FF in big endian, but not in little endian + for i := 1; i < fr.Bytes; i++ { + buf[i] = 0xFF + } + _, err = fr.BigEndian.Element(&buf) + assert.NoError(err) + _, err = fr.LittleEndian.Element(&buf) + assert.Error(err) + + { + // hashing buf with big endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + { + // hashing buf with little endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + + storedStates := make([][]byte, len(randInputs)) + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } +} diff --git a/ecc/bw6-761/twistededwards/eddsa/eddsa_test.go b/ecc/bw6-761/twistededwards/eddsa/eddsa_test.go index d9b595e894..905eca2e21 100644 --- a/ecc/bw6-761/twistededwards/eddsa/eddsa_test.go +++ b/ecc/bw6-761/twistededwards/eddsa/eddsa_test.go @@ -16,13 +16,14 @@ import ( "fmt" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/mimc" "github.com/consensys/gnark-crypto/ecc/bw6-761/twistededwards" "github.com/consensys/gnark-crypto/hash" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_BW6_761.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader) diff --git a/hash/all/allhashes.go b/hash/all/allhashes.go new file mode 100644 index 0000000000..cb7b5947e3 --- /dev/null +++ b/hash/all/allhashes.go @@ -0,0 +1,11 @@ +package all + +import ( + _ "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/mimc" + _ "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc" + _ "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/mimc" + _ "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/mimc" + _ "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" + _ "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/mimc" + _ "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/mimc" +) diff --git a/hash/doc.go b/hash/doc.go index faefa0669e..b1ed8c91c3 100644 --- a/hash/doc.go +++ b/hash/doc.go @@ -1,5 +1,24 @@ // Package hash provides MiMC hash function defined over implemented curves // +// This package is kept for backwards compatibility. The recommended way to +// initialize hash function is to directly use the constructors in the +// corresponding packages (e.g. ecc/bn254/fr/mimc). Using the direct +// constructors allows to apply options for altering the hash function behavior +// (endianness, input splicing etc.) and returns more specific types with +// additional methods. +// +// See [Importing hash functions] below for more information. +// +// # Importing hash functions +// +// The package follows registration pattern for importing hash functions. To +// import all known hash functions in gnark-crypto, import the +// [github.com/consensys/gnark-crypto/hash/all] package in your code. To import +// only a specific hash, then import the corresponding package directly, e.g. +// [github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc]. The import format should be: +// +// import _ "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" +// // # Length extension attack // // The MiMC hash function is vulnerable to a length extension attack. For diff --git a/hash/hashes.go b/hash/hashes.go index 4388219c0b..ee88d158e4 100644 --- a/hash/hashes.go +++ b/hash/hashes.go @@ -4,17 +4,22 @@ package hash import ( + "fmt" "hash" - - bls377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/mimc" - bls381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc" - bls315 "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/mimc" - bls317 "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/mimc" - bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" - bw633 "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/mimc" - bw761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/mimc" + "strings" ) +var hashes = make([]func() hash.Hash, maxHash) + +// RegisterHash registers a new hash function constructor. Should be called in +// the init function of the hash package. +// +// To register all known hash functions in gnark-crypto, import the +// [github.com/consensys/gnark-crypto/hash/all] package in your code. +func RegisterHash(h Hash, new func() hash.Hash) { + hashes[h] = new +} + // Hash defines an unique identifier for a hash function. type Hash uint @@ -33,6 +38,8 @@ const ( MIMC_BLS24_317 // MIMC_BW6_633 is the MiMC hash function for the BW6-633 curve. MIMC_BW6_633 + + maxHash ) // size of digests in bytes @@ -46,26 +53,21 @@ var digestSize = []uint8{ MIMC_BW6_633: 80, } -// New initializes the hash function. +// New initializes the hash function. This is a convenience function which does +// not allow setting hash-specific options. func (m Hash) New() hash.Hash { - switch m { - case MIMC_BN254: - return bn254.NewMiMC() - case MIMC_BLS12_381: - return bls381.NewMiMC() - case MIMC_BLS12_377: - return bls377.NewMiMC() - case MIMC_BW6_761: - return bw761.NewMiMC() - case MIMC_BLS24_315: - return bls315.NewMiMC() - case MIMC_BLS24_317: - return bls317.NewMiMC() - case MIMC_BW6_633: - return bw633.NewMiMC() - default: - panic("Unknown mimc ID") + if m < maxHash { + f := hashes[m] + if f != nil { + return f() + } } + pkgname, _ := strings.CutPrefix(m.String(), "MIMC_") + pkgname = strings.ToLower(pkgname) + pkgname = strings.ReplaceAll(pkgname, "_", "-") + msg := fmt.Sprintf(`requested hash function #%s not registered. Import the corresponding package to register it: + import _ "github.com/consensys/gnark-crypto/ecc/%s/fr/mimc"`, m.String(), pkgname) + panic(msg) } // String returns the unique identifier of the hash function. @@ -74,24 +76,28 @@ func (m Hash) String() string { case MIMC_BN254: return "MIMC_BN254" case MIMC_BLS12_381: - return "MIMC_BLS381" + return "MIMC_BLS12_381" case MIMC_BLS12_377: - return "MIMC_BLS377" + return "MIMC_BLS12_377" case MIMC_BW6_761: - return "MIMC_BW761" + return "MIMC_BW6_761" case MIMC_BLS24_315: - return "MIMC_BLS315" + return "MIMC_BLS24_315" case MIMC_BLS24_317: - return "MIMC_BLS317" + return "MIMC_BLS24_317" case MIMC_BW6_633: - return "MIMC_BW633" + return "MIMC_BW6_633" default: - panic("Unknown mimc ID") + return "unknown hash function" } } -// Size returns the size of the digest of -// the corresponding hash function +// Available returns true if the hash function is available. +func (m Hash) Available() bool { + return m < maxHash && hashes[m] != nil +} + +// Size returns the size of the digest of the corresponding hash function func (m Hash) Size() int { return int(digestSize[m]) } diff --git a/hash/interface.go b/hash/interface.go new file mode 100644 index 0000000000..5b2c3063b4 --- /dev/null +++ b/hash/interface.go @@ -0,0 +1,16 @@ +package hash + +import "hash" + +// StateStorer allows to store and retrieve the state of a hash function. +type StateStorer interface { + hash.Hash + + // 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. + State() []byte + // SetState sets the state of the hash function from a previously stored + // state retrieved using [StateStorer.State] method. + SetState(state []byte) error +} diff --git a/internal/generator/crypto/hash/mimc/generate.go b/internal/generator/crypto/hash/mimc/generate.go index 3783931ba0..58dc7fb099 100644 --- a/internal/generator/crypto/hash/mimc/generate.go +++ b/internal/generator/crypto/hash/mimc/generate.go @@ -1,7 +1,7 @@ package mimc import ( - "os" + "fmt" "path/filepath" "github.com/consensys/bavard" @@ -16,9 +16,15 @@ func Generate(conf config.Curve, baseDir string, bgen *bavard.BatchGenerator) er {File: filepath.Join(baseDir, "mimc.go"), Templates: []string{"mimc.go.tmpl"}}, {File: filepath.Join(baseDir, "options.go"), Templates: []string{"options.go.tmpl"}}, } - os.Remove(filepath.Join(baseDir, "utils.go")) - os.Remove(filepath.Join(baseDir, "utils_test.go")) - - return bgen.Generate(conf, conf.Package, "./crypto/hash/mimc/template", entries...) + entriesTest := []bavard.Entry{ + {File: filepath.Join(baseDir, "mimc_test.go"), Templates: []string{"tests/mimc_test.go.tmpl"}}, + } + if err := bgen.Generate(conf, conf.Package, "./crypto/hash/mimc/template", entries...); err != nil { + return fmt.Errorf("generate package: %w", err) + } + if err := bgen.Generate(conf, "mimc_test", "./crypto/hash/mimc/template", entriesTest...); err != nil { + return fmt.Errorf("generate tests: %w", err) + } + return nil } diff --git a/internal/generator/crypto/hash/mimc/template/mimc.go.tmpl b/internal/generator/crypto/hash/mimc/template/mimc.go.tmpl index 69d0434980..a50dc24c1c 100644 --- a/internal/generator/crypto/hash/mimc/template/mimc.go.tmpl +++ b/internal/generator/crypto/hash/mimc/template/mimc.go.tmpl @@ -1,13 +1,21 @@ import ( "errors" - "hash" - + stdhash "hash" "math/big" + "sync" + + "github.com/consensys/gnark-crypto/hash" "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr" + "golang.org/x/crypto/sha3" - "sync" ) +func init() { + hash.RegisterHash(hash.MIMC_{{ .EnumID }}, func() stdhash.Hash { + return NewMiMC() + }) +} + const ( {{ if eq .Name "bn254" }} mimcNbRounds = 110 @@ -54,8 +62,8 @@ func GetConstants() []big.Int { return res } -// NewMiMC returns a MiMCImpl object, pure-go reference implementation -func NewMiMC(opts ...Option) hash.Hash { +// NewMiMC returns a MiMC implementation, pure Go reference implementation. +func NewMiMC(opts ...Option) hash.StateStorer { d := new(digest) d.Reset() cfg := mimcOptions(opts...) @@ -244,3 +252,27 @@ func (d *digest) WriteString(rawBytes []byte) error { } return nil } + +// SetState manually sets the state of the hasher to an user-provided value. In +// the context of MiMC, the method expects a byte slice of 32 elements. +func (d *digest) SetState(newState []byte) error { + + if len(newState) != {{ .Fr.NbBytes }} { + return errors.New("the mimc state expects a state of {{ .Fr.NbBytes }} bytes") + } + + if err := d.h.SetBytesCanonical(newState); err != nil { + return errors.New("the provided newState does not represent a valid state") + } + + d.data = nil + + return nil +} + +// State returns the internal state of the hasher +func (d *digest) State() []byte { + _ = d.Sum(nil) // this flushes the hasher + b := d.h.Bytes() + return b[:] +} \ No newline at end of file diff --git a/internal/generator/crypto/hash/mimc/template/tests/mimc_test.go.tmpl b/internal/generator/crypto/hash/mimc/template/tests/mimc_test.go.tmpl new file mode 100644 index 0000000000..fa99cdd4f6 --- /dev/null +++ b/internal/generator/crypto/hash/mimc/template/tests/mimc_test.go.tmpl @@ -0,0 +1,114 @@ +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc/{{.Name}}/fr" + "github.com/consensys/gnark-crypto/ecc/{{.Name}}/fr/mimc" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiMCFiatShamir(t *testing.T) { + fs := fiatshamir.NewTranscript(mimc.NewMiMC(), "c0") + zero := make([]byte, mimc.BlockSize) + err := fs.Bind("c0", zero) + assert.NoError(t, err) + _, err = fs.ComputeChallenge("c0") + assert.NoError(t, err) +} + +func TestByteOrder(t *testing.T) { + assert := require.New(t) + + var buf [fr.Bytes]byte + // if the {{ sub .Fr.NbBytes 1 }} first bytes are FF, it's a valid FF in little endian, but not in big endian + for i := 0; i < fr.Bytes-1; i++ { + buf[i] = 0xFF + } + _, err := fr.BigEndian.Element(&buf) + assert.Error(err) + _, err = fr.LittleEndian.Element(&buf) + assert.NoError(err) + + { + // hashing buf with big endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } + + { + // hashing buf with little endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + buf = [fr.Bytes]byte{} + // if the {{ sub .Fr.NbBytes 1 }} bytes are FF, it's a valid FF in big endian, but not in little endian + for i := 1; i < fr.Bytes; i++ { + buf[i] = 0xFF + } + _, err = fr.BigEndian.Element(&buf) + assert.NoError(err) + _, err = fr.LittleEndian.Element(&buf) + assert.Error(err) + + { + // hashing buf with big endian should succeed + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.BigEndian)) + _, err := mimcHash.Write(buf[:]) + assert.NoError(err) + } + + { + // hashing buf with little endian should fail + mimcHash := mimc.NewMiMC(mimc.WithByteOrder(fr.LittleEndian)) + _, err := mimcHash.Write(buf[:]) + assert.Error(err) + } +} + +func TestSetState(t *testing.T) { + // we use for hashing and retrieving the state + h1 := mimc.NewMiMC() + // only hashing + h2 := mimc.NewMiMC() + // we use for restoring from state + h3 := mimc.NewMiMC() + + randInputs := make([]fr.Element, 10) + for i := range randInputs { + randInputs[i].SetRandom() + } + + storedStates := make([][]byte, len(randInputs)) + + + for i := range randInputs { + storedStates[i] = h1.State() + + h1.Write(randInputs[i].Marshal()) + h2.Write(randInputs[i].Marshal()) + } + dgst1 := h1.Sum(nil) + dgst2 := h2.Sum(nil) + if !bytes.Equal(dgst1, dgst2) { + t.Fatal("hashes do not match") + } + + for i := range storedStates { + if err := h3.SetState(storedStates[i]); err != nil { + t.Fatal(err) + } + for j := i; j < len(randInputs); j++ { + h3.Write(randInputs[j].Marshal()) + } + dgst3 := h3.Sum(nil) + if !bytes.Equal(dgst1, dgst3) { + t.Fatal("hashes do not match") + } + } +} diff --git a/internal/generator/edwards/eddsa/template/eddsa.test.go.tmpl b/internal/generator/edwards/eddsa/template/eddsa.test.go.tmpl index bdc6fdf7b4..86fcd599b2 100644 --- a/internal/generator/edwards/eddsa/template/eddsa.test.go.tmpl +++ b/internal/generator/edwards/eddsa/template/eddsa.test.go.tmpl @@ -11,12 +11,13 @@ import ( "github.com/consensys/gnark-crypto/hash" "github.com/consensys/gnark-crypto/ecc/{{.Name}}/twistededwards" "github.com/consensys/gnark-crypto/ecc/{{.Name}}/fr" + "github.com/consensys/gnark-crypto/ecc/{{.Name}}/fr/mimc" ) func Example() { // instantiate hash function - hFunc := hash.MIMC_{{ .EnumID }}.New() + hFunc := mimc.NewMiMC() // create a eddsa key pair privateKey, _ := GenerateKey(crand.Reader)