From e92a15d3b0f667eee2ce50e284c63587b58459ac Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 25 Jun 2019 15:01:38 +0200 Subject: [PATCH] add mimc7 & fields --- README.md | 3 + field/field.go | 152 +++++++++++++++++++++++++++++++++++++++ field/field_test.go | 39 ++++++++++ mimc7/mimc7.go | 171 ++++++++++++++++++++++++++++++++++++++++++++ mimc7/mimc7_test.go | 137 +++++++++++++++++++++++++++++++++++ 5 files changed, 502 insertions(+) create mode 100644 README.md create mode 100644 field/field.go create mode 100644 field/field_test.go create mode 100644 mimc7/mimc7.go create mode 100644 mimc7/mimc7_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..26abbba --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# go-iden3-crypto [![Go Report Card](https://goreportcard.com/badge/github.com/iden3/go-iden3-crypto)](https://goreportcard.com/report/github.com/iden3/go-iden3-crypto) [![GoDoc](https://godoc.org/github.com/iden3/go-iden3-crypto?status.svg)](https://godoc.org/github.com/iden3/go-iden3-crypto) +Go implementation of some cryptographic primitives used in iden3 + diff --git a/field/field.go b/field/field.go new file mode 100644 index 0000000..4f4f44a --- /dev/null +++ b/field/field.go @@ -0,0 +1,152 @@ +// code originally taken from https://github.com/arnaucube/go-snark (https://github.com/arnaucube/go-snark/blob/master/fields/fq.go), pasted here to ensure compatibility among future changes + +package field + +import ( + "bytes" + "crypto/rand" + "math/big" +) + +// Fq is the Z field over modulus Q +type Fq struct { + Q *big.Int // Q +} + +// NewFq generates a new Fq +func NewFq(q *big.Int) Fq { + return Fq{ + q, + } +} + +// Zero returns a Zero value on the Fq +func (fq Fq) Zero() *big.Int { + return big.NewInt(int64(0)) +} + +// One returns a One value on the Fq +func (fq Fq) One() *big.Int { + return big.NewInt(int64(1)) +} + +// Add performs an addition on the Fq +func (fq Fq) Add(a, b *big.Int) *big.Int { + r := new(big.Int).Add(a, b) + return new(big.Int).Mod(r, fq.Q) +} + +// Double performs a doubling on the Fq +func (fq Fq) Double(a *big.Int) *big.Int { + r := new(big.Int).Add(a, a) + return new(big.Int).Mod(r, fq.Q) +} + +// Sub performs a subtraction on the Fq +func (fq Fq) Sub(a, b *big.Int) *big.Int { + r := new(big.Int).Sub(a, b) + return new(big.Int).Mod(r, fq.Q) +} + +// Neg performs a negation on the Fq +func (fq Fq) Neg(a *big.Int) *big.Int { + m := new(big.Int).Neg(a) + return new(big.Int).Mod(m, fq.Q) +} + +// Mul performs a multiplication on the Fq +func (fq Fq) Mul(a, b *big.Int) *big.Int { + m := new(big.Int).Mul(a, b) + return new(big.Int).Mod(m, fq.Q) +} + +func (fq Fq) MulScalar(base, e *big.Int) *big.Int { + return fq.Mul(base, e) +} + +// Inverse returns the inverse on the Fq +func (fq Fq) Inverse(a *big.Int) *big.Int { + return new(big.Int).ModInverse(a, fq.Q) +} + +// Div performs the division over the finite field +func (fq Fq) Div(a, b *big.Int) *big.Int { + d := fq.Mul(a, fq.Inverse(b)) + return new(big.Int).Mod(d, fq.Q) +} + +// Square performs a square operation on the Fq +func (fq Fq) Square(a *big.Int) *big.Int { + m := new(big.Int).Mul(a, a) + return new(big.Int).Mod(m, fq.Q) +} + +// Exp performs the exponential over Fq +func (fq Fq) Exp(base *big.Int, e *big.Int) *big.Int { + res := fq.One() + rem := fq.Copy(e) + exp := base + + for !bytes.Equal(rem.Bytes(), big.NewInt(int64(0)).Bytes()) { + if BigIsOdd(rem) { + res = fq.Mul(res, exp) + } + exp = fq.Square(exp) + rem = new(big.Int).Rsh(rem, 1) + } + return res +} + +func (fq Fq) Rand() (*big.Int, error) { + + maxbits := fq.Q.BitLen() + b := make([]byte, (maxbits/8)-1) + _, err := rand.Read(b) + if err != nil { + return nil, err + } + r := new(big.Int).SetBytes(b) + rq := new(big.Int).Mod(r, fq.Q) + + // r over q, nil + return rq, nil +} + +func (fq Fq) IsZero(a *big.Int) bool { + return bytes.Equal(a.Bytes(), fq.Zero().Bytes()) +} + +func (fq Fq) Copy(a *big.Int) *big.Int { + return new(big.Int).SetBytes(a.Bytes()) +} + +func (fq Fq) Affine(a *big.Int) *big.Int { + nq := fq.Neg(fq.Q) + + aux := a + if aux.Cmp(big.NewInt(int64(0))) == -1 { // negative value + if aux.Cmp(nq) != 1 { // aux less or equal nq + aux = new(big.Int).Mod(aux, fq.Q) + } + if aux.Cmp(big.NewInt(int64(0))) == -1 { // negative value + aux = new(big.Int).Add(aux, fq.Q) + } + } else { + if aux.Cmp(fq.Q) != -1 { // aux greater or equal nq + aux = new(big.Int).Mod(aux, fq.Q) + } + } + return aux +} + +func (fq Fq) Equal(a, b *big.Int) bool { + aAff := fq.Affine(a) + bAff := fq.Affine(b) + return bytes.Equal(aAff.Bytes(), bAff.Bytes()) +} + +func BigIsOdd(n *big.Int) bool { + one := big.NewInt(int64(1)) + and := new(big.Int).And(n, one) + return bytes.Equal(and.Bytes(), big.NewInt(int64(1)).Bytes()) +} diff --git a/field/field_test.go b/field/field_test.go new file mode 100644 index 0000000..0717a16 --- /dev/null +++ b/field/field_test.go @@ -0,0 +1,39 @@ +// code originally taken from https://github.com/arnaucube/go-snark (https://github.com/arnaucube/go-snark/blob/master/fields/fq.go), pasted here to ensure compatibility among future changes + +package field + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func iToBig(a int) *big.Int { + return big.NewInt(int64(a)) +} + +func TestFq1(t *testing.T) { + fq1 := NewFq(iToBig(7)) + + res := fq1.Add(iToBig(4), iToBig(4)) + assert.Equal(t, iToBig(1), fq1.Affine(res)) + + res = fq1.Double(iToBig(5)) + assert.Equal(t, iToBig(3), fq1.Affine(res)) + + res = fq1.Sub(iToBig(5), iToBig(7)) + assert.Equal(t, iToBig(5), fq1.Affine(res)) + + res = fq1.Neg(iToBig(5)) + assert.Equal(t, iToBig(2), fq1.Affine(res)) + + res = fq1.Mul(iToBig(5), iToBig(11)) + assert.Equal(t, iToBig(6), fq1.Affine(res)) + + res = fq1.Inverse(iToBig(4)) + assert.Equal(t, iToBig(2), res) + + res = fq1.Square(iToBig(5)) + assert.Equal(t, iToBig(4), res) +} diff --git a/mimc7/mimc7.go b/mimc7/mimc7.go new file mode 100644 index 0000000..a519617 --- /dev/null +++ b/mimc7/mimc7.go @@ -0,0 +1,171 @@ +package mimc7 + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/iden3/go-iden3/crypto/field" +) + +const SEED = "mimc" + +// RElem is a big.Int of maximum 253 bits +type RElem *big.Int + +var constants = generateConstantsData() + +type constantsData struct { + maxFieldVal *big.Int + seedHash *big.Int + iv *big.Int + fqR field.Fq + nRounds int + cts []*big.Int +} + +func getIV(seed string) { +} + +func generateConstantsData() constantsData { + var constants constantsData + + r, ok := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) + if !ok { + + } + fqR := field.NewFq(r) + constants.fqR = fqR + + // maxFieldVal is the R value of the Finite Field + constants.maxFieldVal = constants.fqR.Q + + constants.seedHash = new(big.Int).SetBytes(crypto.Keccak256([]byte(SEED))) + c := new(big.Int).SetBytes(crypto.Keccak256([]byte(SEED + "_iv"))) + constants.iv = new(big.Int).Mod(c, constants.maxFieldVal) + + constants.nRounds = 91 + cts, err := getConstants(constants.fqR, SEED, constants.nRounds) + if err != nil { + panic(err) + } + constants.cts = cts + return constants +} + +// BigIntToRElem checks if given big.Int fits in a Field R element, and returns the RElem type +func BigIntToRElem(a *big.Int) (RElem, error) { + if a.Cmp(constants.maxFieldVal) != -1 { + return RElem(a), errors.New("Given big.Int don't fits in the Finite Field over R") + } + return RElem(a), nil +} + +//BigIntsToRElems converts from array of *big.Int to array of RElem +func BigIntsToRElems(arr []*big.Int) ([]RElem, error) { + o := make([]RElem, len(arr)) + for i, a := range arr { + e, err := BigIntToRElem(a) + if err != nil { + return o, fmt.Errorf("element in position %v don't fits in Finite Field over R", i) + } + o[i] = e + } + return o, nil +} + +// RElemsToBigInts converts from array of RElem to array of *big.Int +func RElemsToBigInts(arr []RElem) []*big.Int { + o := make([]*big.Int, len(arr)) + for i, a := range arr { + o[i] = a + } + return o +} + +func getConstants(fqR field.Fq, seed string, nRounds int) ([]*big.Int, error) { + cts := make([]*big.Int, nRounds) + cts[0] = big.NewInt(int64(0)) + c := new(big.Int).SetBytes(crypto.Keccak256([]byte(SEED))) + for i := 1; i < nRounds; i++ { + c = new(big.Int).SetBytes(crypto.Keccak256(c.Bytes())) + + n := fqR.Affine(c) + cts[i] = n + } + return cts, nil +} + +// MIMC7HashGeneric performs the MIMC7 hash over a RElem, in a generic way, where it can be specified the Finite Field over R, and the number of rounds +func MIMC7HashGeneric(fqR field.Fq, xIn, k *big.Int, nRounds int) (*big.Int, error) { + cts, err := getConstants(fqR, SEED, nRounds) + if err != nil { + return &big.Int{}, err + } + var r *big.Int + for i := 0; i < nRounds; i++ { + var t *big.Int + if i == 0 { + t = fqR.Add(xIn, k) + } else { + t = fqR.Add(fqR.Add(r, k), cts[i]) + } + t2 := fqR.Square(t) + t4 := fqR.Square(t2) + r = fqR.Mul(fqR.Mul(t4, t2), t) + } + return fqR.Affine(fqR.Add(r, k)), nil +} + +// HashGeneric performs the MIMC7 hash over a RElem array, in a generic way, where it can be specified the Finite Field over R, and the number of rounds +func HashGeneric(iv *big.Int, arrEl []RElem, fqR field.Fq, nRounds int) (RElem, error) { + arr := RElemsToBigInts(arrEl) + r := iv + var err error + for i := 0; i < len(arr); i++ { + r, err = MIMC7HashGeneric(fqR, r, arr[i], nRounds) + if err != nil { + return r, err + } + } + return RElem(r), nil +} + +// MIMC7Hash performs the MIMC7 hash over a RElem, using the Finite Field over R and the number of rounds setted in the `constants` variable +func MIMC7Hash(xIn, k *big.Int) *big.Int { + var r *big.Int + for i := 0; i < constants.nRounds; i++ { + var t *big.Int + if i == 0 { + t = constants.fqR.Add(xIn, k) + } else { + t = constants.fqR.Add(constants.fqR.Add(r, k), constants.cts[i]) + } + t2 := constants.fqR.Square(t) + t4 := constants.fqR.Square(t2) + r = constants.fqR.Mul(constants.fqR.Mul(t4, t2), t) + } + return constants.fqR.Affine(constants.fqR.Add(r, k)) +} + +// Hash performs the MIMC7 hash over a RElem array +func Hash(arrEl []RElem, key *big.Int) RElem { + arr := RElemsToBigInts(arrEl) + var r *big.Int + if key == nil { + r = constants.fqR.Zero() + } else { + r = key + } + // r := constants.iv + for i := 0; i < len(arr); i++ { + r = constants.fqR.Add( + constants.fqR.Add( + r, + arr[i], + ), + MIMC7Hash(arr[i], r)) + } + return RElem(r) +} diff --git a/mimc7/mimc7_test.go b/mimc7/mimc7_test.go new file mode 100644 index 0000000..14d2e52 --- /dev/null +++ b/mimc7/mimc7_test.go @@ -0,0 +1,137 @@ +package mimc7 + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/iden3/go-iden3/crypto/field" + "github.com/stretchr/testify/assert" +) + +func TestUtils(t *testing.T) { + b1 := big.NewInt(int64(1)) + b2 := big.NewInt(int64(2)) + b3 := big.NewInt(int64(3)) + arrBigInt := []*big.Int{b1, b2, b3} + + // *big.Int array to RElem array + rElems, err := BigIntsToRElems(arrBigInt) + assert.Nil(t, err) + + // RElem array to *big.Int array + bElems := RElemsToBigInts(rElems) + + assert.Equal(t, arrBigInt, bElems) + + r, ok := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) + assert.True(t, ok) + + // greater or equal than R will give error when passing bigInts to RElems, to fit in the R Finite Field + overR, ok := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495618", 10) + assert.True(t, ok) + _, err = BigIntsToRElems([]*big.Int{b1, overR, b2}) + assert.True(t, err != nil) + + _, err = BigIntsToRElems([]*big.Int{b1, r, b2}) + assert.True(t, err != nil) + + // smaller than R will not give error when passing bigInts to RElems, to fit in the R Finite Field + underR, ok := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495616", 10) + assert.True(t, ok) + _, err = BigIntsToRElems([]*big.Int{b1, underR, b2}) + assert.Nil(t, err) +} + +func TestMIMC7Generic(t *testing.T) { + b1 := big.NewInt(int64(1)) + b2 := big.NewInt(int64(2)) + b3 := big.NewInt(int64(3)) + + r, ok := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) + assert.True(t, ok) + fqR := field.NewFq(r) + + bigArray := []*big.Int{b1, b2, b3} + elementsArray, err := BigIntsToRElems(bigArray) + assert.Nil(t, err) + + // Generic Hash + mhg, err := MIMC7HashGeneric(fqR, b1, b2, 91) + assert.Nil(t, err) + assert.Equal(t, "10594780656576967754230020536574539122676596303354946869887184401991294982664", mhg.String()) + hg, err := HashGeneric(fqR.Zero(), elementsArray, fqR, 91) + assert.Nil(t, err) + assert.Equal(t, "6464402164086696096195815557694604139393321133243036833927490113253119343397", (*big.Int)(hg).String()) +} + +func TestMIMC7(t *testing.T) { + b12 := big.NewInt(int64(12)) + b45 := big.NewInt(int64(45)) + b78 := big.NewInt(int64(78)) + b41 := big.NewInt(int64(41)) + + // h1, hash of 1 elements + bigArray1 := []*big.Int{b12} + elementsArray1, err := BigIntsToRElems(bigArray1) + assert.Nil(t, err) + + h1 := Hash(elementsArray1, nil) + assert.Nil(t, err) + // same hash value than the iden3js and circomlib tests: + assert.Equal(t, "0x"+hex.EncodeToString((*big.Int)(h1).Bytes()), "0x237c92644dbddb86d8a259e0e923aaab65a93f1ec5758b8799988894ac0958fd") + + // h2a, hash of 2 elements + bigArray2a := []*big.Int{b78, b41} + elementsArray2a, err := BigIntsToRElems(bigArray2a) + assert.Nil(t, err) + + mh2a := MIMC7Hash(b12, b45) + assert.Nil(t, err) + assert.Equal(t, "0x"+hex.EncodeToString((*big.Int)(mh2a).Bytes()), "0x2ba7ebad3c6b6f5a20bdecba2333c63173ca1a5f2f49d958081d9fa7179c44e4") + + h2a := Hash(elementsArray2a, nil) + assert.Nil(t, err) + // same hash value than the iden3js and circomlib tests: + assert.Equal(t, "0x"+hex.EncodeToString((*big.Int)(h2a).Bytes()), "0x067f3202335ea256ae6e6aadcd2d5f7f4b06a00b2d1e0de903980d5ab552dc70") + + // h2b, hash of 2 elements + bigArray2b := []*big.Int{b12, b45} + elementsArray2b, err := BigIntsToRElems(bigArray2b) + assert.Nil(t, err) + + mh2b := MIMC7Hash(b12, b45) + assert.Nil(t, err) + assert.Equal(t, "0x"+hex.EncodeToString((*big.Int)(mh2b).Bytes()), "0x2ba7ebad3c6b6f5a20bdecba2333c63173ca1a5f2f49d958081d9fa7179c44e4") + + h2b := Hash(elementsArray2b, nil) + assert.Nil(t, err) + // same hash value than the iden3js and circomlib tests: + assert.Equal(t, "0x"+hex.EncodeToString((*big.Int)(h2b).Bytes()), "0x15ff7fe9793346a17c3150804bcb36d161c8662b110c50f55ccb7113948d8879") + + // h4, hash of 4 elements + bigArray4 := []*big.Int{b12, b45, b78, b41} + elementsArray4, err := BigIntsToRElems(bigArray4) + assert.Nil(t, err) + + h4 := Hash(elementsArray4, nil) + assert.Nil(t, err) + // same hash value than the iden3js and circomlib tests: + assert.Equal(t, "0x"+hex.EncodeToString((*big.Int)(h4).Bytes()), "0x284bc1f34f335933a23a433b6ff3ee179d682cd5e5e2fcdd2d964afa85104beb") +} + +func BenchmarkMIMC7(b *testing.B) { + b12 := big.NewInt(int64(12)) + b45 := big.NewInt(int64(45)) + b78 := big.NewInt(int64(78)) + b41 := big.NewInt(int64(41)) + bigArray4 := []*big.Int{b12, b45, b78, b41} + elementsArray4, err := BigIntsToRElems(bigArray4) + assert.Nil(b, err) + + var h4 RElem + for i := 0; i < b.N; i++ { + h4 = Hash(elementsArray4, nil) + } + println(h4) +}