use ff::Field; use group::Curve; use std::iter; use super::{ circuit::{Advice, Assignment, Circuit, Column, ConstraintSystem, Fixed}, lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, Permutation, ProvingKey, }; use crate::arithmetic::{eval_polynomial, CurveAffine, FieldExt}; use crate::poly::{ commitment::{Blind, Params}, multiopen::{self, ProverQuery}, Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, }; use crate::transcript::TranscriptWrite; /// This creates a proof for the provided `circuit` when given the public /// parameters `params` and the proving key [`ProvingKey`] that was /// generated previously for the same circuit. pub fn create_proof, ConcreteCircuit: Circuit>( params: &Params, pk: &ProvingKey, circuits: &[ConcreteCircuit], instances: &[&[Polynomial]], transcript: &mut T, ) -> Result<(), Error> { for instance in instances.iter() { if instance.len() != pk.vk.cs.num_instance_columns { return Err(Error::IncompatibleParams); } } // Hash verification key into transcript pk.vk .hash_into(transcript) .map_err(|_| Error::TranscriptError)?; let domain = &pk.vk.domain; let mut meta = ConstraintSystem::default(); let config = ConcreteCircuit::configure(&mut meta); struct InstanceSingle<'a, C: CurveAffine> { pub instance_values: &'a [Polynomial], pub instance_polys: Vec>, pub instance_cosets: Vec>, } let instance: Vec> = instances .iter() .map(|instance| -> Result, Error> { let instance_commitments_projective: Vec<_> = instance .iter() .map(|poly| params.commit_lagrange(poly, Blind::default())) .collect(); let mut instance_commitments = vec![C::identity(); instance_commitments_projective.len()]; C::Curve::batch_normalize(&instance_commitments_projective, &mut instance_commitments); let instance_commitments = instance_commitments; drop(instance_commitments_projective); metrics::counter!("instance_commitments", instance_commitments.len() as u64); for commitment in &instance_commitments { transcript .common_point(*commitment) .map_err(|_| Error::TranscriptError)?; } let instance_polys: Vec<_> = instance .iter() .map(|poly| { let lagrange_vec = domain.lagrange_from_vec(poly.to_vec()); domain.lagrange_to_coeff(lagrange_vec) }) .collect(); let instance_cosets: Vec<_> = meta .instance_queries .iter() .map(|&(column, at)| { let poly = instance_polys[column.index()].clone(); domain.coeff_to_extended(poly, at) }) .collect(); Ok(InstanceSingle { instance_values: *instance, instance_polys, instance_cosets, }) }) .collect::, _>>()?; struct AdviceSingle { pub advice_values: Vec>, pub advice_polys: Vec>, pub advice_cosets: Vec>, pub advice_blinds: Vec>, } let advice: Vec> = circuits .iter() .map(|circuit| -> Result, Error> { struct WitnessCollection { pub advice: Vec>, _marker: std::marker::PhantomData, } impl Assignment for WitnessCollection { fn enter_region(&mut self, _: N) where NR: Into, N: FnOnce() -> NR, { // Do nothing; we don't care about regions in this context. } fn exit_region(&mut self) { // Do nothing; we don't care about regions in this context. } fn assign_advice( &mut self, _: A, column: Column, row: usize, to: V, ) -> Result<(), Error> where V: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into, { *self .advice .get_mut(column.index()) .and_then(|v| v.get_mut(row)) .ok_or(Error::BoundsFailure)? = to()?; Ok(()) } fn assign_fixed( &mut self, _: A, _: Column, _: usize, _: V, ) -> Result<(), Error> where V: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into, { // We only care about advice columns here Ok(()) } fn copy( &mut self, _: &Permutation, _: usize, _: usize, _: usize, _: usize, ) -> Result<(), Error> { // We only care about advice columns here Ok(()) } fn push_namespace(&mut self, _: N) where NR: Into, N: FnOnce() -> NR, { // Do nothing; we don't care about namespaces in this context. } fn pop_namespace(&mut self, _: Option) { // Do nothing; we don't care about namespaces in this context. } } let mut witness = WitnessCollection { advice: vec![domain.empty_lagrange(); meta.num_advice_columns], _marker: std::marker::PhantomData, }; // Synthesize the circuit to obtain the witness and other information. circuit.synthesize(&mut witness, config.clone())?; let witness = witness; // Compute commitments to advice column polynomials let advice_blinds: Vec<_> = witness .advice .iter() .map(|_| Blind(C::Scalar::rand())) .collect(); let advice_commitments_projective: Vec<_> = witness .advice .iter() .zip(advice_blinds.iter()) .map(|(poly, blind)| params.commit_lagrange(poly, *blind)) .collect(); let mut advice_commitments = vec![C::identity(); advice_commitments_projective.len()]; C::Curve::batch_normalize(&advice_commitments_projective, &mut advice_commitments); let advice_commitments = advice_commitments; drop(advice_commitments_projective); metrics::counter!("advice_commitments", advice_commitments.len() as u64); for commitment in &advice_commitments { transcript .write_point(*commitment) .map_err(|_| Error::TranscriptError)?; } let advice_polys: Vec<_> = witness .advice .clone() .into_iter() .map(|poly| domain.lagrange_to_coeff(poly)) .collect(); let advice_cosets: Vec<_> = meta .advice_queries .iter() .map(|&(column, at)| { let poly = advice_polys[column.index()].clone(); domain.coeff_to_extended(poly, at) }) .collect(); Ok(AdviceSingle { advice_values: witness.advice, advice_polys, advice_cosets, advice_blinds, }) }) .collect::, _>>()?; // Sample theta challenge for keeping lookup columns linearly independent let theta = ChallengeTheta::get(transcript); let lookups: Vec>> = instance .iter() .zip(advice.iter()) .map(|(instance, advice)| -> Result, Error> { // Construct and commit to permuted values for each lookup pk.vk .cs .lookups .iter() .map(|lookup| { lookup.commit_permuted( &pk, ¶ms, &domain, theta, &advice.advice_values, &pk.fixed_values, &instance.instance_values, &advice.advice_cosets, &pk.fixed_cosets, &instance.instance_cosets, transcript, ) }) .collect() }) .collect::, _>>()?; // Sample beta challenge let beta = ChallengeBeta::get(transcript); // Sample gamma challenge let gamma = ChallengeGamma::get(transcript); let permutations: Vec>> = instance .iter() .zip(advice.iter()) .map(|(instance, advice)| -> Result, Error> { // Commit to permutations, if any. pk.vk .cs .permutations .iter() .zip(pk.permutations.iter()) .map(|(p, pkey)| { p.commit( params, pk, pkey, &advice.advice_values, &pk.fixed_values, &instance.instance_values, beta, gamma, transcript, ) }) .collect() }) .collect::, _>>()?; let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { // Construct and commit to products for each lookup lookups .into_iter() .map(|lookup| lookup.commit_product(&pk, ¶ms, theta, beta, gamma, transcript)) .collect::, _>>() }) .collect::, _>>()?; // Obtain challenge for keeping all separate gates linearly independent let y = ChallengeY::get(transcript); let (permutations, permutation_expressions): (Vec>, Vec>) = permutations .into_iter() .zip(advice.iter()) .zip(instance.iter()) .map(|((permutations, advice), instance)| { // Evaluate the h(X) polynomial's constraint system expressions for the permutation constraints, if any. let tmp: Vec<_> = permutations .into_iter() .zip(pk.vk.cs.permutations.iter()) .zip(pk.permutations.iter()) .map(|((p, argument), pkey)| { p.construct( pk, argument, pkey, &advice.advice_cosets, &pk.fixed_cosets, &instance.instance_cosets, beta, gamma, ) }) .collect(); tmp.into_iter().unzip() }) .unzip(); let (lookups, lookup_expressions): (Vec>, Vec>) = lookups .into_iter() .map(|lookups| { // Evaluate the h(X) polynomial's constraint system expressions for the lookup constraints, if any. let tmp: Vec<_> = lookups .into_iter() .map(|p| p.construct(pk, theta, beta, gamma)) .collect(); tmp.into_iter().unzip() }) .unzip(); let expressions = advice .iter() .zip(instance.iter()) .zip(permutation_expressions.into_iter()) .zip(lookup_expressions.into_iter()) .flat_map( |(((advice, instance), permutation_expressions), lookup_expressions)| { iter::empty() // Custom constraints .chain(meta.gates.iter().map(move |(_, poly)| { poly.evaluate( &|scalar| pk.vk.domain.constant_extended(scalar), &|index| pk.fixed_cosets[index].clone(), &|index| advice.advice_cosets[index].clone(), &|index| instance.instance_cosets[index].clone(), &|a, b| a + &b, &|a, b| a * &b, &|a, scalar| a * scalar, ) })) // Permutation constraints, if any. .chain(permutation_expressions.into_iter().flatten()) // Lookup constraints, if any. .chain(lookup_expressions.into_iter().flatten()) }, ); // Construct the vanishing argument let vanishing = vanishing::Argument::construct(params, domain, expressions, y, transcript)?; let x = ChallengeX::get(transcript); // Compute and hash instance evals for each circuit instance for instance in instance.iter() { // Evaluate polynomials at omega^i x let instance_evals: Vec<_> = meta .instance_queries .iter() .map(|&(column, at)| { eval_polynomial( &instance.instance_polys[column.index()], domain.rotate_omega(*x, at), ) }) .collect(); // Hash each instance column evaluation for eval in instance_evals.iter() { transcript .write_scalar(*eval) .map_err(|_| Error::TranscriptError)?; } } // Compute and hash advice evals for each circuit instance for advice in advice.iter() { // Evaluate polynomials at omega^i x let advice_evals: Vec<_> = meta .advice_queries .iter() .map(|&(column, at)| { eval_polynomial( &advice.advice_polys[column.index()], domain.rotate_omega(*x, at), ) }) .collect(); // Hash each advice column evaluation for eval in advice_evals.iter() { transcript .write_scalar(*eval) .map_err(|_| Error::TranscriptError)?; } } // Compute and hash fixed evals (shared across all circuit instances) let fixed_evals: Vec<_> = meta .fixed_queries .iter() .map(|&(column, at)| { eval_polynomial(&pk.fixed_polys[column.index()], domain.rotate_omega(*x, at)) }) .collect(); // Hash each fixed column evaluation for eval in fixed_evals.iter() { transcript .write_scalar(*eval) .map_err(|_| Error::TranscriptError)?; } let vanishing = vanishing.evaluate(x, transcript)?; // Evaluate the permutations, if any, at omega^i x. let permutations: Vec>> = permutations .into_iter() .map(|permutations| -> Result, _> { permutations .into_iter() .zip(pk.permutations.iter()) .map(|(p, pkey)| p.evaluate(pk, pkey, x, transcript)) .collect::, _>>() }) .collect::, _>>()?; // Evaluate the lookups, if any, at omega^i x. let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { lookups .into_iter() .map(|p| p.evaluate(pk, x, transcript)) .collect::, _>>() }) .collect::, _>>()?; let instances = instance .iter() .zip(advice.iter()) .zip(permutations.iter()) .zip(lookups.iter()) .flat_map(|(((instance, advice), permutations), lookups)| { iter::empty() .chain( pk.vk .cs .instance_queries .iter() .map(move |&(column, at)| ProverQuery { point: domain.rotate_omega(*x, at), poly: &instance.instance_polys[column.index()], blind: Blind::default(), }), ) .chain( pk.vk .cs .advice_queries .iter() .map(move |&(column, at)| ProverQuery { point: domain.rotate_omega(*x, at), poly: &advice.advice_polys[column.index()], blind: advice.advice_blinds[column.index()], }), ) .chain( permutations .iter() .zip(pk.permutations.iter()) .flat_map(move |(p, pkey)| p.open(pk, pkey, x)) .into_iter(), ) .chain(lookups.iter().flat_map(move |p| p.open(pk, x)).into_iter()) }) .chain( pk.vk .cs .fixed_queries .iter() .map(|&(column, at)| ProverQuery { point: domain.rotate_omega(*x, at), poly: &pk.fixed_polys[column.index()], blind: Blind::default(), }), ) // We query the h(X) polynomial at x .chain(vanishing.open(x)); multiopen::create_proof(params, transcript, instances).map_err(|_| Error::OpeningError) }