From 880541e43b7544248fc99433d74e1f0ece3b449d Mon Sep 17 00:00:00 2001 From: Arasu Arun <8504041+arasuarun@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:03:30 -0400 Subject: [PATCH] Move to bellpepper (#3) * cosmetic change from bellperson to bellpepper_core * add ec_gpu_gen for DensityTracking in solver.rs * changed module name to bellpepper * added macro for SpartanShape impl * updated solver.rs, removed DensityTracker * copied over test_shape_cs from nova * cargo fmt * Update Cargo.toml dependencies * Update Cargo.toml --------- Co-authored-by: Srinath Setty --- Cargo.toml | 5 +- src/{bellperson => bellpepper}/mod.rs | 5 +- src/{bellperson => bellpepper}/r1cs.rs | 85 +++--- src/{bellperson => bellpepper}/shape_cs.rs | 2 +- src/{bellperson => bellpepper}/solver.rs | 122 ++++---- src/bellpepper/test_shape_cs.rs | 326 +++++++++++++++++++++ src/lib.rs | 9 +- 7 files changed, 442 insertions(+), 112 deletions(-) rename src/{bellperson => bellpepper}/mod.rs (93%) rename src/{bellperson => bellpepper}/r1cs.rs (62%) rename src/{bellperson => bellpepper}/shape_cs.rs (98%) rename src/{bellperson => bellpepper}/solver.rs (61%) create mode 100644 src/bellpepper/test_shape_cs.rs diff --git a/Cargo.toml b/Cargo.toml index c69ab10..a040868 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license-file = "LICENSE" keywords = ["zkSNARKs", "cryptography", "proofs"] [dependencies] -bellperson = { version = "0.25", default-features = false } +bellpepper-core = "0.2.1" ff = { version = "0.13.0", features = ["derive"] } digest = "0.10" sha3 = "0.10" @@ -21,7 +21,6 @@ rand_chacha = "0.3" itertools = "0.11" subtle = "2.5" pasta_curves = { version = "0.5", features = ["repr-c", "serde"] } -neptune = { version = "10.0.0", default-features = false } generic-array = "0.14" num-bigint = { version = "0.4", features = ["serde", "rand"] } num-traits = "0.2" @@ -54,6 +53,4 @@ proptest = "1.2.0" default = [] # Compiles in portable mode, w/o ISA extensions => binary can be executed on all systems. portable = ["pasta-msm/portable"] -cuda = ["neptune/cuda", "neptune/pasta", "neptune/arity24"] -opencl = ["neptune/opencl", "neptune/pasta", "neptune/arity24"] flamegraph = ["pprof/flamegraph", "pprof/criterion"] diff --git a/src/bellperson/mod.rs b/src/bellpepper/mod.rs similarity index 93% rename from src/bellperson/mod.rs rename to src/bellpepper/mod.rs index 8687dbe..bc2b5b7 100644 --- a/src/bellperson/mod.rs +++ b/src/bellpepper/mod.rs @@ -5,18 +5,19 @@ pub mod r1cs; pub mod shape_cs; pub mod solver; +pub mod test_shape_cs; #[cfg(test)] mod tests { use crate::{ - bellperson::{ + bellpepper::{ r1cs::{SpartanShape, SpartanWitness}, shape_cs::ShapeCS, solver::SatisfyingAssignment, }, traits::Group, }; - use bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError}; + use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::PrimeField; fn synthesize_alloc_bit>( diff --git a/src/bellperson/r1cs.rs b/src/bellpepper/r1cs.rs similarity index 62% rename from src/bellperson/r1cs.rs rename to src/bellpepper/r1cs.rs index 3964a50..62806a0 100644 --- a/src/bellperson/r1cs.rs +++ b/src/bellpepper/r1cs.rs @@ -2,14 +2,14 @@ #![allow(non_snake_case)] -use super::{shape_cs::ShapeCS, solver::SatisfyingAssignment}; +use super::{shape_cs::ShapeCS, solver::SatisfyingAssignment, test_shape_cs::TestShapeCS}; use crate::{ errors::SpartanError, r1cs::{R1CSInstance, R1CSShape, R1CSWitness, R1CS}, traits::Group, CommitmentKey, }; -use bellperson::{Index, LinearCombination}; +use bellpepper_core::{Index, LinearCombination}; use ff::PrimeField; /// `SpartanWitness` provide a method for acquiring an `R1CSInstance` and `R1CSWitness` from implementers. @@ -48,46 +48,53 @@ where } } -impl SpartanShape for ShapeCS -where - G::Scalar: PrimeField, -{ - fn r1cs_shape(&self) -> (R1CSShape, CommitmentKey) { - let mut A: Vec<(usize, usize, G::Scalar)> = Vec::new(); - let mut B: Vec<(usize, usize, G::Scalar)> = Vec::new(); - let mut C: Vec<(usize, usize, G::Scalar)> = Vec::new(); - - let mut num_cons_added = 0; - let mut X = (&mut A, &mut B, &mut C, &mut num_cons_added); - - let num_inputs = self.num_inputs(); - let num_constraints = self.num_constraints(); - let num_vars = self.num_aux(); - - for constraint in self.constraints.iter() { - add_constraint( - &mut X, - num_vars, - &constraint.0, - &constraint.1, - &constraint.2, - ); +macro_rules! impl_spartan_shape { + ( $name:ident) => { + impl SpartanShape for $name + where + G::Scalar: PrimeField, + { + fn r1cs_shape(&self) -> (R1CSShape, CommitmentKey) { + let mut A: Vec<(usize, usize, G::Scalar)> = Vec::new(); + let mut B: Vec<(usize, usize, G::Scalar)> = Vec::new(); + let mut C: Vec<(usize, usize, G::Scalar)> = Vec::new(); + + let mut num_cons_added = 0; + let mut X = (&mut A, &mut B, &mut C, &mut num_cons_added); + + let num_inputs = self.num_inputs(); + let num_constraints = self.num_constraints(); + let num_vars = self.num_aux(); + + for constraint in self.constraints.iter() { + add_constraint( + &mut X, + num_vars, + &constraint.0, + &constraint.1, + &constraint.2, + ); + } + + assert_eq!(num_cons_added, num_constraints); + + let S: R1CSShape = { + // Don't count One as an input for shape's purposes. + let res = R1CSShape::new(num_constraints, num_vars, num_inputs - 1, &A, &B, &C); + res.unwrap() + }; + + let ck = R1CS::::commitment_key(&S); + + (S, ck) + } } - - assert_eq!(num_cons_added, num_constraints); - - let S: R1CSShape = { - // Don't count One as an input for shape's purposes. - let res = R1CSShape::new(num_constraints, num_vars, num_inputs - 1, &A, &B, &C); - res.unwrap() - }; - - let ck = R1CS::::commitment_key(&S); - - (S, ck) - } + }; } +impl_spartan_shape!(ShapeCS); +impl_spartan_shape!(TestShapeCS); + fn add_constraint( X: &mut ( &mut Vec<(usize, usize, S)>, diff --git a/src/bellperson/shape_cs.rs b/src/bellpepper/shape_cs.rs similarity index 98% rename from src/bellperson/shape_cs.rs rename to src/bellpepper/shape_cs.rs index bb96463..5a50649 100644 --- a/src/bellperson/shape_cs.rs +++ b/src/bellpepper/shape_cs.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::traits::Group; -use bellperson::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; +use bellpepper_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; use core::fmt::Write; use ff::{Field, PrimeField}; diff --git a/src/bellperson/solver.rs b/src/bellpepper/solver.rs similarity index 61% rename from src/bellperson/solver.rs rename to src/bellpepper/solver.rs index 0eaf088..8b1261c 100644 --- a/src/bellperson/solver.rs +++ b/src/bellpepper/solver.rs @@ -3,26 +3,13 @@ use crate::traits::Group; use ff::{Field, PrimeField}; -use bellperson::{ - multiexp::DensityTracker, ConstraintSystem, Index, LinearCombination, SynthesisError, Variable, -}; +use bellpepper_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; /// A `ConstraintSystem` which calculates witness values for a concrete instance of an R1CS circuit. -#[derive(PartialEq)] pub struct SatisfyingAssignment where G::Scalar: PrimeField, { - // Density of queries - a_aux_density: DensityTracker, - b_input_density: DensityTracker, - b_aux_density: DensityTracker, - - // Evaluations of A, B, C polynomials - a: Vec, - b: Vec, - c: Vec, - // Assignments of variables pub(crate) input_assignment: Vec, pub(crate) aux_assignment: Vec, @@ -36,39 +23,18 @@ where fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt .debug_struct("SatisfyingAssignment") - .field("a_aux_density", &self.a_aux_density) - .field("b_input_density", &self.b_input_density) - .field("b_aux_density", &self.b_aux_density) - .field( - "a", - &self - .a - .iter() - .map(|v| format!("Fr({v:?})")) - .collect::>(), - ) - .field( - "b", - &self - .b - .iter() - .map(|v| format!("Fr({v:?})")) - .collect::>(), - ) - .field( - "c", - &self - .c - .iter() - .map(|v| format!("Fr({v:?})")) - .collect::>(), - ) .field("input_assignment", &self.input_assignment) .field("aux_assignment", &self.aux_assignment) .finish() } } +impl PartialEq for SatisfyingAssignment { + fn eq(&self, other: &SatisfyingAssignment) -> bool { + self.input_assignment == other.input_assignment && self.aux_assignment == other.aux_assignment + } +} + impl ConstraintSystem for SatisfyingAssignment where G::Scalar: PrimeField, @@ -77,16 +43,8 @@ where fn new() -> Self { let input_assignment = vec![G::Scalar::ONE]; - let mut d = DensityTracker::new(); - d.add_element(); Self { - a_aux_density: DensityTracker::new(), - b_input_density: d, - b_aux_density: DensityTracker::new(), - a: vec![], - b: vec![], - c: vec![], input_assignment, aux_assignment: vec![], } @@ -99,8 +57,6 @@ where AR: Into, { self.aux_assignment.push(f()?); - self.a_aux_density.add_element(); - self.b_aux_density.add_element(); Ok(Variable(Index::Aux(self.aux_assignment.len() - 1))) } @@ -112,7 +68,6 @@ where AR: Into, { self.input_assignment.push(f()?); - self.b_input_density.add_element(); Ok(Variable(Index::Input(self.input_assignment.len() - 1))) } @@ -148,18 +103,61 @@ where true } - fn extend(&mut self, other: Self) { - self.a_aux_density.extend(other.a_aux_density, false); - self.b_input_density.extend(other.b_input_density, true); - self.b_aux_density.extend(other.b_aux_density, false); - - self.a.extend(other.a); - self.b.extend(other.b); - self.c.extend(other.c); - + fn extend(&mut self, other: &Self) { self.input_assignment // Skip first input, which must have been a temporarily allocated one variable. .extend(&other.input_assignment[1..]); - self.aux_assignment.extend(other.aux_assignment); + self.aux_assignment.extend(other.aux_assignment.clone()); + } + + fn is_witness_generator(&self) -> bool { + true + } + + fn extend_inputs(&mut self, new_inputs: &[G::Scalar]) { + self.input_assignment.extend(new_inputs); + } + + fn extend_aux(&mut self, new_aux: &[G::Scalar]) { + self.aux_assignment.extend(new_aux); + } + + fn allocate_empty( + &mut self, + aux_n: usize, + inputs_n: usize, + ) -> (&mut [G::Scalar], &mut [G::Scalar]) { + let allocated_aux = { + let i = self.aux_assignment.len(); + self.aux_assignment.resize(aux_n + i, G::Scalar::ZERO); + &mut self.aux_assignment[i..] + }; + + let allocated_inputs = { + let i = self.input_assignment.len(); + self.input_assignment.resize(inputs_n + i, G::Scalar::ZERO); + &mut self.input_assignment[i..] + }; + + (allocated_aux, allocated_inputs) + } + + fn inputs_slice(&self) -> &[G::Scalar] { + &self.input_assignment + } + + fn aux_slice(&self) -> &[G::Scalar] { + &self.aux_assignment + } +} + +#[allow(dead_code)] +impl SatisfyingAssignment { + pub fn scalar_inputs(&self) -> Vec { + self.input_assignment.clone() + } + + pub fn scalar_aux(&self) -> Vec { + self.aux_assignment.clone() } } diff --git a/src/bellpepper/test_shape_cs.rs b/src/bellpepper/test_shape_cs.rs new file mode 100644 index 0000000..cf43a7a --- /dev/null +++ b/src/bellpepper/test_shape_cs.rs @@ -0,0 +1,326 @@ +//! Support for generating R1CS shape using bellpepper. +//! `TestShapeCS` implements a superset of `ShapeCS`, adding non-trivial namespace support for use in testing. + +use std::{ + cmp::Ordering, + collections::{BTreeMap, HashMap}, +}; + +use crate::traits::Group; +use bellpepper_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; +use core::fmt::Write; +use ff::{Field, PrimeField}; + +#[derive(Clone, Copy)] +struct OrderedVariable(Variable); + +#[derive(Debug)] +enum NamedObject { + Constraint(usize), + Var(Variable), + Namespace, +} + +impl Eq for OrderedVariable {} +impl PartialEq for OrderedVariable { + fn eq(&self, other: &OrderedVariable) -> bool { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) | (Index::Aux(ref a), Index::Aux(ref b)) => a == b, + _ => false, + } + } +} +impl PartialOrd for OrderedVariable { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for OrderedVariable { + fn cmp(&self, other: &Self) -> Ordering { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) | (Index::Aux(ref a), Index::Aux(ref b)) => { + a.cmp(b) + } + (Index::Input(_), Index::Aux(_)) => Ordering::Less, + (Index::Aux(_), Index::Input(_)) => Ordering::Greater, + } + } +} + +#[allow(clippy::upper_case_acronyms)] +/// `TestShapeCS` is a `ConstraintSystem` for creating `R1CSShape`s for a circuit. +pub struct TestShapeCS +where + G::Scalar: PrimeField + Field, +{ + named_objects: HashMap, + current_namespace: Vec, + #[allow(clippy::type_complexity)] + /// All constraints added to the `TestShapeCS`. + pub constraints: Vec<( + LinearCombination, + LinearCombination, + LinearCombination, + String, + )>, + inputs: Vec, + aux: Vec, +} + +fn proc_lc( + terms: &LinearCombination, +) -> BTreeMap { + let mut map = BTreeMap::new(); + for (var, &coeff) in terms.iter() { + map + .entry(OrderedVariable(var)) + .or_insert_with(|| Scalar::ZERO) + .add_assign(&coeff); + } + + // Remove terms that have a zero coefficient to normalize + let mut to_remove = vec![]; + for (var, coeff) in map.iter() { + if coeff.is_zero().into() { + to_remove.push(*var) + } + } + + for var in to_remove { + map.remove(&var); + } + + map +} + +impl TestShapeCS +where + G::Scalar: PrimeField, +{ + #[allow(unused)] + /// Create a new, default `TestShapeCS`, + pub fn new() -> Self { + TestShapeCS::default() + } + + /// Returns the number of constraints defined for this `TestShapeCS`. + pub fn num_constraints(&self) -> usize { + self.constraints.len() + } + + /// Returns the number of inputs defined for this `TestShapeCS`. + pub fn num_inputs(&self) -> usize { + self.inputs.len() + } + + /// Returns the number of aux inputs defined for this `TestShapeCS`. + pub fn num_aux(&self) -> usize { + self.aux.len() + } + + /// Print all public inputs, aux inputs, and constraint names. + #[allow(dead_code)] + pub fn pretty_print_list(&self) -> Vec { + let mut result = Vec::new(); + + for input in &self.inputs { + result.push(format!("INPUT {input}")); + } + for aux in &self.aux { + result.push(format!("AUX {aux}")); + } + + for (_a, _b, _c, name) in &self.constraints { + result.push(name.to_string()); + } + + result + } + + /// Print all iputs and a detailed representation of each constraint. + #[allow(dead_code)] + pub fn pretty_print(&self) -> String { + let mut s = String::new(); + + for input in &self.inputs { + writeln!(s, "INPUT {}", &input).unwrap() + } + + let negone = -::ONE; + + let powers_of_two = (0..G::Scalar::NUM_BITS) + .map(|i| G::Scalar::from(2u64).pow_vartime([u64::from(i)])) + .collect::>(); + + let pp = |s: &mut String, lc: &LinearCombination| { + s.push('('); + let mut is_first = true; + for (var, coeff) in proc_lc::(lc) { + if coeff == negone { + s.push_str(" - ") + } else if !is_first { + s.push_str(" + ") + } + is_first = false; + + if coeff != ::ONE && coeff != negone { + for (i, x) in powers_of_two.iter().enumerate() { + if x == &coeff { + write!(s, "2^{i} . ").unwrap(); + break; + } + } + + write!(s, "{coeff:?} . ").unwrap() + } + + match var.0.get_unchecked() { + Index::Input(i) => { + write!(s, "`I{}`", &self.inputs[i]).unwrap(); + } + Index::Aux(i) => { + write!(s, "`A{}`", &self.aux[i]).unwrap(); + } + } + } + if is_first { + // Nothing was visited, print 0. + s.push('0'); + } + s.push(')'); + }; + + for (a, b, c, name) in &self.constraints { + s.push('\n'); + + write!(s, "{name}: ").unwrap(); + pp(&mut s, a); + write!(s, " * ").unwrap(); + pp(&mut s, b); + s.push_str(" = "); + pp(&mut s, c); + } + + s.push('\n'); + + s + } + + /// Associate `NamedObject` with `path`. + /// `path` must not already have an associated object. + 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 Default for TestShapeCS +where + G::Scalar: PrimeField, +{ + fn default() -> Self { + let mut map = HashMap::new(); + map.insert("ONE".into(), NamedObject::Var(TestShapeCS::::one())); + TestShapeCS { + named_objects: map, + current_namespace: vec![], + constraints: vec![], + inputs: vec![String::from("ONE")], + aux: vec![], + } + } +} + +impl ConstraintSystem for TestShapeCS +where + G::Scalar: PrimeField, +{ + type Root = Self; + + fn alloc(&mut self, annotation: A, _f: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, + { + let path = compute_path(&self.current_namespace, &annotation().into()); + self.aux.push(path); + + Ok(Variable::new_unchecked(Index::Aux(self.aux.len() - 1))) + } + + fn alloc_input(&mut self, annotation: A, _f: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, + { + let path = compute_path(&self.current_namespace, &annotation().into()); + self.inputs.push(path); + + Ok(Variable::new_unchecked(Index::Input(self.inputs.len() - 1))) + } + + fn enforce(&mut self, annotation: A, a: LA, b: LB, c: LC) + where + A: FnOnce() -> AR, + AR: Into, + LA: FnOnce(LinearCombination) -> LinearCombination, + LB: FnOnce(LinearCombination) -> LinearCombination, + LC: FnOnce(LinearCombination) -> LinearCombination, + { + let path = compute_path(&self.current_namespace, &annotation().into()); + let index = self.constraints.len(); + self.set_named_obj(path.clone(), NamedObject::Constraint(index)); + + let a = a(LinearCombination::zero()); + let b = b(LinearCombination::zero()); + let c = c(LinearCombination::zero()); + + self.constraints.push((a, b, c, path)); + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + 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); + } + + fn pop_namespace(&mut self) { + assert!(self.current_namespace.pop().is_some()); + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } +} + +fn compute_path(ns: &[String], this: &str) -> String { + assert!( + !this.chars().any(|a| a == '/'), + "'/' is not allowed in names" + ); + + let mut name = String::new(); + + let mut needs_separation = false; + for ns in ns.iter().chain(Some(this.to_string()).iter()) { + if needs_separation { + name += "/"; + } + + name += ns; + needs_separation = true; + } + + name +} diff --git a/src/lib.rs b/src/lib.rs index 39f2ba4..92c0b9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ #![forbid(unsafe_code)] // private modules -mod bellperson; +mod bellpepper; mod r1cs; // public modules @@ -21,12 +21,12 @@ pub mod provider; pub mod spartan; pub mod traits; -use crate::bellperson::{ +use crate::bellpepper::{ r1cs::{SpartanShape, SpartanWitness}, shape_cs::ShapeCS, solver::SatisfyingAssignment, }; -use ::bellperson::{Circuit, ConstraintSystem}; +use bellpepper_core::{Circuit, ConstraintSystem}; use core::marker::PhantomData; use errors::SpartanError; use ff::Field; @@ -99,6 +99,7 @@ impl, C: Circuit> SNARK, circuit: C) -> Result { let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); let _ = circuit.synthesize(&mut cs); + let (u, w) = cs .r1cs_instance_and_witness(&pk.S, &pk.ck) .map_err(|_e| SpartanError::UnSat)?; @@ -168,7 +169,7 @@ fn compute_digest(o: &T) -> G::Scalar { mod tests { use super::*; use crate::provider::bn256_grumpkin::bn256; - use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError}; + use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; use ff::PrimeField;