Quickstart
This guide will help you set up a basic example that folds a circuit that describes the identify function . The entire code can be found at https://github.com/snarkify/sirius-quickstart.
Step 1: Create a New Rust Project
Install rust with rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shCreate a new project with cargo
cargo new my-project --bin;
cd my-project;Add 'sirius' to dependencies
cargo add --git https://github.com/snarkify/sirius/ --tag v0.1.1;Step 2: Implement `MyStepCircuit`
Full code can be found at https://github.com/snarkify/sirius-quickstart/blob/main/src/main.rs
Let's break down each section of this code
Import Necessary Modules
First, import the necessary modules and types from the Sirius library:
use std::path::Path;
use sirius::{
ff::Field,
ivc::{
step_circuit::{trivial, AssignedCell, ConstraintSystem, Layouter},
SynthesisError,
},
prelude::{
bn256::{new_default_pp, C1Affine, C1Scalar, C2Affine, C2Scalar},
CommitmentKey, PrimeField, StepCircuit, IVC,
},
};This block imports modules for handling fields, circuits, and the incremental verification process. It also sets up the types for the BN256 and Grumpkin elliptic curves.
Define Constants and Circuit Structure
Define constants and the structure for your custom circuit:
Explanation:
Folding Steps (
FOLD_STEP_COUNT): Specifies the number of steps the IVC process will execute. Each step represents a round of computation in the circuit.Arity (
A1andA2): Defines the input/output size per step for the primary and secondary circuits. Set to1in this example, meaning one input and one output per step.Initial Inputs (
PRIMARY_Z_0andSECONDARY_Z_0): These are the starting values passed to the circuits at the zero step. Here, they are initialized to zero (C1Scalar::ZEROandC2Scalar::ZERO), but in a real circuit, you should replace these with the actual values required by your circuit.Commitment Key Size (
PRIMARY_COMMITMENT_KEY_SIZEandSECONDARY_COMMITMENT_KEY_SIZE): Defines the size of the commitment keys for the circuits. Start with the provided values, but increase if your circuit requires more.Table Size (
PRIMARY_CIRCUIT_TABLE_SIZEandSECONDARY_CIRCUIT_TABLE_SIZE): Specifies the lookup table sizes. A minimum of 17 is needed, but increase the size if your circuit demands more complex operations.
Implement the StepCircuit Trait
Next, define a basic implementation of the StepCircuit trait:
In this section, the StepCircuit trait is implemented for the MyStepCircuit struct:
struct MyConfigActs as a template for configuring your circuit. Although it's empty in this example, it usually stores important constrain system configuration details.struct MyStepCircuitRepresents the actual step-circuit logic. In this example, it simply returns the input unchanged, making it a trivial circuit. This is useful as a starting point or for testing purposes, but real circuits would involve more complex operations.fn configureThis method is where the circuit's configuration is set up. It initializes any necessary columns & gates & lookups in the constraint system. The method currently returns an empty configuration, but in a more advanced circuit, it would be populated with the relevant data.fn synthesize_stepThis is the core of the circuit, where the computation for each step happens. It takes the configuration and an array of input variables (z_i), and returns the next step's output. In this trivial example, the output is simply the input, but this is where you would implement the actual logic for your step-circuit's step function.
This example serves as a basic template. When developing your own circuits, you would replace the trivial operations with the actual logic needed for your application.
Check `StepCircuit` trait for more details
Set Up Commitment Keys
Next, we need to set up the commitment keys for our circuits. This involves generating or loading keys for the elliptic curves used in the primary and secondary circuits.
Explanation:
Storing Generated Keys: The commitment keys are generated and stored in the
.cachefolder. This is done to avoid the need to regenerate the keys every time you run the program, which saves time and computational resources.First Key Generation: The first time the keys are generated, it can take a significant amount of time. This is because key generation for cryptographic curves involves complex computations. Once generated, these keys are cached for future use.
Elliptic Curves: In this example, keys are generated for two different elliptic curves:
bn256for the primary circuit andgrumpkinfor the secondary circuit.Use of
unsafe: Theunsafeblock is necessary because the functions that load or generate the keys operate directly on memory-mapped files. This approach is used for performance reasons - mapping keys directly from memory is faster than serializing and deserializing them. However, it also requires careful handling to ensure the integrity of the cached files.
This setup is crucial for ensuring that your circuits can efficiently use these cryptographic keys without incurring unnecessary delays in key generation each time the program is executed.
Initialize PublicParams
Explanation:
new_default_pp: This function initializes thePublicParamswith mostly default settings. It simplifies the setup process by automatically configuring the majority of parameters. However, if your circuit requires specific customizations or more advanced settings, you should create thePublicParamsmanually, specifying types and values directly.PublicParams: This structure not only store essential configuration data but also lock in the structure of the PLONKish constraint system for the entire IVC process. When you createpp, theconfigureandsynthesizemethods of your circuits are called internally to collect and organize the constraints, ensuring consistency across all folding steps in the IVC.
Check `PublicParams` Struct for more details
Perform Folding Steps and Verification
Finally, perform the folding steps and verify the computation:
Explanation:
IVC::new: This function initializes the Incrementally Verifiable Computation (IVC) instance and automatically performs the zero step using the provided initial inputs (PRIMARY_Z_0andSECONDARY_Z_0). This zero step sets up the initial state for the folding process, ensuring that the IVC begins with the correct starting conditions.IVC::fold_step: During each call to fold_step, the circuit's configure and synthesize methods are invoked few times. These methods define and execute the circuit's constraints and logic for that step. In a real-world circuit, you should update the circuit's witness (private inputs) between steps to reflect the changing state of the computation. This ensures that each folding step processes the correct data as the IVC progresses.'DebugMode': The debug_mode argument (set to true in this example) enables additional checks at each folding step. Specifically, it invokes the
MockProverfrom the Halo2 library to verify the correctness of the computation at each step. While this mode is invaluable for debugging and ensuring correctness during development, it introduces a performance overhead. If you disabledebug_modefor performance reasons (by setting it to false), errors will only be detected at the end of the entire computation during the call toIVC::verify.IVC::verify: The verify method is called at the end of the IVC process to check the validity of the entire computation. However, it only reports whether the computation was successful or if an error occurred at some point during the process. Ifdebug_modeis disabled, this method lacks the detailed information to pinpoint where the error happened—only whether the final result is valid or not.
Check `IVC` struct for more details
Step 3: Run
Running the Example
1. First Run
To run the example for the first time, use the following command:
This will compile the project in release mode, which is optimized for speed. During this initial run, the Set Up Commitment Keys for the BN256 and Grumpkin curves will be generated and cached. This process may take some time, so running in release mode ensures it completes as quickly as possible.
2. Subsequent Runs
For subsequent runs, you can use the following command without the --release flag:
This will reuse the previously generated commitment keys, so the process will be faster, and there’s no need to recompile in release mode unless you're making significant changes or need the performance optimization again.
3. Expected Output
When the example runs successfully, you should see output indicating that the folding steps were executed and verified successfully:
Summary
In this Quickstart, we explored setting up a basic example with Sirius IVC, including initializing with PublicParams, performing folding steps, and understanding the role of debug mode and verification. We implemented a trivial step circuit that returns its input unchanged, laying the groundwork for more complex circuits.
For a deeper dive into implementing a real step circuit with dynamic witness management, check out the Fold a Summation Circuit section.
Last updated