diff --git a/src/circuit.rs b/src/circuit.rs index 7876e54..1abaa9d 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -3,7 +3,7 @@ use elsa::map::FrozenMap; use ff::PrimeField; use itertools::Itertools; -use crate::{witness::{CSWtns, ProtostarWtns, ProtostarLhsWtns}, gate::{Gatebb, Gate}, constraint_system::{Variable, ConstraintSystem, CommitKind, Visibility, CS, Constraint}, utils::poly_utils::check_poly, circuit::circuit_operations::{AttachedAdvice, AttachedPolynomialAdvice, AttachedAdvicePub}, external_interface::{RunIndex, RunAllocator} }; +use crate::{witness::{CSWtns, ProtostarWtns, ProtostarLhsWtns}, gate::{Gatebb, Gate}, constraint_system::{Variable, ProtoGalaxyConstraintSystem, CommitKind, Visibility, CS, Constraint}, utils::poly_utils::check_poly, circuit::circuit_operations::{AttachedAdvice, AttachedPolynomialAdvice, AttachedAdvicePub}, external_interface::{RunIndex, RunAllocator} }; use self::circuit_operations::CircuitOperation; @@ -184,7 +184,7 @@ pub mod circuit_operations { pub struct Circuit<'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> { gate_registry: FrozenMap>, - pub cs: ConstraintSystem<'circuit, F, G>, + pub cs: ProtoGalaxyConstraintSystem<'circuit, F, G>, ops: Vec + 'circuit>>>, max_degree: usize, // round_counter : usize, @@ -197,7 +197,7 @@ where G: Gate<'circuit, F> + From>, { pub fn new(max_degree: usize, num_rounds: usize) -> Self { - let cs = ConstraintSystem::new(num_rounds, max_degree); + let cs = ProtoGalaxyConstraintSystem::new(num_rounds); let mut prep = Self { gate_registry: FrozenMap::new(), cs, @@ -325,7 +325,7 @@ where } pub struct ConstructedCircuit<'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> { - circuit: Circuit<'circuit, F, G>, + pub circuit: Circuit<'circuit, F, G>, run_allocator: RefCell, } @@ -342,6 +342,18 @@ impl<'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> fn deallocate<'constructed>(&'constructed self, idx: RunIndex) { self.run_allocator.borrow_mut().deallocate(idx); } + + pub fn perepare_protostar_chellanges(&self, mut beta: F) -> Vec { + let m = self.circuit.cs.constr_spec().num_nonlinear_constraints; + let mut p = 1; + let mut protostar_challenges = vec![]; + while p < m { + protostar_challenges.push(beta); + beta = beta * beta; + p = p * 2; + } + protostar_challenges + } } pub struct CircuitRun<'constructed, 'circuit, F: PrimeField, G: Gate<'circuit, F> + From>>{ @@ -368,15 +380,8 @@ where } } - pub fn end(&self, mut beta: F) -> ProtostarWtns<'constructed, 'circuit, F, G> { - let m = self.constructed.circuit.cs.constr_spec().num_nonlinear_constraints; - let mut p = 1; - let mut protostar_challenges = vec![]; - while p <= m { - protostar_challenges.push(beta); - beta = beta * beta; - p = p * 2; - } + pub fn end(&self, beta: F) -> ProtostarWtns { + let protostar_challenges = self.constructed.perepare_protostar_chellanges(beta); let mut pubs = vec![]; let mut round_wtns = vec![]; @@ -391,7 +396,6 @@ where round_wtns, pubs, protostar_challenges, - circuit: self.constructed, }, error: F::ZERO, } @@ -415,7 +419,7 @@ where self.constructed.circuit.cs.iter_constraints() } - pub fn finish(self) -> (CSWtns<'circuit, F, G>, &'constructed ConstraintSystem<'circuit, F, G>) { + pub fn finish(self) -> (CSWtns<'circuit, F, G>, &'constructed ProtoGalaxyConstraintSystem<'circuit, F, G>) { let Self {constructed, cs, round_counter: _, run_idx} = self; constructed.deallocate(run_idx); diff --git a/src/constraint_system.rs b/src/constraint_system.rs index 07cc11c..1feac67 100644 --- a/src/constraint_system.rs +++ b/src/constraint_system.rs @@ -1,6 +1,7 @@ -use std::marker::PhantomData; +use std::{marker::PhantomData, collections::BTreeMap}; use ff::PrimeField; +use itertools::Itertools; use crate::{gate::Gate, circuit::ExternalValue}; @@ -46,24 +47,18 @@ pub struct Constraint<'c, F: PrimeField, G: Gate<'c, F>>{ #[derive(Debug, Clone)] struct ConstraintGroup<'c, F: PrimeField, G: Gate<'c, F>> { pub entries: Vec>, - #[allow(dead_code)] - pub kind: CommitKind, pub num_rhs: usize, - pub max_degree: usize, } impl<'c, F: PrimeField, G: Gate<'c, F>> ConstraintGroup<'c, F, G> { - pub fn new(kind: CommitKind, max_degree: usize) -> Self { + pub fn new() -> Self { Self { entries: Default::default(), - kind, num_rhs: Default::default(), - max_degree, } } pub fn constrain(&mut self, inputs: &[Variable], gate: G) { - assert!(gate.d() <= self.max_degree, "Constraint degree is too large for this group."); assert!(gate.i() == inputs.len(), "Invalid amount of arguments supplied."); self.num_rhs += gate.o(); @@ -75,7 +70,10 @@ impl<'c, F: PrimeField, G: Gate<'c, F>> ConstraintGroup<'c, F, G> { /// /// Any witness used for this constraint system has to at least comply with the spec. #[derive(Debug, Default, Clone, PartialEq)] -pub struct RoundWitnessSpec(pub usize, pub usize); +pub struct RoundWitnessSpec{ + pub pubs: usize, + pub privs: usize, +} /// Witness shape specification: a collection of specifications for each round /// @@ -94,7 +92,7 @@ pub struct ConstrSpec { pub max_degree: usize, } -pub trait CS<'c, F: PrimeField, G: Gate<'c, F>> { +pub trait CS<'c, F: PrimeField, G: Gate<'c, F>> { fn num_rounds(&self) -> usize; fn last_round(&self) -> usize { @@ -118,43 +116,42 @@ pub trait CS<'c, F: PrimeField, G: Gate<'c, F>> { fn extval(&mut self, size: usize) -> Vec>; } +/// This CS is made specifically for ProtoGalaxy. +/// We will probably make interface for this part #[derive(Debug, Clone)] -pub struct ConstraintSystem<'c, F: PrimeField, G: Gate<'c, F>> { - spec: WitnessSpec, - constraint_groups: [ConstraintGroup<'c, F, G>; 3], +pub struct ProtoGalaxyConstraintSystem<'c, F: PrimeField, G: Gate<'c, F>> { + pub spec: WitnessSpec, + pub max_degree: usize, + linear_constraints: ConstraintGroup<'c, F, G>, + non_linear_constraints: BTreeMap>, } -impl<'c, F: PrimeField, G: Gate<'c, F>> ConstraintSystem<'c, F, G> { - pub fn new(num_rounds: usize, max_degree: usize) -> Self { - let constraint_groups = [ - ConstraintGroup::new(CommitKind::Trivial, 0), // FIXME: correct max_degree - ConstraintGroup::new(CommitKind::Group, max_degree), - ConstraintGroup::new(CommitKind::Zero, 1), - ]; - +impl<'c, F: PrimeField, G: Gate<'c, F>> ProtoGalaxyConstraintSystem<'c, F, G> { + pub fn new(num_rounds: usize) -> Self { Self { spec: WitnessSpec{ round_specs: vec![RoundWitnessSpec::default(); num_rounds], num_exts: 0, num_ints: 0 }, - constraint_groups, - } - } - - /// A (short-lived) cursor to the constraint group of a given kind - fn constraint_group(&mut self, kind: CommitKind) -> &mut ConstraintGroup<'c, F, G> { - match kind { - CommitKind::Trivial => &mut self.constraint_groups[0], - CommitKind::Group => &mut self.constraint_groups[1], - CommitKind::Zero => &mut self.constraint_groups[2], + max_degree: 0, + linear_constraints: ConstraintGroup::new(), + non_linear_constraints: BTreeMap::new(), } } // would love to add this to the trait, but crab god said not yet // https://github.com/rust-lang/rust/issues/91611 pub fn iter_constraints(&self) -> impl Iterator> { - self.constraint_groups.iter().flat_map(|cg| cg.entries.iter()) + self.iter_linear_constraints().chain(self.iter_non_linear_constraints()) + } + + pub fn iter_linear_constraints(&self) -> impl Iterator> { + self.linear_constraints.entries.iter() + } + + pub fn iter_non_linear_constraints(&self) -> impl Iterator> { + self.non_linear_constraints.iter().flat_map(|(_, cg)| cg.entries.iter()) } } -impl<'c, F: PrimeField, G: Gate<'c, F>> CS<'c, F, G> for ConstraintSystem<'c, F, G> { +impl<'c, F: PrimeField, G: Gate<'c, F>> CS<'c, F, G> for ProtoGalaxyConstraintSystem<'c, F, G> { fn num_rounds(&self) -> usize { self.spec.round_specs.len() } @@ -168,22 +165,22 @@ impl<'c, F: PrimeField, G: Gate<'c, F>> CS<'c, F, G> for ConstraintSystem<'c, F, } fn constr_spec(&self) -> ConstrSpec { - let num_lin_constraints = self.constraint_groups[2].num_rhs; - let num_nonlinear_constraints = self.constraint_groups[1].num_rhs; - let max_degree = self.constraint_groups[1].max_degree; + let num_lin_constraints = self.linear_constraints.num_rhs; + let num_nonlinear_constraints = self.non_linear_constraints.iter().map(|(_, cg)| cg.num_rhs).sum(); + let max_degree = self.max_degree; ConstrSpec { num_lin_constraints, num_nonlinear_constraints, max_degree } } fn alloc_in_round(&mut self, round: usize, visibility: Visibility, size: usize) -> Vec { let prev = match visibility { Visibility::Public => { - let prev = self.spec.round_specs[round].0; - self.spec.round_specs[round].0 += size; + let prev = self.spec.round_specs[round].pubs; + self.spec.round_specs[round].pubs += size; prev }, Visibility::Private => { - let prev = self.spec.round_specs[round].1; - self.spec.round_specs[round].1 += size; + let prev = self.spec.round_specs[round].privs; + self.spec.round_specs[round].privs += size; prev }, }; @@ -191,8 +188,17 @@ impl<'c, F: PrimeField, G: Gate<'c, F>> CS<'c, F, G> for ConstraintSystem<'c, F, (prev..prev+size).into_iter().map(|index| Variable { visibility, round, index }).collect() } - fn constrain(&mut self, kind: CommitKind, inputs: &[Variable], gate: G) { - self.constraint_group(kind).constrain(inputs, gate); + fn constrain(&mut self, _: CommitKind, inputs: &[Variable], gate: G) { + self.max_degree = self.max_degree.max(gate.d()); + match gate.d().cmp(&1) { + std::cmp::Ordering::Less => panic!("Constraint of degree 0"), + std::cmp::Ordering::Equal => { + self.linear_constraints.constrain(inputs, gate) + }, + std::cmp::Ordering::Greater => { + self.non_linear_constraints.entry(gate.d()).or_insert(ConstraintGroup::new()).constrain(inputs, gate) + }, + } } fn extval(&mut self, size: usize) -> Vec> { diff --git a/src/folding/shape.rs b/src/folding/shape.rs index b04e9bd..ebe2f88 100644 --- a/src/folding/shape.rs +++ b/src/folding/shape.rs @@ -4,7 +4,7 @@ use ff::PrimeField; use halo2::halo2curves::CurveAffine; use itertools::Itertools; -use crate::{utils::arith_helper::{log2_ceil, ev}, constraint_system::{WitnessSpec, ConstraintSystem, CS, ConstrSpec}, gate::Gate, witness::Module}; +use crate::{utils::arith_helper::{log2_ceil, ev}, constraint_system::{WitnessSpec, ProtoGalaxyConstraintSystem, CS, ConstrSpec}, gate::Gate, witness::Module}; /// Encode value as field elements. pub trait FEncoding { @@ -19,14 +19,14 @@ pub struct Shape { } impl Shape { - pub fn new<'c,F:PrimeField,G:Gate<'c,F>>(c: &ConstraintSystem<'c, F, G>) -> Self { + pub fn new<'c,F:PrimeField,G:Gate<'c,F>>(c: &ProtoGalaxyConstraintSystem<'c, F, G>) -> Self { let wspec = c.witness_spec().clone(); let cspec = c.constr_spec().clone(); Self{wspec, cspec} } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ProtostarLhs> { pub round_commitments: Vec, pub pubs: Vec>, @@ -37,7 +37,7 @@ impl> ProtostarLhs { pub fn validate_shape(&self, shape: &Shape) { shape.wspec.round_specs.iter().zip_eq(self.pubs.iter()) .map(|(rspec,rpubs)|{ - assert_eq!(rspec.0,rpubs.len()) + assert_eq!(rspec.pubs, rpubs.len()) }).count(); assert_eq!(self.pubs.len(), self.round_commitments.len()); @@ -115,7 +115,7 @@ impl> Fold { lhs_acc.add_assign(diff); let lhs = lhs_acc; let nt = F::ONE-t; - let error = nt*error_acc + t*error_inc + t*nt*ev(&cross_terms, t); + let error = nt*error_acc + t*error_inc - t*nt*ev(&cross_terms, t); ProtostarInstance { lhs, error } } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9c646c9..88e9ea3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,4 +11,5 @@ pub mod utils; pub mod folding; pub mod test; pub mod gatelib; -pub mod external_interface; \ No newline at end of file +pub mod external_interface; +pub mod prover; \ No newline at end of file diff --git a/src/prover.rs b/src/prover.rs new file mode 100644 index 0000000..7b28ce1 --- /dev/null +++ b/src/prover.rs @@ -0,0 +1,266 @@ +use ff::{PrimeField, BatchInvert}; +use halo2::arithmetic::lagrange_interpolate; +use itertools::Itertools; + +use crate::{witness::ProtostarLhsWtns, gate::Gate, circuit::PolyOp, constraint_system::{ProtoGalaxyConstraintSystem, Visibility}, utils::{cross_terms_combination::{combine_cross_terms, EvalLayout}, field_precomp::FieldUtils}}; + +pub struct ProtoGalaxyProver { + +} + +impl ProtoGalaxyProver +{ + pub fn new() -> Self { + Self { + + } + } + + fn calculate_powers< + 'circuit, + F: PrimeField + FieldUtils, + G: Gate<'circuit, F> + From> + > ( + &self, + cs: &ProtoGalaxyConstraintSystem<'circuit, F, G>, + template: &ProtostarLhsWtns, + ) -> (Vec>, Vec>) { + let mut pubs_degrees: Vec> = template.pubs.iter().map(|v| v.iter().map(|_| 0).collect_vec()).collect_vec(); + let mut privs_degrees: Vec> = template.round_wtns.iter().map(|v| v.iter().map(|_| 0).collect_vec()).collect_vec(); + + for constraint in cs.iter_non_linear_constraints() { + for variable in &constraint.inputs { + match variable.visibility { + Visibility::Public => pubs_degrees[variable.round][variable.index] = pubs_degrees[variable.round][variable.index].max(constraint.gate.d()), + Visibility::Private => privs_degrees[variable.round][variable.index] = privs_degrees[variable.round][variable.index].max(constraint.gate.d()), + } + } + } + (pubs_degrees, privs_degrees) + } + + fn calculate_layout< + 'circuit, + F: PrimeField + FieldUtils, + G: Gate<'circuit, F> + From> + > ( + &self, + cs: &ProtoGalaxyConstraintSystem<'circuit, F, G>, + ) -> Vec { + let mut layout: Vec = vec![]; + for constraint in cs.iter_non_linear_constraints() { + if layout.len() == 0 || layout[layout.len() - 1].deg != constraint.gate.d() { + layout.push(EvalLayout{deg: constraint.gate.d(), amount: 0}) + } + layout.last_mut().unwrap().amount += constraint.gate.o(); + } + layout + } + + fn build_variable_combinations_storage(&self, degrees: &Vec>) -> Vec>> { + degrees.iter().map(|d| d.iter().map(|d| Vec::with_capacity(d + 1)).collect_vec()).collect_vec() + } + + fn fill_variable_combinations(&self, storage: &mut Vec>>, degrees: &Vec>, a: &Vec>, b: &Vec>) { + storage.iter_mut().zip_eq(degrees.iter()).zip_eq(a.iter().zip_eq(b.iter())).map(|((s, d), (a, b))| { + s.iter_mut().zip_eq(d.iter()).zip_eq(a.iter().zip_eq(b.iter())).map(|((res, d), (a, b))| { + if *d != 0 { // min gate degree here is 2 (were iterating over nonlinear) + res.push(*a); + res.push(*b); + let diff = *b - *a; + let mut base = *b; + for _ in 1..*d { + base += diff; + res.push(base) + } + } + }).last() + }).last(); + } + + fn combine_challenges(&self, a: &Vec, b: &Vec) -> Vec<[F; 2]> { + let pg_challenges = a.iter().zip_eq(b.iter()).map(|(a, b)| [a.clone(), b.clone()]).collect_vec(); + pg_challenges + } + + fn prepare_interpolation_points(&self, d: usize, log_ceil: usize) -> Vec { + (2..(d + log_ceil + 1)).map(|x| F::from(x as u64)).collect_vec() + } + + fn leave_quotient<'a, F: PrimeField>(&self, evals: &'a mut Vec) ->&'a [F]{ + let e_0 = evals[0]; + evals[0] = F::ZERO; + let mut e_next = evals[1]; + evals[1] = F::ZERO; + let diff = e_next - e_0; + let mut invs = (2..evals.len()).map(|i| F::from((i * (i - 1)) as u64)).collect_vec(); + invs.batch_invert(); + + for (eval, inv) in evals.iter_mut().skip(2).zip_eq(invs.iter()) { + e_next += diff; + *eval -= e_next; + *eval *= inv; + } + evals.split_at(2).1 + } + + fn evaluate< + 'circuit, + F: PrimeField + FieldUtils, + G: Gate<'circuit, F> + From> + > ( + &self, + cs: &ProtoGalaxyConstraintSystem<'circuit, F, G>, + pubs_combinations: &Vec>>, + privs_combinations: &Vec>>, + ) -> Vec{ + let mut evals = vec![]; + for constraint in cs.iter_non_linear_constraints() { + for d in 0..constraint.gate.d() + 1 { + evals.extend(constraint.gate.exec(&constraint.inputs.iter().map(|var| { + match var.visibility { + Visibility::Public => pubs_combinations[var.round][var.index][d], + Visibility::Private => privs_combinations[var.round][var.index][d], + } + }).collect_vec())) + } + } + evals + } + + pub fn prove<'circuit, F: PrimeField + FieldUtils, G: Gate<'circuit, F> + From>>( + &self, + a: &ProtostarLhsWtns, + b: &ProtostarLhsWtns, + cs: &ProtoGalaxyConstraintSystem<'circuit, F, G>, + ) -> Vec { + let (pubs_degrees, privs_degrees) = self.calculate_powers(cs, a); + let layout = self.calculate_layout(cs); + + let mut privs_combinations = self.build_variable_combinations_storage(&privs_degrees); + let mut pubs_combinations = self.build_variable_combinations_storage(&pubs_degrees); + + // ^ that might be moved to 'new' + + self.fill_variable_combinations(&mut privs_combinations, &privs_degrees, &a.round_wtns, &b.round_wtns); + self.fill_variable_combinations(&mut pubs_combinations, &pubs_degrees, &a.pubs, &b.pubs); + + let evals = self.evaluate(cs, &pubs_combinations, &privs_combinations); + let pg_challenges = self.combine_challenges(&a.protostar_challenges, &b.protostar_challenges); + let mut cross_terms = combine_cross_terms(evals, layout, pg_challenges); + let cross_terms = self.leave_quotient(&mut cross_terms); + + let points = self.prepare_interpolation_points(cs.max_degree, a.protostar_challenges.len()); + lagrange_interpolate(&points, cross_terms) + } +} + +#[cfg(test)] +mod test { + use std::rc::Rc; + + use ff::Field; + use group::Group; + use halo2::halo2curves::{bn256, grumpkin}; + use itertools::unfold; + use rand_core::OsRng; + use crate::{gate::Gatebb, circuit::{Circuit, Advice}, gadgets::input::input, witness::{Module, compute_error_term, ProtostarLhsWtns, ProtostarWtns}, folding::shape::{Fold, Shape}}; + + use super::*; + + #[test] + fn pg_prover() { + type F = bn256::Fr; + type C = bn256::G1Affine; + + let mut circuit = Circuit::new(4, 2); + let inputs = circuit.ext_val(4); + let input_vars = inputs.iter().map(|i| input(&mut circuit, *i, 0)).collect_vec(); + let mul_a_res = circuit.advice(1, Advice::new( + 2, + 1, + |args, _| vec![args[0] * args[1]] + ), vec![input_vars[0], input_vars[1]])[0]; + circuit.constrain(&[input_vars[0], input_vars[1], mul_a_res], Gatebb::::new(2, 3, 1, Rc::new(|args, _| + {let res = vec![args[0] * args[1] - args[2]]; res} + ), vec![])); + + let mul_b_res = circuit.advice(1, Advice::new( + 2, + 1, + |args, _| vec![args[0] * args[1]] + ), vec![input_vars[2], input_vars[3]])[0]; + circuit.constrain(&[input_vars[2], input_vars[3], mul_b_res], Gatebb::::new(2, 3, 1, Rc::new(|args, _| vec![args[0] * args[1] - args[2]]), vec![])); + + let sum_res = circuit.advice(1, Advice::new( + 2, + 1, + |args, _| vec![args[0] + args[1]] + ), vec![mul_a_res, mul_b_res])[0]; + circuit.constrain(&[mul_a_res, mul_b_res, sum_res], Gatebb::::new(1, 3, 1, Rc::new(|args, _| vec![args[0] + args[1] - args[2]]), vec![])); + + + let constructed = circuit.finalize(); + + let mut run_a = constructed.spawn(); + let mut run_b = constructed.spawn(); + + for i in inputs { + run_a.set_ext(i, F::random(OsRng)); + run_b.set_ext(i, F::random(OsRng)); + } + + run_a.execute(1); + run_b.execute(1); + + let beta_a = F::random(OsRng); + let beta_b = F::random(OsRng); + + let pgp = ProtoGalaxyProver::new(); + + let a_wtns = run_a.end(beta_a); + let b_wtns = run_b.end(beta_b); + + // Now we create random witnesses with same shape + + let mut a_wtns = ProtostarWtns::random_like(&mut OsRng, &a_wtns); + let mut b_wtns = ProtostarWtns::random_like(&mut OsRng, &b_wtns); + + let q = pgp.prove(&a_wtns.lhs, &b_wtns.lhs, &constructed.circuit.cs); + + let a_err = compute_error_term(&a_wtns.lhs, &constructed.circuit.cs); + let b_err = compute_error_term(&b_wtns.lhs, &constructed.circuit.cs); + a_wtns.error = a_err; + b_wtns.error = b_err; + + let t = F::random(OsRng); + let commitment_key = a_wtns.lhs.round_wtns.iter().map(|w| w.iter().map(|_| C::random(OsRng)).collect_vec()).collect_vec(); + + let mut fold_wtns = a_wtns.clone(); + fold_wtns.neg(); + fold_wtns.add_assign(b_wtns.clone()); + fold_wtns.scale(t); + fold_wtns.add_assign(a_wtns.clone()); + + let fold_wtns_commited = fold_wtns.commit(&commitment_key); + let fold_err = compute_error_term(&fold_wtns.lhs, &constructed.circuit.cs); + + let q_eval: F = unfold(F::ONE, |next| { + let tmp = *next; + *next = *next * t; + Some(tmp) + }).zip(q.iter()).map(|(pow, c)| c * pow).sum(); + + assert_eq!(fold_err, a_err + (b_err - a_err) * t + t * (t - F::ONE) * q_eval); + + let a_wtns_commited = a_wtns.commit(&commitment_key); + let b_wtns_commited = b_wtns.commit(&commitment_key); + + let mut fold = Fold::new(a_wtns_commited, b_wtns_commited, q, Shape::new(&constructed.circuit.cs)); + fold.challenge(t); + let folded_commited = fold.fold(); + + assert_eq!(folded_commited.lhs, fold_wtns_commited.lhs); + assert_eq!(fold_err, folded_commited.error); + } +} \ No newline at end of file diff --git a/src/utils/arith_helper.rs b/src/utils/arith_helper.rs index 3415a79..79d81ce 100644 --- a/src/utils/arith_helper.rs +++ b/src/utils/arith_helper.rs @@ -14,7 +14,7 @@ pub fn log2_ceil>(x: T) -> usize { ret } -pub fn ev(poly: &Vec, x: F) -> F { +pub fn ev(poly: &Vec, x: F) -> F { let mut ret = F::ZERO; let l = poly.len(); for i in 0..l { diff --git a/src/witness.rs b/src/witness.rs index 63a8fff..681b9df 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -4,8 +4,9 @@ use std::marker::PhantomData; use ff::PrimeField; use halo2::{halo2curves::CurveAffine, arithmetic::best_multiexp}; use itertools::Itertools; +use rand_core::RngCore; -use crate::{gate::Gate, constraint_system::{ConstraintSystem, Variable, CS, Visibility, WitnessSpec}, commitment::{CommitmentKey, CkWtns, CtRound, ErrGroup, CkRelaxed}, circuit::{ExternalValue, ConstructedCircuit, PolyOp}, utils::field_precomp::FieldUtils, folding::shape::{ProtostarLhs, ProtostarInstance}}; +use crate::{gate::Gate, constraint_system::{ProtoGalaxyConstraintSystem, Variable, CS, Visibility, WitnessSpec}, commitment::{CommitmentKey, CkWtns, CtRound, ErrGroup, CkRelaxed}, circuit::{ExternalValue, ConstructedCircuit, PolyOp}, utils::field_precomp::FieldUtils, folding::shape::{ProtostarLhs, ProtostarInstance}}; #[derive(Clone)] pub struct RoundWtns { @@ -31,14 +32,14 @@ pub struct CSWtns<'c, F: PrimeField, G: Gate<'c, F>> { impl<'c, F:PrimeField, G: Gate<'c, F>> CSWtns<'c, F, G>{ - pub fn new(cs: &ConstraintSystem<'c, F, G>) -> Self { + pub fn new(cs: &ProtoGalaxyConstraintSystem<'c, F, G>) -> Self { let mut wtns = vec![]; let WitnessSpec{round_specs, num_exts, num_ints} = cs.witness_spec(); for round_spec in round_specs { - wtns.push(RoundWtns{pubs: vec![None; round_spec.0], privs: vec![None; round_spec.1]}) + wtns.push(RoundWtns{pubs: vec![None; round_spec.pubs], privs: vec![None; round_spec.privs]}) } let ext_vals = repeat(None).take(*num_exts).collect(); @@ -146,67 +147,77 @@ pub trait Module { fn scale(&mut self, scale: F) -> (); } -pub struct ProtostarLhsWtns<'constructed, 'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> { +#[derive(Clone, PartialEq, Eq)] +pub struct ProtostarLhsWtns { pub round_wtns: Vec>, pub pubs: Vec>, pub protostar_challenges: Vec, - pub circuit: &'constructed ConstructedCircuit<'circuit, F, G>, } -impl<'constructed, 'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> ProtostarLhsWtns<'constructed, 'circuit, F, G> { - pub fn commit> (&self, commitment_key: Vec>) -> ProtostarLhs { +impl ProtostarLhsWtns { + pub fn commit> (&self, commitment_key: &Vec>) -> ProtostarLhs { ProtostarLhs { round_commitments: self.round_wtns.iter().zip_eq(commitment_key).map(|(wtns, ck)| best_multiexp(&wtns, &ck).into()).collect_vec(), pubs: self.pubs.clone(), protostar_challenges: self.protostar_challenges.clone(), } } + + pub fn random_like(mut rng: &mut RNG, other: &Self) -> Self { + Self { + round_wtns: other.round_wtns.iter().map(|r| r.iter().map(|_| F::random(&mut rng)).collect_vec()).collect_vec(), + pubs: other.pubs.iter().map(|r| r.iter().map(|_| F::random(&mut rng)).collect_vec()).collect_vec(), + protostar_challenges: other.protostar_challenges.iter().map(|_| F::random(&mut rng)).collect_vec(), + } + } } -impl<'constructed, 'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> Module for ProtostarLhsWtns<'constructed, 'circuit, F, G> { +impl Module for ProtostarLhsWtns { fn add_assign(&mut self, other: Self) -> () { - self.round_wtns.iter_mut().zip_eq(other.round_wtns.iter()).map(|(s, o)| { - s.iter_mut().zip_eq(o.iter()).map(|(s, o)| *s = *s + o) - }).last(); - self.pubs.iter_mut().zip_eq(other.pubs.iter()).map(|(s, o)| { - s.iter_mut().zip_eq(o.iter()).map(|(s, o)| *s = *s + o) - }).last(); - self.protostar_challenges.iter_mut().zip_eq(other.protostar_challenges.iter()).map(|(s, o)| { + self.round_wtns.iter_mut().zip_eq(other.round_wtns.iter()).for_each(|(s, o)| { + s.iter_mut().zip_eq(o.iter()).for_each(|(s, o)| *s = *s + o) + }); + self.pubs.iter_mut().zip_eq(other.pubs.iter()).for_each(|(s, o)| { + s.iter_mut().zip_eq(o.iter()).for_each(|(s, o)| *s = *s + o) + }); + self.protostar_challenges.iter_mut().zip_eq(other.protostar_challenges.iter()).for_each(|(s, o)| { *s = *s + o - }).last(); + }); } fn neg(&mut self) -> () { - self.round_wtns.iter_mut().map(|s| { - s.iter_mut().map(|s| *s = -*s) - }).last(); - self.pubs.iter_mut().map(|s| { - s.iter_mut().map(|s| *s = -*s) - }).last(); - self.protostar_challenges.iter_mut().map(|s| { + self.round_wtns.iter_mut().for_each(|s| { + s.iter_mut().for_each(|s| *s = -*s) + }); + self.pubs.iter_mut().for_each(|s| { + s.iter_mut().for_each(|s| *s = -*s) + }); + self.protostar_challenges.iter_mut().for_each(|s| { *s = -*s - }).last(); + }); } fn scale(&mut self, scale: F) -> () { - self.round_wtns.iter_mut().map(|s| { - s.iter_mut().map(|s| *s = *s * scale) - }).last(); - self.pubs.iter_mut().map(|s| { - s.iter_mut().map(|s| *s = *s * scale) - }).last(); - self.protostar_challenges.iter_mut().map(|s| { + self.round_wtns.iter_mut().for_each(|s| { + s.iter_mut().for_each(|s| *s = *s * scale) + }); + self.pubs.iter_mut().for_each(|s| { + s.iter_mut().for_each(|s| *s = *s * scale) + }); + self.protostar_challenges.iter_mut().for_each(|s| { *s = *s * scale - }).last(); + }); } } -pub struct ProtostarWtns<'constructed, 'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> { - pub lhs: ProtostarLhsWtns<'constructed, 'circuit, F, G>, + +#[derive(Clone, PartialEq, Eq)] +pub struct ProtostarWtns { + pub lhs: ProtostarLhsWtns, pub error: F } -impl<'constructed, 'circuit, F: PrimeField, G: Gate<'circuit, F> + From>> Module for ProtostarWtns<'constructed, 'circuit, F, G> { +impl Module for ProtostarWtns { fn add_assign(&mut self, other: Self) -> () { self.error += other.error; self.lhs.add_assign(other.lhs); @@ -223,11 +234,62 @@ impl<'constructed, 'circuit, F: PrimeField, G: Gate<'circuit, F> + From + From>> ProtostarWtns<'constructed, 'circuit, F, G> { - pub fn commit> (&self, commitment_key: Vec>) -> ProtostarInstance { +impl ProtostarWtns { + pub fn commit> (&self, commitment_key: &Vec>) -> ProtostarInstance { ProtostarInstance { lhs: self.lhs.commit(commitment_key), error: self.error, } } -} \ No newline at end of file + + pub fn random_like(mut rng: &mut RNG, other: &Self) -> Self { + Self { + lhs: ProtostarLhsWtns::random_like(rng, &other.lhs), + error: other.error, + } + } +} + +pub fn compute_error_term<'circuit, F: PrimeField, G: Gate<'circuit, F>>(wtns: &ProtostarLhsWtns, cs: &ProtoGalaxyConstraintSystem<'circuit, F, G>) -> F { + let betas = &wtns.protostar_challenges; + let mut results = vec![]; + for constr in cs.iter_non_linear_constraints() { + let input_values: Vec<_> = constr.inputs.iter().map(|&x| match x.visibility { + Visibility::Public => wtns.pubs[x.round][x.index], + Visibility::Private => wtns.round_wtns[x.round][x.index], + }).collect(); + results.extend(constr.gate.exec(&input_values)); + } + + assert!(betas.len() > 0, "No challenges supplied for error_term"); + let mut mid = 1 << (betas.len() - 1); + assert!(mid < results.len()); + assert!(mid * 2 >= results.len()); + + // example + // results : |.|.|.|.|.|.|.| + // idx : 0 1 2 3 4 5 6 + // mid : ^ + // beta^4 : | | | | |1|1|1| + // beta^2 : | | |1|1| | |1| + // beta^1 : | |1| |1| |1| | + // + // split at mid : |.|.|.|.| |.|.|.| + // old idx : 0 1 2 3 4 5 6 + // + // Now we multiply right half by beta^4 and element-wise add it to first half inplace + // + // results : | r0 + r4 * beta^4 | r1 + r5 * beta^4 | r2 + r6 * beta^4 | r3 | junk | junk | junk | + // + // Now we forget about junk und repeat with first half until single element is left. + + for beta_pow in betas.iter().rev() { + let (left, right) = results.split_at_mut(mid); + for (l, r) in left.iter_mut().zip(right.iter()) { + *l = *l + *r * beta_pow; + } + mid /= 2; + } + + results[0] +}