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