Skip to main content

snarkvm_algorithms/polycommit/sonic_pc/
mod.rs

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