From ec22baa3d79ff54074aa7747af6635ab45126676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Men=C3=A9ndez?= Date: Wed, 20 Nov 2024 09:21:10 +0100 Subject: [PATCH] include exclusion proof verifier allowing to append old leafs --- arbo/hints.go | 39 +++++++++++++++++++ arbo/verifier.go | 68 +++++++++++++++++++++++++++++++--- arbo/verifier_bls12377_test.go | 3 +- arbo/verifier_bn254_test.go | 5 +-- 4 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 arbo/hints.go diff --git a/arbo/hints.go b/arbo/hints.go new file mode 100644 index 0000000..1f33fc3 --- /dev/null +++ b/arbo/hints.go @@ -0,0 +1,39 @@ +package arbo + +import ( + "fmt" + "math/big" + + "github.com/consensys/gnark/constraint/solver" +) + +func init() { + solver.RegisterHint(replaceSiblingHint) +} + +// replaceSiblingHint gnark hint function receives the new sibling to set as +// first input, the index of the sibling to be replaced as second input, and the +// rest of the siblings as the rest of the inputs. The function should return +// the new siblings with the replacement done. +func replaceSiblingHint(_ *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != len(outputs)+2 { + return fmt.Errorf("invalid number of inputs/outputs") + } + // get the new sibling and the index to replace + newSibling := inputs[0] + index := int(inputs[1].Int64()) + fmt.Println(outputs) + if index >= len(outputs) { + return fmt.Errorf("invalid index") + } + siblings := inputs[2:] + for i := 0; i < len(outputs); i++ { + if i == index { + outputs[i] = outputs[i].Set(newSibling) + } else { + outputs[i] = outputs[i].Set(siblings[i]) + } + } + fmt.Println(outputs) + return nil +} diff --git a/arbo/verifier.go b/arbo/verifier.go index a74c2c5..3b3fafc 100644 --- a/arbo/verifier.go +++ b/arbo/verifier.go @@ -1,8 +1,6 @@ package arbo -import ( - "github.com/consensys/gnark/frontend" -) +import "github.com/consensys/gnark/frontend" type Hash func(frontend.API, ...frontend.Variable) (frontend.Variable, error) @@ -39,9 +37,46 @@ func isValid(api frontend.API, sibling, prevSibling, leaf, prevLeaf frontend.Var return api.Select(api.Or(cmp1, cmp2), 1, 0) } -// CheckProof receives the parameters of a proof of Arbo to recalculate the -// root with them and compare it with the provided one, verifiying the proof. -func CheckProof(api frontend.API, hFn Hash, key, value, root frontend.Variable, siblings []frontend.Variable) error { +// replaceFirstPaddedSibling function replaces the first padded sibling with the +// new sibling provided. The function receives the new sibling, the siblings and +// returns the new siblings with the replacement done. It first calculates the +// index of the first padded sibling and then calls the hint function to replace +// it. The hint function should return the new siblings with the replacement +// done. The function ensures that the replacement was done correctly. +func replaceFirstPaddedSibling(api frontend.API, newSibling frontend.Variable, siblings []frontend.Variable) ([]frontend.Variable, error) { + // the valid siblins are always the first n siblings that are not zero, so + // we need to iterate through the siblings in reverse order to find the + // first non-zero sibling and count the number of valid siblings from there, + // so the index of the last padded sibling is the number of valid siblings + index := frontend.Variable(0) + nonZeroFound := frontend.Variable(0) + for i := len(siblings) - 1; i >= 0; i-- { + isNotZero := strictCmp(api, siblings[i], 0) + nonZeroFound = api.Or(nonZeroFound, isNotZero) + index = api.Add(index, nonZeroFound) + } + // call the hint function to replace the sibling with the index to be + // replaced, the new sibling and the rest of the siblings + newSiblings, err := api.Compiler().NewHint(replaceSiblingHint, len(siblings), + append([]frontend.Variable{newSibling, index}, siblings...)...) + if err != nil { + return nil, err + } + // check that the hint successfully replaced the first padded sibling + newSiblingFound := frontend.Variable(0) + for i := 0; i < len(newSiblings); i++ { + correctIndex := api.IsZero(api.Sub(index, frontend.Variable(i))) + correctSibling := api.IsZero(api.Sub(newSiblings[i], newSibling)) + newSiblingFound = api.Or(newSiblingFound, api.And(correctIndex, correctSibling)) + } + api.AssertIsEqual(newSiblingFound, 1) + return newSiblings, nil +} + +// CheckInclusionProof receives the parameters of an inclusion proof of Arbo to +// recalculate the root with them and compare it with the provided one, +// verifiying the proof. +func CheckInclusionProof(api frontend.API, hFn Hash, key, value, root frontend.Variable, siblings []frontend.Variable) error { // calculate the path from the provided key to decide which leaf is the // correct one in every level of the tree path := api.ToBinary(key, len(siblings)) @@ -70,3 +105,24 @@ func CheckProof(api frontend.API, hFn Hash, key, value, root frontend.Variable, api.AssertIsEqual(leafKey, root) return nil } + +// CheckExclusionProof receives the parameters of a exclusion proof of Arbo to +// recalculate the root with them and compare it with the provided one. It +// differs from CheckInclusionProof in that it receives the old key and value +// to calculate the old leaf key and replace the first padded sibling with the +// new sibling, then verifies the proof calling CheckInclusionProof with the +// old sibling added to the siblings. +func CheckExclusionProof(api frontend.API, hFn Hash, key, value, oldKey, oldValue, root frontend.Variable, siblings []frontend.Variable) error { + // calculate the old leaf key + newLeafKey, err := hFn(api, oldKey, oldValue, 1) + if err != nil { + return err + } + // replace the first padded sibling with the new sibling + newSiblings, err := replaceFirstPaddedSibling(api, newLeafKey, siblings) + if err != nil { + return err + } + // verify the proof with the old sibling added to the siblings + return CheckInclusionProof(api, hFn, key, value, root, newSiblings) +} diff --git a/arbo/verifier_bls12377_test.go b/arbo/verifier_bls12377_test.go index b5a1500..1c0ec14 100644 --- a/arbo/verifier_bls12377_test.go +++ b/arbo/verifier_bls12377_test.go @@ -41,8 +41,7 @@ func (circuit *testVerifierBLS12377) Define(api frontend.API) error { h.Write(data...) return h.Sum(), nil } - - return CheckProof(api, hash, circuit.Key, circuit.Value, circuit.Root, circuit.Siblings[:]) + return CheckInclusionProof(api, hash, circuit.Key, circuit.Value, circuit.Root, circuit.Siblings[:]) } func TestVerifierBLS12377(t *testing.T) { diff --git a/arbo/verifier_bn254_test.go b/arbo/verifier_bn254_test.go index d579d0d..68045a8 100644 --- a/arbo/verifier_bn254_test.go +++ b/arbo/verifier_bn254_test.go @@ -1,7 +1,6 @@ package arbo import ( - "encoding/json" "fmt" "math/big" "testing" @@ -27,7 +26,7 @@ type testVerifierBN254 struct { func (circuit *testVerifierBN254) Define(api frontend.API) error { // use poseidon hash function - return CheckProof(api, poseidon.Hash, circuit.Key, circuit.Value, circuit.Root, circuit.Siblings[:]) + return CheckInclusionProof(api, poseidon.Hash, circuit.Key, circuit.Value, circuit.Root, circuit.Siblings[:]) } func TestVerifierBN254(t *testing.T) { @@ -60,8 +59,6 @@ func TestVerifierBN254(t *testing.T) { Value: value, Siblings: fSiblings, } - binputs, _ := json.MarshalIndent(inputs, " ", " ") - fmt.Println("inputs", string(binputs)) assert := test.NewAssert(t) assert.SolvingSucceeded(&testVerifierBN254{}, &inputs, test.WithCurves(ecc.BN254), test.WithBackends(backend.GROTH16)) }