halo2_axiom/poly/kzg/multiopen/shplonk/
prover.rs

1use std::fmt::Debug;
2use std::hash::Hash;
3use std::marker::PhantomData;
4use std::ops::MulAssign;
5
6use super::{
7    construct_intermediate_sets, ChallengeU, ChallengeV, ChallengeY, Commitment, RotationSet,
8};
9use crate::arithmetic::{
10    eval_polynomial, evaluate_vanishing_polynomial, kate_division, lagrange_interpolate,
11    parallelize, powers, CurveAffine,
12};
13use crate::helpers::SerdeCurveAffine;
14use crate::poly::commitment::{Blind, ParamsProver, Prover};
15use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG};
16use crate::poly::query::{PolynomialPointer, ProverQuery};
17use crate::poly::{Coeff, Polynomial};
18use crate::transcript::{EncodedChallenge, TranscriptWrite};
19
20use crate::multicore::IntoParallelIterator;
21use ff::Field;
22use group::Curve;
23use pairing::Engine;
24use rand_core::RngCore;
25
26use std::io;
27
28#[cfg(feature = "multicore")]
29use crate::multicore::ParallelIterator;
30
31fn div_by_vanishing<F: Field>(poly: Polynomial<F, Coeff>, roots: &[F]) -> Vec<F> {
32    let poly = roots
33        .iter()
34        .fold(poly.values, |poly, point| kate_division(&poly, *point));
35
36    poly
37}
38
39struct CommitmentExtension<'a, C: CurveAffine> {
40    commitment: Commitment<C::Scalar, PolynomialPointer<'a, C>>,
41    low_degree_equivalent: Polynomial<C::Scalar, Coeff>,
42}
43
44impl<'a, C: CurveAffine> Commitment<C::Scalar, PolynomialPointer<'a, C>> {
45    fn extend(&self, points: &[C::Scalar]) -> CommitmentExtension<'a, C> {
46        let poly = lagrange_interpolate(points, &self.evals()[..]);
47
48        let low_degree_equivalent = Polynomial {
49            values: poly,
50            _marker: PhantomData,
51        };
52
53        CommitmentExtension {
54            commitment: self.clone(),
55            low_degree_equivalent,
56        }
57    }
58}
59
60impl<'a, C: CurveAffine> CommitmentExtension<'a, C> {
61    fn linearisation_contribution(&self, u: C::Scalar) -> Polynomial<C::Scalar, Coeff> {
62        let p_x = self.commitment.get().poly;
63        let r_eval = eval_polynomial(&self.low_degree_equivalent.values[..], u);
64        p_x - r_eval
65    }
66
67    fn quotient_contribution(&self) -> Polynomial<C::Scalar, Coeff> {
68        let len = self.low_degree_equivalent.len();
69        let mut p_x = self.commitment.get().poly.clone();
70        parallelize(&mut p_x.values[0..len], |lhs, start| {
71            for (lhs, rhs) in lhs
72                .iter_mut()
73                .zip(self.low_degree_equivalent.values[start..].iter())
74            {
75                *lhs -= *rhs;
76            }
77        });
78        p_x
79    }
80}
81
82struct RotationSetExtension<'a, C: CurveAffine> {
83    commitments: Vec<CommitmentExtension<'a, C>>,
84    points: Vec<C::Scalar>,
85}
86
87impl<'a, C: CurveAffine> RotationSet<C::Scalar, PolynomialPointer<'a, C>> {
88    fn extend(self, commitments: Vec<CommitmentExtension<'a, C>>) -> RotationSetExtension<'a, C> {
89        RotationSetExtension {
90            commitments,
91            points: self.points,
92        }
93    }
94}
95
96/// Concrete KZG prover with SHPLONK variant
97#[derive(Debug)]
98pub struct ProverSHPLONK<'a, E: Engine> {
99    params: &'a ParamsKZG<E>,
100}
101
102impl<'a, E: Engine> ProverSHPLONK<'a, E> {
103    /// Given parameters creates new prover instance
104    pub fn new(params: &'a ParamsKZG<E>) -> Self {
105        Self { params }
106    }
107}
108
109/// Create a multi-opening proof
110impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme<E>>
111    for ProverSHPLONK<'params, E>
112where
113    E::G1Affine: SerdeCurveAffine<ScalarExt = E::Fr, CurveExt = E::G1>,
114    E::G2Affine: SerdeCurveAffine,
115    E::Fr: Hash,
116{
117    const QUERY_INSTANCE: bool = false;
118
119    fn new(params: &'params ParamsKZG<E>) -> Self {
120        Self { params }
121    }
122
123    /// Create a multi-opening proof
124    fn create_proof<
125        'com,
126        Ch: EncodedChallenge<E::G1Affine>,
127        T: TranscriptWrite<E::G1Affine, Ch>,
128        R,
129        I,
130    >(
131        &self,
132        _: R,
133        transcript: &mut T,
134        queries: I,
135    ) -> io::Result<()>
136    where
137        I: IntoIterator<Item = ProverQuery<'com, E::G1Affine>> + Clone,
138        R: RngCore,
139    {
140        // TODO: explore if it is safe to use same challenge
141        // for different sets that are already combined with anoter challenge
142        let y: ChallengeY<_> = transcript.squeeze_challenge_scalar();
143
144        let quotient_contribution = |rotation_set: &RotationSetExtension<E::G1Affine>| {
145            // [P_i_0(X) - R_i_0(X), P_i_1(X) - R_i_1(X), ... ]
146            #[allow(clippy::needless_collect)]
147            let numerators = rotation_set
148                .commitments
149                .as_slice()
150                .into_par_iter()
151                .map(|commitment| commitment.quotient_contribution())
152                .collect::<Vec<_>>();
153
154            // define numerator polynomial as
155            // N_i_j(X) = (P_i_j(X) - R_i_j(X))
156            // and combine polynomials with same evaluation point set
157            // N_i(X) = linear_combinination(y, N_i_j(X))
158            // where y is random scalar to combine numerator polynomials
159            let n_x = numerators
160                .into_iter()
161                .zip(powers(*y))
162                .map(|(numerator, power_of_y)| numerator * power_of_y)
163                .reduce(|acc, numerator| acc + &numerator)
164                .unwrap();
165
166            let points = &rotation_set.points[..];
167
168            // quotient contribution of this evaluation set is
169            // Q_i(X) = N_i(X) / Z_i(X) where
170            // Z_i(X) = (x - r_i_0) * (x - r_i_1) * ...
171            let mut poly = div_by_vanishing(n_x, points);
172            poly.resize(self.params.n as usize, E::Fr::ZERO);
173
174            Polynomial {
175                values: poly,
176                _marker: PhantomData,
177            }
178        };
179
180        let intermediate_sets = construct_intermediate_sets(queries);
181        let (rotation_sets, super_point_set) = (
182            intermediate_sets.rotation_sets,
183            intermediate_sets.super_point_set,
184        );
185
186        let rotation_sets: Vec<RotationSetExtension<E::G1Affine>> = rotation_sets
187            .into_par_iter()
188            .map(|rotation_set| {
189                let commitments: Vec<CommitmentExtension<E::G1Affine>> = rotation_set
190                    .commitments
191                    .as_slice()
192                    .into_par_iter()
193                    .map(|commitment_data| commitment_data.extend(&rotation_set.points))
194                    .collect();
195                rotation_set.extend(commitments)
196            })
197            .collect();
198
199        let v: ChallengeV<_> = transcript.squeeze_challenge_scalar();
200
201        #[allow(clippy::needless_collect)]
202        let quotient_polynomials = rotation_sets
203            .as_slice()
204            .into_par_iter()
205            .map(quotient_contribution)
206            .collect::<Vec<_>>();
207
208        let h_x: Polynomial<E::Fr, Coeff> = quotient_polynomials
209            .into_iter()
210            .zip(powers(*v))
211            .map(|(poly, power_of_v)| poly * power_of_v)
212            .reduce(|acc, poly| acc + &poly)
213            .unwrap();
214
215        let h = self.params.commit(&h_x, Blind::default()).to_affine();
216        transcript.write_point(h)?;
217        let u: ChallengeU<_> = transcript.squeeze_challenge_scalar();
218
219        let linearisation_contribution = |rotation_set: RotationSetExtension<E::G1Affine>| {
220            let mut diffs = super_point_set.clone();
221            for point in rotation_set.points.iter() {
222                diffs.remove(point);
223            }
224            let diffs = diffs.into_iter().collect::<Vec<_>>();
225
226            // calculate difference vanishing polynomial evaluation
227            let z_i = evaluate_vanishing_polynomial(&diffs[..], *u);
228
229            // inner linearisation contibutions are
230            // [P_i_0(X) - r_i_0, P_i_1(X) - r_i_1, ... ] where
231            // r_i_j = R_i_j(u) is the evaluation of low degree equivalent polynomial
232            // where u is random evaluation point
233            #[allow(clippy::needless_collect)]
234            let inner_contributions = rotation_set
235                .commitments
236                .as_slice()
237                .into_par_iter()
238                .map(|commitment| commitment.linearisation_contribution(*u))
239                .collect::<Vec<_>>();
240
241            // define inner contributor polynomial as
242            // L_i_j(X) = (P_i_j(X) - r_i_j)
243            // and combine polynomials with same evaluation point set
244            // L_i(X) = linear_combinination(y, L_i_j(X))
245            // where y is random scalar to combine inner contibutors
246            let l_x: Polynomial<E::Fr, Coeff> = inner_contributions
247                .into_iter()
248                .zip(powers(*y))
249                .map(|(poly, power_of_y)| poly * power_of_y)
250                .reduce(|acc, poly| acc + &poly)
251                .unwrap();
252
253            // finally scale l_x by difference vanishing polynomial evaluation z_i
254            (l_x * z_i, z_i)
255        };
256
257        #[allow(clippy::type_complexity)]
258        let (linearisation_contibutions, z_diffs): (
259            Vec<Polynomial<E::Fr, Coeff>>,
260            Vec<E::Fr>,
261        ) = rotation_sets
262            .into_par_iter()
263            .map(linearisation_contribution)
264            .unzip();
265
266        let l_x: Polynomial<E::Fr, Coeff> = linearisation_contibutions
267            .into_iter()
268            .zip(powers(*v))
269            .map(|(poly, power_of_v)| poly * power_of_v)
270            .reduce(|acc, poly| acc + &poly)
271            .unwrap();
272
273        let super_point_set = super_point_set.into_iter().collect::<Vec<_>>();
274        let zt_eval = evaluate_vanishing_polynomial(&super_point_set[..], *u);
275        let l_x = l_x - &(h_x * zt_eval);
276
277        // sanity check
278        #[cfg(debug_assertions)]
279        {
280            let must_be_zero = eval_polynomial(&l_x.values[..], *u);
281            assert_eq!(must_be_zero, E::Fr::ZERO);
282        }
283
284        let mut h_x = div_by_vanishing(l_x, &[*u]);
285
286        // normalize coefficients by the coefficient of the first polynomial
287        let z_0_diff_inv = z_diffs[0].invert().unwrap();
288        for h_i in h_x.iter_mut() {
289            h_i.mul_assign(z_0_diff_inv)
290        }
291
292        let h_x = Polynomial {
293            values: h_x,
294            _marker: PhantomData,
295        };
296
297        let h = self.params.commit(&h_x, Blind::default()).to_affine();
298        transcript.write_point(h)?;
299
300        Ok(())
301    }
302}