Generate and deploy your verifier contract

Prepare your Groth16 verifying key

Pairing operations over BLS12-381 are cheaper than BN254 on Garaga, so if possible, prefer using BLS12-381. The security of this curve is also much closer to 128 bits than BN254.

To deploy a groth16 verifier, you need the associated verifying key in .json format. Both BN254 and BLS12-381 are supported.

Snarkjs and Gnark jsons output are supported out of the box.

You can find a bunch of example of verifying keys that will work directly in the hydra/garaga/starknet/groth16_contract_generator/examples folder.

While Snarkjs jsons export are easy and working well, the gnark documentation is a bit outdated. Below we show a quick example on how to export the verifying key from your Gnark circuit .

Gnark export to json example
package main

import (
	"encoding/json"
	"os"

	"github.com/consensys/gnark-crypto/ecc"
	"github.com/consensys/gnark/backend/groth16"
	"github.com/consensys/gnark/frontend"
	"github.com/consensys/gnark/frontend/cs/r1cs"
)

// CubicCircuit defines a simple circuit
// x**3 + x + 5 == y
type CubicCircuit struct {
	// struct tags on a variable is optional
	// default uses variable name and secret visibility.
	X frontend.Variable `gnark:"x"`
	Y frontend.Variable `gnark:",public"`
	B frontend.Variable
	A frontend.Variable `gnark:",public"`
}

// Define declares the circuit constraints
// x**3 + x + 5 == y
func (circuit *CubicCircuit) Define(api frontend.API) error {
	x3 := api.Mul(circuit.X, circuit.X, circuit.X)
	api.AssertIsEqual(circuit.Y, api.Add(x3, circuit.X, 5))
	api.AssertIsEqual(circuit.B, circuit.A)
	return nil
}

func main() {
	// compiles our circuit into a R1CS
	var circuit CubicCircuit
	ccs, _ := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)

	// groth16 zkSNARK: Setup
	pk, vk, _ := groth16.Setup(ccs)

	// witness definition
	assignment := CubicCircuit{X: 3, Y: 35, A: 3, B: 3}
	witness, _ := frontend.NewWitness(&assignment, ecc.BN254.ScalarField())
	publicWitness, _ := witness.Public()

	// groth16: Prove & Verify
	proof, _ := groth16.Prove(ccs, pk, witness)

	groth16.Verify(proof, vk, publicWitness)
	vk.ExportSolidity(os.Stdout)
	// Export to JSON:
	schema, _ := frontend.NewSchema(&circuit)
	pubWitnessJSON, _ := publicWitness.ToJSON(schema)

	SaveToJSON("gnark_vk_bn254.json", vk)
	SaveToJSON("gnark_proof_bn254.json", proof)
	os.WriteFile("gnark_public_bn254.json", pubWitnessJSON, 0644)

}

func SaveToJSON(filePath string, v interface{}) error {
	jsonData, err := json.MarshalIndent(v, "", "  ")

	if err != nil {
		return err
	}

	err = os.WriteFile(filePath, jsonData, 0644)
	if err != nil {
		return err
	}
	return nil
}

Generate the Smart contract code

Using the Developer setup & guides or the Python package, you should now have access to the Garaga CLI from your terminal, using the command garaga

garaga

Once your verifying key is ready, you can use the following command :

garaga gen --system groth16 --vk vk.json

You should see an output like this :

The curve identifier is automatically detected from the content of your verifying key.

Build your contract

The generated verifier contract only verifies a proof and returns the public inputs on success. You can extend it to add your dapp logic, or use it as a library call from another contract.

The generated template is as follows. The main endpoint is verify_groth16_proof_[curve_name].

The function takes a single full_proof_with_hints: Span<felt252> parameter (generated using the garaga calldata command or the SDK) and returns:

  • Result::Ok(public_inputs) if the proof is valid

  • Result::Err(error) if the proof is invalid

Declare and deploy your contract

When you are satisfied with your contract it's time to send it to Starknet!

The CLI provides utilities to simplify the process. Otherwise, it is a similar process than for every other contracts, please refer to Starknet documentation. You will to create a file containing the following variables, depending on if you want to declare & deploy on Starknet Sepolia or Starknet Mainnet. Create the following file and update its content.

Then, you can run the command garaga declare, which will build the contract and declare it to Starknet. If the class hash is already deployed, it will return it as well. Declaring the contract involves sending all its bytecode and it is quite an expensive operation. Make sure you dapp is properly tested before!

This command will return the class hash, used in the next step. Finally, to deploy the contract, a use garaga deploy :

The contract address will be prompted. Be sure to save it! \

Last updated

Was this helpful?