Generate and deploy your verifier contract
Prepare your Groth16 verifying key
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 .
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 :
(venv) :~/garaga-flow garaga gen --system groth16 --vk vk_bls.json
Please enter the name of your project. Press enter for default name. [my_project]:
Detected curve: CurveID.BLS12_381
⠧ Generating Smart Contract project for ProofSystem.Groth16 using vk_bls.json...
Done!
Smart Contract project created:
/home/garaga-flow/my_project/
├── Scarb.lock
├── Scarb.toml
├── src/
│ ├── groth16_verifier.cairo
│ ├── groth16_verifier_constants.cairo
│ └── lib.cairo
└── target/
└── dev/
├── groth16_example_bls12_381.starknet_artifacts.json
├── groth16_example_bls12_381_Groth16VerifierBLS12_381.compiled_contract
│ _class.json
└── groth16_example_bls12_381_Groth16VerifierBLS12_381.contract_class.js
on
You can now modify the groth16_verifier.cairo file to adapt the verifier to your
use case.
The curve identifier is automatically detected from the content of your verifying key.
Build your contract
The generated template is as follow. The main endpoint to call is verify_groth16_proof_[curve_name]
If the verification succeeds, it will call the internal function process_public_inputs.
This function is the starting point of your dapp logic.
use garaga::definitions::E12DMulQuotient;
use garaga::groth16::{Groth16Proof, MPCheckHintBLS12_381};
use super::groth16_verifier_constants::{N_PUBLIC_INPUTS, vk, ic, precomputed_lines};
#[starknet::interface]
trait IGroth16VerifierBLS12_381<TContractState> {
fn verify_groth16_proof_bls12_381(
ref self: TContractState,
groth16_proof: Groth16Proof,
mpcheck_hint: MPCheckHintBLS12_381,
small_Q: E12DMulQuotient,
msm_hint: Array<felt252>,
) -> bool;
}
#[starknet::contract]
mod Groth16VerifierBLS12_381 {
use starknet::SyscallResultTrait;
use garaga::definitions::{G1Point, G1G2Pair, E12DMulQuotient};
use garaga::groth16::{
multi_pairing_check_bls12_381_3P_2F_with_extra_miller_loop_result, Groth16Proof,
MPCheckHintBLS12_381
};
use garaga::ec_ops::{G1PointTrait, G2PointTrait, ec_safe_add};
use super::{N_PUBLIC_INPUTS, vk, ic, precomputed_lines};
const ECIP_OPS_CLASS_HASH: felt252 =
0x29aefd3c293b3d97a9caf77fac5f3c23a6ab8c7e70190ce8d7a12ac71ceac4c;
use starknet::ContractAddress;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl IGroth16VerifierBLS12_381 of super::IGroth16VerifierBLS12_381<ContractState> {
fn verify_groth16_proof_bls12_381(
ref self: ContractState,
groth16_proof: Groth16Proof,
mpcheck_hint: MPCheckHintBLS12_381,
small_Q: E12DMulQuotient,
msm_hint: Array<felt252>,
) -> bool {
// DO NOT EDIT THIS FUNCTION UNLESS YOU KNOW WHAT YOU ARE DOING.
// ONLY EDIT THE process_public_inputs FUNCTION BELOW.
groth16_proof.a.assert_on_curve(1);
groth16_proof.b.assert_on_curve(1);
groth16_proof.c.assert_on_curve(1);
let ic = ic.span();
let vk_x: G1Point = match ic.len() {
0 => panic!("Malformed VK"),
1 => *ic.at(0),
_ => {
// Start serialization with the hint array directly to avoid copying it.
let mut msm_calldata: Array<felt252> = msm_hint;
// Add the points from VK and public inputs to the proof.
Serde::serialize(@ic.slice(1, N_PUBLIC_INPUTS), ref msm_calldata);
Serde::serialize(@groth16_proof.public_inputs, ref msm_calldata);
// Complete with the curve indentifier (1 for BLS12_381):
msm_calldata.append(1);
// Call the multi scalar multiplication endpoint on the Garaga ECIP ops contract
// to obtain vk_x.
let mut _vx_x_serialized = core::starknet::syscalls::library_call_syscall(
ECIP_OPS_CLASS_HASH.try_into().unwrap(),
selector!("msm_g1"),
msm_calldata.span()
)
.unwrap_syscall();
ec_safe_add(
Serde::<G1Point>::deserialize(ref _vx_x_serialized).unwrap(), *ic.at(0), 1
)
}
};
// Perform the pairing check.
let check = multi_pairing_check_bls12_381_3P_2F_with_extra_miller_loop_result(
G1G2Pair { p: vk_x, q: vk.gamma_g2 },
G1G2Pair { p: groth16_proof.c, q: vk.delta_g2 },
G1G2Pair { p: groth16_proof.a.negate(1), q: groth16_proof.b },
vk.alpha_beta_miller_loop_result,
precomputed_lines.span(),
mpcheck_hint,
small_Q
);
if check == true {
self
.process_public_inputs(
starknet::get_caller_address(), groth16_proof.public_inputs
);
return true;
} else {
return false;
}
}
}
#[generate_trait]
impl InternalFunctions of InternalFunctionsTrait {
fn process_public_inputs(
ref self: ContractState, user: ContractAddress, public_inputs: Span<u256>,
) { // Process the public inputs with respect to the caller address (user).
// Update the storage, emit events, call other contracts, etc.
}
}
}
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.
SEPOLIA_RPC_URL="https://free-rpc.nethermind.io/sepolia-juno"
SEPOLIA_ACCOUNT_PRIVATE_KEY=0x1
SEPOLIA_ACCOUNT_ADDRESS=0x2
MAINNET_RPC_URL="https://"
MAINNET_ACCOUNT_PRIVATE_KEY=0x3
MAINNET_ACCOUNT_ADDRESS=0x4
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!
Usage: garaga declare [OPTIONS]
Declare your smart contract to Starknet. Obtain its class hash and a explorer link.
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --project-path DIRECTORY Path to the Cairo project holding the Scarb.toml file to declare │
│ [default: /home/felt/garaga-flow/my_project/] │
│ --env-file FILE Path to the environment file │
│ [default: /home/felt/garaga-flow/my_project/.secrets] │
│ --network [sepolia|mainnet] Starknet network [default: sepolia] │
│ --fee TEXT Fee token type [eth, strk] [default: eth] │
│ --help -h Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
garaga declare --project-path my_project/ --env-file .secrets --network sepolia --fee strk
This command will return the class hash, used in the next step.
Finally, to deploy the contract, a use garaga deploy
:
Usage: garaga deploy [OPTIONS]
Deploy an instance of a smart contract class hash to Starknet. Obtain its address, the available endpoints and a explorer link.
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ * --class-hash TEXT Contract class hash to deploy. Can be decimal or hex string [default: None] [required] │
│ --env-file FILE Path to the environment file containing rpc, address, private_key │
│ [default: /home/felt/garaga-flow/my_project/.secrets] │
│ --network [sepolia|mainnet] Starknet network [default: sepolia] │
│ --fee TEXT Fee token type [eth, strk] [default: strk] │
│ --help -h Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
The contract address will be prompted. Be sure to save it! \
Last updated
Was this helpful?