Compiler APIs
For circuit developers, most of the necessary APIs are provided in the expander_compiler::frontend module. If you need to perform more advanced development on top of the compiler, you may need to use other components. This document primarily discusses the contents of the frontend module.
Introduction of other modules can be found in Rust Internal Modules.
The following items are defined:
pub use circuit::declare_circuit;
pub type API<C> = builder::RootBuilder<C>;
pub use crate::circuit::config::*;
pub use crate::field::{Field, BN254, GF2, M31};
pub use crate::utils::error::Error;
pub use api::BasicAPI;
pub use builder::Variable;
pub use circuit::Define;
pub use witness::WitnessSolver;
pub fn compile<C: Config, Cir: internal::DumpLoadTwoVariables<Variable> + Define<C> + Clone>(
circuit: &Cir,
) -> Result<CompileResult<C>, Error> {
// implementation
}
pub struct CompileResult<C: Config> {
pub witness_solver: WitnessSolver<C>,
pub layered_circuit: layered::Circuit<C>,
}
pub mod internal {
pub use super::circuit::{
declare_circuit_default, declare_circuit_dump_into, declare_circuit_field_type,
declare_circuit_load_from, declare_circuit_num_vars,
};
pub use super::variables::{DumpLoadTwoVariables, DumpLoadVariables};
pub use crate::utils::serde::Serde;
}
pub mod extra {
pub use super::api::UnconstrainedAPI;
pub use crate::utils::serde::Serde;
}
Declaring a Circuit
The declare_circuit macro helps define the structure of a circuit. For example:
declare_circuit!(Circuit {
x: Variable,
y: Variable,
});
You can use more complex structures, such as [[Variable; 256]; N_HASHES]. The defined struct will look like this:
pub struct Circuit<T> {
pub x: T,
pub y: T,
}
Long Arrays
If you need a very long array, you might need to use Vec instead of a fixed-size array in the structure. Due to Rust's limitations, the syntax for this definition is slightly different from that of Vec:
declare_circuit!(Circuit {
x: [[[Variable]]],
y: [[Variable]],
z: [[[Variable]; 10]]
});
The actual structure definition will look like this:
pub struct Circuit<T> {
pub x: Vec<Vec<Vec<T>>>,
pub y: Vec<Vec<T>>,
pub z: Vec<[Vec<T>; 10]>,
}
API Overview
The API is similar to gnark's frontend API. C represents the configuration for the specified field.
Currently, the Config and Field types are one-to-one:
- Fields:
BN254,GF2,M31 - Configs:
BN254Config,GF2Config,M31Config
Many functions and structs use Config as a template parameter.
Error Handling
The Error type is returned by many functions and includes UserError and InternalError. UserError typically indicates an issue with your circuit definition, while InternalError suggests a problem within the compiler itself. Please contact us if you encounter an InternalError.
Basic API
The BasicAPI trait provides a set of operations similar to those in gnark. The semantics of xor, or, and and are consistent with gnark.
pub trait BasicAPI<C: Config> {
fn add(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn sub(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn mul(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn div(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>, checked: bool) -> Variable;
fn neg(&mut self, x: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn inverse(&mut self, x: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn is_zero(&mut self, x: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn xor(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn or(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn and(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>) -> Variable;
fn assert_is_zero(&mut self, x: impl ToVariableOrValue<C::CircuitField>);
fn assert_is_non_zero(&mut self, x: impl ToVariableOrValue<C::CircuitField>);
fn assert_is_bool(&mut self, x: impl ToVariableOrValue<C::CircuitField>);
fn assert_is_equal(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>);
fn assert_is_different(&mut self, x: impl ToVariableOrValue<C::CircuitField>, y: impl ToVariableOrValue<C::CircuitField>);
fn get_random_value(&mut self) -> Variable;
}
Variables
The Variable type is similar to gnark's frontend Variable.
Define Trait
The Define trait, similar to gnark's define, needs to be implemented for your circuit structure to call the compile function.
pub trait Define<C: Config> {
fn define(&self, api: &mut RootBuilder<C>);
}
WitnessSolver
The WitnessSolver is similar to the InputSolver in the Go version of ExpanderCompilerCollection.
Compile Function
The compile function is similar to gnark's frontend.compile. The compilation result, CompileResult, is similar to the Go version of ExpanderCompilerCollection.
Internal Module
The internal module contains items for internal use, such as macros for proper expansion. Circuit developers typically do not need to handle these.
Extra Module
The extra module includes additional items:
UnconstrainedAPI: Provides operations with semantics consistent with Circom's operators. These operations do not generate constraints and are only called during the witness solving stage. Circuit developers need to manually constrain the results of these operations.Serde: Definesserialize_into()anddeserialize_from(). These functions can be used to dump compilation results to a file.