snarkvm_algorithms/polycommit/sonic_pc/
mod.rs

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