在gnark v0.8.1中实现gRPC:Proof等类型转Go原生类型及序列化问题
Groth16 组件序列化/反序列化问题(gnark v0.8.1)
问题背景
在gnark v0.8.1环境下,需要将groth16.Proof、验证密钥(Verification Key)、公开见证(Public Witness)转换为Go原生[]byte类型。自行实现Serialize()和DeSerialize()函数时遇到错误。
核心问题
groth16.Proof是接口类型,其具体实现依赖于所选的椭圆曲线类型,直接使用gob进行序列化/反序列化时,无法统一处理不同曲线的Proof实例,且依赖内部类型会导致兼容性问题。
现有实现代码
import ( "bytes" "encoding/gob" "errors" "fmt" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/witness" "github.com/consensys/gnark/std/accumulator/merkle/internal" "github.com/consensys/gnark/std/recursion/groth16" "github.com/consensys/gnark-crypto/ecc" ) func Serialize(x any) ([]byte, error) { var buf bytes.Buffer enc := gob.NewEncoder(&buf) if err := enc.Encode(x); err != nil { return nil, err } fmt.Printf("buf.Bytes(): %v\n", buf.Bytes()) return buf.Bytes(), nil } func DeSerialize(x []byte, returnType string) (interface{}, error) { fmt.Printf("x: %v\n", x) fmt.Printf("returnType: %v\n", returnType) buf := bytes.NewBuffer(x) dec := gob.NewDecoder(buf) switch returnType { case "proof": var proof groth16.Proof gob.Register(internal.Proof{}) if err := dec.Decode(&proof); err != nil { fmt.Printf("----> Decode err: %v\n", err) return groth16.NewProof(ecc.BN254), err } return proof, nil case "publicWitness": var w witness.Witness if err := dec.Decode(&w); err != nil { res, _ := frontend.NewWitness(nil, ecc.BN254.ScalarField()) return res, err } return w, nil case "verificationKey": var vk groth16.VerifyingKey if err := dec.Decode(&vk); err != nil { return vk, err } return vk, nil default: return nil, errors.New("wrong returnType") } }
错误信息
运行代码时触发如下错误:
Decode err: gob: local interface type *groth16.Proof can only be decoded from remote interface type; received concrete type
Proof 结构定义(内部实现)
Proof = struct { Ar G1Affine = struct { X[4] uint; Y Element; }; Krs G1Affine; Bs G2Affine = struct { X E2 = struct { A0 Element; A1 Element; }; Y E2; }; Commitment G1Affine; CommitmentPok G1Affine; }
解决方案
gnark的核心组件(groth16.Proof、VerifyingKey、Witness等)都内置实现了BinaryMarshaler和BinaryUnmarshaler接口,无需使用gob序列化,直接调用内置方法即可完成类型与[]byte的转换,同时自动适配曲线类型差异。
修改后的序列化/反序列化函数
import ( "errors" "fmt" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/witness" "github.com/consensys/gnark/std/recursion/groth16" "github.com/consensys/gnark-crypto/ecc" ) // Serialize 将gnark组件转换为[]byte,依赖内置的MarshalBinary方法 func Serialize(x any) ([]byte, error) { marshaler, ok := x.(interface{ MarshalBinary() ([]byte, error) }) if !ok { return nil, fmt.Errorf("type %T does not support binary serialization", x) } return marshaler.MarshalBinary() } // Deserialize 将[]byte转换为指定类型的gnark组件,需要传入曲线ID以创建对应实例 func Deserialize(data []byte, curve ecc.ID, returnType string) (interface{}, error) { switch returnType { case "proof": proof := groth16.NewProof(curve) if err := proof.UnmarshalBinary(data); err != nil { return nil, err } return proof, nil case "publicWitness": w, err := frontend.NewWitness(nil, curve.ScalarField()) if err != nil { return nil, err } if err := w.UnmarshalBinary(data); err != nil { return nil, err } return w, nil case "verificationKey": vk := groth16.NewVerifyingKey(curve) if err := vk.UnmarshalBinary(data); err != nil { return nil, err } return vk, nil default: return nil, errors.New("invalid returnType: must be proof/publicWitness/verificationKey") } }
方案说明
- 依赖内置方法:gnark为所有核心密码学组件实现了标准的二进制序列化接口,无需手动处理曲线相关的类型差异,避免依赖内部类型(如
internal.Proof)导致的兼容性问题。 - 曲线ID参数:反序列化时需要传入曲线ID,确保创建的实例与序列化时的曲线类型一致,避免跨曲线的类型错误。
- 类型安全:通过类型断言确保输入类型支持序列化,比gob更贴合gnark的设计逻辑,减少序列化失败的概率。
内容的提问来源于stack exchange,提问作者Anupam Shaw
相关产品推荐
相关产品推荐

