diff --git a/backend/groth16/bls12-377/prove.go b/backend/groth16/bls12-377/prove.go index ea5f3f3161..2f4bd83495 100644 --- a/backend/groth16/bls12-377/prove.go +++ b/backend/groth16/bls12-377/prove.go @@ -6,7 +6,9 @@ package groth16 import ( + "errors" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +389,8 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { return a } + +// ExportProof not implemented for BLS12-377 +func (vk *Proof) ExportProof(publicSignals []string, w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bls12-377/verify.go b/backend/groth16/bls12-377/verify.go index 7a86e3dc76..7c2e1c2d57 100644 --- a/backend/groth16/bls12-377/verify.go +++ b/backend/groth16/bls12-377/verify.go @@ -133,3 +133,8 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { return errors.New("not implemented") } + +// ExportVerifyingKey not implemented for BLS12-377 +func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bls12-381/prove.go b/backend/groth16/bls12-381/prove.go index 4573814e46..41d3f9d5bf 100644 --- a/backend/groth16/bls12-381/prove.go +++ b/backend/groth16/bls12-381/prove.go @@ -6,7 +6,9 @@ package groth16 import ( + "encoding/json" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +389,45 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { return a } + +// ExportProof serializes a Groth16 proof into a JSON format compatible with snarkjs +// and writes it to the provided writer. +// This is an experimental feature and the export format / compatibility with external tools +// has not been thoroughly tested. +func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { + // G1 -> [x,y,"1"] + g1 := func(P curve.G1Affine) []string { + return []string{ + P.X.BigInt(new(big.Int)).String(), + P.Y.BigInt(new(big.Int)).String(), + "1", + } + } + // G2 -> [[x0,x1],[y0,y1],["1","0"]] + g2 := func(P curve.G2Affine) [][]string { + return [][]string{ + {P.X.A0.BigInt(new(big.Int)).String(), P.X.A1.BigInt(new(big.Int)).String()}, + {P.Y.A0.BigInt(new(big.Int)).String(), P.Y.A1.BigInt(new(big.Int)).String()}, + {"1", "0"}, + } + } + + out := map[string]any{ + "protocol": "groth16", + "curve": "bls12381", + "pi_a": g1(proof.Ar), // A + "pi_b": g2(proof.Bs), // B + "pi_c": g1(proof.Krs), // C + } + if len(publicSignals) > 0 { + out["publicSignals"] = publicSignals + } + + if len(proof.Commitments) > 0 { + return fmt.Errorf("proof contains commitments, but snarkjs verifier does not support them") + } + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(out) +} diff --git a/backend/groth16/bls12-381/verify.go b/backend/groth16/bls12-381/verify.go index cf47d66ceb..cfa7b72567 100644 --- a/backend/groth16/bls12-381/verify.go +++ b/backend/groth16/bls12-381/verify.go @@ -6,9 +6,11 @@ package groth16 import ( + "encoding/json" "errors" "fmt" "io" + "math/big" "time" "github.com/consensys/gnark-crypto/ecc" @@ -133,3 +135,84 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { return errors.New("not implemented") } + +// ExportVerifyingKey serializes the verifying key into a JSON format compatible with snarkjs +// and writes it to the provided writer. +// This is an experimental feature and the export format / compatibility with external tools +// has not been thoroughly tested. +func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { + if vk == nil { + return fmt.Errorf("verifying key is nil") + } + + // g1 -> []string{x,y,"1"} + g1 := func(p curve.G1Affine) []string { + return []string{ + p.X.BigInt(new(big.Int)).String(), + p.Y.BigInt(new(big.Int)).String(), + "1", + } + } + + // g2 -> [][]string{{x0,x1},{y0,y1},{"1","0"}} + g2 := func(p curve.G2Affine) [][]string { + return [][]string{ + {p.X.A0.BigInt(new(big.Int)).String(), p.X.A1.BigInt(new(big.Int)).String()}, + {p.Y.A0.BigInt(new(big.Int)).String(), p.Y.A1.BigInt(new(big.Int)).String()}, + {"1", "0"}, + } + } + + // vk_alphabeta_12 = e(alpha, beta) -> 2x3x2 + ab, err := curve.Pair( + []curve.G1Affine{vk.G1.Alpha}, + []curve.G2Affine{vk.G2.Beta}, + ) + if err != nil { + return fmt.Errorf("pairing(alpha,beta) failed: %w", err) + } + + gt := [][][]string{ + { + {ab.C0.B0.A0.BigInt(new(big.Int)).String(), ab.C0.B0.A1.BigInt(new(big.Int)).String()}, + {ab.C0.B1.A0.BigInt(new(big.Int)).String(), ab.C0.B1.A1.BigInt(new(big.Int)).String()}, + {ab.C0.B2.A0.BigInt(new(big.Int)).String(), ab.C0.B2.A1.BigInt(new(big.Int)).String()}, + }, + { + {ab.C1.B0.A0.BigInt(new(big.Int)).String(), ab.C1.B0.A1.BigInt(new(big.Int)).String()}, + {ab.C1.B1.A0.BigInt(new(big.Int)).String(), ab.C1.B1.A1.BigInt(new(big.Int)).String()}, + {ab.C1.B2.A0.BigInt(new(big.Int)).String(), ab.C1.B2.A1.BigInt(new(big.Int)).String()}, + }, + } + + var ic [][]string + for _, p := range vk.G1.K { + ic = append(ic, g1(p)) + } + + out := struct { + Protocol string `json:"protocol"` + Curve string `json:"curve"` + NPublic int `json:"nPublic"` + VKAlpha1 []string `json:"vk_alpha_1"` + VKBeta2 [][]string `json:"vk_beta_2"` + VKGamma2 [][]string `json:"vk_gamma_2"` + VKDelta2 [][]string `json:"vk_delta_2"` + VKAlphaBeta [][][]string `json:"vk_alphabeta_12,omitempty"` + IC [][]string `json:"IC"` + }{ + Protocol: "groth16", + Curve: "bls12381", + NPublic: vk.NbPublicWitness(), + VKAlpha1: g1(vk.G1.Alpha), + VKBeta2: g2(vk.G2.Beta), + VKGamma2: g2(vk.G2.Gamma), + VKDelta2: g2(vk.G2.Delta), + VKAlphaBeta: gt, + IC: ic, + } + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(out) +} diff --git a/backend/groth16/bls24-315/prove.go b/backend/groth16/bls24-315/prove.go index 684c7ca42f..434ca52fd7 100644 --- a/backend/groth16/bls24-315/prove.go +++ b/backend/groth16/bls24-315/prove.go @@ -6,7 +6,9 @@ package groth16 import ( + "errors" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +389,8 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { return a } + +// ExportProof not implemented for BLS24-315 +func (vk *Proof) ExportProof(publicSignals []string, w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bls24-315/verify.go b/backend/groth16/bls24-315/verify.go index 59318d9067..b6ef34a05f 100644 --- a/backend/groth16/bls24-315/verify.go +++ b/backend/groth16/bls24-315/verify.go @@ -133,3 +133,8 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { return errors.New("not implemented") } + +// ExportVerifyingKey not implemented for BLS24-315 +func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bls24-317/prove.go b/backend/groth16/bls24-317/prove.go index 311ccb95a1..cc4f09eb99 100644 --- a/backend/groth16/bls24-317/prove.go +++ b/backend/groth16/bls24-317/prove.go @@ -6,7 +6,9 @@ package groth16 import ( + "errors" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +389,8 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { return a } + +// ExportProof not implemented for BLS24-317 +func (vk *Proof) ExportProof(publicSignals []string, w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bls24-317/verify.go b/backend/groth16/bls24-317/verify.go index 2623657a68..aa992c22c4 100644 --- a/backend/groth16/bls24-317/verify.go +++ b/backend/groth16/bls24-317/verify.go @@ -133,3 +133,8 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { return errors.New("not implemented") } + +// ExportVerifyingKey not implemented for BLS24-317 +func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bn254/prove.go b/backend/groth16/bn254/prove.go index 41810928d7..44fd1ae27a 100644 --- a/backend/groth16/bn254/prove.go +++ b/backend/groth16/bn254/prove.go @@ -6,7 +6,9 @@ package groth16 import ( + "encoding/json" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +389,45 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { return a } + +// ExportProof serializes a Groth16 proof into a JSON format compatible with snarkjs +// and writes it to the provided writer. +// This is an experimental feature and the export format / compatibility with external tools +// has not been thoroughly tested. +func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { + // G1 -> [x,y,"1"] + g1 := func(P curve.G1Affine) []string { + return []string{ + P.X.BigInt(new(big.Int)).String(), + P.Y.BigInt(new(big.Int)).String(), + "1", + } + } + // G2 -> [[x0,x1],[y0,y1],["1","0"]] + g2 := func(P curve.G2Affine) [][]string { + return [][]string{ + {P.X.A0.BigInt(new(big.Int)).String(), P.X.A1.BigInt(new(big.Int)).String()}, + {P.Y.A0.BigInt(new(big.Int)).String(), P.Y.A1.BigInt(new(big.Int)).String()}, + {"1", "0"}, + } + } + + out := map[string]any{ + "protocol": "groth16", + "curve": "bn254", + "pi_a": g1(proof.Ar), // A + "pi_b": g2(proof.Bs), // B + "pi_c": g1(proof.Krs), // C + } + if len(publicSignals) > 0 { + out["publicSignals"] = publicSignals + } + + if len(proof.Commitments) > 0 { + return fmt.Errorf("proof contains commitments, but snarkjs verifier does not support them") + } + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(out) +} diff --git a/backend/groth16/bn254/verify.go b/backend/groth16/bn254/verify.go index f293b314ff..969a3150f0 100644 --- a/backend/groth16/bn254/verify.go +++ b/backend/groth16/bn254/verify.go @@ -8,6 +8,7 @@ package groth16 import ( "bytes" "crypto/sha256" + "encoding/json" "errors" "fmt" "io" @@ -229,3 +230,84 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor return err } + +// ExportVerifyingKey serializes the verifying key into a JSON format compatible with snarkjs +// and writes it to the provided writer. +// This is an experimental feature and the export format / compatibility with external tools +// has not been thoroughly tested. +func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { + if vk == nil { + return fmt.Errorf("verifying key is nil") + } + + // g1 -> []string{x,y,"1"} + g1 := func(p curve.G1Affine) []string { + return []string{ + p.X.BigInt(new(big.Int)).String(), + p.Y.BigInt(new(big.Int)).String(), + "1", + } + } + + // g2 -> [][]string{{x0,x1},{y0,y1},{"1","0"}} + g2 := func(p curve.G2Affine) [][]string { + return [][]string{ + {p.X.A0.BigInt(new(big.Int)).String(), p.X.A1.BigInt(new(big.Int)).String()}, + {p.Y.A0.BigInt(new(big.Int)).String(), p.Y.A1.BigInt(new(big.Int)).String()}, + {"1", "0"}, + } + } + + // vk_alphabeta_12 = e(alpha, beta) + ab, err := curve.Pair( + []curve.G1Affine{vk.G1.Alpha}, + []curve.G2Affine{vk.G2.Beta}, + ) + if err != nil { + return fmt.Errorf("pairing(alpha,beta) failed: %w", err) + } + + gt := [][][]string{ + { + {ab.C0.B0.A0.BigInt(new(big.Int)).String(), ab.C0.B0.A1.BigInt(new(big.Int)).String()}, + {ab.C0.B1.A0.BigInt(new(big.Int)).String(), ab.C0.B1.A1.BigInt(new(big.Int)).String()}, + {ab.C0.B2.A0.BigInt(new(big.Int)).String(), ab.C0.B2.A1.BigInt(new(big.Int)).String()}, + }, + { + {ab.C1.B0.A0.BigInt(new(big.Int)).String(), ab.C1.B0.A1.BigInt(new(big.Int)).String()}, + {ab.C1.B1.A0.BigInt(new(big.Int)).String(), ab.C1.B1.A1.BigInt(new(big.Int)).String()}, + {ab.C1.B2.A0.BigInt(new(big.Int)).String(), ab.C1.B2.A1.BigInt(new(big.Int)).String()}, + }, + } + + var ic [][]string + for _, p := range vk.G1.K { + ic = append(ic, g1(p)) + } + + out := struct { + Protocol string `json:"protocol"` + Curve string `json:"curve"` + NPublic int `json:"nPublic"` + VKAlpha1 []string `json:"vk_alpha_1"` + VKBeta2 [][]string `json:"vk_beta_2"` + VKGamma2 [][]string `json:"vk_gamma_2"` + VKDelta2 [][]string `json:"vk_delta_2"` + VKAlphaBeta [][][]string `json:"vk_alphabeta_12,omitempty"` + IC [][]string `json:"IC"` + }{ + Protocol: "groth16", + Curve: "bn254", + NPublic: vk.NbPublicWitness(), + VKAlpha1: g1(vk.G1.Alpha), + VKBeta2: g2(vk.G2.Beta), + VKGamma2: g2(vk.G2.Gamma), + VKDelta2: g2(vk.G2.Delta), + VKAlphaBeta: gt, + IC: ic, + } + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(out) +} diff --git a/backend/groth16/bw6-633/prove.go b/backend/groth16/bw6-633/prove.go index 6736c40d99..e2921abb16 100644 --- a/backend/groth16/bw6-633/prove.go +++ b/backend/groth16/bw6-633/prove.go @@ -6,7 +6,9 @@ package groth16 import ( + "errors" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +389,8 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { return a } + +// ExportProof not implemented for BW6-633 +func (vk *Proof) ExportProof(publicSignals []string, w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bw6-633/verify.go b/backend/groth16/bw6-633/verify.go index 399fdf4891..48e4ce7f0c 100644 --- a/backend/groth16/bw6-633/verify.go +++ b/backend/groth16/bw6-633/verify.go @@ -133,3 +133,8 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { return errors.New("not implemented") } + +// ExportVerifyingKey not implemented for BW6-633 +func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bw6-761/prove.go b/backend/groth16/bw6-761/prove.go index 4475ffd298..15667a8229 100644 --- a/backend/groth16/bw6-761/prove.go +++ b/backend/groth16/bw6-761/prove.go @@ -6,7 +6,9 @@ package groth16 import ( + "errors" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +389,8 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { return a } + +// ExportProof not implemented for BW6-761 +func (vk *Proof) ExportProof(publicSignals []string, w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/bw6-761/verify.go b/backend/groth16/bw6-761/verify.go index e1ba4a95a4..03ea5f6910 100644 --- a/backend/groth16/bw6-761/verify.go +++ b/backend/groth16/bw6-761/verify.go @@ -133,3 +133,8 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { return errors.New("not implemented") } + +// ExportVerifyingKey not implemented for BW6-761 +func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { + return errors.New("not implemented") +} diff --git a/backend/groth16/groth16.go b/backend/groth16/groth16.go index 3c4d1b5645..299f4b0cea 100644 --- a/backend/groth16/groth16.go +++ b/backend/groth16/groth16.go @@ -13,6 +13,7 @@ import ( "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/snarkjs" "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" @@ -56,6 +57,9 @@ type Proof interface { // Raw methods for faster serialization-deserialization. Does not perform checks on the data. // Only use if you are sure of the data you are reading comes from trusted source. gnarkio.WriterRawTo + + // Methods required to generate a proof JSON file compatible with snarkjs + snarkjs.Proof } // ProvingKey represents a Groth16 ProvingKey @@ -108,6 +112,9 @@ type VerifyingKey interface { // supported on the CurveID(). solidity.VerifyingKey + // Methods required to generate a verification key JSON file compatible with snarkjs + snarkjs.VerifyingKey + // NbPublicWitness returns number of elements expected in the public witness NbPublicWitness() int diff --git a/backend/snarkjs/proof.go b/backend/snarkjs/proof.go new file mode 100644 index 0000000000..59a7ff6824 --- /dev/null +++ b/backend/snarkjs/proof.go @@ -0,0 +1,10 @@ +package snarkjs + +import ( + "io" +) + +// VerifyingKey is the interface for verifying keys in the SnarkJS backend. +type Proof interface { + ExportProof([]string, io.Writer) error +} diff --git a/backend/snarkjs/verifyingkey.go b/backend/snarkjs/verifyingkey.go new file mode 100644 index 0000000000..e2b33dc0b4 --- /dev/null +++ b/backend/snarkjs/verifyingkey.go @@ -0,0 +1,8 @@ +package snarkjs + +import "io" + +// VerifyingKey is the interface for verifying keys in the SnarkJS backend. +type VerifyingKey interface { + ExportVerifyingKey(io.Writer) error +}