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.