Browse Source

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.
master
arnaucube 2 years ago
parent
commit
43cad713b0
4 changed files with 162 additions and 13 deletions
  1. +86
    -0
      circomproofs.go
  2. +63
    -0
      circomproofs_test.go
  3. +10
    -12
      tree.go
  4. +3
    -1
      tree_test.go

+ 86
- 0
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
}

+ 63
- 0
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"}`)
}

+ 10
- 12
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.

+ 3
- 1
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)

Loading…
Cancel
Save