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 rand::{RngCore, 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        rng: Option<&mut dyn RngCore>,
186    ) -> Result<(Vec<LabeledCommitment<Commitment<E>>>, Vec<Randomness<E>>), PCError> {
187        let rng = &mut OptionalRng(rng);
188        let commit_time = start_timer!(|| "Committing to polynomials");
189
190        let mut pool = snarkvm_utilities::ExecutionPool::<Result<_, _>>::new();
191        for p in polynomials {
192            let seed = rng.0.as_mut().map(|r| {
193                let mut seed = [0u8; 32];
194                r.fill_bytes(&mut seed);
195                seed
196            });
197
198            kzg10::KZG10::<E>::check_degrees_and_bounds(
199                universal_prover.max_degree,
200                ck.enforced_degree_bounds.as_deref(),
201                p.clone(),
202            )?;
203            let degree_bound = p.degree_bound();
204            let hiding_bound = p.hiding_bound();
205            let label = p.label().to_string();
206
207            pool.add_job(move || {
208                let mut rng = seed.map(rand::rngs::StdRng::from_seed);
209                add_to_trace!(|| "PC::Commit", || format!(
210                    "Polynomial {} of degree {}, degree bound {:?}, and hiding bound {:?}",
211                    label,
212                    p.degree(),
213                    degree_bound,
214                    hiding_bound,
215                ));
216
217                let (comm, rand) = {
218                    let rng_ref = rng.as_mut().map(|s| s as _);
219                    match p.polynomial {
220                        PolynomialWithBasis::Lagrange { evaluations } => {
221                            let domain = crate::fft::EvaluationDomain::new(evaluations.evaluations.len()).unwrap();
222                            let lagrange_basis = ck
223                                .lagrange_basis(domain)
224                                .ok_or(PCError::UnsupportedLagrangeBasisSize(domain.size()))?;
225                            assert!(domain.size().is_power_of_two());
226                            assert!(lagrange_basis.size().is_power_of_two());
227                            kzg10::KZG10::commit_lagrange(
228                                &lagrange_basis,
229                                &evaluations.evaluations,
230                                hiding_bound,
231                                rng_ref,
232                            )?
233                        }
234                        PolynomialWithBasis::Monomial { polynomial, degree_bound } => {
235                            let powers = if let Some(degree_bound) = degree_bound {
236                                ck.shifted_powers_of_beta_g(degree_bound).unwrap()
237                            } else {
238                                ck.powers()
239                            };
240
241                            kzg10::KZG10::commit(&powers, &polynomial, hiding_bound, rng_ref)?
242                        }
243                    }
244                };
245
246                Ok((LabeledCommitment::new(label.to_string(), comm, degree_bound), rand))
247            });
248        }
249        let results: Vec<Result<_, PCError>> = pool.execute_all();
250
251        let mut labeled_comms = Vec::with_capacity(results.len());
252        let mut randomness = Vec::with_capacity(results.len());
253        for result in results {
254            let (comm, rand) = result?;
255            labeled_comms.push(comm);
256            randomness.push(rand);
257        }
258
259        end_timer!(commit_time);
260        Ok((labeled_comms, randomness))
261    }
262
263    pub fn combine_for_open<'a>(
264        universal_prover: &UniversalProver<E>,
265        ck: &CommitterUnionKey<E>,
266        labeled_polynomials: impl ExactSizeIterator<Item = &'a LabeledPolynomial<E::Fr>>,
267        rands: impl ExactSizeIterator<Item = &'a Randomness<E>>,
268        fs_rng: &mut S,
269    ) -> Result<(DensePolynomial<E::Fr>, Randomness<E>)>
270    where
271        Randomness<E>: 'a,
272        Commitment<E>: 'a,
273    {
274        ensure!(labeled_polynomials.len() == rands.len());
275        let mut to_combine = Vec::with_capacity(labeled_polynomials.len());
276
277        for (p, r) in labeled_polynomials.zip_eq(rands) {
278            let enforced_degree_bounds: Option<&[usize]> = ck.enforced_degree_bounds.as_deref();
279
280            kzg10::KZG10::<E>::check_degrees_and_bounds(universal_prover.max_degree, enforced_degree_bounds, p)?;
281            let challenge = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
282            to_combine.push((challenge, p.polynomial().to_dense(), r));
283        }
284
285        Ok(Self::combine_polynomials(to_combine))
286    }
287
288    /// On input a list of labeled polynomials and a query set, `open` outputs a
289    /// proof of evaluation of the polynomials at the points in the query
290    /// set.
291    pub fn batch_open<'a>(
292        universal_prover: &UniversalProver<E>,
293        ck: &CommitterUnionKey<E>,
294        labeled_polynomials: impl ExactSizeIterator<Item = &'a LabeledPolynomial<E::Fr>>,
295        query_set: &QuerySet<E::Fr>,
296        rands: impl ExactSizeIterator<Item = &'a Randomness<E>>,
297        fs_rng: &mut S,
298    ) -> Result<BatchProof<E>>
299    where
300        Randomness<E>: 'a,
301        Commitment<E>: 'a,
302    {
303        ensure!(labeled_polynomials.len() == rands.len());
304        let poly_rand: HashMap<_, _> =
305            labeled_polynomials.into_iter().zip_eq(rands).map(|(poly, r)| (poly.label(), (poly, r))).collect();
306
307        let open_time = start_timer!(|| format!(
308            "Opening {} polynomials at query set of size {}",
309            poly_rand.len(),
310            query_set.len(),
311        ));
312
313        let mut query_to_labels_map = BTreeMap::new();
314
315        for (label, (point_name, point)) in query_set.iter() {
316            let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
317            labels.1.insert(label);
318        }
319
320        let mut pool = snarkvm_utilities::ExecutionPool::<_>::with_capacity(query_to_labels_map.len());
321        for (_point_name, (&query, labels)) in query_to_labels_map.into_iter() {
322            let mut query_polys = Vec::with_capacity(labels.len());
323            let mut query_rands = Vec::with_capacity(labels.len());
324
325            for label in labels {
326                let (polynomial, rand) =
327                    poly_rand.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
328
329                query_polys.push(*polynomial);
330                query_rands.push(*rand);
331            }
332            let (polynomial, rand) =
333                Self::combine_for_open(universal_prover, ck, query_polys.into_iter(), query_rands.into_iter(), fs_rng)?;
334            let _randomizer = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
335
336            pool.add_job(move || {
337                let proof_time = start_timer!(|| "Creating proof");
338                let proof = kzg10::KZG10::open(&ck.powers(), &polynomial, query, &rand);
339                end_timer!(proof_time);
340                proof
341            });
342        }
343        let batch_proof = pool.execute_all().into_iter().collect::<Result<_, _>>().map(BatchProof).map_err(Into::into);
344        end_timer!(open_time);
345
346        batch_proof
347    }
348
349    pub fn batch_check<'a>(
350        vk: &UniversalVerifier<E>,
351        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
352        query_set: &QuerySet<E::Fr>,
353        values: &Evaluations<E::Fr>,
354        proof: &BatchProof<E>,
355        fs_rng: &mut S,
356    ) -> Result<bool>
357    where
358        Commitment<E>: 'a,
359    {
360        let commitments: BTreeMap<_, _> = commitments.into_iter().map(|c| (c.label().to_owned(), c)).collect();
361        let batch_check_time = start_timer!(|| format!(
362            "Checking {} commitments at query set of size {}",
363            commitments.len(),
364            query_set.len(),
365        ));
366        let mut query_to_labels_map = BTreeMap::new();
367
368        for (label, (point_name, point)) in query_set.iter() {
369            let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
370            labels.1.insert(label);
371        }
372
373        assert_eq!(proof.0.len(), query_to_labels_map.len());
374
375        let mut randomizer = E::Fr::one();
376
377        let mut combined_comms = BTreeMap::new();
378        let mut combined_witness = E::G1Projective::zero();
379        let mut combined_adjusted_witness = E::G1Projective::zero();
380
381        ensure!(query_to_labels_map.len() == proof.0.len());
382        for ((_query_name, (query, labels)), p) in query_to_labels_map.into_iter().zip_eq(&proof.0) {
383            let mut comms_to_combine: Vec<&'_ LabeledCommitment<_>> = Vec::new();
384            let mut values_to_combine = Vec::new();
385            for label in labels.into_iter() {
386                let commitment =
387                    commitments.get(label).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
388
389                let v_i = values
390                    .get(&(label.clone(), *query))
391                    .ok_or(PCError::MissingEvaluation { label: label.to_string() })?;
392
393                comms_to_combine.push(commitment);
394                values_to_combine.push(*v_i);
395            }
396
397            Self::accumulate_elems(
398                &mut combined_comms,
399                &mut combined_witness,
400                &mut combined_adjusted_witness,
401                vk,
402                comms_to_combine.into_iter(),
403                *query,
404                values_to_combine.into_iter(),
405                p,
406                Some(randomizer),
407                fs_rng,
408            )?;
409
410            randomizer = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
411        }
412
413        let result = Self::check_elems(vk, combined_comms, combined_witness, combined_adjusted_witness);
414        end_timer!(batch_check_time);
415        result
416    }
417
418    pub fn open_combinations<'a>(
419        universal_prover: &UniversalProver<E>,
420        ck: &CommitterUnionKey<E>,
421        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
422        polynomials: impl IntoIterator<Item = LabeledPolynomial<E::Fr>>,
423        rands: impl IntoIterator<Item = &'a Randomness<E>>,
424        query_set: &QuerySet<E::Fr>,
425        fs_rng: &mut S,
426    ) -> Result<BatchLCProof<E>>
427    where
428        Randomness<E>: 'a,
429        Commitment<E>: 'a,
430    {
431        let label_map =
432            polynomials.into_iter().zip_eq(rands).map(|(p, r)| (p.to_label(), (p, r))).collect::<BTreeMap<_, _>>();
433
434        let mut lc_polynomials = Vec::new();
435        let mut lc_randomness = Vec::new();
436        let mut lc_info = Vec::new();
437
438        for lc in linear_combinations {
439            let lc_label = lc.label().to_string();
440            let mut poly = DensePolynomial::zero();
441            let mut randomness = Randomness::empty();
442            let mut degree_bound = None;
443            let mut hiding_bound = None;
444
445            let num_polys = lc.len();
446            // We filter out l.is_one() entries because those constants are not committed to
447            // and used directly by the verifier.
448            for (coeff, label) in lc.iter().filter(|(_, l)| !l.is_one()) {
449                let label: &String = label.try_into().expect("cannot be one!");
450                let (cur_poly, cur_rand) =
451                    label_map.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
452                if let Some(cur_degree_bound) = cur_poly.degree_bound() {
453                    if num_polys != 1 {
454                        bail!(PCError::EquationHasDegreeBounds(lc_label));
455                    }
456                    assert!(coeff.is_one(), "Coefficient must be one for degree-bounded equations");
457                    if let Some(old_degree_bound) = degree_bound {
458                        assert_eq!(old_degree_bound, cur_degree_bound)
459                    } else {
460                        degree_bound = cur_poly.degree_bound();
461                    }
462                }
463                // Some(_) > None, always.
464                hiding_bound = core::cmp::max(hiding_bound, cur_poly.hiding_bound());
465                poly += (*coeff, cur_poly.polynomial());
466                randomness += (*coeff, *cur_rand);
467            }
468
469            let lc_poly = LabeledPolynomial::new(lc_label.clone(), poly, degree_bound, hiding_bound);
470            lc_polynomials.push(lc_poly);
471            lc_randomness.push(randomness);
472            lc_info.push((lc_label, degree_bound));
473        }
474
475        let proof =
476            Self::batch_open(universal_prover, ck, lc_polynomials.iter(), query_set, lc_randomness.iter(), fs_rng)?;
477
478        Ok(BatchLCProof { proof })
479    }
480
481    /// Checks that `values` are the true evaluations at `query_set` of the
482    /// polynomials committed in `labeled_commitments`.
483    pub fn check_combinations<'a>(
484        vk: &UniversalVerifier<E>,
485        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
486        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
487        query_set: &QuerySet<E::Fr>,
488        evaluations: &Evaluations<E::Fr>,
489        proof: &BatchLCProof<E>,
490        fs_rng: &mut S,
491    ) -> Result<bool>
492    where
493        Commitment<E>: 'a,
494    {
495        let BatchLCProof { proof } = proof;
496        let label_comm_map = commitments.into_iter().map(|c| (c.label(), c)).collect::<BTreeMap<_, _>>();
497
498        let mut lc_commitments = Vec::new();
499        let mut lc_info = Vec::new();
500        let mut evaluations = evaluations.clone();
501
502        let lc_processing_time = start_timer!(|| "Combining commitments");
503        for lc in linear_combinations {
504            let lc_label = lc.label().to_string();
505            let num_polys = lc.len();
506
507            let mut degree_bound = None;
508            let mut coeffs_and_comms = Vec::new();
509
510            for (coeff, label) in lc.iter() {
511                if label.is_one() {
512                    for ((label, _), ref mut eval) in evaluations.iter_mut() {
513                        if label == &lc_label {
514                            **eval -= coeff;
515                        }
516                    }
517                } else {
518                    let label: &String = label.try_into().unwrap();
519                    let &cur_comm = label_comm_map
520                        .get(label as &str)
521                        .ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
522
523                    if cur_comm.degree_bound().is_some() {
524                        if num_polys != 1 || !coeff.is_one() {
525                            bail!(PCError::EquationHasDegreeBounds(lc_label));
526                        }
527                        degree_bound = cur_comm.degree_bound();
528                    }
529                    coeffs_and_comms.push((*coeff, cur_comm.commitment()));
530                }
531            }
532            let lc_time = start_timer!(|| format!("Combining {num_polys} commitments for {lc_label}"));
533            lc_commitments.push(Self::combine_commitments(coeffs_and_comms));
534            end_timer!(lc_time);
535            lc_info.push((lc_label, degree_bound));
536        }
537        end_timer!(lc_processing_time);
538
539        let combined_comms_norm_time = start_timer!(|| "Normalizing commitments");
540        let comms = Self::normalize_commitments(lc_commitments);
541        ensure!(lc_info.len() == comms.len());
542        let lc_commitments = lc_info
543            .into_iter()
544            .zip_eq(comms)
545            .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
546            .collect::<Vec<_>>();
547        end_timer!(combined_comms_norm_time);
548
549        Self::batch_check(vk, &lc_commitments, query_set, &evaluations, proof, fs_rng)
550    }
551}
552
553impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
554    fn combine_polynomials<'a, B: Borrow<DensePolynomial<E::Fr>>>(
555        coeffs_polys_rands: impl IntoIterator<Item = (E::Fr, B, &'a Randomness<E>)>,
556    ) -> (DensePolynomial<E::Fr>, Randomness<E>) {
557        let mut combined_poly = DensePolynomial::zero();
558        let mut combined_rand = Randomness::empty();
559        for (coeff, poly, rand) in coeffs_polys_rands {
560            let poly = poly.borrow();
561            if coeff.is_one() {
562                combined_poly += poly;
563                combined_rand += rand;
564            } else {
565                combined_poly += (coeff, poly);
566                combined_rand += (coeff, rand);
567            }
568        }
569        (combined_poly, combined_rand)
570    }
571
572    /// MSM for `commitments` and `coeffs`
573    fn combine_commitments<'a>(
574        coeffs_and_comms: impl IntoIterator<Item = (E::Fr, &'a Commitment<E>)>,
575    ) -> E::G1Projective {
576        let (scalars, bases): (Vec<_>, Vec<_>) = coeffs_and_comms.into_iter().map(|(f, c)| (f.into(), c.0)).unzip();
577        VariableBase::msm(&bases, &scalars)
578    }
579
580    fn normalize_commitments(commitments: Vec<E::G1Projective>) -> impl ExactSizeIterator<Item = Commitment<E>> {
581        let comms = E::G1Projective::batch_normalization_into_affine(commitments);
582        comms.into_iter().map(|c| kzg10::KZGCommitment(c))
583    }
584}
585
586impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
587    #[allow(clippy::too_many_arguments)]
588    fn accumulate_elems<'a>(
589        combined_comms: &mut BTreeMap<Option<usize>, E::G1Projective>,
590        combined_witness: &mut E::G1Projective,
591        combined_adjusted_witness: &mut E::G1Projective,
592        vk: &UniversalVerifier<E>,
593        commitments: impl ExactSizeIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
594        point: E::Fr,
595        values: impl ExactSizeIterator<Item = E::Fr>,
596        proof: &kzg10::KZGProof<E>,
597        randomizer: Option<E::Fr>,
598        fs_rng: &mut S,
599    ) -> Result<()> {
600        let acc_time = start_timer!(|| "Accumulating elements");
601        // Keeps track of running combination of values
602        let mut combined_values = E::Fr::zero();
603
604        // Iterates through all of the commitments and accumulates common degree_bound
605        // elements in a BTreeMap
606        ensure!(commitments.len() == values.len());
607        for (labeled_comm, value) in commitments.into_iter().zip_eq(values) {
608            let acc_timer = start_timer!(|| format!("Accumulating {}", labeled_comm.label()));
609            let curr_challenge = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
610
611            combined_values += &(value * curr_challenge);
612
613            let comm = labeled_comm.commitment();
614            let degree_bound = labeled_comm.degree_bound();
615
616            // Applying opening challenge and randomness (used in batch_checking)
617            let coeff = randomizer.unwrap_or_else(E::Fr::one) * curr_challenge;
618            let comm_with_challenge: E::G1Projective = comm.0.mul(coeff);
619
620            // Accumulate values in the BTreeMap
621            *combined_comms.entry(degree_bound).or_insert_with(E::G1Projective::zero) += &comm_with_challenge;
622            end_timer!(acc_timer);
623        }
624
625        // Push expected results into list of elems. Power will be the negative of the
626        // expected power
627        let mut bases = vec![vk.vk.g, -proof.w];
628        let mut coeffs = vec![combined_values, point];
629        if let Some(random_v) = proof.random_v {
630            bases.push(vk.vk.gamma_g);
631            coeffs.push(random_v);
632        }
633        *combined_witness += if let Some(randomizer) = randomizer {
634            coeffs.iter_mut().for_each(|c| *c *= randomizer);
635            proof.w.mul(randomizer)
636        } else {
637            proof.w.to_projective()
638        };
639        let coeffs = coeffs.into_iter().map(|c| c.into()).collect::<Vec<_>>();
640        *combined_adjusted_witness += VariableBase::msm(&bases, &coeffs);
641        end_timer!(acc_time);
642        Ok(())
643    }
644
645    fn check_elems(
646        vk: &UniversalVerifier<E>,
647        combined_comms: BTreeMap<Option<usize>, E::G1Projective>,
648        combined_witness: E::G1Projective,
649        combined_adjusted_witness: E::G1Projective,
650    ) -> Result<bool> {
651        let check_time = start_timer!(|| "Checking elems");
652        let mut g1_projective_elems = Vec::with_capacity(combined_comms.len() + 2);
653        let mut g2_prepared_elems = Vec::with_capacity(combined_comms.len() + 2);
654
655        for (degree_bound, comm) in combined_comms.into_iter() {
656            let shift_power = if let Some(degree_bound) = degree_bound {
657                // Find the appropriate prepared shift for the degree bound.
658                vk.prepared_negative_powers_of_beta_h
659                    .get(&degree_bound)
660                    .cloned()
661                    .ok_or(PCError::UnsupportedDegreeBound(degree_bound))?
662            } else {
663                vk.vk.prepared_h.clone()
664            };
665
666            g1_projective_elems.push(comm);
667            g2_prepared_elems.push(shift_power);
668        }
669
670        g1_projective_elems.push(-combined_adjusted_witness);
671        g2_prepared_elems.push(vk.vk.prepared_h.clone());
672
673        g1_projective_elems.push(-combined_witness);
674        g2_prepared_elems.push(vk.vk.prepared_beta_h.clone());
675
676        let g1_prepared_elems_iter = E::G1Projective::batch_normalization_into_affine(g1_projective_elems)
677            .into_iter()
678            .map(|a| a.prepare())
679            .collect::<Vec<_>>();
680
681        ensure!(g1_prepared_elems_iter.len() == g2_prepared_elems.len());
682        let g1_g2_prepared = g1_prepared_elems_iter.iter().zip_eq(g2_prepared_elems.iter());
683        let is_one: bool = E::product_of_pairings(g1_g2_prepared).is_one();
684        end_timer!(check_time);
685        Ok(is_one)
686    }
687}
688
689#[cfg(test)]
690mod tests {
691    #![allow(non_camel_case_types)]
692
693    use super::{CommitterKey, SonicKZG10};
694    use crate::{crypto_hash::PoseidonSponge, polycommit::test_templates::*};
695    use snarkvm_curves::bls12_377::{Bls12_377, Fq};
696    use snarkvm_utilities::{FromBytes, ToBytes, rand::TestRng};
697
698    use rand::distributions::Distribution;
699
700    type Sponge = PoseidonSponge<Fq, 2, 1>;
701    type PC_Bls12_377 = SonicKZG10<Bls12_377, Sponge>;
702
703    #[test]
704    fn test_committer_key_serialization() {
705        let rng = &mut TestRng::default();
706        let max_degree = rand::distributions::Uniform::from(8..=64).sample(rng);
707        let supported_degree = rand::distributions::Uniform::from(1..=max_degree).sample(rng);
708
709        let lagrange_size = |d: usize| if d.is_power_of_two() { d } else { d.next_power_of_two() >> 1 };
710
711        let pp = PC_Bls12_377::load_srs(max_degree).unwrap();
712
713        let (ck, _vk) = PC_Bls12_377::trim(&pp, supported_degree, [lagrange_size(supported_degree)], 0, None).unwrap();
714
715        let ck_bytes = ck.to_bytes_le().unwrap();
716        let ck_recovered: CommitterKey<Bls12_377> = FromBytes::read_le(&ck_bytes[..]).unwrap();
717        let ck_recovered_bytes = ck_recovered.to_bytes_le().unwrap();
718
719        assert_eq!(&ck_bytes, &ck_recovered_bytes);
720    }
721
722    #[test]
723    fn test_single_poly() {
724        single_poly_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
725    }
726
727    #[test]
728    fn test_quadratic_poly_degree_bound_multiple_queries() {
729        quadratic_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
730    }
731
732    #[test]
733    fn test_linear_poly_degree_bound() {
734        linear_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
735    }
736
737    #[test]
738    fn test_single_poly_degree_bound() {
739        single_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
740    }
741
742    #[test]
743    fn test_single_poly_degree_bound_multiple_queries() {
744        single_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
745    }
746
747    #[test]
748    fn test_two_polys_degree_bound_single_query() {
749        two_polys_degree_bound_single_query_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
750    }
751
752    #[test]
753    fn test_full_end_to_end() {
754        full_end_to_end_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
755        println!("Finished bls12-377");
756    }
757
758    #[test]
759    fn test_single_equation() {
760        single_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
761        println!("Finished bls12-377");
762    }
763
764    #[test]
765    fn test_two_equation() {
766        two_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
767        println!("Finished bls12-377");
768    }
769
770    #[test]
771    fn test_two_equation_degree_bound() {
772        two_equation_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
773        println!("Finished bls12-377");
774    }
775
776    #[test]
777    fn test_full_end_to_end_equation() {
778        full_end_to_end_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
779        println!("Finished bls12-377");
780    }
781
782    #[test]
783    #[should_panic]
784    fn test_bad_degree_bound() {
785        bad_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
786        println!("Finished bls12-377");
787    }
788
789    #[test]
790    fn test_lagrange_commitment() {
791        crate::polycommit::test_templates::lagrange_test_template::<Bls12_377, Sponge>()
792            .expect("test failed for bls12-377");
793        println!("Finished bls12-377");
794    }
795}