Documentation Index
Fetch the complete documentation index at: https://mintlify.com/provablehq/snarkvm/llms.txt
Use this file to discover all available pages before exploring further.
The snarkvm-circuit crate provides circuit equivalents of all console types, enabling zero-knowledge proof generation through constraint system synthesis. It implements the R1CS (Rank-1 Constraint System) framework used by the Marlin proof system.
Architecture
The circuit crate mirrors the structure of the console crate:
use snarkvm_circuit::prelude::*;
// Circuit modules
use snarkvm_circuit::{
environment, // Circuit environment and constraint tracking
types, // Circuit types (Field, Boolean, Integer, etc.)
program, // Circuit program types (Request, Response, etc.)
account, // Circuit account types
algorithms, // Circuit cryptographic algorithms
collections, // Circuit collections
network, // Circuit network configuration
};
Constraint System
R1CS Structure
The circuit crate builds an R1CS constraint system where each constraint has the form:
Where A, B, and C are linear combinations of variables:
use snarkvm_circuit_environment::prelude::*;
// Linear combinations track variables and their coefficients
type LinearCombination<F> = {
constant: F,
terms: Vec<(Variable<F>, F)>,
value: F,
};
Source: circuit/environment/src/helpers/linear_combination.rs:36-42
Variable Types
Circuit variables come in three modes:
pub enum Mode {
Constant, // Known at compile time, no constraints
Public, // Public inputs to the proof
Private, // Private witness values
}
Source: circuit/environment/src/helpers/mode.rs:21-25
Console/Circuit Synchronization
The circuit and console crates must remain synchronized. Every console type has a corresponding circuit type with identical structure and API. When modifying one, always update the other.
Inject and Eject Traits
Circuit types convert to/from console types using Inject and Eject:
pub trait Inject {
type Primitive;
/// Initializes a circuit of the given mode and primitive value.
fn new(mode: Mode, value: Self::Primitive) -> Self;
/// Initializes a constant of the given primitive value.
fn constant(value: Self::Primitive) -> Self;
}
pub trait Eject {
type Primitive;
/// Ejects the mode and primitive value of the circuit type.
fn eject(&self) -> (Mode, Self::Primitive);
/// Ejects the mode of the circuit type.
fn eject_mode(&self) -> Mode;
/// Ejects the circuit type as a primitive value.
fn eject_value(&self) -> Self::Primitive;
}
Source: circuit/environment/src/traits/inject.rs:19-36, circuit/environment/src/traits/eject.rs:18-38
Constraint Counting
The environment tracks resource usage:
pub struct Count(pub Constant, pub Public, pub Private, pub Constraints);
impl Count {
/// Returns exact counts.
pub const fn is(num_constants: u64, num_public: u64,
num_private: u64, num_constraints: u64) -> Self;
/// Returns upper bound counts.
pub const fn less_than(num_constants: u64, num_public: u64,
num_private: u64, num_constraints: u64) -> Self;
}
Source: circuit/environment/src/helpers/count.rs:26-53
Testing Constraints
Use constraint counting in tests:
use snarkvm_circuit_environment::{Circuit, assert_scope};
Circuit::scope("test_boolean_and", || {
let a = Boolean::<Circuit>::new(Mode::Private, true);
let b = Boolean::<Circuit>::new(Mode::Private, false);
let c = a & b;
// Assert (constants, public, private, constraints)
assert_scope!(0, 0, 3, 3);
});
R1CS Generation
Enforcing Constraints
The environment provides methods to add constraints:
pub trait Environment {
/// Adds one constraint enforcing that `(A * B) == C`.
fn enforce<Fn, A, B, C>(constraint: Fn) -> Result<(), ConstraintUnsatisfied>
where
Fn: FnOnce() -> (A, B, C),
A: Into<LinearCombination<Self::BaseField>>,
B: Into<LinearCombination<Self::BaseField>>,
C: Into<LinearCombination<Self::BaseField>>;
/// Adds one constraint enforcing that the given boolean is `true`.
fn assert<Boolean: Into<LinearCombination<Self::BaseField>>>(
boolean: Boolean,
) -> Result<(), ConstraintUnsatisfied>;
/// Adds one constraint enforcing that `A == B`.
fn assert_eq<A, B>(a: A, b: B) -> Result<(), ConstraintUnsatisfied>
where
A: Into<LinearCombination<Self::BaseField>>,
B: Into<LinearCombination<Self::BaseField>>;
/// Adds one constraint enforcing that `A != B`.
fn assert_neq<A, B>(a: A, b: B) -> Result<(), ConstraintUnsatisfied>
where
A: Into<LinearCombination<Self::BaseField>>,
B: Into<LinearCombination<Self::BaseField>>;
}
Source: circuit/environment/src/environment.rs:64-108
Extract the complete constraint system:
impl<E: Environment> E {
/// Returns the R1CS circuit, resetting the circuit.
fn eject_r1cs_and_reset() -> R1CS<Self::BaseField>;
/// Returns the R1CS assignment, resetting the circuit.
fn eject_assignment_and_reset() -> Assignment<Field>;
}
pub struct R1CS<F: PrimeField> {
constants: Vec<Variable<F>>,
public: Vec<Variable<F>>,
private: Vec<Variable<F>>,
constraints: Vec<Arc<Constraint<F>>>,
}
Source: circuit/environment/src/environment.rs:183-189, circuit/environment/src/helpers/r1cs.rs:63-71
Environment Scoping
Use scopes to organize constraint generation:
impl<E: Environment> E {
/// Enters a new scope for the environment.
fn scope<S: Into<String>, Fn, Output>(name: S, logic: Fn) -> Output
where
Fn: FnOnce() -> Output;
/// Returns constraint counts for the current scope.
fn count_in_scope() -> (u64, u64, u64, u64, (u64, u64, u64));
}
// Example usage
Circuit::scope("hash_function", || {
// Constraints added here are tracked separately
let hash = hash_to_field(&input);
// Get counts for just this scope
let (constants, public, private, constraints, _) = Circuit::count_in_scope();
println!("Hash used {constraints} constraints");
});
Source: circuit/environment/src/environment.rs:60-62, circuit/environment/src/environment.rs:154-163
Resource Limits
Set limits on circuit size:
impl<E: Environment> E {
/// Sets the variable limit for the circuit.
fn set_variable_limit(limit: Option<u64>);
/// Returns the variable limit for the circuit, if one exists.
fn get_variable_limit() -> Option<u64>;
/// Sets the constraint limit for the circuit.
fn set_constraint_limit(limit: Option<u64>);
/// Returns the constraint limit for the circuit, if one exists.
fn get_constraint_limit() -> Option<u64>;
}
Source: circuit/environment/src/environment.rs:165-175
Example: Circuit Synthesis
use snarkvm_circuit::prelude::*;
// Define a simple circuit
fn verify_hash<A: Aleo>(input: Field<A>, expected: Field<A>) {
// Compute hash (generates constraints)
let hash = Poseidon4::<A>::hash(&[input]);
// Assert equality (adds 1 constraint)
A::assert_eq(hash, expected);
}
// Synthesize the circuit
Circuit::scope("hash_verification", || {
let input = Field::<Circuit>::new(Mode::Private, console::Field::from(42u64));
let expected = Field::<Circuit>::new(Mode::Public, console::Field::from(123u64));
verify_hash(input, expected);
// Get final counts
let (constants, public, private, constraints, nonzeros) = Circuit::count();
println!("Total constraints: {constraints}");
// Extract R1CS for proving
let r1cs = Circuit::eject_r1cs_and_reset();
});
Best Practices
- Always test constraint counts - Use
assert_scope! to verify expected resource usage
- Minimize constraint generation - Prefer constant operations when possible
- Keep console/circuit synchronized - Same structure, same API, same tests
- Use scopes for organization - Track resource usage per component
- Test satisfaction - Use
is_satisfied() to verify constraint correctness
See Also