From 43cad713b0f9dcb494b231c8455f016b1587d2cd Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 16 Jun 2021 08:54:00 +0200 Subject: [PATCH] Add Tree Circom Verifier Proofs Circom Verifier Proofs allow to verify through a zkSNARK proof the inclusion/exclusion of a leaf in a tree. This commit adds the needed code in go to generate the circuit inputs for a CircomVerifierProof. --- circomproofs.go | 86 ++++++++++++++++++++++++++++++++++++++++++++ circomproofs_test.go | 63 ++++++++++++++++++++++++++++++++ tree.go | 22 ++++++------ tree_test.go | 4 ++- 4 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 circomproofs.go create mode 100644 circomproofs_test.go diff --git a/circomproofs.go b/circomproofs.go new file mode 100644 index 0000000..607266f --- /dev/null +++ b/circomproofs.go @@ -0,0 +1,86 @@ +package arbo + +import ( + "encoding/json" +) + +// CircomVerifierProof contains the needed data to check a Circom Verifier Proof +// inside a circom circuit. CircomVerifierProof allow to verify through a +// zkSNARK proof the inclusion/exclusion of a leaf in a tree. +type CircomVerifierProof struct { + Root []byte `json:"root"` + Siblings [][]byte `json:"siblings"` + OldKey []byte `json:"oldKey"` + OldValue []byte `json:"oldValue"` + IsOld0 bool `json:"isOld0"` + Key []byte `json:"key"` + Value []byte `json:"value"` + Fnc int `json:"fnc"` // 0: inclusion, 1: non inclusion +} + +// MarshalJSON implements the JSON marshaler +func (cvp CircomVerifierProof) MarshalJSON() ([]byte, error) { + m := make(map[string]interface{}) + + m["root"] = BytesToBigInt(cvp.Root).String() + m["siblings"] = siblingsToStringArray(cvp.Siblings) + m["oldKey"] = BytesToBigInt(cvp.OldKey).String() + m["oldValue"] = BytesToBigInt(cvp.OldValue).String() + if cvp.IsOld0 { + m["isOld0"] = "1" + } else { + m["isOld0"] = "0" + } + m["key"] = BytesToBigInt(cvp.Key).String() + m["value"] = BytesToBigInt(cvp.Value).String() + m["fnc"] = cvp.Fnc + + return json.Marshal(m) +} + +func siblingsToStringArray(s [][]byte) []string { + var r []string + for i := 0; i < len(s); i++ { + r = append(r, BytesToBigInt(s[i]).String()) + } + return r +} + +func (t *Tree) fillMissingEmptySiblings(s [][]byte) [][]byte { + for i := len(s); i < t.maxLevels; i++ { + s = append(s, emptyValue) + } + return s +} + +// GenerateCircomVerifierProof generates a CircomVerifierProof for a given key +// in the Tree +func (t *Tree) GenerateCircomVerifierProof(k []byte) (*CircomVerifierProof, error) { + kAux, v, siblings, existence, err := t.GenProof(k) + if err != nil && err != ErrKeyNotFound { + return nil, err + } + var cp CircomVerifierProof + cp.Root = t.Root() + s, err := UnpackSiblings(t.hashFunction, siblings) + if err != nil { + return nil, err + } + cp.Siblings = t.fillMissingEmptySiblings(s) + if !existence { + cp.OldKey = kAux + cp.OldValue = v + } else { + cp.OldKey = emptyValue + cp.OldValue = emptyValue + } + cp.Key = k + cp.Value = v + if existence { + cp.Fnc = 0 // inclusion + } else { + cp.Fnc = 1 // non inclusion + } + + return &cp, nil +} diff --git a/circomproofs_test.go b/circomproofs_test.go new file mode 100644 index 0000000..0f3c048 --- /dev/null +++ b/circomproofs_test.go @@ -0,0 +1,63 @@ +package arbo + +import ( + "encoding/json" + "math/big" + "testing" + + qt "github.com/frankban/quicktest" + "go.vocdoni.io/dvote/db" +) + +func TestCircomVerifierProof(t *testing.T) { + c := qt.New(t) + database, err := db.NewBadgerDB(c.TempDir()) + c.Assert(err, qt.IsNil) + tree, err := NewTree(database, 4, HashFunctionPoseidon) + c.Assert(err, qt.IsNil) + defer tree.db.Close() //nolint:errcheck + + bLen := tree.HashFunction().Len() + + testVector := [][]int64{ + {1, 11}, + {2, 22}, + {3, 33}, + {4, 44}, + } + for i := 0; i < len(testVector); i++ { + k := BigIntToBytes(bLen, big.NewInt(testVector[i][0])) + v := BigIntToBytes(bLen, big.NewInt(testVector[i][1])) + if err := tree.Add(k, v); err != nil { + t.Fatal(err) + } + } + + // proof of existence + k := BigIntToBytes(bLen, big.NewInt(int64(2))) + cvp, err := tree.GenerateCircomVerifierProof(k) + c.Assert(err, qt.IsNil) + jCvp, err := json.Marshal(cvp) + c.Assert(err, qt.IsNil) + // test vector checked with a circom circuit (arbo/testvectors/circom) + c.Assert(string(jCvp), qt.Equals, `{"fnc":0,"isOld0":"0","key":"2","oldK`+ + `ey":"0","oldValue":"0","root":"1355816845522055904274785395894906304622`+ + `6645447188878859760119761585093422436","siblings":["1162013050763544193`+ + `2056895853942898236773847390796721536119314875877874016518","5158240518`+ + `874928563648144881543092238925265313977134167935552944620041388700","0"`+ + `,"0"],"value":"22"}`) + + // proof of non-existence + k = BigIntToBytes(bLen, big.NewInt(int64(5))) + cvp, err = tree.GenerateCircomVerifierProof(k) + c.Assert(err, qt.IsNil) + jCvp, err = json.Marshal(cvp) + c.Assert(err, qt.IsNil) + // test vector checked with a circom circuit (arbo/testvectors/circom) + c.Assert(string(jCvp), qt.Equals, `{"fnc":1,"isOld0":"0","key":"5","oldK`+ + `ey":"1","oldValue":"11","root":"135581684552205590427478539589490630462`+ + `26645447188878859760119761585093422436","siblings":["756056982086999933`+ + `1905412009838015295115276841209205575174464206730109811365","1276103081`+ + `3800436751877086580591648324911598798716611088294049841213649313596","0`+ + `","0"],"value":"11"}`) +} diff --git a/tree.go b/tree.go index 58c7e83..9ab2b05 100644 --- a/tree.go +++ b/tree.go @@ -534,10 +534,10 @@ func (t *Tree) Update(k, v []byte) error { return t.dbBatch.Write() } -// GenProof generates a MerkleTree proof for the given key. If the key exists in -// the Tree, the proof will be of existence, if the key does not exist in the -// tree, the proof will be of non-existence. -func (t *Tree) GenProof(k []byte) ([]byte, []byte, error) { +// GenProof generates a MerkleTree proof for the given key. The leaf value is +// returned, together with the packed siblings of the proof, and a boolean +// parameter that indicates if the proof is of existence (true) or not (false). +func (t *Tree) GenProof(k []byte) ([]byte, []byte, []byte, bool, error) { keyPath := make([]byte, t.hashFunction.Len()) copy(keyPath[:], k) @@ -546,20 +546,18 @@ func (t *Tree) GenProof(k []byte) ([]byte, []byte, error) { var siblings [][]byte _, value, siblings, err := t.down(k, t.root, siblings, path, 0, true) if err != nil { - return nil, nil, err + return nil, nil, nil, false, err } + s := PackSiblings(t.hashFunction, siblings) + leafK, leafV := ReadLeafValue(value) if !bytes.Equal(k, leafK) { - fmt.Println("key not in Tree") - fmt.Println(leafK) - fmt.Println(leafV) - // TODO proof of non-existence - panic("unimplemented") + // key not in tree, proof of non-existence + return leafK, leafV, s, false, err } - s := PackSiblings(t.hashFunction, siblings) - return leafV, s, nil + return leafK, leafV, s, true, nil } // PackSiblings packs the siblings into a byte array. diff --git a/tree_test.go b/tree_test.go index c7dc45b..52f9052 100644 --- a/tree_test.go +++ b/tree_test.go @@ -305,9 +305,11 @@ func TestGenProofAndVerify(t *testing.T) { k := BigIntToBytes(bLen, big.NewInt(int64(7))) v := BigIntToBytes(bLen, big.NewInt(int64(14))) - proofV, siblings, err := tree.GenProof(k) + kAux, proofV, siblings, existence, err := tree.GenProof(k) c.Assert(err, qt.IsNil) c.Assert(proofV, qt.DeepEquals, v) + c.Assert(k, qt.DeepEquals, kAux) + c.Assert(existence, qt.IsTrue) verif, err := CheckProof(tree.hashFunction, k, v, tree.Root(), siblings) c.Assert(err, qt.IsNil)