From c72ce52b3ba6f14694354a8ce4b29fe3fb3b0b78 Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Wed, 20 Aug 2025 14:22:01 +0300 Subject: [PATCH 1/6] feat(groth16): support export of verification key and proof to snarkjs-compatible JSON - added ExportVerifyingKey(io.Writer) for VerifyingKey - added ExportProof([]string, io.Writer) for Proof - supported curves: BLS12-381 and BN254 --- backend/groth16/bls12-377/prove.go | 7 +++ backend/groth16/bls12-377/verify.go | 5 ++ backend/groth16/bls12-381/prove.go | 46 ++++++++++++++++ backend/groth16/bls12-381/verify.go | 83 +++++++++++++++++++++++++++++ backend/groth16/bls24-315/prove.go | 7 +++ backend/groth16/bls24-315/verify.go | 5 ++ backend/groth16/bls24-317/prove.go | 7 +++ backend/groth16/bls24-317/verify.go | 5 ++ backend/groth16/bn254/prove.go | 46 ++++++++++++++++ backend/groth16/bn254/verify.go | 82 ++++++++++++++++++++++++++++ backend/groth16/bw6-633/prove.go | 7 +++ backend/groth16/bw6-633/verify.go | 5 ++ backend/groth16/bw6-761/prove.go | 7 +++ backend/groth16/bw6-761/verify.go | 5 ++ backend/groth16/groth16.go | 7 +++ backend/snarkjs/proof.go | 10 ++++ backend/snarkjs/verifyingkey.go | 8 +++ 17 files changed, 342 insertions(+) create mode 100644 backend/snarkjs/proof.go create mode 100644 backend/snarkjs/verifyingkey.go 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..0a65f14d17 100644 --- a/backend/groth16/bls12-381/prove.go +++ b/backend/groth16/bls12-381/prove.go @@ -6,7 +6,10 @@ package groth16 import ( + "bytes" + "encoding/json" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +390,46 @@ 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 { + const fpSize = 48 // BLS12-381 field element size (bytes) + + var buf bytes.Buffer + if _, err := proof.WriteRawTo(&buf); err != nil { + return fmt.Errorf("failed to serialize proof: %w", err) + } + proofBytes := buf.Bytes() + + offset := 0 + readBig := func(n int) *big.Int { + s := proofBytes[offset : offset+n] + offset += n + return new(big.Int).SetBytes(s) + } + + // parse proof elements + Ax, Ay := readBig(fpSize), readBig(fpSize) + Bx1, Bx0, By1, By0 := readBig(fpSize), readBig(fpSize), readBig(fpSize), readBig(fpSize) + Cx, Cy := readBig(fpSize), readBig(fpSize) + + data := map[string]any{ + "protocol": "groth16", + "curve": "bls12381", + "pi_a": []string{Ax.String(), Ay.String(), "1"}, + "pi_b": [][]string{ + {Bx0.String(), Bx1.String()}, + {By0.String(), By1.String()}, + {"1", "0"}, + }, + "pi_c": []string{Cx.String(), Cy.String(), "1"}, + "publicSignals": publicSignals, + } + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(data) +} diff --git a/backend/groth16/bls12-381/verify.go b/backend/groth16/bls12-381/verify.go index cf47d66ceb..14ea6596d0 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..0f9dd77ed3 100644 --- a/backend/groth16/bn254/prove.go +++ b/backend/groth16/bn254/prove.go @@ -6,7 +6,10 @@ package groth16 import ( + "bytes" + "encoding/json" "fmt" + "io" "math/big" "runtime" "time" @@ -387,3 +390,46 @@ 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 { + const fpSize = 32 // BN254 field element size (bytes) + + var buf bytes.Buffer + if _, err := proof.WriteRawTo(&buf); err != nil { + return fmt.Errorf("failed to serialize proof: %w", err) + } + proofBytes := buf.Bytes() + + offset := 0 + readBig := func(n int) *big.Int { + s := proofBytes[offset : offset+n] + offset += n + return new(big.Int).SetBytes(s) + } + + // parse proof elements + Ax, Ay := readBig(fpSize), readBig(fpSize) + Bx1, Bx0, By1, By0 := readBig(fpSize), readBig(fpSize), readBig(fpSize), readBig(fpSize) + Cx, Cy := readBig(fpSize), readBig(fpSize) + + data := map[string]any{ + "protocol": "groth16", + "curve": "bn254", + "pi_a": []string{Ax.String(), Ay.String(), "1"}, + "pi_b": [][]string{ + {Bx0.String(), Bx1.String()}, + {By0.String(), By1.String()}, + {"1", "0"}, + }, + "pi_c": []string{Cx.String(), Cy.String(), "1"}, + "publicSignals": publicSignals, + } + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(data) +} 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 +} From 723346f9868d0352a455b71986de6169ac2353af Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Fri, 5 Sep 2025 13:04:59 +0300 Subject: [PATCH 2/6] fix cursor --- backend/groth16/bls12-381/verify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/groth16/bls12-381/verify.go b/backend/groth16/bls12-381/verify.go index 14ea6596d0..cfa7b72567 100644 --- a/backend/groth16/bls12-381/verify.go +++ b/backend/groth16/bls12-381/verify.go @@ -163,7 +163,7 @@ func (vk *VerifyingKey) ExportVerifyingKey(w io.Writer) error { } } - // vk_alphabeta_12 = e(alpha, beta) -> 2x3x2 строки + // vk_alphabeta_12 = e(alpha, beta) -> 2x3x2 ab, err := curve.Pair( []curve.G1Affine{vk.G1.Alpha}, []curve.G2Affine{vk.G2.Beta}, From 079594aa985a02b126b6aa25be71a9ca8f205665 Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Fri, 5 Sep 2025 13:15:03 +0300 Subject: [PATCH 3/6] Fix: Bug: ExportProof Helper Fails Buffer Length Validation --- backend/groth16/bls12-381/prove.go | 44 ++++++++++++++++++++++++++---- backend/groth16/bn254/prove.go | 44 ++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/backend/groth16/bls12-381/prove.go b/backend/groth16/bls12-381/prove.go index 0a65f14d17..b8cd61209a 100644 --- a/backend/groth16/bls12-381/prove.go +++ b/backend/groth16/bls12-381/prove.go @@ -405,16 +405,48 @@ func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { proofBytes := buf.Bytes() offset := 0 - readBig := func(n int) *big.Int { + readBig := func(n int) (*big.Int, error) { + if offset+n > len(proofBytes) { + return nil, fmt.Errorf("invalid proof encoding: need %d bytes at offset %d, have %d", n, offset, len(proofBytes)-offset) + } s := proofBytes[offset : offset+n] offset += n - return new(big.Int).SetBytes(s) + return new(big.Int).SetBytes(s), nil } - // parse proof elements - Ax, Ay := readBig(fpSize), readBig(fpSize) - Bx1, Bx0, By1, By0 := readBig(fpSize), readBig(fpSize), readBig(fpSize), readBig(fpSize) - Cx, Cy := readBig(fpSize), readBig(fpSize) + // parse proof elements with bounds checks + Ax, err := readBig(fpSize) + if err != nil { + return err + } + Ay, err := readBig(fpSize) + if err != nil { + return err + } + Bx1, err := readBig(fpSize) + if err != nil { + return err + } + Bx0, err := readBig(fpSize) + if err != nil { + return err + } + By1, err := readBig(fpSize) + if err != nil { + return err + } + By0, err := readBig(fpSize) + if err != nil { + return err + } + Cx, err := readBig(fpSize) + if err != nil { + return err + } + Cy, err := readBig(fpSize) + if err != nil { + return err + } data := map[string]any{ "protocol": "groth16", diff --git a/backend/groth16/bn254/prove.go b/backend/groth16/bn254/prove.go index 0f9dd77ed3..7ef927e41c 100644 --- a/backend/groth16/bn254/prove.go +++ b/backend/groth16/bn254/prove.go @@ -405,16 +405,48 @@ func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { proofBytes := buf.Bytes() offset := 0 - readBig := func(n int) *big.Int { + readBig := func(n int) (*big.Int, error) { + if offset+n > len(proofBytes) { + return nil, fmt.Errorf("invalid proof encoding: need %d bytes at offset %d, have %d", n, offset, len(proofBytes)-offset) + } s := proofBytes[offset : offset+n] offset += n - return new(big.Int).SetBytes(s) + return new(big.Int).SetBytes(s), nil } - // parse proof elements - Ax, Ay := readBig(fpSize), readBig(fpSize) - Bx1, Bx0, By1, By0 := readBig(fpSize), readBig(fpSize), readBig(fpSize), readBig(fpSize) - Cx, Cy := readBig(fpSize), readBig(fpSize) + // parse proof elements with bounds checks + Ax, err := readBig(fpSize) + if err != nil { + return err + } + Ay, err := readBig(fpSize) + if err != nil { + return err + } + Bx1, err := readBig(fpSize) + if err != nil { + return err + } + Bx0, err := readBig(fpSize) + if err != nil { + return err + } + By1, err := readBig(fpSize) + if err != nil { + return err + } + By0, err := readBig(fpSize) + if err != nil { + return err + } + Cx, err := readBig(fpSize) + if err != nil { + return err + } + Cy, err := readBig(fpSize) + if err != nil { + return err + } data := map[string]any{ "protocol": "groth16", From b45cf160455829b1316ff05b1057dc6bcc7a8e4d Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Wed, 10 Sep 2025 10:29:40 +0300 Subject: [PATCH 4/6] update export, tests, go fmt --- backend/groth16/bls12-381/prove.go | 92 +++++++++++------------------- backend/groth16/bn254/prove.go | 92 +++++++++++------------------- 2 files changed, 68 insertions(+), 116 deletions(-) diff --git a/backend/groth16/bls12-381/prove.go b/backend/groth16/bls12-381/prove.go index b8cd61209a..f264ad46a3 100644 --- a/backend/groth16/bls12-381/prove.go +++ b/backend/groth16/bls12-381/prove.go @@ -6,7 +6,6 @@ package groth16 import ( - "bytes" "encoding/json" "fmt" "io" @@ -396,72 +395,49 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { // 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 { - const fpSize = 48 // BLS12-381 field element size (bytes) - - var buf bytes.Buffer - if _, err := proof.WriteRawTo(&buf); err != nil { - return fmt.Errorf("failed to serialize proof: %w", err) + // 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", + } } - proofBytes := buf.Bytes() - - offset := 0 - readBig := func(n int) (*big.Int, error) { - if offset+n > len(proofBytes) { - return nil, fmt.Errorf("invalid proof encoding: need %d bytes at offset %d, have %d", n, offset, len(proofBytes)-offset) + // 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"}, } - s := proofBytes[offset : offset+n] - offset += n - return new(big.Int).SetBytes(s), nil } - // parse proof elements with bounds checks - Ax, err := readBig(fpSize) - if err != nil { - return err - } - Ay, err := readBig(fpSize) - if err != nil { - return err - } - Bx1, err := readBig(fpSize) - if err != nil { - return err - } - Bx0, err := readBig(fpSize) - if err != nil { - return err - } - By1, err := readBig(fpSize) - if err != nil { - return err - } - By0, err := readBig(fpSize) - if err != nil { - return err - } - Cx, err := readBig(fpSize) - if err != nil { - return err + 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 } - Cy, err := readBig(fpSize) - if err != nil { - return err + if len(publicSignals) > 0 { + out["publicSignals"] = publicSignals } - data := map[string]any{ - "protocol": "groth16", - "curve": "bls12381", - "pi_a": []string{Ax.String(), Ay.String(), "1"}, - "pi_b": [][]string{ - {Bx0.String(), Bx1.String()}, - {By0.String(), By1.String()}, - {"1", "0"}, - }, - "pi_c": []string{Cx.String(), Cy.String(), "1"}, - "publicSignals": publicSignals, + if len(proof.Commitments) > 0 { + extra := struct { + Commitments [][]string `json:"commitments"` + CommitmentPok []string `json:"commitment_pok"` + }{ + Commitments: make([][]string, 0, len(proof.Commitments)), + CommitmentPok: g1(proof.CommitmentPok), + } + for _, c := range proof.Commitments { + extra.Commitments = append(extra.Commitments, g1(c)) + } + out["extra"] = extra } enc := json.NewEncoder(w) enc.SetIndent("", " ") - return enc.Encode(data) + return enc.Encode(out) } diff --git a/backend/groth16/bn254/prove.go b/backend/groth16/bn254/prove.go index 7ef927e41c..0f9d53579d 100644 --- a/backend/groth16/bn254/prove.go +++ b/backend/groth16/bn254/prove.go @@ -6,7 +6,6 @@ package groth16 import ( - "bytes" "encoding/json" "fmt" "io" @@ -396,72 +395,49 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { // 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 { - const fpSize = 32 // BN254 field element size (bytes) - - var buf bytes.Buffer - if _, err := proof.WriteRawTo(&buf); err != nil { - return fmt.Errorf("failed to serialize proof: %w", err) + // 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", + } } - proofBytes := buf.Bytes() - - offset := 0 - readBig := func(n int) (*big.Int, error) { - if offset+n > len(proofBytes) { - return nil, fmt.Errorf("invalid proof encoding: need %d bytes at offset %d, have %d", n, offset, len(proofBytes)-offset) + // 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"}, } - s := proofBytes[offset : offset+n] - offset += n - return new(big.Int).SetBytes(s), nil } - // parse proof elements with bounds checks - Ax, err := readBig(fpSize) - if err != nil { - return err - } - Ay, err := readBig(fpSize) - if err != nil { - return err - } - Bx1, err := readBig(fpSize) - if err != nil { - return err - } - Bx0, err := readBig(fpSize) - if err != nil { - return err - } - By1, err := readBig(fpSize) - if err != nil { - return err - } - By0, err := readBig(fpSize) - if err != nil { - return err + 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 } - Cx, err := readBig(fpSize) - if err != nil { - return err - } - Cy, err := readBig(fpSize) - if err != nil { - return err + if len(publicSignals) > 0 { + out["publicSignals"] = publicSignals } - data := map[string]any{ - "protocol": "groth16", - "curve": "bn254", - "pi_a": []string{Ax.String(), Ay.String(), "1"}, - "pi_b": [][]string{ - {Bx0.String(), Bx1.String()}, - {By0.String(), By1.String()}, - {"1", "0"}, - }, - "pi_c": []string{Cx.String(), Cy.String(), "1"}, - "publicSignals": publicSignals, + if len(proof.Commitments) > 0 { + extra := struct { + Commitments [][]string `json:"commitments"` + CommitmentPok []string `json:"commitment_pok"` + }{ + Commitments: make([][]string, 0, len(proof.Commitments)), + CommitmentPok: g1(proof.CommitmentPok), + } + for _, c := range proof.Commitments { + extra.Commitments = append(extra.Commitments, g1(c)) + } + out["extra"] = extra } enc := json.NewEncoder(w) enc.SetIndent("", " ") - return enc.Encode(data) + return enc.Encode(out) } From dc264ab13e6982f9b29e089eaccb54ab85b54d7c Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Wed, 10 Sep 2025 10:35:38 +0300 Subject: [PATCH 5/6] fix: Incorrect Curve Identifier in ExportProof --- backend/groth16/bn254/prove.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/groth16/bn254/prove.go b/backend/groth16/bn254/prove.go index 0f9d53579d..cea6a084c6 100644 --- a/backend/groth16/bn254/prove.go +++ b/backend/groth16/bn254/prove.go @@ -414,7 +414,7 @@ func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { out := map[string]any{ "protocol": "groth16", - "curve": "bls12381", + "curve": "bn254", "pi_a": g1(proof.Ar), // A "pi_b": g2(proof.Bs), // B "pi_c": g1(proof.Krs), // C From 014682b5e653031fa60a2f9847d01feffc473530 Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Thu, 11 Sep 2025 13:59:33 +0300 Subject: [PATCH 6/6] update: reject proofs with commitments --- backend/groth16/bls12-381/prove.go | 12 +----------- backend/groth16/bn254/prove.go | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/backend/groth16/bls12-381/prove.go b/backend/groth16/bls12-381/prove.go index f264ad46a3..41d3f9d5bf 100644 --- a/backend/groth16/bls12-381/prove.go +++ b/backend/groth16/bls12-381/prove.go @@ -424,17 +424,7 @@ func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { } if len(proof.Commitments) > 0 { - extra := struct { - Commitments [][]string `json:"commitments"` - CommitmentPok []string `json:"commitment_pok"` - }{ - Commitments: make([][]string, 0, len(proof.Commitments)), - CommitmentPok: g1(proof.CommitmentPok), - } - for _, c := range proof.Commitments { - extra.Commitments = append(extra.Commitments, g1(c)) - } - out["extra"] = extra + return fmt.Errorf("proof contains commitments, but snarkjs verifier does not support them") } enc := json.NewEncoder(w) diff --git a/backend/groth16/bn254/prove.go b/backend/groth16/bn254/prove.go index cea6a084c6..44fd1ae27a 100644 --- a/backend/groth16/bn254/prove.go +++ b/backend/groth16/bn254/prove.go @@ -424,17 +424,7 @@ func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { } if len(proof.Commitments) > 0 { - extra := struct { - Commitments [][]string `json:"commitments"` - CommitmentPok []string `json:"commitment_pok"` - }{ - Commitments: make([][]string, 0, len(proof.Commitments)), - CommitmentPok: g1(proof.CommitmentPok), - } - for _, c := range proof.Commitments { - extra.Commitments = append(extra.Commitments, g1(c)) - } - out["extra"] = extra + return fmt.Errorf("proof contains commitments, but snarkjs verifier does not support them") } enc := json.NewEncoder(w)