作者:ZKSwap
上篇⽂章中 ,我们研读了ZKSync中 better_cs如何⽣成single proof、aggregation proof的电路逻辑等实现。在这篇⽂章中,我们继续研读ZKSync的聚合证明,我们重点关注better_better_cs如何⽣成聚合证明。
还是⽤上⼀篇的这张代码调⽤图,我们这篇着重讲create_proof。
我们分析的bellman_ce代码版本是beta分⽀,commit id 为 48441155ec7006bf7bfac553b5fb7d466d7fcd00。
Aggregation_proof 的⽣成
create_proof 这个函数在 bellman_ce/src/plonk/better_better_cs/proof/mod.rs 中,将近 2000 ⾏代码。
⼤体上,分为以下⼏个步骤:
在这个函数中,我们看到了熟悉的 MainGate 函数,从 上⼀版如何实现聚合 中,我们知道这个⽤于⻔的设计,可以实现 custom gate,达到优化电路的⽬的。⽽除开 custom gate,ZKSync 中还使⽤了plonkup(即 plonk + lookup table) 来提升效率。
我们在之前的⽂章中,已经讲解过plonkup的原理了,简单来说,就是预计算有效的input/output组成 lookup table,prover需要证明witness在这个table⾥,详细内容请参⻅ ZKSwap团队解读Plookup原理 。ZKSync 对 plonkup 的实现,并不是将 custom gate 和 plonkup 分开的,⽽是结合在⼀起来优化电路设计的。 我们下⾯看看,MainGate trait 中的接⼝,是如何和 plonkup 结合的。
Lookup 的使⽤
在上⼀节的 create_proof 函数中,线性化⽤到了 gate 的 contribute_into_linearization_for_public_inputs 函数,我们以它为例,来看看 lookup 的使⽤。这个代码在 bellman_ce/src/plonk/better_better_cs/cs.rs 中。
fn contribute_into_linearization_for_public_inputs( &self, _domain_size: usize, _public_inputs: &[ E: :Fr], _at: E: :Fr, queried_values: & std: : collections: :HashMap, monomials_storage: & AssembledPolynomialStorageForMonomialForms, challenges: &[ E: :Fr], worker: &Worker ) -> Result, SynthesisError> {}⽤到的传⼊参数有 hashmap 格式的 queried_values、单项式缓存值、随机数数组 queried_values 这个参数是在 create_proof 时,根据排序的query 列表⽣成的,key 是多项式,value是 Fr 值。query 列表的排序规则是先 witness、gate 的 selector 次之、gate 的setup 再次之,这个 SortedGateQueries 的结构是:
pub struct SortedGateQueries { pub state_polys: Vec < Vec >, // state 多项式 pub witness_polys: Vec < Vec >, // witness 多项式 pub gate_selectors: Vec < Box < dyn GateInternal>>, // gate 的selectors pub gate_setup_polys: Vec < Vec < Vec >>, // gate setup 多项式 }代码中,调⽤ sort_queries_for_linearization 函数来⽣成 SortedGateQueries ,这个函数也在当前 mod.rs ⽂件中。这个函数输⼊参数是 gate 数组,输出即为 SortedGateQueries 。
fn sort_queries_for_linearization (gates: & Vec < Box < dyn GateInternal>>, max_dilation: usize ) -> SortedGateQueries { }函数会对传⼊的 gate 数组遍历,根据 gate 返回的多项式数组,将其按照 VariablesPolynomial , WitnessPolynomial, GateSetupPolynomial 的不同类型,将多项式存⼊ SortedGateQueries 中。
回到 contribute_into_linearization_for_public_inputs 函数,可以看到,它会从queried_values 中,获取 a/b/c/d的值。⽽ Q_a/Q_b/Q_c/Q_d/Q_m的值,都是从create_proof刚开始⽣成的单项式缓存数据中取到的,也是⼀个lookup table的概念。
这个单项式缓存的值是从电路的setup获得的,即电路确定了,那么电路的⻔就确定了,则在⽣成proof时,这些数据都已经有了,可以直接将setup预计算的结果,放⼊lookup table中,查询使⽤数据。
let mut monomials_storage = Self::create_monomial_storage( &worker, &omegas_inv_bitreversed, &values_storage, true )?; monomials_storage.extend_from_setup(setup)?;最后,结合a/b/c/d和Q_a/Q_b/Q_c/Q_d,可以⾮常⽅便的构造出多项式。
// 可以看到,⾮常⾼效的get取到了数据 let a_value = *queried_values.get(&PolynomialInConstraint::from_id(PolyIdentifier::VariablesPolynomia l( 0 ))) .ok_or(SynthesisError::AssignmentMissing)?; let b_value = *queried_values.get(&PolynomialInConstraint::from_id(PolyIdentifier::VariablesPolynomia l( 1 ))) .ok_or(SynthesisError::AssignmentMissing)?; let c_value = *queried_values.get(&PolynomialInConstraint::from_id(PolyIdentifier::VariablesPolynomia l( 2 ))) .ok_or(SynthesisError::AssignmentMissing)?; let d_value = *queried_values.get(&PolynomialInConstraint::from_id(PolyIdentifier::VariablesPolynomia l( 3 ))) .ok_or(SynthesisError::AssignmentMissing)?; let d_next_value = *queried_values.get(&PolynomialInConstraint::from_id_and_dilation(PolyIdentifier::Varia blesPolynomial( 3 ), 1 )) .ok_or(SynthesisError::AssignmentMissing)?; let name = < Self as GateInternal>::name(& self ); // get_ploy 也是查找table的⽅式获取多项式 // Q_a * A let mut result = monomials_storage.get_poly(PolyIdentifier::GateSetupPolynomial(name, 0 )).clone(); result.scale(&worker, a_value); // Q_b * B let poly_ref = monomials_storage.get_poly(PolyIdentifier::GateSetupPolynomial(name, 1 )); result.add_assign_scaled(&worker, poly_ref, &b_value); // Q_c * C let poly_ref = monomials_storage.get_poly(PolyIdentifier::GateSetupPolynomial(name, 2 )); result.add_assign_scaled(&worker, poly_ref, &c_value); // Q_d * D let poly_ref = monomials_storage.get_poly(PolyIdentifier::GateSetupPolynomial(name, 3 )); result.add_assign_scaled(&worker, poly_ref, &d_value); // Q_m * A*B let mut tmp = a_value; tmp.mul_assign(&b_value); let poly_ref = monomials_storage.get_poly(PolyIdentifier::GateSetupPolynomial(name, 4 )); result.add_assign_scaled(&worker, poly_ref, &tmp); // Q_const let poly_ref = monomials_storage.get_poly(PolyIdentifier::GateSetupPolynomial(name, 5 )); result.add_assign(&worker, poly_ref); // Q_dNext * D_next let poly_ref = monomials_storage.get_poly(PolyIdentifier::GateSetupPolynomial(name, 6 )); result.add_assign_scaled(&worker, poly_ref, &d_next_value); result.scale(&worker, challenges[ 0 ]); // 结果都存⼊result中 Ok (result)另外⼏个MainGate⾥的接⼝函数,都是⼀样的,结合lookup table,⾮常容易的计算出多项式。
综上,ZKSync将witness、gate的selector、setup放⼊lookup table中,在⽣成proof时,使⽤lookuptable,直接查询⽽不是再次计算,加快⽣成速度,提升prover效率。
