Circom2Gnark is a Go module that allows you to parse and verify Circom proofs using the Gnark library. You can:
- Verify Circom proofs using the Gnark verifier.
- Recursively verify Circom proofs within a Gnark circuit.
This enables interoperability between Circom-generated proofs and Go applications using Gnark for zkSNARK proofs, using Groth16 and the bn254 curve.
This work is based on the vocdoni/go-snark Circom compatible implementation created by @arnaucube
To use Circom2Gnark in your Go project, you need to import it and ensure it's included in your go.mod
file:
go get github.com/vocdoni/circom2gnark
In your Go code, import the parser package:
import "github.com/vocdoni/circom2gnark/parser"
- Parsing Circom Data: Parse proofs, verification keys, and public signals generated by Circom/SnarkJS.
- Verification with Gnark: Verify Circom proofs using Gnark's verifier outside of a circuit.
- Recursive Verification: Verify Circom proofs recursively within a Gnark circuit, enabling proof composition and aggregation.
Circom2Gnark provides functions to unmarshal Circom proofs, verification keys, and public signals from JSON files generated by Circom/SnarkJS.
// Read the proof, verification key, and public signals from files
proofData, err := os.ReadFile("proof.json")
if err != nil {
log.Fatalf("failed to read proof: %v", err)
}
vkData, err := os.ReadFile("vkey.json")
if err != nil {
log.Fatalf("failed to read verification key: %v", err)
}
publicSignalsData, err := os.ReadFile("public_signals.json")
if err != nil {
log.Fatalf("failed to read public signals: %v", err)
}
// Unmarshal the JSON data
snarkProof, err := parser.UnmarshalCircomProofJSON(proofData)
if err != nil {
log.Fatalf("failed to unmarshal proof: %v", err)
}
snarkVk, err := parser.UnmarshalCircomVerificationKeyJSON(vkData)
if err != nil {
log.Fatalf("failed to unmarshal verification key: %v", err)
}
publicSignals, err := parser.UnmarshalCircomPublicSignalsJSON(publicSignalsData)
if err != nil {
log.Fatalf("failed to unmarshal public signals: %v", err)
}
After parsing, you can convert the Circom proof and verification key into Gnark-compatible formats and verify the proof using Gnark's verifier.
// Convert Circom proof to Gnark proof
gnarkProof, err := parser.ConvertCircomToGnark(snarkProof, snarkVk, publicSignals)
if err != nil {
log.Fatalf("failed to convert Circom proof to Gnark proof: %v", err)
}
// Verify the proof using Gnark verifier
verified, err := parser.VerifyProof(gnarkProof)
if err != nil {
log.Fatalf("failed to verify proof: %v", err)
}
if !verified {
log.Fatalf("proof verification failed")
}
fmt.Println("Proof verification succeeded!")
Circom2Gnark allows you to verify Circom proofs recursively within a Gnark circuit. This is useful for building higher-level circuits that include verification of other proofs.
// Convert Circom proof to Gnark recursion data
recursionData, recursionPlaceholders, err := parser.ConvertCircomToGnarkRecursion(snarkProof, snarkVk, publicSignals)
if err != nil {
log.Fatalf("failed to convert Circom proof to Gnark recursion proof: %v", err)
}
// Create a placeholder circuit
placeholderCircuit := &VerifyCircomProofCircuit{
Proof: recursionPlaceholders.Proof,
VerifyingKey: recursionPlaceholders.Vk,
PublicInputs: recursionPlaceholders.Witness,
}
// Compile the circuit
ccs, pk, vk, err := CompileCircuit(placeholderCircuit)
if err != nil {
log.Fatalf("failed to compile circuit: %v", err)
}
// Create the circuit assignment with actual values
circuitAssignment := &VerifyCircomProofCircuit{
Proof: recursionData.Proof,
VerifyingKey: recursionData.Vk,
PublicInputs: recursionData.PublicInputs,
}
// Create the witness
witnessFull, err := frontend.NewWitness(circuitAssignment, ecc.BN254.ScalarField())
if err != nil {
log.Fatalf("failed to create witness: %v", err)
}
// Generate the proof
proof, err := groth16.Prove(ccs, pk, witnessFull)
if err != nil {
log.Fatalf("proving failed: %v", err)
}
// Verify the recursive proof
err = groth16.Verify(proof, vk, witnessFull.Public())
if err != nil {
log.Fatalf("verification failed: %v", err)
}
fmt.Println("Recursive proof verification succeeded!")
There is a complete example at the example
directory, demosttrating how to use circom2gnark to verify a Circom proof and recursively verify it within a Gnark circuit.
The Circom JSON files can be found at example/circom_data
.
$ cd example
$ go run .
Verifying proof with Gnark verifier...
13:06:06 DBG verifier done backend=groth16 curve=bn254 took=0.876262
Proof verification succeeded!
Now let's build a new circuit to verify the Circom proof recursively
Found existing pk.bin, vk.bin, and circuit.r1cs. Loading them...
Loading circuit...
Loading proving key...
Loading verifying key...
Loading artifacts total time: 1m39.934126612s
Creating witness...
Witness creation time: 783.317µs
Proving...
13:07:49 DBG constraint system solver done nbConstraints=4772531 took=2695.463075
13:08:01 DBG prover done acceleration=none backend=groth16 curve=bn254 nbConstraints=4772531 took=12045.051975
Proving Recursion time: 14.740656608s
Verifying...
13:08:01 DBG verifier done backend=groth16 curve=bn254 took=1.492666
Recursive proof verification succeeded! took 1.528964ms