use crate::traits::Engine;
use bellpepper_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable};
use ff::{Field, PrimeField};
#[cfg(debug_assertions)]
use std::collections::HashMap;
#[cfg(debug_assertions)]
#[derive(Debug)]
enum NamedObject {
Constraint,
Var,
Namespace,
}
pub struct ShapeCS<E: Engine>
where
E::Scalar: PrimeField + Field,
{
#[cfg(debug_assertions)]
named_objects: HashMap<String, NamedObject>,
#[cfg(debug_assertions)]
current_namespace: Vec<String>,
#[cfg(not(debug_assertions))]
namespace_depth: usize,
pub constraints: Vec<(
LinearCombination<E::Scalar>,
LinearCombination<E::Scalar>,
LinearCombination<E::Scalar>,
)>,
#[cfg(debug_assertions)]
inputs: Vec<String>,
#[cfg(debug_assertions)]
aux: Vec<String>,
#[cfg(not(debug_assertions))]
num_inputs: usize,
#[cfg(not(debug_assertions))]
num_aux: usize,
}
impl<E: Engine> ShapeCS<E>
where
E::Scalar: PrimeField,
{
pub fn new() -> Self {
ShapeCS::default()
}
pub fn num_constraints(&self) -> usize {
self.constraints.len()
}
pub fn num_inputs(&self) -> usize {
#[cfg(debug_assertions)]
{
self.inputs.len()
}
#[cfg(not(debug_assertions))]
{
self.num_inputs
}
}
pub fn num_aux(&self) -> usize {
#[cfg(debug_assertions)]
{
self.aux.len()
}
#[cfg(not(debug_assertions))]
{
self.num_aux
}
}
#[cfg(debug_assertions)]
fn set_named_obj(&mut self, path: String, to: NamedObject) {
assert!(
!self.named_objects.contains_key(&path),
"tried to create object at existing path: {path}"
);
self.named_objects.insert(path, to);
}
}
impl<E: Engine> Default for ShapeCS<E>
where
E::Scalar: PrimeField,
{
fn default() -> Self {
ShapeCS {
#[cfg(debug_assertions)]
named_objects: {
let mut map = HashMap::new();
map.insert("ONE".into(), NamedObject::Var);
map
},
#[cfg(debug_assertions)]
current_namespace: vec![],
#[cfg(not(debug_assertions))]
namespace_depth: 0,
constraints: vec![],
#[cfg(debug_assertions)]
inputs: vec![String::from("ONE")],
#[cfg(debug_assertions)]
aux: vec![],
#[cfg(not(debug_assertions))]
num_inputs: 1,
#[cfg(not(debug_assertions))]
num_aux: 0,
}
}
}
impl<E: Engine> ConstraintSystem<E::Scalar> for ShapeCS<E>
where
E::Scalar: PrimeField,
{
type Root = Self;
fn alloc<F, A, AR>(&mut self, _annotation: A, _f: F) -> Result<Variable, SynthesisError>
where
F: FnOnce() -> Result<E::Scalar, SynthesisError>,
A: FnOnce() -> AR,
AR: Into<String>,
{
#[cfg(debug_assertions)]
{
let path = compute_path(&self.current_namespace, &_annotation().into());
self.aux.push(path);
Ok(Variable::new_unchecked(Index::Aux(self.aux.len() - 1)))
}
#[cfg(not(debug_assertions))]
{
let idx = self.num_aux;
self.num_aux += 1;
Ok(Variable::new_unchecked(Index::Aux(idx)))
}
}
fn alloc_input<F, A, AR>(&mut self, _annotation: A, _f: F) -> Result<Variable, SynthesisError>
where
F: FnOnce() -> Result<E::Scalar, SynthesisError>,
A: FnOnce() -> AR,
AR: Into<String>,
{
#[cfg(debug_assertions)]
{
let path = compute_path(&self.current_namespace, &_annotation().into());
self.inputs.push(path);
Ok(Variable::new_unchecked(Index::Input(self.inputs.len() - 1)))
}
#[cfg(not(debug_assertions))]
{
let idx = self.num_inputs;
self.num_inputs += 1;
Ok(Variable::new_unchecked(Index::Input(idx)))
}
}
fn enforce<A, AR, LA, LB, LC>(&mut self, _annotation: A, a: LA, b: LB, c: LC)
where
A: FnOnce() -> AR,
AR: Into<String>,
LA: FnOnce(LinearCombination<E::Scalar>) -> LinearCombination<E::Scalar>,
LB: FnOnce(LinearCombination<E::Scalar>) -> LinearCombination<E::Scalar>,
LC: FnOnce(LinearCombination<E::Scalar>) -> LinearCombination<E::Scalar>,
{
#[cfg(debug_assertions)]
{
let path = compute_path(&self.current_namespace, &_annotation().into());
self.set_named_obj(path, NamedObject::Constraint);
}
let a = a(LinearCombination::zero());
let b = b(LinearCombination::zero());
let c = c(LinearCombination::zero());
self.constraints.push((a, b, c));
}
fn push_namespace<NR, N>(&mut self, _name_fn: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
#[cfg(debug_assertions)]
{
let name = _name_fn().into();
let path = compute_path(&self.current_namespace, &name);
self.set_named_obj(path, NamedObject::Namespace);
self.current_namespace.push(name);
}
#[cfg(not(debug_assertions))]
{
self.namespace_depth += 1;
}
}
fn pop_namespace(&mut self) {
#[cfg(debug_assertions)]
{
assert!(self.current_namespace.pop().is_some());
}
#[cfg(not(debug_assertions))]
{
assert!(self.namespace_depth > 0);
self.namespace_depth -= 1;
}
}
fn get_root(&mut self) -> &mut Self::Root {
self
}
}
#[cfg(debug_assertions)]
fn compute_path(ns: &[String], this: &str) -> String {
assert!(
!this.chars().any(|a| a == '/'),
"'/' is not allowed in names"
);
let mut name = ns.join("/");
if !name.is_empty() {
name.push('/');
}
name.push_str(this);
name
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
#[cfg(debug_assertions)]
fn test_compute_path() {
let ns = vec!["path".to_string(), "to".to_string(), "dir".to_string()];
let this = "file";
assert_eq!(compute_path(&ns, this), "path/to/dir/file");
let ns = vec!["".to_string(), "".to_string(), "".to_string()];
let this = "file";
assert_eq!(compute_path(&ns, this), "///file");
}
#[test]
#[should_panic(expected = "'/' is not allowed in names")]
#[cfg(debug_assertions)]
fn test_compute_path_invalid() {
let ns = vec!["path".to_string(), "to".to_string(), "dir".to_string()];
let this = "fi/le";
compute_path(&ns, this);
}
}