mirror of
https://github.com/arnaucube/arbo.git
synced 2026-01-08 15:01:29 +01:00
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.
This commit is contained in:
86
circomproofs.go
Normal file
86
circomproofs.go
Normal file
@@ -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
circomproofs_test.go
Normal file
63
circomproofs_test.go
Normal file
@@ -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"}`)
|
||||
}
|
||||
28
tree.go
28
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
|
||||
}
|
||||
|
||||
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")
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
|
||||
s := PackSiblings(t.hashFunction, siblings)
|
||||
return leafV, s, nil
|
||||
|
||||
leafK, leafV := ReadLeafValue(value)
|
||||
if !bytes.Equal(k, leafK) {
|
||||
// key not in tree, proof of non-existence
|
||||
return leafK, leafV, s, false, err
|
||||
}
|
||||
|
||||
return leafK, leafV, s, true, nil
|
||||
}
|
||||
|
||||
// PackSiblings packs the siblings into a byte array.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user