snark_verifier/verifier/plonk/
proof.rs

1use crate::{
2    loader::{LoadedScalar, Loader},
3    pcs::{self, AccumulationScheme, AccumulatorEncoding, PolynomialCommitmentScheme},
4    util::{
5        arithmetic::{CurveAffine, Field, Rotation},
6        msm::Msm,
7        transcript::TranscriptRead,
8        Itertools,
9    },
10    verifier::plonk::protocol::{
11        CommonPolynomial::Lagrange, CommonPolynomialEvaluation, LinearizationStrategy,
12        PlonkProtocol, Query,
13    },
14    Error,
15};
16use std::{collections::BTreeMap, iter};
17
18/// Proof of PLONK with [`PolynomialCommitmentScheme`] that has
19/// [`AccumulationScheme`].
20#[derive(Clone, Debug)]
21pub struct PlonkProof<C, L, AS>
22where
23    C: CurveAffine,
24    L: Loader<C>,
25    AS: AccumulationScheme<C, L> + PolynomialCommitmentScheme<C, L, Output = AS::Accumulator>,
26{
27    /// Computed commitments of instance polynomials.
28    pub committed_instances: Option<Vec<L::LoadedEcPoint>>,
29    /// Commitments of witness polynomials read from transcript.
30    pub witnesses: Vec<L::LoadedEcPoint>,
31    /// Challenges squeezed from transcript.
32    pub challenges: Vec<L::LoadedScalar>,
33    /// Quotient commitments read from transcript.
34    pub quotients: Vec<L::LoadedEcPoint>,
35    /// Query point squeezed from transcript.
36    pub z: L::LoadedScalar,
37    /// Evaluations read from transcript.
38    pub evaluations: Vec<L::LoadedScalar>,
39    /// Proof of [`PolynomialCommitmentScheme`].
40    pub pcs: <AS as PolynomialCommitmentScheme<C, L>>::Proof,
41    /// Old [`AccumulationScheme::Accumulator`]s read from instnaces.
42    pub old_accumulators: Vec<AS::Accumulator>,
43}
44
45impl<C, L, AS> PlonkProof<C, L, AS>
46where
47    C: CurveAffine,
48    L: Loader<C>,
49    AS: AccumulationScheme<C, L> + PolynomialCommitmentScheme<C, L, Output = AS::Accumulator>,
50{
51    /// Reads each part from transcript as [`PlonkProof`].
52    pub fn read<T, AE>(
53        svk: &<AS as PolynomialCommitmentScheme<C, L>>::VerifyingKey,
54        protocol: &PlonkProtocol<C, L>,
55        instances: &[Vec<L::LoadedScalar>],
56        transcript: &mut T,
57    ) -> Result<Self, Error>
58    where
59        T: TranscriptRead<C, L>,
60        AE: AccumulatorEncoding<C, L, Accumulator = AS::Accumulator>,
61    {
62        if let Some(transcript_initial_state) = &protocol.transcript_initial_state {
63            transcript.common_scalar(transcript_initial_state)?;
64        }
65
66        if protocol.num_instance != instances.iter().map(|instances| instances.len()).collect_vec()
67        {
68            return Err(Error::InvalidInstances);
69        }
70
71        let committed_instances = if let Some(ick) = &protocol.instance_committing_key {
72            let loader = transcript.loader();
73            let bases =
74                ick.bases.iter().map(|value| loader.ec_point_load_const(value)).collect_vec();
75            let constant = ick.constant.as_ref().map(|value| loader.ec_point_load_const(value));
76
77            let committed_instances = instances
78                .iter()
79                .map(|instances| {
80                    instances
81                        .iter()
82                        .zip(bases.iter())
83                        .map(|(scalar, base)| Msm::<C, L>::base(base) * scalar)
84                        .chain(constant.as_ref().map(Msm::base))
85                        .sum::<Msm<_, _>>()
86                        .evaluate(None)
87                })
88                .collect_vec();
89            for committed_instance in committed_instances.iter() {
90                transcript.common_ec_point(committed_instance)?;
91            }
92
93            Some(committed_instances)
94        } else {
95            for instances in instances.iter() {
96                for instance in instances.iter() {
97                    transcript.common_scalar(instance)?;
98                }
99            }
100
101            None
102        };
103
104        let (witnesses, challenges) = {
105            let (witnesses, challenges) = protocol
106                .num_witness
107                .iter()
108                .zip(protocol.num_challenge.iter())
109                .map(|(&n, &m)| {
110                    Ok((transcript.read_n_ec_points(n)?, transcript.squeeze_n_challenges(m)))
111                })
112                .collect::<Result<Vec<_>, Error>>()?
113                .into_iter()
114                .unzip::<_, _, Vec<_>, Vec<_>>();
115
116            (
117                witnesses.into_iter().flatten().collect_vec(),
118                challenges.into_iter().flatten().collect_vec(),
119            )
120        };
121
122        let quotients = transcript.read_n_ec_points(protocol.quotient.num_chunk())?;
123
124        let z = transcript.squeeze_challenge();
125        let evaluations = transcript.read_n_scalars(protocol.evaluations.len())?;
126
127        let pcs = <AS as PolynomialCommitmentScheme<C, L>>::read_proof(
128            svk,
129            &Self::empty_queries(protocol),
130            transcript,
131        )?;
132
133        let old_accumulators = protocol
134            .accumulator_indices
135            .iter()
136            .map(|accumulator_indices| {
137                AE::from_repr(
138                    &accumulator_indices.iter().map(|&(i, j)| &instances[i][j]).collect_vec(),
139                )
140            })
141            .collect::<Result<Vec<_>, _>>()?;
142
143        Ok(Self {
144            committed_instances,
145            witnesses,
146            challenges,
147            quotients,
148            z,
149            evaluations,
150            pcs,
151            old_accumulators,
152        })
153    }
154
155    /// Empty queries
156    pub fn empty_queries(protocol: &PlonkProtocol<C, L>) -> Vec<pcs::Query<Rotation>> {
157        // `preprocessed` should always be non-empty, unless the circuit has no constraints or constants
158        protocol.queries.iter().map(|query| pcs::Query::new(query.poly, query.rotation)).collect()
159    }
160
161    pub(super) fn queries(
162        &self,
163        protocol: &PlonkProtocol<C, L>,
164        mut evaluations: BTreeMap<Query, L::LoadedScalar>,
165    ) -> Vec<pcs::Query<Rotation, L::LoadedScalar>> {
166        if protocol.queries.is_empty() {
167            return vec![];
168        }
169        let loader = evaluations[&protocol.queries[0]].loader();
170        let rotations =
171            protocol.queries.iter().map(|query| query.rotation).sorted().dedup().collect_vec();
172        let loaded_shifts = if let Some(domain) = protocol.domain_as_witness.as_ref() {
173            // the `rotation`s are still constants, it is only generator `omega` that might be witness
174            BTreeMap::from_iter(
175                rotations.into_iter().map(|rotation| (rotation, domain.rotate_one(rotation))),
176            )
177        } else {
178            BTreeMap::from_iter(rotations.into_iter().map(|rotation| {
179                (
180                    rotation,
181                    loader.load_const(&protocol.domain.rotate_scalar(C::Scalar::ONE, rotation)),
182                )
183            }))
184        };
185        Self::empty_queries(protocol)
186            .into_iter()
187            .zip(protocol.queries.iter().map(|query| evaluations.remove(query).unwrap()))
188            .map(|(query, eval)| {
189                let shift = loaded_shifts[&query.shift].clone();
190                query.with_evaluation(shift, eval)
191            })
192            .collect()
193    }
194
195    pub(super) fn commitments<'a>(
196        &'a self,
197        protocol: &'a PlonkProtocol<C, L>,
198        common_poly_eval: &CommonPolynomialEvaluation<C, L>,
199        evaluations: &mut BTreeMap<Query, L::LoadedScalar>,
200    ) -> Result<Vec<Msm<'a, C, L>>, Error> {
201        let loader = common_poly_eval.zn().loader();
202        let mut commitments = iter::empty()
203            .chain(protocol.preprocessed.iter().map(Msm::base))
204            .chain(
205                self.committed_instances
206                    .as_ref()
207                    .map(|committed_instances| {
208                        committed_instances.iter().map(Msm::base).collect_vec()
209                    })
210                    .unwrap_or_else(|| {
211                        iter::repeat_with(Default::default)
212                            .take(protocol.num_instance.len())
213                            .collect_vec()
214                    }),
215            )
216            .chain(self.witnesses.iter().map(Msm::base))
217            .collect_vec();
218
219        let numerator = protocol.quotient.numerator.evaluate(
220            &|scalar| Ok(Msm::constant(loader.load_const(&scalar))),
221            &|poly| Ok(Msm::constant(common_poly_eval.get(poly).clone())),
222            &|query| {
223                evaluations
224                    .get(&query)
225                    .cloned()
226                    .map(Msm::constant)
227                    .or_else(|| {
228                        (query.rotation == Rotation::cur())
229                            .then(|| commitments.get(query.poly).cloned())
230                            .flatten()
231                    })
232                    .ok_or_else(|| Error::InvalidProtocol(format!("Missing query {query:?}")))
233            },
234            &|index| {
235                self.challenges
236                    .get(index)
237                    .cloned()
238                    .map(Msm::constant)
239                    .ok_or_else(|| Error::InvalidProtocol(format!("Missing challenge {index}")))
240            },
241            &|a| Ok(-a?),
242            &|a, b| Ok(a? + b?),
243            &|a, b| {
244                let (a, b) = (a?, b?);
245                match (a.size(), b.size()) {
246                    (0, _) => Ok(b * &a.try_into_constant().unwrap()),
247                    (_, 0) => Ok(a * &b.try_into_constant().unwrap()),
248                    (_, _) => Err(Error::InvalidProtocol("Invalid linearization".to_string())),
249                }
250            },
251            &|a, scalar| Ok(a? * &loader.load_const(&scalar)),
252        )?;
253
254        let quotient_query = Query::new(
255            protocol.preprocessed.len() + protocol.num_instance.len() + self.witnesses.len(),
256            Rotation::cur(),
257        );
258        let quotient = common_poly_eval
259            .zn()
260            .pow_const(protocol.quotient.chunk_degree as u64)
261            .powers(self.quotients.len())
262            .into_iter()
263            .zip(self.quotients.iter().map(Msm::base))
264            .map(|(coeff, chunk)| chunk * &coeff)
265            .sum::<Msm<_, _>>();
266        match protocol.linearization {
267            Some(LinearizationStrategy::WithoutConstant) => {
268                let linearization_query = Query::new(quotient_query.poly + 1, Rotation::cur());
269                let (msm, constant) = numerator.split();
270                commitments.push(quotient);
271                commitments.push(msm);
272                evaluations.insert(
273                    quotient_query,
274                    (constant.unwrap_or_else(|| loader.load_zero())
275                        + evaluations.get(&linearization_query).unwrap())
276                        * common_poly_eval.zn_minus_one_inv(),
277                );
278            }
279            Some(LinearizationStrategy::MinusVanishingTimesQuotient) => {
280                let (msm, constant) =
281                    (numerator - quotient * common_poly_eval.zn_minus_one()).split();
282                commitments.push(msm);
283                evaluations.insert(quotient_query, constant.unwrap_or_else(|| loader.load_zero()));
284            }
285            None => {
286                commitments.push(quotient);
287                evaluations.insert(
288                    quotient_query,
289                    numerator.try_into_constant().ok_or_else(|| {
290                        Error::InvalidProtocol("Invalid linearization".to_string())
291                    })? * common_poly_eval.zn_minus_one_inv(),
292                );
293            }
294        }
295
296        Ok(commitments)
297    }
298
299    pub(super) fn evaluations(
300        &self,
301        protocol: &PlonkProtocol<C, L>,
302        instances: &[Vec<L::LoadedScalar>],
303        common_poly_eval: &CommonPolynomialEvaluation<C, L>,
304    ) -> Result<BTreeMap<Query, L::LoadedScalar>, Error> {
305        let loader = common_poly_eval.zn().loader();
306        let instance_evals = protocol.instance_committing_key.is_none().then(|| {
307            let offset = protocol.preprocessed.len();
308            let queries = {
309                let range = offset..offset + protocol.num_instance.len();
310                protocol
311                    .quotient
312                    .numerator
313                    .used_query()
314                    .into_iter()
315                    .filter(move |query| range.contains(&query.poly))
316            };
317            queries
318                .map(move |query| {
319                    let instances = instances[query.poly - offset].iter();
320                    let l_i_minus_r = (-query.rotation.0..)
321                        .map(|i_minus_r| common_poly_eval.get(Lagrange(i_minus_r)));
322                    let eval = loader.sum_products(&instances.zip(l_i_minus_r).collect_vec());
323                    (query, eval)
324                })
325                .collect_vec()
326        });
327
328        let evals = iter::empty()
329            .chain(instance_evals.into_iter().flatten())
330            .chain(protocol.evaluations.iter().cloned().zip(self.evaluations.iter().cloned()))
331            .collect();
332
333        Ok(evals)
334    }
335}