Skip to main content

snarkvm_algorithms/polycommit/sonic_pc/
mod.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{
17    AlgebraicSponge,
18    fft::DensePolynomial,
19    msm::variable_base::VariableBase,
20    polycommit::{PCError, kzg10},
21    srs::{UniversalProver, UniversalVerifier},
22};
23use hashbrown::HashMap;
24use itertools::Itertools;
25use snarkvm_curves::traits::{AffineCurve, PairingCurve, PairingEngine, ProjectiveCurve};
26use snarkvm_fields::{One, Zero};
27
28use anyhow::{Result, bail, ensure};
29use rand::{Rng, SeedableRng};
30use std::{
31    borrow::Borrow,
32    collections::{BTreeMap, BTreeSet},
33    convert::TryInto,
34    marker::PhantomData,
35    ops::Mul,
36};
37
38mod data_structures;
39pub use data_structures::*;
40
41mod polynomial;
42pub use polynomial::*;
43
44/// Polynomial commitment based on [\[KZG10\]][kzg], with degree enforcement and
45/// batching taken from [[MBKM19, “Sonic”]][sonic] (more precisely, their
46/// counterparts in [[Gabizon19, “AuroraLight”]][al] that avoid negative G1
47/// powers). The (optional) hiding property of the commitment scheme follows the
48/// approach described in [[CHMMVW20, “Marlin”]][marlin].
49///
50/// [kzg]: http://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf
51/// [sonic]: https://eprint.iacr.org/2019/099
52/// [al]: https://eprint.iacr.org/2019/601
53/// [marlin]: https://eprint.iacr.org/2019/1047
54#[derive(Clone, Debug)]
55pub struct SonicKZG10<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> {
56    _engine: PhantomData<(E, S)>,
57}
58
59impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
60    pub fn load_srs(max_degree: usize) -> Result<UniversalParams<E>, PCError> {
61        kzg10::KZG10::load_srs(max_degree)
62    }
63
64    pub fn trim(
65        pp: &UniversalParams<E>,
66        supported_degree: usize,
67        supported_lagrange_sizes: impl IntoIterator<Item = usize>,
68        supported_hiding_bound: usize,
69        enforced_degree_bounds: Option<&[usize]>,
70    ) -> Result<(CommitterKey<E>, UniversalVerifier<E>)> {
71        let trim_time = start_timer!(|| "Trimming public parameters");
72        let max_degree = pp.max_degree();
73
74        let enforced_degree_bounds = enforced_degree_bounds.map(|bounds| {
75            let mut v = bounds.to_vec();
76            v.sort_unstable();
77            v.dedup();
78            v
79        });
80
81        let (shifted_powers_of_beta_g, shifted_powers_of_beta_times_gamma_g) = if let Some(enforced_degree_bounds) =
82            enforced_degree_bounds.as_ref()
83        {
84            if enforced_degree_bounds.is_empty() {
85                (None, None)
86            } else {
87                let highest_enforced_degree_bound = *enforced_degree_bounds.last().unwrap();
88                if highest_enforced_degree_bound > supported_degree {
89                    bail!(
90                        "The highest enforced degree bound {highest_enforced_degree_bound} is larger than the supported degree {supported_degree}"
91                    );
92                }
93
94                let lowest_shift_degree = max_degree - highest_enforced_degree_bound;
95
96                let shifted_ck_time = start_timer!(|| format!(
97                    "Constructing `shifted_powers_of_beta_g` of size {}",
98                    max_degree - lowest_shift_degree + 1
99                ));
100
101                let shifted_powers_of_beta_g = pp.powers_of_beta_g(lowest_shift_degree, pp.max_degree() + 1)?;
102                let mut shifted_powers_of_beta_times_gamma_g = BTreeMap::new();
103                // Also add degree 0.
104                for degree_bound in enforced_degree_bounds {
105                    let shift_degree = max_degree - degree_bound;
106                    // We have an additional degree in `powers_of_beta_times_gamma_g` beyond
107                    // `powers_of_beta_g`.
108                    let powers_for_degree_bound = pp
109                        .powers_of_beta_times_gamma_g()
110                        .range(shift_degree..max_degree.min(shift_degree + supported_hiding_bound) + 2)
111                        .map(|(_k, v)| *v)
112                        .collect();
113                    shifted_powers_of_beta_times_gamma_g.insert(*degree_bound, powers_for_degree_bound);
114                }
115
116                end_timer!(shifted_ck_time);
117
118                (Some(shifted_powers_of_beta_g), Some(shifted_powers_of_beta_times_gamma_g))
119            }
120        } else {
121            (None, None)
122        };
123
124        let powers_of_beta_g = pp.powers_of_beta_g(0, supported_degree + 1)?;
125        let powers_of_beta_times_gamma_g = pp
126            .powers_of_beta_times_gamma_g()
127            .range(0..=(supported_hiding_bound + 1))
128            .map(|(_k, v)| *v)
129            .collect::<Vec<_>>();
130        if powers_of_beta_times_gamma_g.len() != supported_hiding_bound + 2 {
131            return Err(
132                PCError::HidingBoundToolarge { hiding_poly_degree: supported_hiding_bound, num_powers: 0 }.into()
133            );
134        }
135
136        let mut lagrange_bases_at_beta_g = BTreeMap::new();
137        for size in supported_lagrange_sizes {
138            let lagrange_time = start_timer!(|| format!("Constructing `lagrange_bases` of size {size}"));
139            if !size.is_power_of_two() {
140                bail!("The Lagrange basis size ({size}) is not a power of two")
141            }
142            if size > pp.max_degree() + 1 {
143                bail!("The Lagrange basis size ({size}) is larger than the supported degree ({})", pp.max_degree() + 1)
144            }
145            let domain = crate::fft::EvaluationDomain::new(size).unwrap();
146            let lagrange_basis_at_beta_g = pp.lagrange_basis(domain)?;
147            assert!(lagrange_basis_at_beta_g.len().is_power_of_two());
148            lagrange_bases_at_beta_g.insert(domain.size(), lagrange_basis_at_beta_g);
149            end_timer!(lagrange_time);
150        }
151
152        let ck = CommitterKey {
153            powers_of_beta_g,
154            lagrange_bases_at_beta_g,
155            powers_of_beta_times_gamma_g,
156            shifted_powers_of_beta_g,
157            shifted_powers_of_beta_times_gamma_g,
158            enforced_degree_bounds,
159        };
160
161        let vk = pp.to_universal_verifier()?;
162
163        end_timer!(trim_time);
164        Ok((ck, vk))
165    }
166
167    /// Outputs commitments to `polynomials`.
168    ///
169    /// If `polynomials[i].is_hiding()`, then the `i`-th commitment is hiding
170    /// up to `polynomials.hiding_bound()` queries.
171    ///
172    /// `rng` should not be `None` if `polynomials[i].is_hiding() == true` for
173    /// any `i`.
174    ///
175    /// If for some `i`, `polynomials[i].is_hiding() == false`, then the
176    /// corresponding randomness is `Randomness<E>::empty()`.
177    ///
178    /// If for some `i`, `polynomials[i].degree_bound().is_some()`, then that
179    /// polynomial will have the corresponding degree bound enforced.
180    #[allow(clippy::format_push_string)]
181    pub fn commit<'b>(
182        universal_prover: &UniversalProver<E>,
183        ck: &CommitterUnionKey<E>,
184        polynomials: impl IntoIterator<Item = LabeledPolynomialWithBasis<'b, E::Fr>>,
185        mut rng: Option<&mut dyn Rng>,
186    ) -> Result<(Vec<LabeledCommitment<Commitment<E>>>, Vec<Randomness<E>>), PCError> {
187        let commit_time = start_timer!(|| "Committing to polynomials");
188
189        let mut pool = snarkvm_utilities::ExecutionPool::<Result<_, _>>::new();
190        for p in polynomials {
191            let seed = rng.as_mut().map(|r| {
192                let mut seed = [0u8; 32];
193                r.fill_bytes(&mut seed);
194                seed
195            });
196
197            kzg10::KZG10::<E>::check_degrees_and_bounds(
198                universal_prover.max_degree,
199                ck.enforced_degree_bounds.as_deref(),
200                p.clone(),
201            )?;
202            let degree_bound = p.degree_bound();
203            let hiding_bound = p.hiding_bound();
204            let label = p.label().to_string();
205
206            pool.add_job(move || {
207                let mut rng = seed.map(rand::rngs::StdRng::from_seed);
208                add_to_trace!(|| "PC::Commit", || format!(
209                    "Polynomial {} of degree {}, degree bound {:?}, and hiding bound {:?}",
210                    label,
211                    p.degree(),
212                    degree_bound,
213                    hiding_bound,
214                ));
215
216                let (comm, rand) = {
217                    let rng_ref = rng.as_mut().map(|s| s as _);
218                    match p.polynomial {
219                        PolynomialWithBasis::Lagrange { evaluations } => {
220                            let domain = crate::fft::EvaluationDomain::new(evaluations.evaluations.len()).unwrap();
221                            let lagrange_basis = ck
222                                .lagrange_basis(domain)
223                                .ok_or(PCError::UnsupportedLagrangeBasisSize(domain.size()))?;
224                            assert!(domain.size().is_power_of_two());
225                            assert!(lagrange_basis.size().is_power_of_two());
226                            kzg10::KZG10::commit_lagrange(
227                                &lagrange_basis,
228                                &evaluations.evaluations,
229                                hiding_bound,
230                                rng_ref,
231                            )?
232                        }
233                        PolynomialWithBasis::Monomial { polynomial, degree_bound } => {
234                            let powers = if let Some(degree_bound) = degree_bound {
235                                ck.shifted_powers_of_beta_g(degree_bound).unwrap()
236                            } else {
237                                ck.powers()
238                            };
239
240                            kzg10::KZG10::commit(&powers, &polynomial, hiding_bound, rng_ref)?
241                        }
242                    }
243                };
244
245                Ok((LabeledCommitment::new(label.to_string(), comm, degree_bound), rand))
246            });
247        }
248        let results: Vec<Result<_, PCError>> = pool.execute_all();
249
250        let mut labeled_comms = Vec::with_capacity(results.len());
251        let mut randomness = Vec::with_capacity(results.len());
252        for result in results {
253            let (comm, rand) = result?;
254            labeled_comms.push(comm);
255            randomness.push(rand);
256        }
257
258        end_timer!(commit_time);
259        Ok((labeled_comms, randomness))
260    }
261
262    pub fn combine_for_open<'a>(
263        universal_prover: &UniversalProver<E>,
264        ck: &CommitterUnionKey<E>,
265        labeled_polynomials: impl ExactSizeIterator<Item = &'a LabeledPolynomial<E::Fr>>,
266        rands: impl ExactSizeIterator<Item = &'a Randomness<E>>,
267        fs_rng: &mut S,
268    ) -> Result<(DensePolynomial<E::Fr>, Randomness<E>)>
269    where
270        Randomness<E>: 'a,
271        Commitment<E>: 'a,
272    {
273        ensure!(labeled_polynomials.len() == rands.len());
274        let mut to_combine = Vec::with_capacity(labeled_polynomials.len());
275
276        for (p, r) in labeled_polynomials.zip_eq(rands) {
277            let enforced_degree_bounds: Option<&[usize]> = ck.enforced_degree_bounds.as_deref();
278
279            kzg10::KZG10::<E>::check_degrees_and_bounds(universal_prover.max_degree, enforced_degree_bounds, p)?;
280            let challenge = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
281            to_combine.push((challenge, p.polynomial().to_dense(), r));
282        }
283
284        Ok(Self::combine_polynomials(to_combine))
285    }
286
287    /// On input a list of labeled polynomials and a query set, `open` outputs a
288    /// proof of evaluation of the polynomials at the points in the query
289    /// set.
290    pub fn batch_open<'a>(
291        universal_prover: &UniversalProver<E>,
292        ck: &CommitterUnionKey<E>,
293        labeled_polynomials: impl ExactSizeIterator<Item = &'a LabeledPolynomial<E::Fr>>,
294        query_set: &QuerySet<E::Fr>,
295        rands: impl ExactSizeIterator<Item = &'a Randomness<E>>,
296        fs_rng: &mut S,
297    ) -> Result<BatchProof<E>>
298    where
299        Randomness<E>: 'a,
300        Commitment<E>: 'a,
301    {
302        ensure!(labeled_polynomials.len() == rands.len());
303        let poly_rand: HashMap<_, _> =
304            labeled_polynomials.into_iter().zip_eq(rands).map(|(poly, r)| (poly.label(), (poly, r))).collect();
305
306        let open_time = start_timer!(|| format!(
307            "Opening {} polynomials at query set of size {}",
308            poly_rand.len(),
309            query_set.len(),
310        ));
311
312        let mut query_to_labels_map = BTreeMap::new();
313
314        for (label, (point_name, point)) in query_set.iter() {
315            let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
316            labels.1.insert(label);
317        }
318
319        let mut proofs = Vec::new();
320        for (_point_name, (&query, labels)) in query_to_labels_map.into_iter() {
321            let mut query_polys = Vec::with_capacity(labels.len());
322            let mut query_rands = Vec::with_capacity(labels.len());
323
324            for label in labels {
325                let (polynomial, rand) =
326                    poly_rand.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
327
328                query_polys.push(*polynomial);
329                query_rands.push(*rand);
330            }
331            let (polynomial, rand) =
332                Self::combine_for_open(universal_prover, ck, query_polys.into_iter(), query_rands.into_iter(), fs_rng)?;
333
334            let proof_time = start_timer!(|| "Creating proof");
335            let proof = kzg10::KZG10::open(&ck.powers(), &polynomial, query, &rand)?;
336            end_timer!(proof_time);
337            proofs.push(proof);
338            let mut sponge_copy = fs_rng.clone();
339            for proof in &proofs {
340                proof.absorb_into_sponge(&mut sponge_copy);
341            }
342            let _ = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
343            let _randomizer = sponge_copy.squeeze_short_nonnative_field_element::<E::Fr>();
344        }
345        let batch_proof = BatchProof(proofs);
346        end_timer!(open_time);
347
348        Ok(batch_proof)
349    }
350
351    pub fn batch_check<'a>(
352        vk: &UniversalVerifier<E>,
353        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
354        query_set: &QuerySet<E::Fr>,
355        values: &Evaluations<E::Fr>,
356        proof: &BatchProof<E>,
357        fs_rng: &mut S,
358    ) -> Result<bool>
359    where
360        Commitment<E>: 'a,
361    {
362        let commitments: BTreeMap<_, _> = commitments.into_iter().map(|c| (c.label().to_owned(), c)).collect();
363        let batch_check_time = start_timer!(|| format!(
364            "Checking {} commitments at query set of size {}",
365            commitments.len(),
366            query_set.len(),
367        ));
368        let mut query_to_labels_map = BTreeMap::new();
369
370        for (label, (point_name, point)) in query_set.iter() {
371            let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
372            labels.1.insert(label);
373        }
374
375        assert_eq!(proof.0.len(), query_to_labels_map.len());
376
377        let mut randomizer = E::Fr::one();
378
379        let mut combined_comms = BTreeMap::new();
380        let mut combined_witness = E::G1Projective::zero();
381        let mut combined_adjusted_witness = E::G1Projective::zero();
382
383        ensure!(query_to_labels_map.len() == proof.0.len());
384        for (i, ((_query_name, (query, labels)), p)) in query_to_labels_map.into_iter().zip_eq(&proof.0).enumerate() {
385            let mut comms_to_combine: Vec<&'_ LabeledCommitment<_>> = Vec::new();
386            let mut values_to_combine = Vec::new();
387            for label in labels.into_iter() {
388                let commitment =
389                    commitments.get(label).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
390
391                let v_i = values
392                    .get(&(label.clone(), *query))
393                    .ok_or(PCError::MissingEvaluation { label: label.to_string() })?;
394
395                comms_to_combine.push(commitment);
396                values_to_combine.push(*v_i);
397            }
398
399            Self::accumulate_elems(
400                &mut combined_comms,
401                &mut combined_witness,
402                &mut combined_adjusted_witness,
403                vk,
404                comms_to_combine.into_iter(),
405                *query,
406                values_to_combine.into_iter(),
407                p,
408                Some(randomizer),
409                fs_rng,
410            )?;
411
412            let mut sponge_copy = fs_rng.clone();
413            for proof in &proof.0[..(i + 1)] {
414                proof.absorb_into_sponge(&mut sponge_copy);
415            }
416
417            let _ = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
418
419            randomizer = sponge_copy.squeeze_short_nonnative_field_element::<E::Fr>();
420        }
421
422        let result = Self::check_elems(vk, combined_comms, combined_witness, combined_adjusted_witness);
423        end_timer!(batch_check_time);
424        result
425    }
426
427    pub fn open_combinations<'a>(
428        universal_prover: &UniversalProver<E>,
429        ck: &CommitterUnionKey<E>,
430        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
431        polynomials: impl IntoIterator<Item = LabeledPolynomial<E::Fr>>,
432        rands: impl IntoIterator<Item = &'a Randomness<E>>,
433        query_set: &QuerySet<E::Fr>,
434        fs_rng: &mut S,
435    ) -> Result<BatchLCProof<E>>
436    where
437        Randomness<E>: 'a,
438        Commitment<E>: 'a,
439    {
440        let label_map =
441            polynomials.into_iter().zip_eq(rands).map(|(p, r)| (p.to_label(), (p, r))).collect::<BTreeMap<_, _>>();
442
443        let mut lc_polynomials = Vec::new();
444        let mut lc_randomness = Vec::new();
445        let mut lc_info = Vec::new();
446
447        for lc in linear_combinations {
448            let lc_label = lc.label().to_string();
449            let mut poly = DensePolynomial::zero();
450            let mut randomness = Randomness::empty();
451            let mut degree_bound = None;
452            let mut hiding_bound = None;
453
454            let num_polys = lc.len();
455            // We filter out l.is_one() entries because those constants are not committed to
456            // and used directly by the verifier.
457            for (coeff, label) in lc.iter().filter(|(_, l)| !l.is_one()) {
458                let label: &String = label.try_into().expect("cannot be one!");
459                let (cur_poly, cur_rand) =
460                    label_map.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
461                if let Some(cur_degree_bound) = cur_poly.degree_bound() {
462                    if num_polys != 1 {
463                        bail!(PCError::EquationHasDegreeBounds(lc_label));
464                    }
465                    assert!(coeff.is_one(), "Coefficient must be one for degree-bounded equations");
466                    if let Some(old_degree_bound) = degree_bound {
467                        assert_eq!(old_degree_bound, cur_degree_bound)
468                    } else {
469                        degree_bound = cur_poly.degree_bound();
470                    }
471                }
472                // Some(_) > None, always.
473                hiding_bound = core::cmp::max(hiding_bound, cur_poly.hiding_bound());
474                poly += (*coeff, cur_poly.polynomial());
475                randomness += (*coeff, *cur_rand);
476            }
477
478            let lc_poly = LabeledPolynomial::new(lc_label.clone(), poly, degree_bound, hiding_bound);
479            lc_polynomials.push(lc_poly);
480            lc_randomness.push(randomness);
481            lc_info.push((lc_label, degree_bound));
482        }
483
484        let proof =
485            Self::batch_open(universal_prover, ck, lc_polynomials.iter(), query_set, lc_randomness.iter(), fs_rng)?;
486
487        Ok(BatchLCProof { proof })
488    }
489
490    /// Checks that `values` are the true evaluations at `query_set` of the
491    /// polynomials committed in `labeled_commitments`.
492    pub fn check_combinations<'a>(
493        vk: &UniversalVerifier<E>,
494        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
495        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
496        query_set: &QuerySet<E::Fr>,
497        evaluations: &Evaluations<E::Fr>,
498        proof: &BatchLCProof<E>,
499        fs_rng: &mut S,
500    ) -> Result<bool>
501    where
502        Commitment<E>: 'a,
503    {
504        let BatchLCProof { proof } = proof;
505        let label_comm_map = commitments.into_iter().map(|c| (c.label(), c)).collect::<BTreeMap<_, _>>();
506
507        let mut lc_commitments = Vec::new();
508        let mut lc_info = Vec::new();
509        let mut evaluations = evaluations.clone();
510
511        let lc_processing_time = start_timer!(|| "Combining commitments");
512        for lc in linear_combinations {
513            let lc_label = lc.label().to_string();
514            let num_polys = lc.len();
515
516            let mut degree_bound = None;
517            let mut coeffs_and_comms = Vec::new();
518
519            for (coeff, label) in lc.iter() {
520                if label.is_one() {
521                    for ((label, _), ref mut eval) in evaluations.iter_mut() {
522                        if label == &lc_label {
523                            **eval -= coeff;
524                        }
525                    }
526                } else {
527                    let label: &String = label.try_into().unwrap();
528                    let &cur_comm = label_comm_map
529                        .get(label as &str)
530                        .ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
531
532                    if cur_comm.degree_bound().is_some() {
533                        if num_polys != 1 || !coeff.is_one() {
534                            bail!(PCError::EquationHasDegreeBounds(lc_label));
535                        }
536                        degree_bound = cur_comm.degree_bound();
537                    }
538                    coeffs_and_comms.push((*coeff, cur_comm.commitment()));
539                }
540            }
541            let lc_time = start_timer!(|| format!("Combining {num_polys} commitments for {lc_label}"));
542            lc_commitments.push(Self::combine_commitments(coeffs_and_comms));
543            end_timer!(lc_time);
544            lc_info.push((lc_label, degree_bound));
545        }
546        end_timer!(lc_processing_time);
547
548        let combined_comms_norm_time = start_timer!(|| "Normalizing commitments");
549        let comms = Self::normalize_commitments(lc_commitments);
550        ensure!(lc_info.len() == comms.len());
551        let lc_commitments = lc_info
552            .into_iter()
553            .zip_eq(comms)
554            .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
555            .collect::<Vec<_>>();
556        end_timer!(combined_comms_norm_time);
557
558        Self::batch_check(vk, &lc_commitments, query_set, &evaluations, proof, fs_rng)
559    }
560}
561
562impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
563    fn combine_polynomials<'a, B: Borrow<DensePolynomial<E::Fr>>>(
564        coeffs_polys_rands: impl IntoIterator<Item = (E::Fr, B, &'a Randomness<E>)>,
565    ) -> (DensePolynomial<E::Fr>, Randomness<E>) {
566        let mut combined_poly = DensePolynomial::zero();
567        let mut combined_rand = Randomness::empty();
568        for (coeff, poly, rand) in coeffs_polys_rands {
569            let poly = poly.borrow();
570            if coeff.is_one() {
571                combined_poly += poly;
572                combined_rand += rand;
573            } else {
574                combined_poly += (coeff, poly);
575                combined_rand += (coeff, rand);
576            }
577        }
578        (combined_poly, combined_rand)
579    }
580
581    /// MSM for `commitments` and `coeffs`
582    fn combine_commitments<'a>(
583        coeffs_and_comms: impl IntoIterator<Item = (E::Fr, &'a Commitment<E>)>,
584    ) -> E::G1Projective {
585        let (scalars, bases): (Vec<_>, Vec<_>) = coeffs_and_comms.into_iter().map(|(f, c)| (f.into(), c.0)).unzip();
586        VariableBase::msm(&bases, &scalars)
587    }
588
589    fn normalize_commitments(commitments: Vec<E::G1Projective>) -> impl ExactSizeIterator<Item = Commitment<E>> {
590        let comms = E::G1Projective::batch_normalization_into_affine(commitments);
591        comms.into_iter().map(|c| kzg10::KZGCommitment(c))
592    }
593}
594
595impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
596    #[allow(clippy::too_many_arguments)]
597    fn accumulate_elems<'a>(
598        combined_comms: &mut BTreeMap<Option<usize>, E::G1Projective>,
599        combined_witness: &mut E::G1Projective,
600        combined_adjusted_witness: &mut E::G1Projective,
601        vk: &UniversalVerifier<E>,
602        commitments: impl ExactSizeIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
603        point: E::Fr,
604        values: impl ExactSizeIterator<Item = E::Fr>,
605        proof: &kzg10::KZGProof<E>,
606        randomizer: Option<E::Fr>,
607        fs_rng: &mut S,
608    ) -> Result<()> {
609        let acc_time = start_timer!(|| "Accumulating elements");
610        // Keeps track of running combination of values
611        let mut combined_values = E::Fr::zero();
612
613        // Iterates through all of the commitments and accumulates common degree_bound
614        // elements in a BTreeMap
615        ensure!(commitments.len() == values.len());
616        for (labeled_comm, value) in commitments.into_iter().zip_eq(values) {
617            let acc_timer = start_timer!(|| format!("Accumulating {}", labeled_comm.label()));
618            let curr_challenge = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
619
620            combined_values += &(value * curr_challenge);
621
622            let comm = labeled_comm.commitment();
623            let degree_bound = labeled_comm.degree_bound();
624
625            // Applying opening challenge and randomness (used in batch_checking)
626            let coeff = randomizer.unwrap_or_else(E::Fr::one) * curr_challenge;
627            let comm_with_challenge: E::G1Projective = comm.0.mul(coeff);
628
629            // Accumulate values in the BTreeMap
630            *combined_comms.entry(degree_bound).or_insert_with(E::G1Projective::zero) += &comm_with_challenge;
631            end_timer!(acc_timer);
632        }
633
634        // Push expected results into list of elems. Power will be the negative of the
635        // expected power
636        let mut bases = vec![vk.vk.g, -proof.w];
637        let mut coeffs = vec![combined_values, point];
638        if let Some(random_v) = proof.random_v {
639            bases.push(vk.vk.gamma_g);
640            coeffs.push(random_v);
641        }
642        *combined_witness += if let Some(randomizer) = randomizer {
643            coeffs.iter_mut().for_each(|c| *c *= randomizer);
644            proof.w.mul(randomizer)
645        } else {
646            proof.w.to_projective()
647        };
648        let coeffs = coeffs.into_iter().map(|c| c.into()).collect::<Vec<_>>();
649        *combined_adjusted_witness += VariableBase::msm(&bases, &coeffs);
650        end_timer!(acc_time);
651        Ok(())
652    }
653
654    fn check_elems(
655        vk: &UniversalVerifier<E>,
656        combined_comms: BTreeMap<Option<usize>, E::G1Projective>,
657        combined_witness: E::G1Projective,
658        combined_adjusted_witness: E::G1Projective,
659    ) -> Result<bool> {
660        let check_time = start_timer!(|| "Checking elems");
661        let mut g1_projective_elems = Vec::with_capacity(combined_comms.len() + 2);
662        let mut g2_prepared_elems = Vec::with_capacity(combined_comms.len() + 2);
663
664        for (degree_bound, comm) in combined_comms.into_iter() {
665            let shift_power = if let Some(degree_bound) = degree_bound {
666                // Find the appropriate prepared shift for the degree bound.
667                vk.prepared_negative_powers_of_beta_h
668                    .get(&degree_bound)
669                    .cloned()
670                    .ok_or(PCError::UnsupportedDegreeBound(degree_bound))?
671            } else {
672                vk.vk.prepared_h.clone()
673            };
674
675            g1_projective_elems.push(comm);
676            g2_prepared_elems.push(shift_power);
677        }
678
679        g1_projective_elems.push(-combined_adjusted_witness);
680        g2_prepared_elems.push(vk.vk.prepared_h.clone());
681
682        g1_projective_elems.push(-combined_witness);
683        g2_prepared_elems.push(vk.vk.prepared_beta_h.clone());
684
685        let g1_prepared_elems_iter = E::G1Projective::batch_normalization_into_affine(g1_projective_elems)
686            .into_iter()
687            .map(|a| a.prepare())
688            .collect::<Vec<_>>();
689
690        ensure!(g1_prepared_elems_iter.len() == g2_prepared_elems.len());
691        let g1_g2_prepared = g1_prepared_elems_iter.iter().zip_eq(g2_prepared_elems.iter());
692        let is_one: bool = E::product_of_pairings(g1_g2_prepared).is_one();
693        end_timer!(check_time);
694        Ok(is_one)
695    }
696}
697
698#[cfg(test)]
699mod tests {
700    #![allow(non_camel_case_types)]
701
702    use super::{CommitterKey, SonicKZG10};
703    use crate::{crypto_hash::PoseidonSponge, polycommit::test_templates::*};
704    use snarkvm_curves::bls12_377::{Bls12_377, Fq};
705    use snarkvm_utilities::{FromBytes, ToBytes, rand::TestRng};
706
707    use rand::distr::Distribution;
708
709    type Sponge = PoseidonSponge<Fq, 2, 1>;
710    type PC_Bls12_377 = SonicKZG10<Bls12_377, Sponge>;
711
712    #[test]
713    fn test_committer_key_serialization() {
714        let rng = &mut TestRng::default();
715        let max_degree = rand::distr::Uniform::new_inclusive(8, 64).unwrap().sample(rng);
716        let supported_degree = rand::distr::Uniform::new_inclusive(1, max_degree).unwrap().sample(rng);
717
718        let lagrange_size = |d: usize| if d.is_power_of_two() { d } else { d.next_power_of_two() >> 1 };
719
720        let pp = PC_Bls12_377::load_srs(max_degree).unwrap();
721
722        let (ck, _vk) = PC_Bls12_377::trim(&pp, supported_degree, [lagrange_size(supported_degree)], 0, None).unwrap();
723
724        let ck_bytes = ck.to_bytes_le().unwrap();
725        let ck_recovered: CommitterKey<Bls12_377> = FromBytes::read_le(&ck_bytes[..]).unwrap();
726        let ck_recovered_bytes = ck_recovered.to_bytes_le().unwrap();
727
728        assert_eq!(&ck_bytes, &ck_recovered_bytes);
729    }
730
731    #[test]
732    fn test_single_poly() {
733        single_poly_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
734    }
735
736    #[test]
737    fn test_quadratic_poly_degree_bound_multiple_queries() {
738        quadratic_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
739    }
740
741    #[test]
742    fn test_linear_poly_degree_bound() {
743        linear_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
744    }
745
746    #[test]
747    fn test_single_poly_degree_bound() {
748        single_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
749    }
750
751    #[test]
752    fn test_single_poly_degree_bound_multiple_queries() {
753        single_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
754    }
755
756    #[test]
757    fn test_two_polys_degree_bound_single_query() {
758        two_polys_degree_bound_single_query_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
759    }
760
761    #[test]
762    fn test_full_end_to_end() {
763        full_end_to_end_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
764        println!("Finished bls12-377");
765    }
766
767    #[test]
768    fn test_single_equation() {
769        single_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
770        println!("Finished bls12-377");
771    }
772
773    #[test]
774    fn test_two_equation() {
775        two_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
776        println!("Finished bls12-377");
777    }
778
779    #[test]
780    fn test_two_equation_degree_bound() {
781        two_equation_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
782        println!("Finished bls12-377");
783    }
784
785    #[test]
786    fn test_full_end_to_end_equation() {
787        full_end_to_end_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
788        println!("Finished bls12-377");
789    }
790
791    #[test]
792    #[should_panic]
793    fn test_bad_degree_bound() {
794        bad_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
795        println!("Finished bls12-377");
796    }
797
798    #[test]
799    fn test_lagrange_commitment() {
800        crate::polycommit::test_templates::lagrange_test_template::<Bls12_377, Sponge>()
801            .expect("test failed for bls12-377");
802        println!("Finished bls12-377");
803    }
804}