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, 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 proofs = Vec::new();
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
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            proofs.push(proof);
339            let mut sponge_copy = fs_rng.clone();
340            for proof in &proofs {
341                proof.absorb_into_sponge(&mut sponge_copy);
342            }
343            let _ = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
344            let _randomizer = sponge_copy.squeeze_short_nonnative_field_element::<E::Fr>();
345        }
346        let batch_proof = BatchProof(proofs);
347        end_timer!(open_time);
348
349        Ok(batch_proof)
350    }
351
352    pub fn batch_check<'a>(
353        vk: &UniversalVerifier<E>,
354        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
355        query_set: &QuerySet<E::Fr>,
356        values: &Evaluations<E::Fr>,
357        proof: &BatchProof<E>,
358        fs_rng: &mut S,
359    ) -> Result<bool>
360    where
361        Commitment<E>: 'a,
362    {
363        let commitments: BTreeMap<_, _> = commitments.into_iter().map(|c| (c.label().to_owned(), c)).collect();
364        let batch_check_time = start_timer!(|| format!(
365            "Checking {} commitments at query set of size {}",
366            commitments.len(),
367            query_set.len(),
368        ));
369        let mut query_to_labels_map = BTreeMap::new();
370
371        for (label, (point_name, point)) in query_set.iter() {
372            let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
373            labels.1.insert(label);
374        }
375
376        assert_eq!(proof.0.len(), query_to_labels_map.len());
377
378        let mut randomizer = E::Fr::one();
379
380        let mut combined_comms = BTreeMap::new();
381        let mut combined_witness = E::G1Projective::zero();
382        let mut combined_adjusted_witness = E::G1Projective::zero();
383
384        ensure!(query_to_labels_map.len() == proof.0.len());
385        for (i, ((_query_name, (query, labels)), p)) in query_to_labels_map.into_iter().zip_eq(&proof.0).enumerate() {
386            let mut comms_to_combine: Vec<&'_ LabeledCommitment<_>> = Vec::new();
387            let mut values_to_combine = Vec::new();
388            for label in labels.into_iter() {
389                let commitment =
390                    commitments.get(label).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
391
392                let v_i = values
393                    .get(&(label.clone(), *query))
394                    .ok_or(PCError::MissingEvaluation { label: label.to_string() })?;
395
396                comms_to_combine.push(commitment);
397                values_to_combine.push(*v_i);
398            }
399
400            Self::accumulate_elems(
401                &mut combined_comms,
402                &mut combined_witness,
403                &mut combined_adjusted_witness,
404                vk,
405                comms_to_combine.into_iter(),
406                *query,
407                values_to_combine.into_iter(),
408                p,
409                Some(randomizer),
410                fs_rng,
411            )?;
412
413            let mut sponge_copy = fs_rng.clone();
414            for proof in &proof.0[..(i + 1)] {
415                proof.absorb_into_sponge(&mut sponge_copy);
416            }
417
418            let _ = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
419
420            randomizer = sponge_copy.squeeze_short_nonnative_field_element::<E::Fr>();
421        }
422
423        let result = Self::check_elems(vk, combined_comms, combined_witness, combined_adjusted_witness);
424        end_timer!(batch_check_time);
425        result
426    }
427
428    pub fn open_combinations<'a>(
429        universal_prover: &UniversalProver<E>,
430        ck: &CommitterUnionKey<E>,
431        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
432        polynomials: impl IntoIterator<Item = LabeledPolynomial<E::Fr>>,
433        rands: impl IntoIterator<Item = &'a Randomness<E>>,
434        query_set: &QuerySet<E::Fr>,
435        fs_rng: &mut S,
436    ) -> Result<BatchLCProof<E>>
437    where
438        Randomness<E>: 'a,
439        Commitment<E>: 'a,
440    {
441        let label_map =
442            polynomials.into_iter().zip_eq(rands).map(|(p, r)| (p.to_label(), (p, r))).collect::<BTreeMap<_, _>>();
443
444        let mut lc_polynomials = Vec::new();
445        let mut lc_randomness = Vec::new();
446        let mut lc_info = Vec::new();
447
448        for lc in linear_combinations {
449            let lc_label = lc.label().to_string();
450            let mut poly = DensePolynomial::zero();
451            let mut randomness = Randomness::empty();
452            let mut degree_bound = None;
453            let mut hiding_bound = None;
454
455            let num_polys = lc.len();
456            // We filter out l.is_one() entries because those constants are not committed to
457            // and used directly by the verifier.
458            for (coeff, label) in lc.iter().filter(|(_, l)| !l.is_one()) {
459                let label: &String = label.try_into().expect("cannot be one!");
460                let (cur_poly, cur_rand) =
461                    label_map.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
462                if let Some(cur_degree_bound) = cur_poly.degree_bound() {
463                    if num_polys != 1 {
464                        bail!(PCError::EquationHasDegreeBounds(lc_label));
465                    }
466                    assert!(coeff.is_one(), "Coefficient must be one for degree-bounded equations");
467                    if let Some(old_degree_bound) = degree_bound {
468                        assert_eq!(old_degree_bound, cur_degree_bound)
469                    } else {
470                        degree_bound = cur_poly.degree_bound();
471                    }
472                }
473                // Some(_) > None, always.
474                hiding_bound = core::cmp::max(hiding_bound, cur_poly.hiding_bound());
475                poly += (*coeff, cur_poly.polynomial());
476                randomness += (*coeff, *cur_rand);
477            }
478
479            let lc_poly = LabeledPolynomial::new(lc_label.clone(), poly, degree_bound, hiding_bound);
480            lc_polynomials.push(lc_poly);
481            lc_randomness.push(randomness);
482            lc_info.push((lc_label, degree_bound));
483        }
484
485        let proof =
486            Self::batch_open(universal_prover, ck, lc_polynomials.iter(), query_set, lc_randomness.iter(), fs_rng)?;
487
488        Ok(BatchLCProof { proof })
489    }
490
491    /// Checks that `values` are the true evaluations at `query_set` of the
492    /// polynomials committed in `labeled_commitments`.
493    pub fn check_combinations<'a>(
494        vk: &UniversalVerifier<E>,
495        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
496        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
497        query_set: &QuerySet<E::Fr>,
498        evaluations: &Evaluations<E::Fr>,
499        proof: &BatchLCProof<E>,
500        fs_rng: &mut S,
501    ) -> Result<bool>
502    where
503        Commitment<E>: 'a,
504    {
505        let BatchLCProof { proof } = proof;
506        let label_comm_map = commitments.into_iter().map(|c| (c.label(), c)).collect::<BTreeMap<_, _>>();
507
508        let mut lc_commitments = Vec::new();
509        let mut lc_info = Vec::new();
510        let mut evaluations = evaluations.clone();
511
512        let lc_processing_time = start_timer!(|| "Combining commitments");
513        for lc in linear_combinations {
514            let lc_label = lc.label().to_string();
515            let num_polys = lc.len();
516
517            let mut degree_bound = None;
518            let mut coeffs_and_comms = Vec::new();
519
520            for (coeff, label) in lc.iter() {
521                if label.is_one() {
522                    for ((label, _), ref mut eval) in evaluations.iter_mut() {
523                        if label == &lc_label {
524                            **eval -= coeff;
525                        }
526                    }
527                } else {
528                    let label: &String = label.try_into().unwrap();
529                    let &cur_comm = label_comm_map
530                        .get(label as &str)
531                        .ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
532
533                    if cur_comm.degree_bound().is_some() {
534                        if num_polys != 1 || !coeff.is_one() {
535                            bail!(PCError::EquationHasDegreeBounds(lc_label));
536                        }
537                        degree_bound = cur_comm.degree_bound();
538                    }
539                    coeffs_and_comms.push((*coeff, cur_comm.commitment()));
540                }
541            }
542            let lc_time = start_timer!(|| format!("Combining {num_polys} commitments for {lc_label}"));
543            lc_commitments.push(Self::combine_commitments(coeffs_and_comms));
544            end_timer!(lc_time);
545            lc_info.push((lc_label, degree_bound));
546        }
547        end_timer!(lc_processing_time);
548
549        let combined_comms_norm_time = start_timer!(|| "Normalizing commitments");
550        let comms = Self::normalize_commitments(lc_commitments);
551        ensure!(lc_info.len() == comms.len());
552        let lc_commitments = lc_info
553            .into_iter()
554            .zip_eq(comms)
555            .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
556            .collect::<Vec<_>>();
557        end_timer!(combined_comms_norm_time);
558
559        Self::batch_check(vk, &lc_commitments, query_set, &evaluations, proof, fs_rng)
560    }
561}
562
563impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
564    fn combine_polynomials<'a, B: Borrow<DensePolynomial<E::Fr>>>(
565        coeffs_polys_rands: impl IntoIterator<Item = (E::Fr, B, &'a Randomness<E>)>,
566    ) -> (DensePolynomial<E::Fr>, Randomness<E>) {
567        let mut combined_poly = DensePolynomial::zero();
568        let mut combined_rand = Randomness::empty();
569        for (coeff, poly, rand) in coeffs_polys_rands {
570            let poly = poly.borrow();
571            if coeff.is_one() {
572                combined_poly += poly;
573                combined_rand += rand;
574            } else {
575                combined_poly += (coeff, poly);
576                combined_rand += (coeff, rand);
577            }
578        }
579        (combined_poly, combined_rand)
580    }
581
582    /// MSM for `commitments` and `coeffs`
583    fn combine_commitments<'a>(
584        coeffs_and_comms: impl IntoIterator<Item = (E::Fr, &'a Commitment<E>)>,
585    ) -> E::G1Projective {
586        let (scalars, bases): (Vec<_>, Vec<_>) = coeffs_and_comms.into_iter().map(|(f, c)| (f.into(), c.0)).unzip();
587        VariableBase::msm(&bases, &scalars)
588    }
589
590    fn normalize_commitments(commitments: Vec<E::G1Projective>) -> impl ExactSizeIterator<Item = Commitment<E>> {
591        let comms = E::G1Projective::batch_normalization_into_affine(commitments);
592        comms.into_iter().map(|c| kzg10::KZGCommitment(c))
593    }
594}
595
596impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
597    #[allow(clippy::too_many_arguments)]
598    fn accumulate_elems<'a>(
599        combined_comms: &mut BTreeMap<Option<usize>, E::G1Projective>,
600        combined_witness: &mut E::G1Projective,
601        combined_adjusted_witness: &mut E::G1Projective,
602        vk: &UniversalVerifier<E>,
603        commitments: impl ExactSizeIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
604        point: E::Fr,
605        values: impl ExactSizeIterator<Item = E::Fr>,
606        proof: &kzg10::KZGProof<E>,
607        randomizer: Option<E::Fr>,
608        fs_rng: &mut S,
609    ) -> Result<()> {
610        let acc_time = start_timer!(|| "Accumulating elements");
611        // Keeps track of running combination of values
612        let mut combined_values = E::Fr::zero();
613
614        // Iterates through all of the commitments and accumulates common degree_bound
615        // elements in a BTreeMap
616        ensure!(commitments.len() == values.len());
617        for (labeled_comm, value) in commitments.into_iter().zip_eq(values) {
618            let acc_timer = start_timer!(|| format!("Accumulating {}", labeled_comm.label()));
619            let curr_challenge = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
620
621            combined_values += &(value * curr_challenge);
622
623            let comm = labeled_comm.commitment();
624            let degree_bound = labeled_comm.degree_bound();
625
626            // Applying opening challenge and randomness (used in batch_checking)
627            let coeff = randomizer.unwrap_or_else(E::Fr::one) * curr_challenge;
628            let comm_with_challenge: E::G1Projective = comm.0.mul(coeff);
629
630            // Accumulate values in the BTreeMap
631            *combined_comms.entry(degree_bound).or_insert_with(E::G1Projective::zero) += &comm_with_challenge;
632            end_timer!(acc_timer);
633        }
634
635        // Push expected results into list of elems. Power will be the negative of the
636        // expected power
637        let mut bases = vec![vk.vk.g, -proof.w];
638        let mut coeffs = vec![combined_values, point];
639        if let Some(random_v) = proof.random_v {
640            bases.push(vk.vk.gamma_g);
641            coeffs.push(random_v);
642        }
643        *combined_witness += if let Some(randomizer) = randomizer {
644            coeffs.iter_mut().for_each(|c| *c *= randomizer);
645            proof.w.mul(randomizer)
646        } else {
647            proof.w.to_projective()
648        };
649        let coeffs = coeffs.into_iter().map(|c| c.into()).collect::<Vec<_>>();
650        *combined_adjusted_witness += VariableBase::msm(&bases, &coeffs);
651        end_timer!(acc_time);
652        Ok(())
653    }
654
655    fn check_elems(
656        vk: &UniversalVerifier<E>,
657        combined_comms: BTreeMap<Option<usize>, E::G1Projective>,
658        combined_witness: E::G1Projective,
659        combined_adjusted_witness: E::G1Projective,
660    ) -> Result<bool> {
661        let check_time = start_timer!(|| "Checking elems");
662        let mut g1_projective_elems = Vec::with_capacity(combined_comms.len() + 2);
663        let mut g2_prepared_elems = Vec::with_capacity(combined_comms.len() + 2);
664
665        for (degree_bound, comm) in combined_comms.into_iter() {
666            let shift_power = if let Some(degree_bound) = degree_bound {
667                // Find the appropriate prepared shift for the degree bound.
668                vk.prepared_negative_powers_of_beta_h
669                    .get(&degree_bound)
670                    .cloned()
671                    .ok_or(PCError::UnsupportedDegreeBound(degree_bound))?
672            } else {
673                vk.vk.prepared_h.clone()
674            };
675
676            g1_projective_elems.push(comm);
677            g2_prepared_elems.push(shift_power);
678        }
679
680        g1_projective_elems.push(-combined_adjusted_witness);
681        g2_prepared_elems.push(vk.vk.prepared_h.clone());
682
683        g1_projective_elems.push(-combined_witness);
684        g2_prepared_elems.push(vk.vk.prepared_beta_h.clone());
685
686        let g1_prepared_elems_iter = E::G1Projective::batch_normalization_into_affine(g1_projective_elems)
687            .into_iter()
688            .map(|a| a.prepare())
689            .collect::<Vec<_>>();
690
691        ensure!(g1_prepared_elems_iter.len() == g2_prepared_elems.len());
692        let g1_g2_prepared = g1_prepared_elems_iter.iter().zip_eq(g2_prepared_elems.iter());
693        let is_one: bool = E::product_of_pairings(g1_g2_prepared).is_one();
694        end_timer!(check_time);
695        Ok(is_one)
696    }
697}
698
699#[cfg(test)]
700mod tests {
701    #![allow(non_camel_case_types)]
702
703    use super::{CommitterKey, SonicKZG10};
704    use crate::{crypto_hash::PoseidonSponge, polycommit::test_templates::*};
705    use snarkvm_curves::bls12_377::{Bls12_377, Fq};
706    use snarkvm_utilities::{FromBytes, ToBytes, rand::TestRng};
707
708    use rand::distributions::Distribution;
709
710    type Sponge = PoseidonSponge<Fq, 2, 1>;
711    type PC_Bls12_377 = SonicKZG10<Bls12_377, Sponge>;
712
713    #[test]
714    fn test_committer_key_serialization() {
715        let rng = &mut TestRng::default();
716        let max_degree = rand::distributions::Uniform::from(8..=64).sample(rng);
717        let supported_degree = rand::distributions::Uniform::from(1..=max_degree).sample(rng);
718
719        let lagrange_size = |d: usize| if d.is_power_of_two() { d } else { d.next_power_of_two() >> 1 };
720
721        let pp = PC_Bls12_377::load_srs(max_degree).unwrap();
722
723        let (ck, _vk) = PC_Bls12_377::trim(&pp, supported_degree, [lagrange_size(supported_degree)], 0, None).unwrap();
724
725        let ck_bytes = ck.to_bytes_le().unwrap();
726        let ck_recovered: CommitterKey<Bls12_377> = FromBytes::read_le(&ck_bytes[..]).unwrap();
727        let ck_recovered_bytes = ck_recovered.to_bytes_le().unwrap();
728
729        assert_eq!(&ck_bytes, &ck_recovered_bytes);
730    }
731
732    #[test]
733    fn test_single_poly() {
734        single_poly_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
735    }
736
737    #[test]
738    fn test_quadratic_poly_degree_bound_multiple_queries() {
739        quadratic_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
740    }
741
742    #[test]
743    fn test_linear_poly_degree_bound() {
744        linear_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
745    }
746
747    #[test]
748    fn test_single_poly_degree_bound() {
749        single_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
750    }
751
752    #[test]
753    fn test_single_poly_degree_bound_multiple_queries() {
754        single_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
755    }
756
757    #[test]
758    fn test_two_polys_degree_bound_single_query() {
759        two_polys_degree_bound_single_query_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
760    }
761
762    #[test]
763    fn test_full_end_to_end() {
764        full_end_to_end_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
765        println!("Finished bls12-377");
766    }
767
768    #[test]
769    fn test_single_equation() {
770        single_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
771        println!("Finished bls12-377");
772    }
773
774    #[test]
775    fn test_two_equation() {
776        two_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
777        println!("Finished bls12-377");
778    }
779
780    #[test]
781    fn test_two_equation_degree_bound() {
782        two_equation_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
783        println!("Finished bls12-377");
784    }
785
786    #[test]
787    fn test_full_end_to_end_equation() {
788        full_end_to_end_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
789        println!("Finished bls12-377");
790    }
791
792    #[test]
793    #[should_panic]
794    fn test_bad_degree_bound() {
795        bad_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
796        println!("Finished bls12-377");
797    }
798
799    #[test]
800    fn test_lagrange_commitment() {
801        crate::polycommit::test_templates::lagrange_test_template::<Bls12_377, Sponge>()
802            .expect("test failed for bls12-377");
803        println!("Finished bls12-377");
804    }
805}