Skip to content

Instantly share code, notes, and snippets.

@miukki
Created June 17, 2021 06:02
Show Gist options
  • Save miukki/1b1fb2b5e418409e1a1cd2c427efb93e to your computer and use it in GitHub Desktop.
Save miukki/1b1fb2b5e418409e1a1cd2c427efb93e to your computer and use it in GitHub Desktop.
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/[email protected]"
)
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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment