diff --git a/arbo/mimc_bls12_377/gnark.pprof b/arbo/mimc_bls12_377/gnark.pprof new file mode 100644 index 0000000..37a6e0a Binary files /dev/null and b/arbo/mimc_bls12_377/gnark.pprof differ diff --git a/arbo/mimc_bls12_377/verifier.go b/arbo/mimc_bls12_377/verifier.go new file mode 100644 index 0000000..068c0e3 --- /dev/null +++ b/arbo/mimc_bls12_377/verifier.go @@ -0,0 +1,74 @@ +package arbo + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/hash/mimc" +) + +// prevLevel function calculates the previous level of the merkle tree given the +// current leaf, the current path bit of the leaf, the validity of the sibling +// and the sibling itself. +func prevLevel(api frontend.API, leaf, ipath, valid, sibling frontend.Variable) (frontend.Variable, error) { + // l, r = path == 1 ? sibling, current : current, sibling + l, r := api.Select(ipath, sibling, leaf), api.Select(ipath, leaf, sibling) + // intermediateLeafKey = H(l | r) + hash, err := mimc.NewMiMC(api) + if err != nil { + return 0, err + } + hash.Write(l, r) + intermediateLeafKey := hash.Sum() + // newCurrent = valid == 1 ? current : intermediateLeafKey + return api.Select(valid, intermediateLeafKey, leaf), nil +} + +// strictCmp function compares a and b and returns: +// +// 1 a != b +// 0 a == b +func strictCmp(api frontend.API, a, b frontend.Variable) frontend.Variable { + return api.Select(api.IsZero(api.Sub(a, b)), 0, 1) +} + +// isValid function returns 1 if the the sibling provided is a valid sibling or +// 0 otherwise. To check if the sibling is valid, its leaf value and it must be +// different from the previous leaf value and the previous sibling. +func isValid(api frontend.API, sibling, prevSibling, leaf, prevLeaf frontend.Variable) frontend.Variable { + cmp1, cmp2 := strictCmp(api, leaf, prevLeaf), strictCmp(api, sibling, prevSibling) + 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, 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)) + // calculate the value leaf to start with it to rebuild the tree + // leafValue = H(key | value | 1) + hash, err := mimc.NewMiMC(api) + if err != nil { + return err + } + hash.Write(key, value, 1) + leafValue := hash.Sum() + api.Println("[gnark] leafKey", key) + api.Println("[gnark] leafValue", leafValue) + // calculate the root and compare it with the provided one + prevLeaf := leafValue + currentLeaf := leafValue + prevSibling := frontend.Variable(0) + for i := len(siblings) - 1; i >= 0; i-- { + // check if the sibling is valid + valid := isValid(api, siblings[i], prevSibling, currentLeaf, prevLeaf) + prevLeaf = currentLeaf + prevSibling = siblings[i] + // compute the next leaf value + currentLeaf, err = prevLevel(api, currentLeaf, path[i], valid, siblings[i]) + if err != nil { + return err + } + } + api.AssertIsEqual(currentLeaf, root) + return nil +} diff --git a/arbo/mimc_bls12_377/verifier_test.go b/arbo/mimc_bls12_377/verifier_test.go new file mode 100644 index 0000000..9f0ecd2 --- /dev/null +++ b/arbo/mimc_bls12_377/verifier_test.go @@ -0,0 +1,134 @@ +package arbo + +import ( + "fmt" + "log" + "math/big" + "os" + "testing" + "time" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/profile" + "github.com/consensys/gnark/test" + arbotree "github.com/vocdoni/arbo" + "go.vocdoni.io/dvote/db" + "go.vocdoni.io/dvote/db/pebbledb" + "go.vocdoni.io/dvote/tree/arbo" + "go.vocdoni.io/dvote/util" +) + +type testVerifierCircuit struct { + Root frontend.Variable + Key frontend.Variable + Value frontend.Variable + Siblings [160]frontend.Variable +} + +func (circuit *testVerifierCircuit) Define(api frontend.API) error { + return CheckProof(api, circuit.Key, circuit.Value, circuit.Root, circuit.Siblings[:]) +} + +func TestVerifier(t *testing.T) { + p := profile.Start() + now := time.Now() + _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), r1cs.NewBuilder, &testVerifierCircuit{}) + fmt.Println("elapsed", time.Since(now)) + p.Stop() + fmt.Println("constrains", p.NbConstraints()) + + assert := test.NewAssert(t) + + // inputs := successInputs(t, 10) + inputs, err := generateCensusProof(10, util.RandomBytes(20), big.NewInt(10).Bytes()) + if err != nil { + t.Fatal(err) + } + // binputs, _ := json.MarshalIndent(inputs, " ", " ") + // fmt.Println("inputs", string(binputs)) + assert.SolvingSucceeded(&testVerifierCircuit{}, &inputs, test.WithCurves(ecc.BLS12_377), test.WithBackends(backend.GROTH16)) +} + +var baseField, _ = new(big.Int).SetString("25825498262808887005865186224201665565126143020923472090132963926938185026661", 10) + +// BigToFF function returns the finite field representation of the big.Int +// provided. It uses Euclidean Modulus and the BN254 curve scalar field to +// represent the provided number. +func BigToFF(iv *big.Int) *big.Int { + z := big.NewInt(0) + if c := iv.Cmp(baseField); c == 0 { + return z + } else if c != 1 && iv.Cmp(z) != -1 { + return iv + } + return z.Mod(iv, baseField) +} + +func generateCensusProof(n int, k, v []byte) (testVerifierCircuit, error) { + dir := os.TempDir() + defer func() { + _ = os.RemoveAll(dir) + }() + database, err := pebbledb.New(db.Options{Path: dir}) + if err != nil { + return testVerifierCircuit{}, err + } + tree, err := arbotree.NewTree(arbotree.Config{ + Database: database, + MaxLevels: 160, + HashFunction: arbotree.HashFunctionMiMC_BLS12_377, + }) + if err != nil { + return testVerifierCircuit{}, err + } + k = BigToFF(new(big.Int).SetBytes(k)).Bytes() + // add the first key-value pair + if err = tree.Add(k, v); err != nil { + return testVerifierCircuit{}, err + } + h := tree.HashFunction() + r, _ := h.Hash(k, v, []byte{1}) + log.Println("[go] leafKey", new(big.Int).SetBytes(k)) + log.Println("[go] leafValue", new(big.Int).SetBytes(r)) + // add random addresses + for i := 1; i < n; i++ { + rk := BigToFF(new(big.Int).SetBytes(util.RandomBytes(20))).Bytes() + rv := new(big.Int).SetBytes(util.RandomBytes(8)).Bytes() + if err = tree.Add(rk, rv); err != nil { + return testVerifierCircuit{}, err + } + } + // generate the proof + _, _, siblings, exist, err := tree.GenProof(k) + if err != nil { + return testVerifierCircuit{}, err + } + if !exist { + return testVerifierCircuit{}, fmt.Errorf("error building the merkle tree: key not found") + } + unpackedSiblings, err := arbo.UnpackSiblings(arbo.HashFunctionPoseidon, siblings) + if err != nil { + return testVerifierCircuit{}, err + } + paddedSiblings := [160]frontend.Variable{} + for i := 0; i < 160; i++ { + if i < len(unpackedSiblings) { + paddedSiblings[i] = arbo.BytesLEToBigInt(unpackedSiblings[i]) + } else { + paddedSiblings[i] = big.NewInt(0) + } + } + root, err := tree.Root() + if err != nil { + return testVerifierCircuit{}, err + } + return testVerifierCircuit{ + Root: root, + Key: k, + Value: new(big.Int).SetBytes(v), + Siblings: paddedSiblings, + }, nil +} diff --git a/arbo/poseidon_bn254/gnark.pprof b/arbo/poseidon_bn254/gnark.pprof new file mode 100644 index 0000000..528d189 Binary files /dev/null and b/arbo/poseidon_bn254/gnark.pprof differ diff --git a/arbo/verifier.go b/arbo/poseidon_bn254/verifier.go similarity index 97% rename from arbo/verifier.go rename to arbo/poseidon_bn254/verifier.go index 3ffe3f8..df47060 100644 --- a/arbo/verifier.go +++ b/arbo/poseidon_bn254/verifier.go @@ -41,7 +41,7 @@ func isValid(api frontend.API, sibling, prevSibling, leaf, prevLeaf frontend.Var func CheckProof(api frontend.API, 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, api.Compiler().FieldBitLen()) + path := api.ToBinary(key, len(siblings)) // calculate the value leaf to start with it to rebuild the tree // leafValue = H(key | value | 1) leafValue, err := poseidon.Hash(api, key, value, 1) diff --git a/arbo/verifier_test.go b/arbo/poseidon_bn254/verifier_test.go similarity index 100% rename from arbo/verifier_test.go rename to arbo/poseidon_bn254/verifier_test.go diff --git a/go.mod b/go.mod index 3349c52..6636e79 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,13 @@ module github.com/vocdoni/gnark-crypto-primitives go 1.23.2 +replace github.com/vocdoni/arbo => ../arbo + require ( github.com/consensys/gnark v0.11.0 github.com/consensys/gnark-crypto v0.14.0 github.com/iden3/go-iden3-crypto v0.0.17 + github.com/vocdoni/arbo v0.0.0-20241114123238-8b237b4e83fa github.com/vocdoni/vocdoni-z-sandbox v0.0.0-20241113074257-1a711ad38a6b go.vocdoni.io/dvote v1.10.2-0.20241024102542-c1ce6d744bc5 )