diff --git a/README.md b/README.md index 87c58e9..2af6f99 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # 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) [![Build Status](https://travis-ci.org/iden3/go-iden3-crypto.svg?branch=master)](https://travis-ci.org/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 + +Go implementation of some cryptographic primitives (that fit inside the SNARK field) used in iden3 diff --git a/go.mod b/go.mod index bba6f24..2cbfeef 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,5 @@ require ( github.com/dchest/blake512 v1.0.0 github.com/ethereum/go-ethereum v1.8.27 github.com/stretchr/testify v1.3.0 - golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 // indirect + golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 ) diff --git a/go.sum b/go.sum index 63114f7..3cbf7c6 100644 --- a/go.sum +++ b/go.sum @@ -14,5 +14,6 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhi golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/poseidon/poseidon.go b/poseidon/poseidon.go new file mode 100644 index 0000000..cb1a828 --- /dev/null +++ b/poseidon/poseidon.go @@ -0,0 +1,184 @@ +package poseidon + +import ( + "bytes" + "errors" + "math/big" + "strconv" + + "github.com/iden3/go-iden3-crypto/field" + "golang.org/x/crypto/blake2b" +) + +const SEED = "poseidon" +const NROUNDSF = 8 +const NROUNDSP = 57 +const T = 6 + +var constants = generateConstantsData() + +type constantsData struct { + fqR field.Fq + c []*big.Int + m [][]*big.Int +} + +// checkBigIntInField checks if given big.Int fits in a Field R element +func checkBigIntInField(a *big.Int, q *big.Int) bool { + if a.Cmp(q) != -1 { + return false + } + return true +} + +// checkBigIntArrayInField checks if given big.Int fits in a Field R element +func checkBigIntArrayInField(arr []*big.Int, q *big.Int) bool { + for _, a := range arr { + if !checkBigIntInField(a, q) { + return false + } + } + return true +} + +func generateConstantsData() constantsData { + var constants constantsData + + r, ok := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) + if !ok { + + } + fqR := field.NewFq(r) + constants.fqR = fqR + constants.c = getPseudoRandom(fqR, SEED+"_constants", big.NewInt(int64(NROUNDSF+NROUNDSP))) + constants.m = getMDS(fqR) + + return constants +} + +func getPseudoRandom(fqR field.Fq, seed string, n *big.Int) []*big.Int { + var res []*big.Int + hash := blake2b.Sum256([]byte(seed)) + for big.NewInt(int64(len(res))).Cmp(n) == -1 { // res < n + newN := fqR.Affine(leByteArrayToBigInt(fqR, hash[:])) + res = append(res, newN) + hash = blake2b.Sum256(hash[:]) + } + return res +} + +func leByteArrayToBigInt(fqR field.Fq, b []byte) *big.Int { + res := fqR.Zero() + for i := 0; i < len(b); i++ { + n := big.NewInt(int64(b[i])) + res = new(big.Int).Add(res, new(big.Int).Lsh(n, uint(i*8))) + } + return res +} + +func nonceToString(n int) string { + r := strconv.Itoa(n) + for len(r) < 4 { + r = "0" + r + } + return r +} + +// https://eprint.iacr.org/2019/458.pdf pag.8 +func getMDS(fqR field.Fq) [][]*big.Int { + nonce := 0 + cauchyMatrix := getPseudoRandom(fqR, SEED+"_matrix_"+nonceToString(nonce), big.NewInt(T*2)) + for !checkAllDifferent(cauchyMatrix) { + nonce += 1 + cauchyMatrix = getPseudoRandom(fqR, SEED+"_matrix_"+nonceToString(nonce), big.NewInt(T*2)) + } + var m [][]*big.Int + for i := 0; i < T; i++ { + var mi []*big.Int + for j := 0; j < T; j++ { + mi = append(mi, fqR.Inverse(fqR.Sub(cauchyMatrix[i], cauchyMatrix[T+j]))) + } + m = append(m, mi) + } + return m +} + +func checkAllDifferent(v []*big.Int) bool { + for i := 0; i < len(v); i++ { + if bytes.Equal(v[i].Bytes(), big.NewInt(int64(0)).Bytes()) { + return false + } + for j := i + 1; j < len(v); j++ { + if bytes.Equal(v[i].Bytes(), v[j].Bytes()) { + return false + } + } + } + return true +} + +// ark computes Add-Round Key, from the paper https://eprint.iacr.org/2019/458.pdf +func ark(state []*big.Int, c *big.Int) []*big.Int { + for i := 0; i < len(state); i++ { + state[i] = constants.fqR.Add(state[i], c) + } + return state +} + +// cubic performs x^3 mod p +func cubic(a *big.Int) *big.Int { + return constants.fqR.Mul(a, constants.fqR.Square(constants.fqR.Square(a))) +} + +// sbox https://eprint.iacr.org/2019/458.pdf pag.6 +func sbox(state []*big.Int, i int) []*big.Int { + if (i < NROUNDSF/2) || (i >= NROUNDSF/2+NROUNDSP) { + for j := 0; j < T; j++ { + state[j] = cubic(state[j]) + } + } else { + state[0] = cubic(state[0]) + } + return state +} + +// mix returns [[matrix]] * [vector] +func mix(state []*big.Int, m [][]*big.Int) []*big.Int { + var newState []*big.Int + for i := 0; i < len(state); i++ { + newState = append(newState, constants.fqR.Zero()) + for j := 0; j < len(state); j++ { + newState[i] = constants.fqR.Add(newState[i], constants.fqR.Mul(m[i][j], state[j])) + } + } + for i := 0; i < len(state); i++ { + state[i] = newState[i] + } + return state +} + +// Hash computes the Poseidon hash for the given inputs +func Hash(inp []*big.Int) (*big.Int, error) { + var state []*big.Int + if len(inp) < 0 || len(inp) > T { + return nil, errors.New("wrong inputs length") + } + if !checkBigIntArrayInField(inp, constants.fqR.Q) { + return nil, errors.New("inputs values not inside Finite Field") + } + + for i := 0; i < len(inp); i++ { + state = append(state, inp[i]) + } + for i := len(inp); i < T; i++ { + state = append(state, constants.fqR.Zero()) + } + + // ARK --> SBox --> M, https://eprint.iacr.org/2019/458.pdf pag.5 + for i := 0; i < NROUNDSF+NROUNDSP; i++ { + state = ark(state, constants.c[i]) + state = sbox(state, i) + state = mix(state, constants.m) + } + return state[0], nil +} diff --git a/poseidon/poseidon_test.go b/poseidon/poseidon_test.go new file mode 100644 index 0000000..506ba73 --- /dev/null +++ b/poseidon/poseidon_test.go @@ -0,0 +1,29 @@ +package poseidon + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "golang.org/x/crypto/blake2b" +) + +func TestBlake2bVersion(t *testing.T) { + h := blake2b.Sum256([]byte("poseidon_constants")) + assert.Equal(t, "e57ba154fb2c47811dc1a2369b27e25a44915b4e4ece4eb8ec74850cb78e01b1", hex.EncodeToString(h[:])) +} + +func TestPoseidon(t *testing.T) { + b1 := big.NewInt(int64(1)) + b2 := big.NewInt(int64(2)) + h, err := Hash([]*big.Int{b1, b2}) + assert.Nil(t, err) + assert.Equal(t, "12242166908188651009877250812424843524687801523336557272219921456462821518061", h.String()) + + b3 := big.NewInt(int64(3)) + b4 := big.NewInt(int64(4)) + h, err = Hash([]*big.Int{b3, b4}) + assert.Nil(t, err) + assert.Equal(t, "17185195740979599334254027721507328033796809509313949281114643312710535000993", h.String()) +}