#[cfg(feature = "std")]
use crate::r1cs::ConstraintTrace;
use crate::r1cs::{LcIndex, LinearCombination, Matrix, SynthesisError, Variable};
use ark_ff::Field;
use ark_std::{
any::{Any, TypeId},
boxed::Box,
cell::{Ref, RefCell, RefMut},
collections::BTreeMap,
format,
rc::Rc,
string::String,
vec,
vec::Vec,
};
pub trait ConstraintSynthesizer<F: Field> {
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> crate::r1cs::Result<()>;
}
#[derive(Debug, Clone)]
pub struct ConstraintSystem<F: Field> {
pub mode: SynthesisMode,
pub num_instance_variables: usize,
pub num_witness_variables: usize,
pub num_constraints: usize,
pub num_linear_combinations: usize,
pub optimization_goal: OptimizationGoal,
pub instance_assignment: Vec<F>,
pub witness_assignment: Vec<F>,
pub cache_map: Rc<RefCell<BTreeMap<TypeId, Box<dyn Any>>>>,
lc_map: BTreeMap<LcIndex, LinearCombination<F>>,
#[cfg(feature = "std")]
constraint_traces: Vec<Option<ConstraintTrace>>,
a_constraints: Vec<LcIndex>,
b_constraints: Vec<LcIndex>,
c_constraints: Vec<LcIndex>,
lc_assignment_cache: Rc<RefCell<BTreeMap<LcIndex, F>>>,
}
impl<F: Field> Default for ConstraintSystem<F> {
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum SynthesisMode {
Setup,
Prove {
construct_matrices: bool,
},
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum OptimizationGoal {
None,
Constraints,
Weight,
}
impl<F: Field> ConstraintSystem<F> {
#[inline]
fn make_row(&self, l: &LinearCombination<F>) -> Vec<(F, usize)> {
let num_input = self.num_instance_variables;
l.0.iter()
.filter_map(|(coeff, var)| {
if coeff.is_zero() {
None
} else {
Some((
*coeff,
var.get_index_unchecked(num_input).expect("no symbolic LCs"),
))
}
})
.collect()
}
pub fn new() -> Self {
Self {
num_instance_variables: 1,
num_witness_variables: 0,
num_constraints: 0,
num_linear_combinations: 0,
a_constraints: Vec::new(),
b_constraints: Vec::new(),
c_constraints: Vec::new(),
instance_assignment: vec![F::one()],
witness_assignment: Vec::new(),
cache_map: Rc::new(RefCell::new(BTreeMap::new())),
#[cfg(feature = "std")]
constraint_traces: Vec::new(),
lc_map: BTreeMap::new(),
lc_assignment_cache: Rc::new(RefCell::new(BTreeMap::new())),
mode: SynthesisMode::Prove {
construct_matrices: true,
},
optimization_goal: OptimizationGoal::Constraints,
}
}
pub fn new_ref() -> ConstraintSystemRef<F> {
ConstraintSystemRef::new(Self::new())
}
pub fn set_mode(&mut self, mode: SynthesisMode) {
self.mode = mode;
}
pub fn is_in_setup_mode(&self) -> bool {
self.mode == SynthesisMode::Setup
}
pub fn optimization_goal(&self) -> OptimizationGoal {
self.optimization_goal
}
pub fn set_optimization_goal(&mut self, goal: OptimizationGoal) {
assert_eq!(self.num_instance_variables, 1);
assert_eq!(self.num_witness_variables, 0);
assert_eq!(self.num_constraints, 0);
assert_eq!(self.num_linear_combinations, 0);
self.optimization_goal = goal;
}
pub fn should_construct_matrices(&self) -> bool {
match self.mode {
SynthesisMode::Setup => true,
SynthesisMode::Prove { construct_matrices } => construct_matrices,
}
}
#[inline]
pub fn zero() -> Variable {
Variable::Zero
}
#[inline]
pub fn one() -> Variable {
Variable::One
}
#[inline]
pub fn new_input_variable<Func>(&mut self, f: Func) -> crate::r1cs::Result<Variable>
where
Func: FnOnce() -> crate::r1cs::Result<F>,
{
let index = self.num_instance_variables;
self.num_instance_variables += 1;
if !self.is_in_setup_mode() {
self.instance_assignment.push(f()?);
}
Ok(Variable::Instance(index))
}
#[inline]
pub fn new_witness_variable<Func>(&mut self, f: Func) -> crate::r1cs::Result<Variable>
where
Func: FnOnce() -> crate::r1cs::Result<F>,
{
let index = self.num_witness_variables;
self.num_witness_variables += 1;
if !self.is_in_setup_mode() {
self.witness_assignment.push(f()?);
}
Ok(Variable::Witness(index))
}
#[inline]
pub fn new_lc(&mut self, lc: LinearCombination<F>) -> crate::r1cs::Result<Variable> {
let index = LcIndex(self.num_linear_combinations);
let var = Variable::SymbolicLc(index);
self.lc_map.insert(index, lc);
self.num_linear_combinations += 1;
Ok(var)
}
#[inline]
pub fn enforce_constraint(
&mut self,
a: LinearCombination<F>,
b: LinearCombination<F>,
c: LinearCombination<F>,
) -> crate::r1cs::Result<()> {
if self.should_construct_matrices() {
let a_index = self.new_lc(a)?.get_lc_index().unwrap();
let b_index = self.new_lc(b)?.get_lc_index().unwrap();
let c_index = self.new_lc(c)?.get_lc_index().unwrap();
self.a_constraints.push(a_index);
self.b_constraints.push(b_index);
self.c_constraints.push(c_index);
}
self.num_constraints += 1;
#[cfg(feature = "std")]
{
let trace = ConstraintTrace::capture();
self.constraint_traces.push(trace);
}
Ok(())
}
fn lc_num_times_used(&self, count_sinks: bool) -> Vec<usize> {
let mut num_times_used = vec![0; self.lc_map.len()];
for (index, lc) in self.lc_map.iter() {
num_times_used[index.0] += count_sinks as usize;
for &(_, var) in lc.iter() {
if var.is_lc() {
let lc_index = var.get_lc_index().expect("should be lc");
num_times_used[lc_index.0] += 1;
}
}
}
num_times_used
}
pub fn transform_lc_map(
&mut self,
transformer: &mut dyn FnMut(
&ConstraintSystem<F>,
usize,
&mut LinearCombination<F>,
) -> (usize, Option<Vec<F>>),
) {
let mut transformed_lc_map = BTreeMap::new();
let mut num_times_used = self.lc_num_times_used(false);
for (&index, lc) in &self.lc_map {
let mut transformed_lc = LinearCombination::new();
for &(coeff, var) in lc.iter() {
if var.is_lc() {
let lc_index = var.get_lc_index().expect("should be lc");
let lc = transformed_lc_map
.get(&lc_index)
.expect("should be inlined");
transformed_lc.extend((lc * coeff).0.into_iter());
num_times_used[lc_index.0] -= 1;
if num_times_used[lc_index.0] == 0 {
transformed_lc_map.remove(&lc_index);
}
} else {
transformed_lc.push((coeff, var));
}
}
transformed_lc.compactify();
let (num_new_witness_variables, new_witness_assignments) =
transformer(&self, num_times_used[index.0], &mut transformed_lc);
transformed_lc_map.insert(index, transformed_lc);
self.num_witness_variables += num_new_witness_variables;
if !self.is_in_setup_mode() && num_new_witness_variables > 0 {
assert!(new_witness_assignments.is_some());
if let Some(new_witness_assignments) = new_witness_assignments {
assert_eq!(new_witness_assignments.len(), num_new_witness_variables);
self.witness_assignment
.extend_from_slice(&new_witness_assignments);
}
}
}
self.lc_map = transformed_lc_map;
}
pub fn inline_all_lcs(&mut self) {
if !self.should_construct_matrices() {
return;
}
self.transform_lc_map(&mut |_, _, _| (0, None));
}
fn outline_lcs(&mut self) {
if !self.should_construct_matrices() {
return;
}
let mut new_witness_linear_combinations = Vec::new();
let mut new_witness_indices = Vec::new();
self.transform_lc_map(&mut |cs, num_times_used, inlined_lc| {
let mut should_dedicate_a_witness_variable = false;
let mut new_witness_index = None;
let mut new_witness_assignment = Vec::new();
let this_used_times = num_times_used + 1;
let this_len = inlined_lc.len();
if this_used_times * this_len > this_used_times + 2 + this_len {
should_dedicate_a_witness_variable = true;
}
if should_dedicate_a_witness_variable {
let witness_index = cs.num_witness_variables;
new_witness_index = Some(witness_index);
if !cs.is_in_setup_mode() {
let mut acc = F::zero();
for (coeff, var) in inlined_lc.iter() {
acc += *coeff * &cs.assigned_value(*var).unwrap();
}
new_witness_assignment.push(acc);
}
new_witness_linear_combinations.push(inlined_lc.clone());
new_witness_indices.push(witness_index);
*inlined_lc = LinearCombination::from(Variable::Witness(witness_index));
}
if new_witness_index.is_some() {
(1, Some(new_witness_assignment))
} else {
(0, None)
}
});
for (new_witness_linear_combination, new_witness_variable) in
new_witness_linear_combinations
.iter()
.zip(new_witness_indices.iter())
{
self.enforce_constraint(
new_witness_linear_combination.clone(),
LinearCombination::from(Self::one()),
LinearCombination::from(Variable::Witness(*new_witness_variable)),
)
.unwrap();
}
}
pub fn finalize(&mut self) {
match self.optimization_goal {
OptimizationGoal::None => self.inline_all_lcs(),
OptimizationGoal::Constraints => self.inline_all_lcs(),
OptimizationGoal::Weight => self.outline_lcs(),
};
}
pub fn to_matrices(&self) -> Option<ConstraintMatrices<F>> {
if let SynthesisMode::Prove {
construct_matrices: false,
} = self.mode
{
None
} else {
let a: Vec<_> = self
.a_constraints
.iter()
.map(|index| self.make_row(self.lc_map.get(index).unwrap()))
.collect();
let b: Vec<_> = self
.b_constraints
.iter()
.map(|index| self.make_row(self.lc_map.get(index).unwrap()))
.collect();
let c: Vec<_> = self
.c_constraints
.iter()
.map(|index| self.make_row(self.lc_map.get(index).unwrap()))
.collect();
let a_num_non_zero: usize = a.iter().map(|lc| lc.len()).sum();
let b_num_non_zero: usize = b.iter().map(|lc| lc.len()).sum();
let c_num_non_zero: usize = c.iter().map(|lc| lc.len()).sum();
let matrices = ConstraintMatrices {
num_instance_variables: self.num_instance_variables,
num_witness_variables: self.num_witness_variables,
num_constraints: self.num_constraints,
a_num_non_zero,
b_num_non_zero,
c_num_non_zero,
a,
b,
c,
};
Some(matrices)
}
}
fn eval_lc(&self, lc: LcIndex) -> Option<F> {
let lc = self.lc_map.get(&lc)?;
let mut acc = F::zero();
for (coeff, var) in lc.iter() {
acc += *coeff * self.assigned_value(*var)?;
}
Some(acc)
}
pub fn is_satisfied(&self) -> crate::r1cs::Result<bool> {
self.which_is_unsatisfied().map(|s| s.is_none())
}
pub fn which_is_unsatisfied(&self) -> crate::r1cs::Result<Option<String>> {
if self.is_in_setup_mode() {
Err(SynthesisError::AssignmentMissing)
} else {
for i in 0..self.num_constraints {
let a = self
.eval_lc(self.a_constraints[i])
.ok_or(SynthesisError::AssignmentMissing)?;
let b = self
.eval_lc(self.b_constraints[i])
.ok_or(SynthesisError::AssignmentMissing)?;
let c = self
.eval_lc(self.c_constraints[i])
.ok_or(SynthesisError::AssignmentMissing)?;
if a * b != c {
let trace;
#[cfg(feature = "std")]
{
trace = self.constraint_traces[i].as_ref().map_or_else(
|| {
eprintln!("Constraint trace requires enabling `ConstraintLayer`");
format!("{}", i)
},
|t| format!("{}", t),
);
}
#[cfg(not(feature = "std"))]
{
trace = format!("{}", i);
}
return Ok(Some(trace));
}
}
Ok(None)
}
}
pub fn assigned_value(&self, v: Variable) -> Option<F> {
match v {
Variable::One => Some(F::one()),
Variable::Zero => Some(F::zero()),
Variable::Witness(idx) => self.witness_assignment.get(idx).copied(),
Variable::Instance(idx) => self.instance_assignment.get(idx).copied(),
Variable::SymbolicLc(idx) => {
let value = self.lc_assignment_cache.borrow().get(&idx).copied();
if value.is_some() {
value
} else {
let value = self.eval_lc(idx)?;
self.lc_assignment_cache.borrow_mut().insert(idx, value);
Some(value)
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstraintMatrices<F: Field> {
pub num_instance_variables: usize,
pub num_witness_variables: usize,
pub num_constraints: usize,
pub a_num_non_zero: usize,
pub b_num_non_zero: usize,
pub c_num_non_zero: usize,
pub a: Matrix<F>,
pub b: Matrix<F>,
pub c: Matrix<F>,
}
#[derive(Debug, Clone)]
pub enum ConstraintSystemRef<F: Field> {
None,
CS(Rc<RefCell<ConstraintSystem<F>>>),
}
impl<F: Field> PartialEq for ConstraintSystemRef<F> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::None, Self::None) => true,
(..) => false,
}
}
}
impl<F: Field> Eq for ConstraintSystemRef<F> {}
#[derive(Debug, Clone)]
pub struct Namespace<F: Field> {
inner: ConstraintSystemRef<F>,
id: Option<tracing::Id>,
}
impl<F: Field> From<ConstraintSystemRef<F>> for Namespace<F> {
fn from(other: ConstraintSystemRef<F>) -> Self {
Self {
inner: other,
id: None,
}
}
}
impl<F: Field> Namespace<F> {
pub fn new(inner: ConstraintSystemRef<F>, id: Option<tracing::Id>) -> Self {
Self { inner, id }
}
pub fn cs(&self) -> ConstraintSystemRef<F> {
self.inner.clone()
}
pub fn leave_namespace(self) {
drop(self)
}
}
impl<F: Field> Drop for Namespace<F> {
fn drop(&mut self) {
if let Some(id) = self.id.as_ref() {
tracing::dispatcher::get_default(|dispatch| dispatch.exit(id))
}
drop(&mut self.inner)
}
}
impl<F: Field> ConstraintSystemRef<F> {
pub fn or(self, other: Self) -> Self {
match self {
ConstraintSystemRef::None => other,
_ => self,
}
}
pub fn is_none(&self) -> bool {
matches!(self, ConstraintSystemRef::None)
}
#[inline]
pub fn new(inner: ConstraintSystem<F>) -> Self {
Self::CS(Rc::new(RefCell::new(inner)))
}
fn inner(&self) -> Option<&Rc<RefCell<ConstraintSystem<F>>>> {
match self {
Self::CS(a) => Some(a),
Self::None => None,
}
}
#[inline]
pub fn borrow(&self) -> Option<Ref<'_, ConstraintSystem<F>>> {
self.inner().map(|cs| cs.borrow())
}
#[inline]
pub fn borrow_mut(&self) -> Option<RefMut<'_, ConstraintSystem<F>>> {
self.inner().map(|cs| cs.borrow_mut())
}
pub fn set_mode(&self, mode: SynthesisMode) {
self.inner().map_or((), |cs| cs.borrow_mut().set_mode(mode))
}
#[inline]
pub fn is_in_setup_mode(&self) -> bool {
self.inner()
.map_or(false, |cs| cs.borrow().is_in_setup_mode())
}
#[inline]
pub fn num_constraints(&self) -> usize {
self.inner().map_or(0, |cs| cs.borrow().num_constraints)
}
#[inline]
pub fn num_instance_variables(&self) -> usize {
self.inner()
.map_or(0, |cs| cs.borrow().num_instance_variables)
}
#[inline]
pub fn num_witness_variables(&self) -> usize {
self.inner()
.map_or(0, |cs| cs.borrow().num_witness_variables)
}
#[inline]
pub fn optimization_goal(&self) -> OptimizationGoal {
self.inner().map_or(OptimizationGoal::Constraints, |cs| {
cs.borrow().optimization_goal()
})
}
#[inline]
pub fn set_optimization_goal(&self, goal: OptimizationGoal) {
self.inner()
.map_or((), |cs| cs.borrow_mut().set_optimization_goal(goal))
}
#[inline]
pub fn should_construct_matrices(&self) -> bool {
self.inner()
.map_or(false, |cs| cs.borrow().should_construct_matrices())
}
#[inline]
pub fn new_input_variable<Func>(&self, f: Func) -> crate::r1cs::Result<Variable>
where
Func: FnOnce() -> crate::r1cs::Result<F>,
{
self.inner()
.ok_or(SynthesisError::MissingCS)
.and_then(|cs| {
if !self.is_in_setup_mode() {
let value = f();
cs.borrow_mut().new_input_variable(|| value)
} else {
cs.borrow_mut().new_input_variable(f)
}
})
}
#[inline]
pub fn new_witness_variable<Func>(&self, f: Func) -> crate::r1cs::Result<Variable>
where
Func: FnOnce() -> crate::r1cs::Result<F>,
{
self.inner()
.ok_or(SynthesisError::MissingCS)
.and_then(|cs| {
if !self.is_in_setup_mode() {
let value = f();
cs.borrow_mut().new_witness_variable(|| value)
} else {
cs.borrow_mut().new_witness_variable(f)
}
})
}
#[inline]
pub fn new_lc(&self, lc: LinearCombination<F>) -> crate::r1cs::Result<Variable> {
self.inner()
.ok_or(SynthesisError::MissingCS)
.and_then(|cs| cs.borrow_mut().new_lc(lc))
}
#[inline]
pub fn enforce_constraint(
&self,
a: LinearCombination<F>,
b: LinearCombination<F>,
c: LinearCombination<F>,
) -> crate::r1cs::Result<()> {
self.inner()
.ok_or(SynthesisError::MissingCS)
.and_then(|cs| cs.borrow_mut().enforce_constraint(a, b, c))
}
pub fn inline_all_lcs(&self) {
if let Some(cs) = self.inner() {
cs.borrow_mut().inline_all_lcs()
}
}
pub fn finalize(&self) {
if let Some(cs) = self.inner() {
cs.borrow_mut().finalize()
}
}
#[inline]
pub fn to_matrices(&self) -> Option<ConstraintMatrices<F>> {
self.inner().and_then(|cs| cs.borrow().to_matrices())
}
pub fn is_satisfied(&self) -> crate::r1cs::Result<bool> {
self.inner()
.map_or(Err(SynthesisError::AssignmentMissing), |cs| {
cs.borrow().is_satisfied()
})
}
pub fn which_is_unsatisfied(&self) -> crate::r1cs::Result<Option<String>> {
self.inner()
.map_or(Err(SynthesisError::AssignmentMissing), |cs| {
cs.borrow().which_is_unsatisfied()
})
}
pub fn assigned_value(&self, v: Variable) -> Option<F> {
self.inner().and_then(|cs| cs.borrow().assigned_value(v))
}
pub fn constraint_names(&self) -> Option<Vec<String>> {
#[cfg(feature = "std")]
{
self.inner().and_then(|cs| {
cs.borrow()
.constraint_traces
.iter()
.map(|trace| {
let mut constraint_path = String::new();
let mut prev_module_path = "";
let mut prefixes = ark_std::collections::BTreeSet::new();
for step in trace.as_ref()?.path() {
let module_path = if prev_module_path == step.module_path {
prefixes.insert(step.module_path.to_string());
String::new()
} else {
let mut parts = step
.module_path
.split("::")
.filter(|&part| part != "r1cs_std" && part != "constraints");
let mut path_so_far = String::new();
for part in parts.by_ref() {
if path_so_far.is_empty() {
path_so_far += part;
} else {
path_so_far += &["::", part].join("");
}
if prefixes.contains(&path_so_far) {
continue;
} else {
prefixes.insert(path_so_far.clone());
break;
}
}
parts.collect::<Vec<_>>().join("::") + "::"
};
prev_module_path = step.module_path;
constraint_path += &["/", &module_path, step.name].join("");
}
Some(constraint_path)
})
.collect::<Option<Vec<_>>>()
})
}
#[cfg(not(feature = "std"))]
{
None
}
}
}
#[cfg(test)]
mod tests {
use crate::r1cs::*;
use ark_ff::One;
use ark_test_curves::bls12_381::Fr;
#[test]
fn matrix_generation() -> crate::r1cs::Result<()> {
let cs = ConstraintSystem::<Fr>::new_ref();
let two = Fr::one() + Fr::one();
let a = cs.new_input_variable(|| Ok(Fr::one()))?;
let b = cs.new_witness_variable(|| Ok(Fr::one()))?;
let c = cs.new_witness_variable(|| Ok(two))?;
cs.enforce_constraint(lc!() + a, lc!() + (two, b), lc!() + c)?;
let d = cs.new_lc(lc!() + a + b)?;
cs.enforce_constraint(lc!() + a, lc!() + d, lc!() + d)?;
let e = cs.new_lc(lc!() + d + d)?;
cs.enforce_constraint(lc!() + Variable::One, lc!() + e, lc!() + e)?;
cs.inline_all_lcs();
let matrices = cs.to_matrices().unwrap();
assert_eq!(matrices.a[0], vec![(Fr::one(), 1)]);
assert_eq!(matrices.b[0], vec![(two, 2)]);
assert_eq!(matrices.c[0], vec![(Fr::one(), 3)]);
assert_eq!(matrices.a[1], vec![(Fr::one(), 1)]);
assert_eq!(matrices.b[1], vec![(Fr::one(), 1), (Fr::one(), 2)]);
assert_eq!(matrices.c[1], vec![(Fr::one(), 1), (Fr::one(), 2)]);
assert_eq!(matrices.a[2], vec![(Fr::one(), 0)]);
assert_eq!(matrices.b[2], vec![(two, 1), (two, 2)]);
assert_eq!(matrices.c[2], vec![(two, 1), (two, 2)]);
Ok(())
}
}