diff --git a/.gitignore b/.gitignore index 9ebc0b7..4f61f39 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ *.Backup +cli/compiledcircuit.json +cli/inputs.json +cli/proofs.json +cli/test.circuit +cli/trustedsetup.json diff --git a/README.md b/README.md index 4c168e4..dbb979b 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Current implementation status: - [![GoDoc](https://godoc.org/github.com/arnaucube/go-snark/r1csqap?status.svg)](https://godoc.org/github.com/arnaucube/go-snark/r1csqap) R1CS to QAP (more details: https://github.com/arnaucube/go-snark/tree/master/r1csqap) - [![GoDoc](https://godoc.org/github.com/arnaucube/go-snark/circuitcompiler?status.svg)](https://godoc.org/github.com/arnaucube/go-snark/circuitcompiler) Circuit Compiler +#### Library usage Example: ```go // compile circuit and get the R1CS @@ -104,6 +105,56 @@ assert.Nil(t, err) assert.True(t, snark.VerifyProof(circuit, setup, proof)) ``` +#### CLI usage + +##### Compile circuit +Having a circuit file `test.circuit`: +``` +func test(x): + aux = x*x + y = aux*x + z = x + y + out = z + 5 +``` +And a inputs file `inputs.json` +``` +[ + 3 +] +``` + +In the command line, execute: +``` +> go-snark compile test.circuit +``` + +This will output the `compiledcircuit.json` file. + +##### Trusted Setup +Having the `compiledcircuit.json`, now we can generate the `TrustedSetup`: +``` +> go-snark trustedsetup compiledcircuit.json +``` +This will create the file `trustedsetup.json` with the TrustedSetup data, and also a `toxic.json` file, with the parameters to delete from the `Trusted Setup`. + + +##### Generate Proofs +Assumming that we have the `compiledcircuit.json` and the `trustedsetup.json`, we can now generate the `Proofs` with the following command: +``` +> go-snark genproofs +``` + +This will store the file `proofs.json`, that contains all the SNARK proofs. + +##### Verify Proofs +Having the `proofs.json`, `compiledcircuit.json`, `trustedsetup.json` files, we can now verify the `Pairings` of the proofs, in order to verify the proofs. +``` +> go-snark verify +``` +This will return a `true` if the proofs are verified, or a `false` if the proofs are not verified. + + + ### Test ``` go test ./... -v diff --git a/circuitcompiler/circuit.go b/circuitcompiler/circuit.go index af16b9e..501364c 100644 --- a/circuitcompiler/circuit.go +++ b/circuitcompiler/circuit.go @@ -144,7 +144,10 @@ func grabVar(signals []string, w []*big.Int, vStr string) *big.Int { } // CalculateWitness calculates the Witness of a Circuit based on the given inputs -func (circ *Circuit) CalculateWitness(inputs []*big.Int) []*big.Int { +func (circ *Circuit) CalculateWitness(inputs []*big.Int) ([]*big.Int, error) { + if len(inputs) != len(circ.Inputs) { + return []*big.Int{}, errors.New("given inputs != circuit.Inputs") + } w := r1csqap.ArrayOfBigZeros(len(circ.Signals)) w[0] = big.NewInt(int64(1)) for i, input := range inputs { @@ -162,5 +165,5 @@ func (circ *Circuit) CalculateWitness(inputs []*big.Int) []*big.Int { w[indexInArray(circ.Signals, constraint.Out)] = new(big.Int).Div(grabVar(circ.Signals, w, constraint.V1), grabVar(circ.Signals, w, constraint.V2)) } } - return w + return w, nil } diff --git a/circuitcompiler/circuit_test.go b/circuitcompiler/circuit_test.go index c1912ca..6f4e49d 100644 --- a/circuitcompiler/circuit_test.go +++ b/circuitcompiler/circuit_test.go @@ -78,6 +78,7 @@ func TestCircuitParser(t *testing.T) { b3 := big.NewInt(int64(3)) inputs := []*big.Int{b3} // Calculate Witness - w := circuit.CalculateWitness(inputs) + w, err := circuit.CalculateWitness(inputs) + assert.Nil(t, err) fmt.Println("w", w) } diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..8905a0c --- /dev/null +++ b/cli/main.go @@ -0,0 +1,248 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "math/big" + "os" + + snark "github.com/arnaucube/go-snark" + "github.com/arnaucube/go-snark/circuitcompiler" + "github.com/arnaucube/go-snark/r1csqap" + "github.com/urfave/cli" +) + +func panicErr(err error) { + if err != nil { + panic(err) + } +} + +var commands = []cli.Command{ + { + Name: "compile", + Aliases: []string{}, + Usage: "compile a circuit", + Action: CompileCircuit, + }, + { + Name: "genproofs", + Aliases: []string{}, + Usage: "generate the snark proofs", + Action: GenerateProofs, + }, + { + Name: "verify", + Aliases: []string{}, + Usage: "verify the snark proofs", + Action: VerifyProofs, + }, +} + +func main() { + app := cli.NewApp() + app.Name = "go-snarks-cli" + app.Version = "0.1.0-alpha" + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "config"}, + } + app.Commands = commands + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +func CompileCircuit(context *cli.Context) error { + fmt.Println("cli") + + // read circuit file + circuitFile, err := os.Open("test.circuit") + panicErr(err) + + // parse circuit code + parser := circuitcompiler.NewParser(bufio.NewReader(circuitFile)) + circuit, err := parser.Parse() + panicErr(err) + fmt.Println("\ncircuit data:", circuit) + + // read inputs file + inputsFile, err := ioutil.ReadFile("inputs.json") + panicErr(err) + + // parse inputs from inputsFile + var inputs []*big.Int + json.Unmarshal([]byte(string(inputsFile)), &inputs) + panicErr(err) + + // calculate wittness + w, err := circuit.CalculateWitness(inputs) + panicErr(err) + fmt.Println("\nwitness", w) + + // flat code to R1CS + fmt.Println("\ngenerating R1CS from flat code") + a, b, c := circuit.GenerateR1CS() + fmt.Println("\nR1CS:") + fmt.Println("a:", a) + fmt.Println("b:", b) + fmt.Println("c:", c) + + // R1CS to QAP + alphas, betas, gammas, zx := snark.Utils.PF.R1CSToQAP(a, b, c) + fmt.Println("qap") + fmt.Println(alphas) + fmt.Println(betas) + fmt.Println(gammas) + + ax, bx, cx, px := snark.Utils.PF.CombinePolynomials(w, alphas, betas, gammas) + + hx := snark.Utils.PF.DivisorPolynomial(px, zx) + + // hx==px/zx so px==hx*zx + // assert.Equal(t, px, snark.Utils.PF.Mul(hx, zx)) + if !r1csqap.BigArraysEqual(px, snark.Utils.PF.Mul(hx, zx)) { + panic(errors.New("px != hx*zx")) + } + + // p(x) = a(x) * b(x) - c(x) == h(x) * z(x) + abc := snark.Utils.PF.Sub(snark.Utils.PF.Mul(ax, bx), cx) + // assert.Equal(t, abc, px) + if !r1csqap.BigArraysEqual(abc, px) { + panic(errors.New("abc != px")) + } + hz := snark.Utils.PF.Mul(hx, zx) + if !r1csqap.BigArraysEqual(abc, hz) { + panic(errors.New("abc != hz")) + } + // assert.Equal(t, abc, hz) + + div, rem := snark.Utils.PF.Div(px, zx) + if !r1csqap.BigArraysEqual(hx, div) { + panic(errors.New("hx != div")) + } + // assert.Equal(t, hx, div) + // assert.Equal(t, rem, r1csqap.ArrayOfBigZeros(4)) + for _, r := range rem { + if !bytes.Equal(r.Bytes(), big.NewInt(int64(0)).Bytes()) { + panic(errors.New("error:error: px/zx rem not equal to zeros")) + } + } + + // calculate trusted setup + setup, err := snark.GenerateTrustedSetup(len(w), *circuit, alphas, betas, gammas, zx) + panicErr(err) + fmt.Println("\nt:", setup.Toxic.T) + + // store circuit to json + jsonData, err := json.Marshal(circuit) + panicErr(err) + // store setup into file + jsonFile, err := os.Create("compiledcircuit.json") + panicErr(err) + defer jsonFile.Close() + jsonFile.Write(jsonData) + jsonFile.Close() + fmt.Println("Compiled Circuit data written to ", jsonFile.Name()) + // store setup to json + jsonData, err = json.Marshal(setup) + panicErr(err) + // store setup into file + jsonFile, err = os.Create("trustedsetup.json") + panicErr(err) + defer jsonFile.Close() + jsonFile.Write(jsonData) + jsonFile.Close() + fmt.Println("Trusted Setup data written to ", jsonFile.Name()) + return nil +} + +func GenerateProofs(context *cli.Context) error { + // open compiledcircuit.json + compiledcircuitFile, err := ioutil.ReadFile("compiledcircuit.json") + panicErr(err) + var circuit circuitcompiler.Circuit + json.Unmarshal([]byte(string(compiledcircuitFile)), &circuit) + panicErr(err) + + // open trustedsetup.json + trustedsetupFile, err := ioutil.ReadFile("trustedsetup.json") + panicErr(err) + var trustedsetup snark.Setup + json.Unmarshal([]byte(string(trustedsetupFile)), &trustedsetup) + panicErr(err) + + // read inputs file + inputsFile, err := ioutil.ReadFile("inputs.json") + panicErr(err) + // parse inputs from inputsFile + var inputs []*big.Int + json.Unmarshal([]byte(string(inputsFile)), &inputs) + panicErr(err) + // calculate wittness + w, err := circuit.CalculateWitness(inputs) + panicErr(err) + fmt.Println("\nwitness", w) + + // flat code to R1CS + a, b, c := circuit.GenerateR1CS() + // R1CS to QAP + alphas, betas, gammas, zx := snark.Utils.PF.R1CSToQAP(a, b, c) + _, _, _, px := snark.Utils.PF.CombinePolynomials(w, alphas, betas, gammas) + hx := snark.Utils.PF.DivisorPolynomial(px, zx) + + proof, err := snark.GenerateProofs(circuit, trustedsetup, hx, w) + panicErr(err) + + fmt.Println("\n proofs:") + fmt.Println(proof) + + // store proofs to json + jsonData, err := json.Marshal(proof) + panicErr(err) + // store proof into file + jsonFile, err := os.Create("proofs.json") + panicErr(err) + defer jsonFile.Close() + jsonFile.Write(jsonData) + jsonFile.Close() + fmt.Println("Proofs data written to ", jsonFile.Name()) + return nil +} + +func VerifyProofs(context *cli.Context) error { + // open proofs.json + proofsFile, err := ioutil.ReadFile("proofs.json") + panicErr(err) + var proof snark.Proof + json.Unmarshal([]byte(string(proofsFile)), &proof) + panicErr(err) + + // open compiledcircuit.json + compiledcircuitFile, err := ioutil.ReadFile("compiledcircuit.json") + panicErr(err) + var circuit circuitcompiler.Circuit + json.Unmarshal([]byte(string(compiledcircuitFile)), &circuit) + panicErr(err) + + // open trustedsetup.json + trustedsetupFile, err := ioutil.ReadFile("trustedsetup.json") + panicErr(err) + var trustedsetup snark.Setup + json.Unmarshal([]byte(string(trustedsetupFile)), &trustedsetup) + panicErr(err) + + verified := snark.VerifyProof(circuit, trustedsetup, proof, true) + if !verified { + fmt.Println("ERROR: proofs not verified") + } else { + fmt.Println("Proofs verified") + } + return nil +} diff --git a/fields/fq12.go b/fields/fq12.go index 066da12..4881a48 100644 --- a/fields/fq12.go +++ b/fields/fq12.go @@ -143,10 +143,13 @@ func BigIsOdd(n *big.Int) bool { } func (fq12 Fq12) Exp(base [2][3][2]*big.Int, e *big.Int) [2][3][2]*big.Int { + // TODO fix bottleneck + res := fq12.One() rem := fq12.Fq2.F.Copy(e) exp := base + // before := time.Now() for !bytes.Equal(rem.Bytes(), big.NewInt(int64(0)).Bytes()) { if BigIsOdd(rem) { res = fq12.Mul(res, exp) @@ -154,6 +157,7 @@ func (fq12 Fq12) Exp(base [2][3][2]*big.Int, e *big.Int) [2][3][2]*big.Int { exp = fq12.Square(exp) rem = new(big.Int).Rsh(rem, 1) } + // fmt.Println("time elapsed:", time.Since(before)) return res } func (fq12 Fq12) Affine(a [2][3][2]*big.Int) [2][3][2]*big.Int { diff --git a/go.mod b/go.mod index 3593c67..4127df9 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,5 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 + github.com/urfave/cli v1.20.0 ) diff --git a/go.sum b/go.sum index e03ee77..00b3a13 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,5 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= diff --git a/r1csqap/r1csqap.go b/r1csqap/r1csqap.go index d764d2c..88b532d 100644 --- a/r1csqap/r1csqap.go +++ b/r1csqap/r1csqap.go @@ -1,6 +1,7 @@ package r1csqap import ( + "bytes" "math/big" "github.com/arnaucube/go-snark/fields" @@ -28,6 +29,17 @@ func ArrayOfBigZeros(num int) []*big.Int { } return r } +func BigArraysEqual(a, b []*big.Int) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if !bytes.Equal(a[i].Bytes(), b[i].Bytes()) { + return false + } + } + return true +} // PolynomialField is the Polynomial over a Finite Field where the polynomial operations are performed type PolynomialField struct { diff --git a/snark.go b/snark.go index 0ac61de..fb54f1e 100644 --- a/snark.go +++ b/snark.go @@ -237,14 +237,16 @@ func GenerateProofs(circuit circuitcompiler.Circuit, setup Setup, hx []*big.Int, } // VerifyProof verifies over the BN128 the Pairings of the Proof -func VerifyProof(circuit circuitcompiler.Circuit, setup Setup, proof Proof) bool { +func VerifyProof(circuit circuitcompiler.Circuit, setup Setup, proof Proof, printVer bool) bool { // e(piA, Va) == e(piA', g2) pairingPiaVa := Utils.Bn.Pairing(proof.PiA, setup.Vk.Vka) pairingPiapG2 := Utils.Bn.Pairing(proof.PiAp, Utils.Bn.G2.G) if !Utils.Bn.Fq12.Equal(pairingPiaVa, pairingPiapG2) { return false } - fmt.Println("✓ e(piA, Va) == e(piA', g2), valid knowledge commitment for A") + if printVer { + fmt.Println("✓ e(piA, Va) == e(piA', g2), valid knowledge commitment for A") + } // e(Vb, piB) == e(piB', g2) pairingVbPib := Utils.Bn.Pairing(setup.Vk.Vkb, proof.PiB) @@ -252,7 +254,9 @@ func VerifyProof(circuit circuitcompiler.Circuit, setup Setup, proof Proof) bool if !Utils.Bn.Fq12.Equal(pairingVbPib, pairingPibpG2) { return false } - fmt.Println("✓ e(Vb, piB) == e(piB', g2), valid knowledge commitment for B") + if printVer { + fmt.Println("✓ e(Vb, piB) == e(piB', g2), valid knowledge commitment for B") + } // e(piC, Vc) == e(piC', g2) pairingPicVc := Utils.Bn.Pairing(proof.PiC, setup.Vk.Vkc) @@ -260,7 +264,9 @@ func VerifyProof(circuit circuitcompiler.Circuit, setup Setup, proof Proof) bool if !Utils.Bn.Fq12.Equal(pairingPicVc, pairingPicpG2) { return false } - fmt.Println("✓ e(piC, Vc) == e(piC', g2), valid knowledge commitment for C") + if printVer { + fmt.Println("✓ e(piC, Vc) == e(piC', g2), valid knowledge commitment for C") + } // Vkx, to then calculate Vkx+piA vkxpia := setup.Vk.A[0] @@ -276,7 +282,9 @@ func VerifyProof(circuit circuitcompiler.Circuit, setup Setup, proof Proof) bool Utils.Bn.Pairing(proof.PiC, Utils.Bn.G2.G))) { return false } - fmt.Println("✓ e(Vkx+piA, piB) == e(piH, Vkz) * e(piC, g2), QAP disibility checked") + if printVer { + fmt.Println("✓ e(Vkx+piA, piB) == e(piH, Vkz) * e(piC, g2), QAP disibility checked") + } // e(Vkx+piA+piC, g2KbetaKgamma) * e(g1KbetaKgamma, piB) // == e(piK, g2Kgamma) @@ -288,7 +296,9 @@ func VerifyProof(circuit circuitcompiler.Circuit, setup Setup, proof Proof) bool if !Utils.Bn.Fq12.Equal(pairingL, pairingR) { return false } - fmt.Println("✓ e(Vkx+piA+piC, g2KbetaKgamma) * e(g1KbetaKgamma, piB) == e(piK, g2Kgamma)") + if printVer { + fmt.Println("✓ e(Vkx+piA+piC, g2KbetaKgamma) * e(g1KbetaKgamma, piB) == e(piK, g2Kgamma)") + } return true } diff --git a/snark_test.go b/snark_test.go index 898fe5d..c11c68f 100644 --- a/snark_test.go +++ b/snark_test.go @@ -34,7 +34,8 @@ func TestZkFromFlatCircuitCode(t *testing.T) { b3 := big.NewInt(int64(3)) inputs := []*big.Int{b3} // wittness - w := circuit.CalculateWitness(inputs) + w, err := circuit.CalculateWitness(inputs) + assert.Nil(t, err) fmt.Println("\nwitness", w) // flat code to R1CS @@ -81,7 +82,7 @@ func TestZkFromFlatCircuitCode(t *testing.T) { fmt.Println("\n proofs:") fmt.Println(proof) before := time.Now() - assert.True(t, VerifyProof(*circuit, setup, proof)) + assert.True(t, VerifyProof(*circuit, setup, proof, false)) fmt.Println("verify proof time elapsed:", time.Since(before)) } @@ -142,11 +143,64 @@ func TestZkFromHardcodedR1CS(t *testing.T) { // calculate trusted setup setup, err := GenerateTrustedSetup(len(w), circuit, alphas, betas, gammas, zx) assert.Nil(t, err) - fmt.Println("t", setup.Toxic.T) // piA = g1 * A(t), piB = g2 * B(t), piC = g1 * C(t), piH = g1 * H(t) proof, err := GenerateProofs(circuit, setup, hx, w) assert.Nil(t, err) - assert.True(t, VerifyProof(circuit, setup, proof)) + assert.True(t, VerifyProof(circuit, setup, proof, true)) +} + +func TestZkMultiplication(t *testing.T) { + + // compile circuit and get the R1CS + flatCode := ` + func test(a, b): + out = a * b + ` + + // parse the code + parser := circuitcompiler.NewParser(strings.NewReader(flatCode)) + circuit, err := parser.Parse() + assert.Nil(t, err) + + b3 := big.NewInt(int64(3)) + b4 := big.NewInt(int64(4)) + inputs := []*big.Int{b3, b4} + // wittness + w, err := circuit.CalculateWitness(inputs) + assert.Nil(t, err) + + // flat code to R1CS + a, b, c := circuit.GenerateR1CS() + + // R1CS to QAP + alphas, betas, gammas, zx := Utils.PF.R1CSToQAP(a, b, c) + + ax, bx, cx, px := Utils.PF.CombinePolynomials(w, alphas, betas, gammas) + + hx := Utils.PF.DivisorPolynomial(px, zx) + + // hx==px/zx so px==hx*zx + assert.Equal(t, px, Utils.PF.Mul(hx, zx)) + + // p(x) = a(x) * b(x) - c(x) == h(x) * z(x) + abc := Utils.PF.Sub(Utils.PF.Mul(ax, bx), cx) + assert.Equal(t, abc, px) + hz := Utils.PF.Mul(hx, zx) + assert.Equal(t, abc, hz) + + div, rem := Utils.PF.Div(px, zx) + assert.Equal(t, hx, div) + assert.Equal(t, rem, r1csqap.ArrayOfBigZeros(1)) + + // calculate trusted setup + setup, err := GenerateTrustedSetup(len(w), *circuit, alphas, betas, gammas, zx) + assert.Nil(t, err) + + // piA = g1 * A(t), piB = g2 * B(t), piC = g1 * C(t), piH = g1 * H(t) + proof, err := GenerateProofs(*circuit, setup, hx, w) + assert.Nil(t, err) + + assert.True(t, VerifyProof(*circuit, setup, proof, false)) }