If working with the python - auto generated cairo code, the main script to work with is make rewrite
tools/make/rewrite.sh
rm-rfsrc/src/tests/*rm-rfsrc/src/circuits/*rm-rfsrc/contracts/groth16_example_bls12_381/*rm-rfsrc/contracts/groth16_example_bn254/*rm-rfsrc/contracts/risc0_verifier_bn254/*set-e# Exit immediately if a command exits with a non-zero statuspythonhydra/garaga/precompiled_circuits/all_circuits.py|| { echo"Error in all_circuits.py"; exit1; }python hydra/garaga/starknet/tests_and_calldata_generators/test_writer.py || { echo "Error in test_writer.py"; exit 1; }
pythonhydra/garaga/starknet/groth16_contract_generator/generator.py|| { echo"Error in generator.py"; exit1; }python hydra/garaga/starknet/groth16_contract_generator/generator_risc0.py || { echo "Error in generator_risc0.py"; exit 1; }
As you can see this will :
Rewrite all the auto-generated cairo code in src/src/circuits/ (with the all_circuits.py script)
Rewrite all the auto-generated cairo tests in src/src/tests/ (with the test_writer.py script)
Rewrite the Groth16 verifiers smart contract templates examples in src/contracts/ (with the generator.py and generator_risc0.py scripts)
Creating auto-generated circuits.
While it's relatively easy to write Cairo circuits yourself if their size is small, it starts to be quite time consuming if you need to build a large amount of them, parametrize them, and the circuits themselves are quite large.
If you want to write a new auto-generated circuit to src/src/circuits, you can define them with python code, and register them to the all_circuits.py file.
Below we show a basic tutorial for a non-parametrized circuit.
Simple isolated example
import garaga.modulo_circuit_structs as structsfrom garaga.definitions import CurveID, get_base_fieldfrom garaga.modulo_circuit import PyFelt, ModuloCircuitcurve_id = CurveID.BN254 # BN254field =get_base_field(curve_id)# Use with field(int) or field.random(). COMPILATION_MODE =1# 1 for Cairo, 0 for CairoZero. circuit =ModuloCircuit( name=f"dummy_{curve_id.name.lower()}, curve_id.value, generic_circuit=False, compilation_mode=COMPILATION_MODE,)# All "structs" expect a name (replicated in the signature) and a list of elements.x, y = circuit.write_struct(structs.u384Span("xy", [field.random(), field.random()]))# All "structs" written with write_struct will be expected in the Cairo# function signature of the given type.z = circuit.write_struct(structs.u384("z", [field(42)]))# With this configuration,# the signature will be fn(xy: Span<u384>, z:u384).# More structs and arbitrary ones are supported as well.# To write constants, use set_or_get_constant.one = circuit.set_or_get_constant(1)# Here happens the core circuit logic.# The core operations are add, sub, mul, inv.# However, more high level methods are available that translate to# a sequence of the core operations (ex: div <=> inv then mul)a = circuit.add(x, y)b = circuit.sub(a, one)c = circuit.mul(b, z)d = circuit.inv(c)f = circuit.div(d, z)# Define the output of the function using structs as well.circuit.extend_struct_output(structs.u384Span("abc", [a, b, c]))circuit.extend_struct_output(structs.u384("f", [f]))# Compile and print the compiled cairo circuit : header =compilation_mode_to_file_header(1) # Cairo 1compiled_code, function_name = circuit.compile_circuit()# Note: some imports are unused in the header, # we should make the compiler aware of the structs used ;)print(header) print(compiled_code)
To obtain the corresponding Cairo code, you can do it like this
Note that the compiler will adapt the signature of the Cairo function depending on the generic_circuit parameter, and retrieve the corresponding modulus inside the function code.
if self.generic_circuit: code =f"#[inline(always)]\nfn {function_name}({signature_input}, curve_index:usize)->{signature_output}{{\n"else: code =f"#[inline(always)]\nfn {function_name}({signature_input})->{signature_output}{{\n"
The base class
Currently, the way we deal with parameterization is encapsulating the earlier ModuloCircuit class into a BaseModuloCircuit class, and adding keyword arguments to the class deriving it.
classBaseModuloCircuit(ABC):""" Base class for all modulo circuits that will be compiled to Cairo code. Parameters: - name: str, the name of the circuit - curve_id: int, the id of the curve - auto_run: bool, whether to run the circuit automatically at initialization. When compiling, this flag must be set to true so the ModuloCircuit class inside the ".circuit" member of this class holds the necessary metadata about the operations that will be compiled. For CairoZero, this flag will be set to False in the Python hint, so that BaseModuloCircuit.run_circuit() can be called on a segment parsed from the CairoZero VM. - compilation mode: 0 (CairoZero) or 1 (Cairo) """def__init__(self,name:str,curve_id:int,auto_run:bool=True,compilation_mode:int=0,
All Cairo function must inherit from the base abstract class
The abstract methods to implement are :
build_input
defbuild_input(self) -> list[PyFelt]:"""This method is used to create the necessary inputs that will be written to the ModuloCircuit.It works in pair with the _run_circuit_inner function, where the _run_circuit_inner will use the output ofthe build_input function to "deserialize" the list of elements and "write" them to the ModuloCircuit class."""
_run_circuit_inner
def_run_circuit_inner(self,input: list[PyFelt]) -> ModuloCircuit:"""This method is responsible for- deserializing the input list of elements,- creating a ModuloCircuit class (or class that derives from ModuloCircuit)- "writing" the inputs to the ModuloCircuit class to obtain ModuloCircuitElements- using the methods add, sub, mul, inv (or higher level methods) of the ModuloCircuit class to define the list of operations on the given inputs.- Returning the ModuloCircuit class in a state where the circuit has been run, and therefore holding the metadata so that its instructions can be compiled to Cairo code."""
At initialization, you must choose a name for the circuit.
Pay attention to the parameter generic_circuit passed to the ModuloCircuit class inside _run_circuit_inner