Created
June 17, 2021 06:02
-
-
Save miukki/1b1fb2b5e418409e1a1cd2c427efb93e to your computer and use it in GitHub Desktop.
Revisions
-
miukki created this gist
Jun 17, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,643 @@ package byteslice import ( "bytes" "context" "crypto/ecdsa" "encoding/json" "io" "math" "math/big" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "vosbor-zkp/cmd/poc/byteslice/circuit" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark-crypto/ecc/bn254/fr" log "github.com/sirupsen/logrus" "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/hash" "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/frontend" ) type OtcTXN struct { Id string Recipients []string } const ( chainID = 5777 chainPORT = 7545 ordId = "1234567" trxJson = `{"id": "1234567","recipients": ["1000000","111111","999999"]}` deployPKey = "97623c71f519f1a5c41933c352de6be2400c7c29099e7b09aecd9c66c2878bc5" // private key from Ganache AbigenVersion = "github.com/ethereum/go-ethereum/cmd/abigen@v1.10.3" ) var ( r1csPath = "byteslice/circuit/mimc.r1cs" pkPath = "byteslice/circuit/mimc.pk" vkPath = "byteslice/circuit/mimc.vk" verifierSol = "registry-contract/contracts/Verifier.sol" wrapperGo = "byteslice/circuit/wrapper.go" basePath = "byteslice/circuit/" abiBinPath = "bin/abigen" ) var workingDir = "" func init() { var err error workingDir, err = os.Getwd() if err != nil { panic(err) } if !strings.Contains(workingDir, "/cmd") { workingDir = workingDir + "/cmd/poc" } r1csPath = filepath.Join(workingDir, r1csPath) pkPath = filepath.Join(workingDir, pkPath) vkPath = filepath.Join(workingDir, vkPath) verifierSol = filepath.Join(workingDir, "../..", verifierSol) wrapperGo = filepath.Join(workingDir, wrapperGo) basePath = filepath.Join(workingDir, basePath) abiBinPath = filepath.Join(workingDir, "../..", abiBinPath) } func Run() error { // check that init was performed if _, err := os.Stat(r1csPath); os.IsNotExist(err) { log.Fatal("please run with --init flag first to serialize circuit, keys and solidity contract") return err } err := execute() if err != nil { log.WithError(err).Error("Failed to setup ADB data") return err } log.Info("Finished setting up ADB data") return nil } func Chainverify() error { log.Info("Generate proof and verify on-chain") // check that init was performed if _, err := os.Stat(r1csPath); os.IsNotExist(err) { log.Fatal("please run with --init flag first to serialize circuit, keys and solidity contract") return err } // deploy smart contract verifierContract, err := deploySolidity() if err != nil { return err } // read R1CS, proving key and verifying keys r1cs := groth16.NewCS(ecc.BN254) pk := groth16.NewProvingKey(ecc.BN254) vk := groth16.NewVerifyingKey(ecc.BN254) err = deserialize(r1cs, r1csPath) if err != nil { return err } err = deserialize(pk, pkPath) if err != nil { return err } err = deserialize(vk, vkPath) if err != nil { return err } // set recipient for who we will generate the proof proofFor := "1000000" // log balance of the account used to deploy the contract log.WithField("recipient", proofFor).Info("generating proof for") //frontend-prover:::Create private witness with sum ([]byte of hash) from trxJson. Prover knows secret--A,B,C and public--HASH var witness *circuit.Circuit witness, sum, err := createWitness(trxJson, proofFor) if err != nil { return err } //setup proof for backend-verifier proof, err := groth16.Prove(r1cs, pk, witness) if err != nil { return err } //backend-verifier:: Create public witness. Verifier knows public--HASH and generated proof groth16.Proof var publicWitness circuit.Circuit publicWitness.Hash.Assign(sum) err = groth16.Verify(proof, vk, &publicWitness) if err != nil { return err } log.Info(`tested Prove/Verify successfully locally, now we can deploy as next step, proof is ready to deploy`) // solidity contract inputs // a, b and c are the 3 ecc points in the proof we feed to the pairing // they are stored in the same order in the golang data structure // each coordinate is a field element, of size fp.Bytes bytes var ( a [2]*big.Int b [2][2]*big.Int c [2]*big.Int input [1]*big.Int ) // get proof bytes var buf bytes.Buffer _, err = proof.WriteRawTo(&buf) if err != nil { return err } proofBytes := buf.Bytes() // proof.Ar, proof.Bs, proof.Krs const fpSize = fp.Bytes a[0] = new(big.Int).SetBytes(proofBytes[fpSize*0 : fpSize*1]) a[1] = new(big.Int).SetBytes(proofBytes[fpSize*1 : fpSize*2]) b[0][0] = new(big.Int).SetBytes(proofBytes[fpSize*2 : fpSize*3]) b[0][1] = new(big.Int).SetBytes(proofBytes[fpSize*3 : fpSize*4]) b[1][0] = new(big.Int).SetBytes(proofBytes[fpSize*4 : fpSize*5]) b[1][1] = new(big.Int).SetBytes(proofBytes[fpSize*5 : fpSize*6]) c[0] = new(big.Int).SetBytes(proofBytes[fpSize*6 : fpSize*7]) c[1] = new(big.Int).SetBytes(proofBytes[fpSize*7 : fpSize*8]) // (correct) public witness input[0] = new(big.Int).SetBytes(sum) // (wrong) public witness //input[0] = new(big.Int).SetUint64(42) // call the contract res, err := verifierContract.VerifyProof(nil, a, b, c, input) log.Info(a,`\\\\\n`, b, `\\\\\n`, c, `\\\\\n`, input, `\\\\\n`, "a, b, c, input") if err != nil { return err } if !res { log.Fatal("calling the verifier on chain didn't succeed, but should have") } log.Info("successfully verified proof on-chain") return nil } func Debug() error { log.Info("Debug byteslice circuit") var mimcCircuit circuit.Circuit // Set transaction ID for this circuit from constant circuit.OrderID = ordId // compiles our circuit into a R1CS log.Info("compiling circuit") r1cs, err := frontend.Compile(ecc.BN254, backend.GROTH16, &mimcCircuit) if err != nil { return err } //setup pk, vk as part of algorithms log.Info("running groth16.Setup") pk, vk, err := groth16.Setup(r1cs) if err != nil { return err } // build witness for party with ID="1000000" proofFor := "1000000" witness, sum, _ := createWitness(trxJson, proofFor) //setup proof proof, err := groth16.Prove(r1cs, pk, witness) if err != nil { return err } //backend-verifier //Assign HASH=Y by verifier (server) var publicWitness circuit.Circuit publicWitness.Hash.Assign(sum) err = groth16.Verify(proof, vk, &publicWitness) if err != nil { return err } log.Info("successfully verified proof on-chain") return nil } func Init() error { log.Info("Initializing circuit and generate solidity") var mimcCircuit circuit.Circuit // Set transaction ID for this circuit from constant circuit.OrderID = ordId // compiles our circuit into a R1CS log.Info("compiling circuit") r1cs, err := frontend.Compile(ecc.BN254, backend.GROTH16, &mimcCircuit) if err != nil { return err } //setup pk, vk as part of algorithms log.Info("running groth16.Setup") pk, vk, err := groth16.Setup(r1cs) if err != nil { return err } // serialize R1CS, proving & verifying key log.WithField("r1csPath", r1csPath).Info("serialize R1CS (circuit)") err = serialize(r1cs, r1csPath) if err != nil { return err } log.WithField("pkPath", pkPath).Info("serialize proving key") err = serialize(pk, pkPath) if err != nil { return err } log.WithField("vkPath", vkPath).Info("serialize verifying key") err = serialize(vk, vkPath) if err != nil { return err } // export verifying key to solidity log.WithField("verifierSol", verifierSol).Info("export solidity verifier") f, err := os.Create(verifierSol) if err != nil { return err } err = vk.ExportSolidity(f) if err != nil { return err } // run abigen to generate go wrapper if _, err = os.Stat(filepath.Join(GOBIN(), "abigen")); os.IsNotExist(err) { err = ensureAbigen() if err != nil { return err } } cmd := exec.Command(abiBinPath, "--sol", verifierSol, "--pkg", "circuit", "--out", wrapperGo) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { log.Error("Error: Could not execute abigen ... ", "error: ", err, ", cmd: ", cmd) return err } return nil } // GOBIN environment variable. func GOBIN() string { if os.Getenv("GOBIN") == "" { log.Fatal("GOBIN not set") } return os.Getenv("GOBIN") } //nolint:gosec func ensureAbigen() error { // Make sure abigen is downloaded and available. argsGet := []string{"get", AbigenVersion} cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), argsGet...) out, err := cmd.CombinedOutput() if err != nil { log.Errorf("could not list pkgs: %v\n%s", err, string(out)) return err } cmd = exec.Command(filepath.Join(GOBIN(), "abigen")) cmd.Args = append(cmd.Args, "--version") log.Info("Checking abigen version ...", strings.Join(cmd.Args, " \\\n")) cmd.Stderr, cmd.Stdout = os.Stderr, os.Stdout if err := cmd.Run(); err != nil { log.Error("Error: Could not Checking abigen version ... ", "error: ", err, ", cmd: ", cmd) return err } return nil } // serialize gnark object to given file func serialize(gnarkObject io.WriterTo, fileName string) error { f, err := os.Create(fileName) if err != nil { return err } _, err = gnarkObject.WriteTo(f) if err != nil { return err } return nil } // deserialize gnark object from given file func deserialize(gnarkObject io.ReaderFrom, fileName string) error { f, err := os.Open(filepath.Join(basePath, filepath.Base(fileName))) if err != nil { return err } _, err = gnarkObject.ReadFrom(f) if err != nil { return err } return nil } func deploySolidity() (*circuit.Verifier, error) { // build server URL server := strings.Join([]string{"http://localhost:", strconv.Itoa(chainPORT)}, "") // log URL of RPC server log.WithField("URL", server).Info("test URL of RPC server") client, err := ethclient.Dial(server) if err != nil { return nil, err } privateKey, err := crypto.HexToECDSA(deployPKey) if err != nil { return nil, err } // get deployer address from public key publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") } deployAddr := crypto.PubkeyToAddress(*publicKeyECDSA) account := common.HexToAddress(deployAddr.String()) balance, err := client.BalanceAt(context.Background(), account, nil) if err != nil { return nil, err } // convert balance to ETH fbalance := new(big.Float) fbalance.SetString(balance.String()) ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))) // log balance of the account used to deploy the contract log.WithField("balance", ethValue).Info("testing balance of account used to deploy") // deploy verifier contract log.Println("deploying verifier contract on chain") nonce, err := client.PendingNonceAt(context.Background(), account) if err != nil { return nil, err } // log nonce log.WithField("nonce", nonce).Info("testing the nonce") gasPrice, err := client.SuggestGasPrice(context.Background()) if err != nil { return nil, err } // setup authentication auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(chainID)) if err != nil { return nil, err } auth.Nonce = big.NewInt(int64(nonce)) auth.Value = big.NewInt(0) // in wei auth.GasLimit = 3000000 // in units auth.GasPrice = gasPrice //Use this when deploying to simulatedBackend // //_, _, verifierContract, err := circuit.DeployVerifier(auth, simulatedBackend) address, tx, verifierContract, err := circuit.DeployVerifier(auth, client) if err != nil { return nil, err } // log address and transaction hash for deploying this contract log.WithField("address", address.Hex()).Info("contract deployed at") log.WithField("hash", tx.Hash().Hex()).Info("hash of deploy transaction") // convert balance to ETH txcost := new(big.Float) txcost.SetString(tx.Cost().String()) ethCosts := new(big.Float).Quo(txcost, big.NewFloat(math.Pow10(18))) log.WithField("cost", ethCosts.String()).Info("cost to deploy in ETH") //simulatedBackend.Commit() return verifierContract, nil } // function to build public witness and hash from Json order string func createWitness(ordstr string, party string) (*circuit.Circuit, []byte, error) { //set witness var witness circuit.Circuit //Store Json in struct and print struct values var order OtcTXN err := json.Unmarshal([]byte(ordstr), &order) if err != nil { return nil, nil, err } log. WithField("tx.Id", order.Id). WithField("tx.Recipients", order.Recipients). Info("Going to generate witness circuit") // stop processing when there are not exactly 3 receipients // ToDo: In a next refactoring step we need to redesign to accept 2+ recipients if len(order.Recipients) != 3 { log.Error("Error: Not exactly 3 recipients found in order ... ", "Receipients: ", order.Recipients) return nil, nil, err } // Set seed to order ID. This will make the proof unique for this order with that ID fullHashFn := hash.MIMC_BN254.New(order.Id) //var fre fr.Element //partyA := fre.SetBytes([]byte(party)) //witness.ByteSliceRecipient.Assign(partyA) // reset hash functions fullHashFn.Reset() first := true var a, b, c fr.Element log.Info("Writing secret witness ByteSliceRecipient for: \"" + party + "\"") a.SetBytes([]byte(party)) witness.ByteSliceRecipient.Assign(a) x := a.Bytes() slice := x[:] _, err = fullHashFn.Write(slice) if err != nil { return nil, nil, err } //assign public witness for _, v := range order.Recipients { // if party does not match the id found in the list of recipients, write slice (complement) if strings.Compare(party, v) != 0 { if first { log.Info("Writing secret witness ByteSliceMissing1 for: \"" + v + "\"") b.SetBytes([]byte(v)) witness.ByteSliceMissing1.Assign(b) // write slice of recipient partyA to hash function b.SetBytes([]byte(v)) x1 := b.Bytes() slice1 := x1[:] _, err = fullHashFn.Write(slice1) if err != nil { return nil, nil, err } first = false } else { log.Info("Writing secret witness ByteSliceMissing2 for: \"" + v + "\"") c.SetBytes([]byte(v)) witness.ByteSliceMissing2.Assign(c) // write slice of recipient partyA to hash function c.SetBytes([]byte(v)) x2 := c.Bytes() slice2 := x2[:] _, err = fullHashFn.Write(slice2) if err != nil { return nil, nil, err } } } } // assign full sum to witness (this is our public witness) sum := fullHashFn.Sum(nil) witness.Hash.Assign(sum) return &witness, sum, nil } func execute() error { log.Info("Generate proof and verify off-chain") // check that init was performed if _, err := os.Stat(r1csPath); os.IsNotExist(err) { log.Fatal("please run with --init flag first to serialize circuit, keys and solidity contract") return err } // read R1CS, proving key and verifying keys r1cs := groth16.NewCS(ecc.BN254) pk := groth16.NewProvingKey(ecc.BN254) vk := groth16.NewVerifyingKey(ecc.BN254) err := deserialize(r1cs, r1csPath) if err != nil { return err } err = deserialize(pk, pkPath) if err != nil { return err } err = deserialize(vk, vkPath) if err != nil { return err } // Generate public witness and sum ([]byte of hash) from trxJson var witness *circuit.Circuit proofFor := "1000000" // build witness for party with ID="1000000" witness, sum, _ := createWitness(trxJson, proofFor) //setup proof proof, err := groth16.Prove(r1cs, pk, witness) if err != nil { return err } //backend-verifier //Assign HASH=Y by verifier (server) var publicWitness circuit.Circuit publicWitness.Hash.Assign(sum) err = groth16.Verify(proof, vk, &publicWitness) if err != nil { return err } log.Info("successfully verified proof on-chain") return nil }