Skip to main content

nova_snark/frontend/util_cs/
test_cs.rs

1//! Test constraint system for use in tests.
2
3use std::collections::HashMap;
4
5use crate::frontend::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable};
6
7use ff::PrimeField;
8
9#[derive(Debug)]
10enum NamedObject {
11  Constraint,
12  Var,
13  Namespace,
14}
15
16/// Constraint system for testing purposes.
17#[derive(Debug)]
18pub struct TestConstraintSystem<Scalar: PrimeField> {
19  named_objects: HashMap<String, NamedObject>,
20  current_namespace: Vec<String>,
21  #[allow(clippy::type_complexity)]
22  constraints: Vec<(
23    LinearCombination<Scalar>,
24    LinearCombination<Scalar>,
25    LinearCombination<Scalar>,
26    String,
27  )>,
28  inputs: Vec<(Scalar, String)>,
29  aux: Vec<(Scalar, String)>,
30}
31
32fn _eval_lc2<Scalar: PrimeField>(
33  terms: &LinearCombination<Scalar>,
34  inputs: &[Scalar],
35  aux: &[Scalar],
36) -> Scalar {
37  let mut acc = Scalar::ZERO;
38
39  for (var, coeff) in terms.iter() {
40    let mut tmp = match var.get_unchecked() {
41      Index::Input(index) => inputs[index],
42      Index::Aux(index) => aux[index],
43    };
44
45    tmp.mul_assign(coeff);
46    acc.add_assign(&tmp);
47  }
48
49  acc
50}
51
52fn eval_lc<Scalar: PrimeField>(
53  terms: &LinearCombination<Scalar>,
54  inputs: &[(Scalar, String)],
55  aux: &[(Scalar, String)],
56) -> Scalar {
57  let mut acc = Scalar::ZERO;
58
59  for (var, coeff) in terms.iter() {
60    let mut tmp = match var.get_unchecked() {
61      Index::Input(index) => inputs[index].0,
62      Index::Aux(index) => aux[index].0,
63    };
64
65    tmp.mul_assign(coeff);
66    acc.add_assign(&tmp);
67  }
68
69  acc
70}
71
72impl<Scalar: PrimeField> Default for TestConstraintSystem<Scalar> {
73  fn default() -> Self {
74    let mut map = HashMap::new();
75    map.insert("ONE".into(), NamedObject::Var);
76
77    TestConstraintSystem {
78      named_objects: map,
79      current_namespace: vec![],
80      constraints: vec![],
81      inputs: vec![(Scalar::ONE, "ONE".into())],
82      aux: vec![],
83    }
84  }
85}
86
87impl<Scalar: PrimeField> TestConstraintSystem<Scalar> {
88  /// Create a new test constraint system.
89  pub fn new() -> Self {
90    Default::default()
91  }
92
93  /// Get the number of constraints
94  pub fn num_constraints(&self) -> usize {
95    self.constraints.len()
96  }
97
98  /// Get path which is unsatisfied
99  pub fn which_is_unsatisfied(&self) -> Option<&str> {
100    for (a, b, c, path) in &self.constraints {
101      let mut a = eval_lc::<Scalar>(a, &self.inputs, &self.aux);
102      let b = eval_lc::<Scalar>(b, &self.inputs, &self.aux);
103      let c = eval_lc::<Scalar>(c, &self.inputs, &self.aux);
104
105      a.mul_assign(&b);
106
107      if a != c {
108        return Some(path);
109      }
110    }
111
112    None
113  }
114
115  /// Check if the constraint system is satisfied.
116  pub fn is_satisfied(&self) -> bool {
117    match self.which_is_unsatisfied() {
118      Some(b) => {
119        #[allow(clippy::print_stdout)]
120        {
121          println!("fail: {b:?}");
122        }
123        false
124      }
125      None => true,
126    }
127  }
128
129  fn set_named_obj(&mut self, path: String, to: NamedObject) {
130    assert!(
131      !self.named_objects.contains_key(&path),
132      "tried to create object at existing path: {path}"
133    );
134
135    self.named_objects.insert(path, to);
136  }
137}
138
139fn compute_path(ns: &[String], this: &str) -> String {
140  assert!(
141    !this.chars().any(|a| a == '/'),
142    "'/' is not allowed in names"
143  );
144
145  if ns.is_empty() {
146    return this.to_string();
147  }
148
149  let name = ns.join("/");
150  format!("{name}/{this}")
151}
152
153impl<Scalar: PrimeField> ConstraintSystem<Scalar> for TestConstraintSystem<Scalar> {
154  type Root = Self;
155
156  fn alloc<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
157  where
158    F: FnOnce() -> Result<Scalar, SynthesisError>,
159    A: FnOnce() -> AR,
160    AR: Into<String>,
161  {
162    let index = self.aux.len();
163    let path = compute_path(&self.current_namespace, &annotation().into());
164    self.aux.push((f()?, path.clone()));
165    let var = Variable::new_unchecked(Index::Aux(index));
166    self.set_named_obj(path, NamedObject::Var);
167
168    Ok(var)
169  }
170
171  fn alloc_input<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
172  where
173    F: FnOnce() -> Result<Scalar, SynthesisError>,
174    A: FnOnce() -> AR,
175    AR: Into<String>,
176  {
177    let index = self.inputs.len();
178    let path = compute_path(&self.current_namespace, &annotation().into());
179    self.inputs.push((f()?, path.clone()));
180    let var = Variable::new_unchecked(Index::Input(index));
181    self.set_named_obj(path, NamedObject::Var);
182
183    Ok(var)
184  }
185
186  fn enforce<A, AR, LA, LB, LC>(&mut self, annotation: A, a: LA, b: LB, c: LC)
187  where
188    A: FnOnce() -> AR,
189    AR: Into<String>,
190    LA: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
191    LB: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
192    LC: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
193  {
194    let path = compute_path(&self.current_namespace, &annotation().into());
195    self.set_named_obj(path.clone(), NamedObject::Constraint);
196
197    let a = a(LinearCombination::zero());
198    let b = b(LinearCombination::zero());
199    let c = c(LinearCombination::zero());
200
201    self.constraints.push((a, b, c, path));
202  }
203
204  fn push_namespace<NR, N>(&mut self, name_fn: N)
205  where
206    NR: Into<String>,
207    N: FnOnce() -> NR,
208  {
209    let name = name_fn().into();
210    let path = compute_path(&self.current_namespace, &name);
211    self.set_named_obj(path, NamedObject::Namespace);
212    self.current_namespace.push(name);
213  }
214
215  fn pop_namespace(&mut self) {
216    assert!(self.current_namespace.pop().is_some());
217  }
218
219  fn get_root(&mut self) -> &mut Self::Root {
220    self
221  }
222}