From 89bb984a78b36ba34eb30ae788114a2ba2a59561 Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Wed, 20 Aug 2025 14:22:01 +0300 Subject: [PATCH 1/7] 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 | 82 +++++++++++++++++++++++++++++ backend/groth16/bn254/prove.go | 46 ++++++++++++++++ backend/groth16/bn254/verify.go | 82 +++++++++++++++++++++++++++++ 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 +++ 11 files changed, 305 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 27071afbde..12d9871c32 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 0a6cab40cd..e7e5f3cb7c 100644 --- a/backend/groth16/bls12-377/verify.go +++ b/backend/groth16/bls12-377/verify.go @@ -141,3 +141,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 c9e267ede3..f2b404c49f 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 28d3d90f75..42395cf415 100644 --- a/backend/groth16/bls12-381/verify.go +++ b/backend/groth16/bls12-381/verify.go @@ -8,6 +8,7 @@ package groth16 import ( "bytes" "crypto/sha256" + "encoding/json" "errors" "fmt" "io" @@ -249,3 +250,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) -> 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/bn254/prove.go b/backend/groth16/bn254/prove.go index 57f52af95f..b46e943092 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 804d295a71..8d0d720d69 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" @@ -240,3 +241,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-761/prove.go b/backend/groth16/bw6-761/prove.go index 396928f44f..c5c2929bdd 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 e2d3d2ae2e..b1fa23fc1c 100644 --- a/backend/groth16/bw6-761/verify.go +++ b/backend/groth16/bw6-761/verify.go @@ -141,3 +141,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 b3ca3584dc..25beb36abd 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" @@ -46,6 +47,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 @@ -98,6 +102,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 91b16f677ea1eaf2f1102fe9986417a1db264e1b Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Fri, 5 Sep 2025 13:04:59 +0300 Subject: [PATCH 2/7] 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 42395cf415..df711ec2c9 100644 --- a/backend/groth16/bls12-381/verify.go +++ b/backend/groth16/bls12-381/verify.go @@ -278,7 +278,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 b11a3138de52c56aca3994c2c28be397f3258c83 Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Fri, 5 Sep 2025 13:15:03 +0300 Subject: [PATCH 3/7] 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 f2b404c49f..e926d1a36d 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 b46e943092..472c8c6ebc 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 fd6c087846698bae6824d69617d6b68520553a4b Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Wed, 10 Sep 2025 10:29:40 +0300 Subject: [PATCH 4/7] 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 e926d1a36d..c01d321a80 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 472c8c6ebc..b8706c8020 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 203e1b07f7f3100ad470d044086c7a4ec4fa46eb Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Wed, 10 Sep 2025 10:35:38 +0300 Subject: [PATCH 5/7] 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 b8706c8020..6d40d917ae 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 f5cb1e9633ec796a823939959d0d6d1b3f287b93 Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Thu, 11 Sep 2025 13:59:33 +0300 Subject: [PATCH 6/7] 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 c01d321a80..8eaa2cbc9d 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 6d40d917ae..8b72af73fa 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) From 423fa5ab732397bc0351bbc528900f652a19f521 Mon Sep 17 00:00:00 2001 From: mysteryon88 Date: Mon, 27 Apr 2026 15:31:33 +0300 Subject: [PATCH 7/7] fix: rename proof export stub receivers --- backend/groth16/bls12-377/prove.go | 2 +- backend/groth16/bw6-761/prove.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/groth16/bls12-377/prove.go b/backend/groth16/bls12-377/prove.go index 12d9871c32..e05775c391 100644 --- a/backend/groth16/bls12-377/prove.go +++ b/backend/groth16/bls12-377/prove.go @@ -391,6 +391,6 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { } // ExportProof not implemented for BLS12-377 -func (vk *Proof) ExportProof(publicSignals []string, w io.Writer) error { +func (proof *Proof) ExportProof(publicSignals []string, 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 c5c2929bdd..b2a8ee358f 100644 --- a/backend/groth16/bw6-761/prove.go +++ b/backend/groth16/bw6-761/prove.go @@ -391,6 +391,6 @@ func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { } // ExportProof not implemented for BW6-761 -func (vk *Proof) ExportProof(publicSignals []string, w io.Writer) error { +func (proof *Proof) ExportProof(publicSignals []string, w io.Writer) error { return errors.New("not implemented") }