Skip to content

Instantly share code, notes, and snippets.

@miukki
Created June 17, 2021 06:02
Show Gist options
  • Select an option

  • Save miukki/1b1fb2b5e418409e1a1cd2c427efb93e to your computer and use it in GitHub Desktop.

Select an option

Save miukki/1b1fb2b5e418409e1a1cd2c427efb93e to your computer and use it in GitHub Desktop.

Revisions

  1. miukki created this gist Jun 17, 2021.
    643 changes: 643 additions & 0 deletions byteslice.go
    Original 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
    }