/ algorithms / src / r1cs / test_constraint_system.rs
test_constraint_system.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphavm library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  use crate::r1cs::{errors::SynthesisError, ConstraintSystem, Index, LinearCombination, OptionalVec, Variable};
 20  use alphavm_fields::Field;
 21  
 22  use cfg_if::cfg_if;
 23  use fxhash::{FxBuildHasher, FxHashMap};
 24  use indexmap::{map::Entry, IndexMap, IndexSet};
 25  use itertools::Itertools;
 26  
 27  /// This field is the scalar field (Fr) of BLS12-377.
 28  pub type Fr = alphavm_curves::bls12_377::Fr;
 29  
 30  #[allow(dead_code)]
 31  #[derive(Debug, Clone)]
 32  enum NamedObject {
 33      Constraint(usize),
 34      Var(Variable),
 35      // contains the list of named objects that belong to it
 36      Namespace(Namespace),
 37  }
 38  
 39  #[derive(Debug, Clone, Default)]
 40  struct Namespace {
 41      children: Vec<NamedObject>,
 42      idx: NamespaceIndex,
 43  }
 44  
 45  impl Namespace {
 46      fn push(&mut self, child: NamedObject) {
 47          self.children.push(child);
 48      }
 49  }
 50  
 51  type InternedField = usize;
 52  type InternedPathSegment = usize;
 53  type NamespaceIndex = usize;
 54  
 55  #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 56  pub struct InternedPath {
 57      parent_namespace: NamespaceIndex,
 58      last_segment: InternedPathSegment,
 59  }
 60  
 61  #[derive(PartialEq, Eq, Hash)]
 62  pub struct TestConstraint {
 63      interned_path: InternedPath,
 64      a: Vec<(Variable, InternedField)>,
 65      b: Vec<(Variable, InternedField)>,
 66      c: Vec<(Variable, InternedField)>,
 67  }
 68  
 69  #[derive(Default, Debug)]
 70  pub struct CurrentNamespace {
 71      segments: Vec<InternedPathSegment>,
 72      indices: Vec<NamespaceIndex>,
 73  }
 74  
 75  impl CurrentNamespace {
 76      fn idx(&self) -> usize {
 77          self.indices.last().copied().unwrap_or(0)
 78      }
 79  
 80      fn pop(&mut self) {
 81          assert!(self.segments.pop().is_some());
 82          assert!(self.indices.pop().is_some());
 83      }
 84  }
 85  
 86  /// Constraint system for testing purposes.
 87  pub struct TestConstraintSystem<F: Field> {
 88      // used to intern full paths in test scenarios, for get and set purposes
 89      interned_full_paths: FxHashMap<Vec<InternedPathSegment>, InternedPath>,
 90      // used to intern namespace segments
 91      interned_path_segments: IndexSet<String, FxBuildHasher>,
 92      // used to intern fields belonging to F
 93      interned_fields: IndexSet<F, FxBuildHasher>,
 94      // contains named objects bound to their (interned) paths; the indices are
 95      // used for NamespaceIndex lookups
 96      named_objects: IndexMap<InternedPath, NamedObject, FxBuildHasher>,
 97      // a stack of current path's segments and the index of the current path's
 98      // index in the named_objects map
 99      current_namespace: CurrentNamespace,
100      // the list of currently applicable constraints
101      constraints: OptionalVec<TestConstraint>,
102      // the list of currently applicable input variables
103      public_variables: OptionalVec<InternedField>,
104      // the list of currently applicable auxiliary variables
105      private_variables: OptionalVec<InternedField>,
106  }
107  
108  impl<F: Field> Default for TestConstraintSystem<F> {
109      fn default() -> Self {
110          let mut interned_path_segments = IndexSet::with_hasher(FxBuildHasher::default());
111          let path_segment = "ONE".to_owned();
112          let interned_path_segment = interned_path_segments.insert_full(path_segment).0;
113          let interned_path = InternedPath { parent_namespace: 0, last_segment: interned_path_segment };
114  
115          cfg_if! {
116              if #[cfg(debug_assertions)] {
117                  let mut interned_full_paths = FxHashMap::default();
118                  interned_full_paths.insert(vec![interned_path_segment], interned_path);
119              } else {
120                  let interned_full_paths = FxHashMap::default();
121              }
122          }
123  
124          let mut named_objects = IndexMap::with_hasher(FxBuildHasher::default());
125          named_objects.insert_full(interned_path, NamedObject::Var(TestConstraintSystem::<F>::one()));
126  
127          let mut interned_fields = IndexSet::with_hasher(FxBuildHasher::default());
128          let interned_field = interned_fields.insert_full(F::one()).0;
129  
130          let mut inputs: OptionalVec<InternedField> = Default::default();
131          inputs.insert(interned_field);
132  
133          let constraints = OptionalVec::default();
134  
135          TestConstraintSystem {
136              interned_full_paths,
137              interned_fields,
138              interned_path_segments,
139              named_objects,
140              current_namespace: Default::default(),
141              constraints,
142              public_variables: inputs,
143              private_variables: Default::default(),
144          }
145      }
146  }
147  
148  impl<F: Field> TestConstraintSystem<F> {
149      pub fn new() -> Self {
150          Self::default()
151      }
152  
153      /// Prints the constraint at which `self` and `other` differ.
154      pub fn diff(&self, other: &Self) {
155          for (i, (self_c, other_c)) in self.constraints.iter().zip(other.constraints.iter()).enumerate() {
156              let self_interned_path = self_c.interned_path;
157              let other_interned_path = other_c.interned_path;
158              if self_c.a != other_c.a {
159                  println!("A row {i} is different:");
160                  println!("self: {}", self.unintern_path(self_interned_path));
161                  println!("other: {}", other.unintern_path(other_interned_path));
162                  break;
163              }
164  
165              if self_c.b != other_c.b {
166                  println!("B row {i} is different:");
167                  println!("self: {}", self.unintern_path(self_interned_path));
168                  println!("other: {}", other.unintern_path(other_interned_path));
169                  break;
170              }
171  
172              if self_c.c != other_c.c {
173                  println!("C row {i} is different:");
174                  println!("self: {}", self.unintern_path(self_interned_path));
175                  println!("other: {}", other.unintern_path(other_interned_path));
176                  break;
177              }
178          }
179      }
180  
181      #[inline]
182      fn intern_path(&self, path: &str) -> InternedPath {
183          let mut vec = vec![];
184  
185          for segment in path.split('/') {
186              vec.push(self.interned_path_segments.get_index_of(segment).unwrap());
187          }
188  
189          *self.interned_full_paths.get(&vec).unwrap()
190      }
191  
192      fn unintern_path(&self, interned_path: InternedPath) -> String {
193          let last_segment = self.interned_path_segments.get_index(interned_path.last_segment).unwrap();
194          let mut reversed_uninterned_segments = vec![last_segment];
195  
196          let mut parent_ns = interned_path.parent_namespace;
197          while parent_ns != 0 {
198              let interned_parent_ns = self.named_objects.get_index(parent_ns).unwrap().0;
199              let parent_segment = self.interned_path_segments.get_index(interned_parent_ns.last_segment).unwrap();
200              reversed_uninterned_segments.push(parent_segment);
201              parent_ns = interned_parent_ns.parent_namespace;
202          }
203  
204          let segments = reversed_uninterned_segments.into_iter().map(|s| s.as_str()).rev();
205          Itertools::intersperse(segments, "/").collect()
206      }
207  
208      pub fn print_named_objects(&self) {
209          for TestConstraint { interned_path, .. } in self.constraints.iter() {
210              println!("{}", self.unintern_path(*interned_path));
211          }
212      }
213  
214      fn eval_lc(&self, terms: &[(Variable, InternedField)]) -> F {
215          let mut acc = F::zero();
216  
217          for &(var, interned_coeff) in terms {
218              let interned_tmp = match var.get_unchecked() {
219                  Index::Public(index) => self.public_variables[index],
220                  Index::Private(index) => self.private_variables[index],
221              };
222              let mut tmp = *self.interned_fields.get_index(interned_tmp).unwrap();
223              let coeff = self.interned_fields.get_index(interned_coeff).unwrap();
224  
225              tmp.mul_assign(coeff);
226              acc.add_assign(tmp);
227          }
228  
229          acc
230      }
231  
232      pub fn which_is_unsatisfied(&self) -> Option<String> {
233          for TestConstraint { interned_path, a, b, c } in self.constraints.iter() {
234              let mut a = self.eval_lc(a.as_ref());
235              let b = self.eval_lc(b.as_ref());
236              let c = self.eval_lc(c.as_ref());
237  
238              a.mul_assign(&b);
239  
240              if a != c {
241                  return Some(self.unintern_path(*interned_path));
242              }
243          }
244  
245          None
246      }
247  
248      #[inline]
249      pub fn is_satisfied(&self) -> bool {
250          self.which_is_unsatisfied().is_none()
251      }
252  
253      #[inline]
254      pub fn num_non_zero(&self) -> (usize, usize, usize) {
255          let mut non_zero_a = 0;
256          let mut non_zero_b = 0;
257          let mut non_zero_c = 0;
258          for TestConstraint { a, b, c, .. } in self.constraints.iter() {
259              non_zero_a += a.len();
260              non_zero_b += b.len();
261              non_zero_c += c.len();
262          }
263          (non_zero_a, non_zero_b, non_zero_c)
264      }
265  
266      #[inline]
267      pub fn num_constraints(&self) -> usize {
268          self.constraints.len()
269      }
270  
271      #[inline]
272      pub fn get_constraint_path(&self, i: usize) -> String {
273          self.unintern_path(self.constraints.iter().nth(i).unwrap().interned_path)
274      }
275  
276      pub fn set(&mut self, path: &str, to: F) {
277          let interned_path = self.intern_path(path);
278          let interned_field = self.interned_fields.insert_full(to).0;
279  
280          match self.named_objects.get(&interned_path) {
281              Some(NamedObject::Var(v)) => match v.get_unchecked() {
282                  Index::Public(index) => self.public_variables[index] = interned_field,
283                  Index::Private(index) => self.private_variables[index] = interned_field,
284              },
285              Some(e) => panic!("tried to set path `{path}` to value, but `{e:?}` already exists there."),
286              _ => panic!("no variable exists at path: {path}"),
287          }
288      }
289  
290      pub fn get(&mut self, path: &str) -> F {
291          let interned_path = self.intern_path(path);
292  
293          let interned_field = match self.named_objects.get(&interned_path) {
294              Some(NamedObject::Var(v)) => match v.get_unchecked() {
295                  Index::Public(index) => self.public_variables[index],
296                  Index::Private(index) => self.private_variables[index],
297              },
298              Some(e) => panic!("tried to get value of path `{path}`, but `{e:?}` exists there (not a variable)"),
299              _ => panic!("no variable exists at path: {path}"),
300          };
301  
302          *self.interned_fields.get_index(interned_field).unwrap()
303      }
304  
305      #[inline]
306      fn set_named_obj(&mut self, interned_path: InternedPath, to: NamedObject) -> NamespaceIndex {
307          match self.named_objects.entry(interned_path) {
308              Entry::Vacant(e) => {
309                  let ns_idx = e.index();
310                  e.insert(to);
311                  ns_idx
312              }
313              Entry::Occupied(e) => {
314                  let interned_segments = e.swap_remove_entry().0;
315                  panic!("tried to create object at existing path: {}", self.unintern_path(interned_segments));
316              }
317          }
318      }
319  
320      #[inline]
321      fn compute_path(&mut self, new_segment: &str) -> InternedPath {
322          let (interned_segment, new) = if let Some(index) = self.interned_path_segments.get_index_of(new_segment) {
323              (index, false)
324          } else {
325              self.interned_path_segments.insert_full(new_segment.to_owned())
326          };
327  
328          // only perform the check for segments not seen before
329          assert!(!new || !new_segment.contains('/'), "'/' is not allowed in names");
330  
331          let interned_path =
332              InternedPath { parent_namespace: self.current_namespace.idx(), last_segment: interned_segment };
333  
334          cfg_if! {
335              if #[cfg(debug_assertions)] {
336                  let mut full_path = self.current_namespace.segments.clone();
337                  full_path.push(interned_segment);
338                  self.interned_full_paths.insert(full_path, interned_path);
339              }
340          }
341  
342          interned_path
343      }
344  
345      #[cfg(not(debug_assertions))]
346      fn purge_namespace(&mut self, namespace: Namespace) {
347          for child_obj in namespace.children {
348              match child_obj {
349                  NamedObject::Var(var) => match var.get_unchecked() {
350                      Index::Private(idx) => {
351                          self.private_variables.remove(idx);
352                      }
353                      Index::Public(idx) => {
354                          self.public_variables.remove(idx);
355                      }
356                  },
357                  NamedObject::Constraint(idx) => {
358                      self.constraints.remove(idx);
359                  }
360                  NamedObject::Namespace(children) => {
361                      self.purge_namespace(children);
362                  }
363              }
364              self.named_objects.swap_remove_index(namespace.idx);
365          }
366      }
367  
368      #[inline]
369      fn register_object_in_namespace(&mut self, named_obj: NamedObject) {
370          if let NamedObject::Namespace(ns) = self.named_objects.get_index_mut(self.current_namespace.idx()).unwrap().1 {
371              ns.push(named_obj);
372          }
373      }
374  }
375  
376  impl<F: Field> ConstraintSystem<F> for TestConstraintSystem<F> {
377      type Root = Self;
378  
379      fn alloc<Fn, A, AR>(&mut self, annotation: A, f: Fn) -> Result<Variable, SynthesisError>
380      where
381          Fn: FnOnce() -> Result<F, SynthesisError>,
382          A: FnOnce() -> AR,
383          AR: AsRef<str>,
384      {
385          let interned_path = self.compute_path(annotation().as_ref());
386          let interned_field = self.interned_fields.insert_full(f()?).0;
387          let index = self.private_variables.insert(interned_field);
388          let var = Variable::new_unchecked(Index::Private(index));
389          let named_obj = NamedObject::Var(var);
390          self.register_object_in_namespace(named_obj.clone());
391          self.set_named_obj(interned_path, named_obj);
392  
393          Ok(var)
394      }
395  
396      fn alloc_input<Fn, A, AR>(&mut self, annotation: A, f: Fn) -> Result<Variable, SynthesisError>
397      where
398          Fn: FnOnce() -> Result<F, SynthesisError>,
399          A: FnOnce() -> AR,
400          AR: AsRef<str>,
401      {
402          let interned_path = self.compute_path(annotation().as_ref());
403          let interned_field = self.interned_fields.insert_full(f()?).0;
404          let index = self.public_variables.insert(interned_field);
405          let var = Variable::new_unchecked(Index::Public(index));
406          let named_obj = NamedObject::Var(var);
407          self.register_object_in_namespace(named_obj.clone());
408          self.set_named_obj(interned_path, named_obj);
409  
410          Ok(var)
411      }
412  
413      fn enforce<A, AR, LA, LB, LC>(&mut self, annotation: A, a: LA, b: LB, c: LC)
414      where
415          A: FnOnce() -> AR,
416          AR: AsRef<str>,
417          LA: FnOnce(LinearCombination<F>) -> LinearCombination<F>,
418          LB: FnOnce(LinearCombination<F>) -> LinearCombination<F>,
419          LC: FnOnce(LinearCombination<F>) -> LinearCombination<F>,
420      {
421          let interned_path = self.compute_path(annotation().as_ref());
422          let index = self.constraints.next_idx();
423          let named_obj = NamedObject::Constraint(index);
424          self.register_object_in_namespace(named_obj.clone());
425          self.set_named_obj(interned_path, named_obj);
426  
427          let mut intern_fields = |uninterned: Vec<(Variable, F)>| -> Vec<(Variable, InternedField)> {
428              uninterned
429                  .into_iter()
430                  .map(|(var, field)| {
431                      let interned_field = self.interned_fields.insert_full(field).0;
432                      (var, interned_field)
433                  })
434                  .collect()
435          };
436  
437          let a = intern_fields(a(LinearCombination::zero()).0);
438          let b = intern_fields(b(LinearCombination::zero()).0);
439          let c = intern_fields(c(LinearCombination::zero()).0);
440  
441          self.constraints.insert(TestConstraint { interned_path, a, b, c });
442      }
443  
444      fn push_namespace<NR: AsRef<str>, N: FnOnce() -> NR>(&mut self, name_fn: N) {
445          let name = name_fn();
446          let interned_path = self.compute_path(name.as_ref());
447          let new_segment = interned_path.last_segment;
448          let named_obj = NamedObject::Namespace(Default::default());
449          self.register_object_in_namespace(named_obj.clone());
450          let namespace_idx = self.set_named_obj(interned_path, named_obj);
451          if let NamedObject::Namespace(ref mut ns) = self.named_objects[namespace_idx] {
452              ns.idx = namespace_idx;
453          };
454  
455          self.current_namespace.segments.push(new_segment);
456          self.current_namespace.indices.push(namespace_idx);
457      }
458  
459      #[cfg(not(debug_assertions))]
460      fn pop_namespace(&mut self) {
461          let namespace = if let NamedObject::Namespace(no) =
462              self.named_objects.swap_remove_index(self.current_namespace.idx()).unwrap().1
463          {
464              no
465          } else {
466              unreachable!()
467          };
468  
469          // remove object belonging to the popped namespace
470          self.purge_namespace(namespace);
471  
472          // update the current namespace
473          self.current_namespace.pop();
474      }
475  
476      #[cfg(debug_assertions)]
477      fn pop_namespace(&mut self) {
478          // don't perform a full cleanup in test conditions, so that all the variables,
479          // constraints and namespace indices remain available throughout the tests
480  
481          self.current_namespace.pop();
482      }
483  
484      #[inline]
485      fn get_root(&mut self) -> &mut Self::Root {
486          self
487      }
488  
489      #[inline]
490      fn num_constraints(&self) -> usize {
491          self.num_constraints()
492      }
493  
494      #[inline]
495      fn num_public_variables(&self) -> usize {
496          self.public_variables.len()
497      }
498  
499      #[inline]
500      fn num_private_variables(&self) -> usize {
501          self.private_variables.len()
502      }
503  
504      #[inline]
505      fn is_in_setup_mode(&self) -> bool {
506          false
507      }
508  }