1use 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
44struct LazyCombination<'a, E: PairingEngine> {
53 label: String,
54 terms: Vec<(E::Fr, &'a LabeledPolynomial<E::Fr>)>,
58 degree_bound: Option<usize>,
62 randomness: Randomness<E>,
63}
64
65#[derive(Clone, Debug)]
76pub struct SonicKZG10<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> {
77 _engine: PhantomData<(E, S)>,
78}
79
80impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
81 pub fn load_srs(max_degree: usize) -> Result<UniversalParams<E>, PCError> {
82 kzg10::KZG10::load_srs(max_degree)
83 }
84
85 pub fn trim(
86 pp: &UniversalParams<E>,
87 supported_degree: usize,
88 supported_lagrange_sizes: impl IntoIterator<Item = usize>,
89 supported_hiding_bound: usize,
90 enforced_degree_bounds: Option<&[usize]>,
91 ) -> Result<(CommitterKey<E>, UniversalVerifier<E>)> {
92 let trim_time = start_timer!(|| "Trimming public parameters");
93 let max_degree = pp.max_degree();
94
95 let enforced_degree_bounds = enforced_degree_bounds.map(|bounds| {
96 let mut v = bounds.to_vec();
97 v.sort_unstable();
98 v.dedup();
99 v
100 });
101
102 let supported_lagrange_sizes: Vec<usize> = supported_lagrange_sizes.into_iter().collect();
105 for &size in &supported_lagrange_sizes {
106 if !size.is_power_of_two() {
107 bail!("The Lagrange basis size ({size}) is not a power of two")
108 }
109 if size > max_degree + 1 {
110 bail!("The Lagrange basis size ({size}) is larger than the supported degree ({})", max_degree + 1)
111 }
112 }
113 if let Some([.., highest_enforced_degree_bound]) = enforced_degree_bounds.as_deref()
114 && *highest_enforced_degree_bound > supported_degree
115 {
116 bail!(
117 "The highest enforced degree bound {highest_enforced_degree_bound} is larger than the supported degree {supported_degree}"
118 );
119 }
120
121 pp.download_powers_for(0..supported_degree + 1)?;
126 for &size in &supported_lagrange_sizes {
127 pp.download_powers_for(0..size)?;
128 }
129
130 let (shifted_powers_of_beta_g, shifted_powers_of_beta_times_gamma_g) = match enforced_degree_bounds.as_deref() {
131 Some(enforced_degree_bounds @ [.., highest_enforced_degree_bound]) => {
133 let lowest_shift_degree = max_degree - highest_enforced_degree_bound;
134
135 let shifted_ck_time = start_timer!(|| format!(
136 "Constructing `shifted_powers_of_beta_g` of size {}",
137 max_degree - lowest_shift_degree + 1
138 ));
139
140 let (store, range) = pp.shared_powers_of_beta_g(lowest_shift_degree, pp.max_degree() + 1)?;
141 let shifted_powers_of_beta_g = Bases::shared(store, range);
142 let mut shifted_powers_of_beta_times_gamma_g = BTreeMap::new();
143 for degree_bound in enforced_degree_bounds {
145 let shift_degree = max_degree - degree_bound;
146 let powers_for_degree_bound = pp
149 .powers_of_beta_times_gamma_g()
150 .range(shift_degree..max_degree.min(shift_degree + supported_hiding_bound) + 2)
151 .map(|(_k, v)| *v)
152 .collect();
153 shifted_powers_of_beta_times_gamma_g.insert(*degree_bound, powers_for_degree_bound);
154 }
155
156 end_timer!(shifted_ck_time);
157
158 (Some(shifted_powers_of_beta_g), Some(shifted_powers_of_beta_times_gamma_g))
159 }
160 _ => (None, None),
161 };
162
163 let (store, range) = pp.shared_powers_of_beta_g(0, supported_degree + 1)?;
164 let powers_of_beta_g = Bases::shared(store, range);
165 let powers_of_beta_times_gamma_g = pp
166 .powers_of_beta_times_gamma_g()
167 .range(0..=(supported_hiding_bound + 1))
168 .map(|(_k, v)| *v)
169 .collect::<Vec<_>>();
170 if powers_of_beta_times_gamma_g.len() != supported_hiding_bound + 2 {
171 return Err(
172 PCError::HidingBoundToolarge { hiding_poly_degree: supported_hiding_bound, num_powers: 0 }.into()
173 );
174 }
175
176 let mut lagrange_bases_at_beta_g = BTreeMap::new();
177 for size in supported_lagrange_sizes {
178 let lagrange_time = start_timer!(|| format!("Constructing `lagrange_bases` of size {size}"));
179 let domain = crate::fft::EvaluationDomain::new(size).unwrap();
181 let lagrange_basis_at_beta_g = pp.lagrange_basis(domain)?;
182 assert!(lagrange_basis_at_beta_g.len().is_power_of_two());
183 lagrange_bases_at_beta_g.insert(domain.size(), lagrange_basis_at_beta_g);
184 end_timer!(lagrange_time);
185 }
186
187 let ck = CommitterKey {
188 powers_of_beta_g,
189 lagrange_bases_at_beta_g,
190 powers_of_beta_times_gamma_g,
191 shifted_powers_of_beta_g,
192 shifted_powers_of_beta_times_gamma_g,
193 enforced_degree_bounds,
194 };
195
196 let vk = pp.to_universal_verifier()?;
197
198 end_timer!(trim_time);
199 Ok((ck, vk))
200 }
201
202 #[allow(clippy::format_push_string)]
216 pub fn commit<'b>(
217 universal_prover: &UniversalProver<E>,
218 ck: &CommitterUnionKey<E>,
219 polynomials: impl IntoIterator<Item = LabeledPolynomialWithBasis<'b, E::Fr>>,
220 mut rng: Option<&mut dyn Rng>,
221 ) -> Result<(Vec<LabeledCommitment<Commitment<E>>>, Vec<Randomness<E>>), PCError> {
222 let commit_time = start_timer!(|| "Committing to polynomials");
223
224 let mut pool = snarkvm_utilities::ExecutionPool::<Result<_, _>>::new();
225 for p in polynomials {
226 let seed = rng.as_mut().map(|r| {
227 let mut seed = [0u8; 32];
228 r.fill_bytes(&mut seed);
229 seed
230 });
231
232 kzg10::KZG10::<E>::check_degrees_and_bounds(
233 universal_prover.max_degree,
234 ck.enforced_degree_bounds.as_deref(),
235 p.clone(),
236 )?;
237 let degree_bound = p.degree_bound();
238 let hiding_bound = p.hiding_bound();
239 let label = p.label().to_string();
240
241 pool.add_job(move || {
242 let mut rng = seed.map(rand::rngs::StdRng::from_seed);
243 add_to_trace!(|| "PC::Commit", || format!(
244 "Polynomial {} of degree {}, degree bound {:?}, and hiding bound {:?}",
245 label,
246 p.degree(),
247 degree_bound,
248 hiding_bound,
249 ));
250
251 let (comm, rand) = {
252 let rng_ref = rng.as_mut().map(|s| s as _);
253 match p.polynomial {
254 PolynomialWithBasis::Lagrange { evaluations } => {
255 let domain = crate::fft::EvaluationDomain::new(evaluations.evaluations.len()).unwrap();
256 let lagrange_basis = ck
257 .lagrange_basis(domain)
258 .ok_or(PCError::UnsupportedLagrangeBasisSize(domain.size()))?;
259 assert!(domain.size().is_power_of_two());
260 assert!(lagrange_basis.size().is_power_of_two());
261 kzg10::KZG10::commit_lagrange(
262 &lagrange_basis,
263 &evaluations.evaluations,
264 hiding_bound,
265 rng_ref,
266 )?
267 }
268 PolynomialWithBasis::Monomial { polynomial, degree_bound } => {
269 let powers = if let Some(degree_bound) = degree_bound {
270 ck.shifted_powers_of_beta_g(degree_bound).unwrap()
271 } else {
272 ck.powers()
273 };
274
275 kzg10::KZG10::commit(&powers, &polynomial, hiding_bound, rng_ref)?
276 }
277 }
278 };
279
280 Ok((LabeledCommitment::new(label.to_string(), comm, degree_bound), rand))
281 });
282 }
283 let results: Vec<Result<_, PCError>> = pool.execute_all();
284
285 let mut labeled_comms = Vec::with_capacity(results.len());
286 let mut randomness = Vec::with_capacity(results.len());
287 for result in results {
288 let (comm, rand) = result?;
289 labeled_comms.push(comm);
290 randomness.push(rand);
291 }
292
293 end_timer!(commit_time);
294 Ok((labeled_comms, randomness))
295 }
296
297 pub fn combine_for_open<'a>(
298 universal_prover: &UniversalProver<E>,
299 ck: &CommitterUnionKey<E>,
300 labeled_polynomials: impl ExactSizeIterator<Item = &'a LabeledPolynomial<E::Fr>>,
301 rands: impl ExactSizeIterator<Item = &'a Randomness<E>>,
302 fs_rng: &mut S,
303 ) -> Result<(DensePolynomial<E::Fr>, Randomness<E>)>
304 where
305 Randomness<E>: 'a,
306 Commitment<E>: 'a,
307 {
308 ensure!(labeled_polynomials.len() == rands.len());
309 let mut to_combine = Vec::with_capacity(labeled_polynomials.len());
310
311 for (p, r) in labeled_polynomials.zip_eq(rands) {
312 let enforced_degree_bounds: Option<&[usize]> = ck.enforced_degree_bounds.as_deref();
313
314 kzg10::KZG10::<E>::check_degrees_and_bounds(universal_prover.max_degree, enforced_degree_bounds, p)?;
315 let challenge = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
316 to_combine.push((challenge, p.polynomial().to_dense(), r));
317 }
318
319 Ok(Self::combine_polynomials(to_combine))
320 }
321
322 fn open_lazy_combinations(
347 universal_prover: &UniversalProver<E>,
348 ck: &CommitterUnionKey<E>,
349 combinations: &[LazyCombination<'_, E>],
350 query_set: &QuerySet<E::Fr>,
351 fs_rng: &mut S,
352 ) -> Result<BatchProof<E>> {
353 let open_time = start_timer!(|| format!(
354 "Opening {} combinations at query set of size {}",
355 combinations.len(),
356 query_set.len(),
357 ));
358
359 let by_label: BTreeMap<&str, &LazyCombination<'_, E>> =
360 combinations.iter().map(|c| (c.label.as_str(), c)).collect();
361
362 let mut query_to_labels_map = BTreeMap::new();
363 for (label, (point_name, point)) in query_set.iter() {
364 let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
365 labels.1.insert(label);
366 }
367
368 let mut proofs = Vec::new();
369 for (_point_name, (&query, labels)) in query_to_labels_map.into_iter() {
370 let mut group = Vec::with_capacity(labels.len());
371 for label in labels {
372 let c = by_label.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
373 group.push(*c);
374 }
375
376 let mut challenges = Vec::with_capacity(group.len());
377 for c in &group {
378 if let Some(bound) = c.degree_bound {
384 ensure!(
392 c.terms.len() == 1,
393 "combination {} carries degree bound {bound} with {} terms; \
394 a degree-bounded combination must have exactly one",
395 c.label,
396 c.terms.len(),
397 );
398 let enforced_degree_bounds: Option<&[usize]> = ck.enforced_degree_bounds.as_deref();
399 kzg10::KZG10::<E>::check_degrees_and_bounds(
400 universal_prover.max_degree,
401 enforced_degree_bounds,
402 c.terms[0].1,
403 )?;
404 }
405 challenges.push(fs_rng.squeeze_short_nonnative_field_element::<E::Fr>());
406 }
407
408 let combine_time = start_timer!(|| format!("Combining {} combinations for the opening", group.len()));
409 let mut polynomial = DensePolynomial::zero();
410 let mut rand = Randomness::empty();
411 for (c, challenge) in group.iter().zip_eq(challenges.iter()) {
412 rand += (*challenge, &c.randomness);
413 for (coeff, p) in &c.terms {
414 let scale = *challenge * coeff;
415 if scale.is_one() {
416 polynomial += p.polynomial();
417 } else {
418 polynomial += (scale, p.polynomial());
419 }
420 }
421 }
422 end_timer!(combine_time);
423
424 let proof_time = start_timer!(|| "Creating proof");
425 let proof = kzg10::KZG10::open(&ck.powers(), &polynomial, query, &rand)?;
426 end_timer!(proof_time);
427 proofs.push(proof);
428
429 let _ = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
430 }
431 end_timer!(open_time);
432
433 Ok(BatchProof(proofs))
434 }
435
436 pub fn batch_open<'a>(
437 universal_prover: &UniversalProver<E>,
438 ck: &CommitterUnionKey<E>,
439 labeled_polynomials: impl ExactSizeIterator<Item = &'a LabeledPolynomial<E::Fr>>,
440 query_set: &QuerySet<E::Fr>,
441 rands: impl ExactSizeIterator<Item = &'a Randomness<E>>,
442 fs_rng: &mut S,
443 ) -> Result<BatchProof<E>>
444 where
445 Randomness<E>: 'a,
446 Commitment<E>: 'a,
447 {
448 ensure!(labeled_polynomials.len() == rands.len());
449 let poly_rand: HashMap<_, _> =
450 labeled_polynomials.into_iter().zip_eq(rands).map(|(poly, r)| (poly.label(), (poly, r))).collect();
451
452 let open_time = start_timer!(|| format!(
453 "Opening {} polynomials at query set of size {}",
454 poly_rand.len(),
455 query_set.len(),
456 ));
457
458 let mut query_to_labels_map = BTreeMap::new();
459
460 for (label, (point_name, point)) in query_set.iter() {
461 let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
462 labels.1.insert(label);
463 }
464
465 let mut proofs = Vec::new();
466 for (_point_name, (&query, labels)) in query_to_labels_map.into_iter() {
467 let mut query_polys = Vec::with_capacity(labels.len());
468 let mut query_rands = Vec::with_capacity(labels.len());
469
470 for label in labels {
471 let (polynomial, rand) =
472 poly_rand.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
473
474 query_polys.push(*polynomial);
475 query_rands.push(*rand);
476 }
477 let (polynomial, rand) =
478 Self::combine_for_open(universal_prover, ck, query_polys.into_iter(), query_rands.into_iter(), fs_rng)?;
479
480 let proof_time = start_timer!(|| "Creating proof");
481 let proof = kzg10::KZG10::open(&ck.powers(), &polynomial, query, &rand)?;
482 end_timer!(proof_time);
483 proofs.push(proof);
484
485 let _ = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
486 }
487 let batch_proof = BatchProof(proofs);
488 end_timer!(open_time);
489
490 Ok(batch_proof)
491 }
492
493 pub fn batch_check<'a>(
494 vk: &UniversalVerifier<E>,
495 commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
496 query_set: &QuerySet<E::Fr>,
497 values: &Evaluations<E::Fr>,
498 proof: &BatchProof<E>,
499 fs_rng: &mut S,
500 ) -> Result<bool>
501 where
502 Commitment<E>: 'a,
503 {
504 let commitments: BTreeMap<_, _> = commitments.into_iter().map(|c| (c.label().to_owned(), c)).collect();
505 let batch_check_time = start_timer!(|| format!(
506 "Checking {} commitments at query set of size {}",
507 commitments.len(),
508 query_set.len(),
509 ));
510 let mut query_to_labels_map = BTreeMap::new();
511
512 for (label, (point_name, point)) in query_set.iter() {
513 let labels = query_to_labels_map.entry(point_name).or_insert((point, BTreeSet::new()));
514 labels.1.insert(label);
515 }
516
517 ensure!(query_to_labels_map.len() == proof.0.len());
518
519 let mut private_sponge = fs_rng.clone();
520 for proof in &proof.0 {
521 proof.absorb_into_sponge(&mut private_sponge);
522 }
523
524 let mut randomizer = E::Fr::one();
525
526 let mut combined_comms = BTreeMap::new();
527 let mut combined_witness = E::G1Projective::zero();
528 let mut combined_adjusted_witness = E::G1Projective::zero();
529
530 for ((_query_name, (query, labels)), p) in query_to_labels_map.into_iter().zip_eq(&proof.0) {
531 let mut comms_to_combine: Vec<&'_ LabeledCommitment<_>> = Vec::new();
532 let mut values_to_combine = Vec::new();
533 for label in labels.into_iter() {
534 let commitment =
535 commitments.get(label).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
536
537 let v_i = values
538 .get(&(label.clone(), *query))
539 .ok_or(PCError::MissingEvaluation { label: label.to_string() })?;
540
541 comms_to_combine.push(commitment);
542 values_to_combine.push(*v_i);
543 }
544
545 Self::accumulate_elems(
546 &mut combined_comms,
547 &mut combined_witness,
548 &mut combined_adjusted_witness,
549 vk,
550 comms_to_combine.into_iter(),
551 *query,
552 values_to_combine.into_iter(),
553 p,
554 Some(randomizer),
555 fs_rng,
556 )?;
557
558 let _ = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
559
560 randomizer = private_sponge.squeeze_short_nonnative_field_element::<E::Fr>();
561 }
562
563 let result = Self::check_elems(vk, combined_comms, combined_witness, combined_adjusted_witness);
564 end_timer!(batch_check_time);
565 result
566 }
567
568 pub fn open_combinations<'a>(
569 universal_prover: &UniversalProver<E>,
570 ck: &CommitterUnionKey<E>,
571 linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
572 polynomials: impl IntoIterator<Item = LabeledPolynomial<E::Fr>>,
573 rands: impl IntoIterator<Item = &'a Randomness<E>>,
574 query_set: &QuerySet<E::Fr>,
575 fs_rng: &mut S,
576 ) -> Result<BatchLCProof<E>>
577 where
578 Randomness<E>: 'a,
579 Commitment<E>: 'a,
580 {
581 let label_map =
582 polynomials.into_iter().zip_eq(rands).map(|(p, r)| (p.to_label(), (p, r))).collect::<BTreeMap<_, _>>();
583
584 let resolve_time = start_timer!(|| "Resolving combination terms");
590 let mut combinations = Vec::new();
591 for lc in linear_combinations {
592 let lc_label = lc.label().to_string();
593 let mut terms = Vec::with_capacity(lc.len());
597 let mut randomness = Randomness::empty();
598 let mut degree_bound = None;
599
600 let num_polys = lc.len();
601 for (coeff, label) in lc.iter().filter(|(_, l)| !l.is_one()) {
604 let label: &String = label.try_into().expect("cannot be one!");
605 let (cur_poly, cur_rand) =
606 label_map.get(label as &str).ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
607 if let Some(cur_degree_bound) = cur_poly.degree_bound() {
608 if num_polys != 1 {
609 bail!(PCError::EquationHasDegreeBounds(lc_label));
610 }
611 assert!(coeff.is_one(), "Coefficient must be one for degree-bounded equations");
612 if let Some(old_degree_bound) = degree_bound {
613 assert_eq!(old_degree_bound, cur_degree_bound)
614 } else {
615 degree_bound = cur_poly.degree_bound();
616 }
617 }
618 terms.push((*coeff, cur_poly));
619 randomness += (*coeff, *cur_rand);
620 }
621
622 combinations.push(LazyCombination { label: lc_label, terms, degree_bound, randomness });
627 }
628 end_timer!(resolve_time);
629
630 let proof = Self::open_lazy_combinations(universal_prover, ck, &combinations, query_set, fs_rng)?;
631
632 Ok(BatchLCProof { proof })
633 }
634
635 pub fn check_combinations<'a>(
638 vk: &UniversalVerifier<E>,
639 linear_combinations: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
640 commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
641 query_set: &QuerySet<E::Fr>,
642 evaluations: &Evaluations<E::Fr>,
643 proof: &BatchLCProof<E>,
644 fs_rng: &mut S,
645 ) -> Result<bool>
646 where
647 Commitment<E>: 'a,
648 {
649 let BatchLCProof { proof } = proof;
650 let label_comm_map = commitments.into_iter().map(|c| (c.label(), c)).collect::<BTreeMap<_, _>>();
651
652 let mut lc_commitments = Vec::new();
653 let mut lc_info = Vec::new();
654 let mut evaluations = evaluations.clone();
655
656 let lc_processing_time = start_timer!(|| "Combining commitments");
657 for lc in linear_combinations {
658 let lc_label = lc.label().to_string();
659 let num_polys = lc.len();
660
661 let mut degree_bound = None;
662 let mut coeffs_and_comms = Vec::new();
663
664 for (coeff, label) in lc.iter() {
665 if label.is_one() {
666 for ((label, _), ref mut eval) in evaluations.iter_mut() {
667 if label == &lc_label {
668 **eval -= coeff;
669 }
670 }
671 } else {
672 let label: &String = label.try_into().unwrap();
673 let &cur_comm = label_comm_map
674 .get(label as &str)
675 .ok_or(PCError::MissingPolynomial { label: label.to_string() })?;
676
677 if cur_comm.degree_bound().is_some() {
678 if num_polys != 1 || !coeff.is_one() {
679 bail!(PCError::EquationHasDegreeBounds(lc_label));
680 }
681 degree_bound = cur_comm.degree_bound();
682 }
683 coeffs_and_comms.push((*coeff, cur_comm.commitment()));
684 }
685 }
686 let lc_time = start_timer!(|| format!("Combining {num_polys} commitments for {lc_label}"));
687 lc_commitments.push(Self::combine_commitments(coeffs_and_comms));
688 end_timer!(lc_time);
689 lc_info.push((lc_label, degree_bound));
690 }
691 end_timer!(lc_processing_time);
692
693 let combined_comms_norm_time = start_timer!(|| "Normalizing commitments");
694 let comms = Self::normalize_commitments(lc_commitments);
695 ensure!(lc_info.len() == comms.len());
696 let lc_commitments = lc_info
697 .into_iter()
698 .zip_eq(comms)
699 .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
700 .collect::<Vec<_>>();
701 end_timer!(combined_comms_norm_time);
702
703 Self::batch_check(vk, &lc_commitments, query_set, &evaluations, proof, fs_rng)
704 }
705}
706
707impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
708 fn combine_polynomials<'a, B: Borrow<DensePolynomial<E::Fr>>>(
709 coeffs_polys_rands: impl IntoIterator<Item = (E::Fr, B, &'a Randomness<E>)>,
710 ) -> (DensePolynomial<E::Fr>, Randomness<E>) {
711 let mut combined_poly = DensePolynomial::zero();
712 let mut combined_rand = Randomness::empty();
713 for (coeff, poly, rand) in coeffs_polys_rands {
714 let poly = poly.borrow();
715 if coeff.is_one() {
716 combined_poly += poly;
717 combined_rand += rand;
718 } else {
719 combined_poly += (coeff, poly);
720 combined_rand += (coeff, rand);
721 }
722 }
723 (combined_poly, combined_rand)
724 }
725
726 fn combine_commitments<'a>(
728 coeffs_and_comms: impl IntoIterator<Item = (E::Fr, &'a Commitment<E>)>,
729 ) -> E::G1Projective {
730 let (scalars, bases): (Vec<_>, Vec<_>) = coeffs_and_comms.into_iter().map(|(f, c)| (f.into(), c.0)).unzip();
731 VariableBase::msm(&bases, &scalars)
732 }
733
734 fn normalize_commitments(commitments: Vec<E::G1Projective>) -> impl ExactSizeIterator<Item = Commitment<E>> {
735 let comms = E::G1Projective::batch_normalization_into_affine(commitments);
736 comms.into_iter().map(|c| kzg10::KZGCommitment(c))
737 }
738}
739
740impl<E: PairingEngine, S: AlgebraicSponge<E::Fq, 2>> SonicKZG10<E, S> {
741 #[allow(clippy::too_many_arguments)]
742 fn accumulate_elems<'a>(
743 combined_comms: &mut BTreeMap<Option<usize>, E::G1Projective>,
744 combined_witness: &mut E::G1Projective,
745 combined_adjusted_witness: &mut E::G1Projective,
746 vk: &UniversalVerifier<E>,
747 commitments: impl ExactSizeIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
748 point: E::Fr,
749 values: impl ExactSizeIterator<Item = E::Fr>,
750 proof: &kzg10::KZGProof<E>,
751 randomizer: Option<E::Fr>,
752 fs_rng: &mut S,
753 ) -> Result<()> {
754 let acc_time = start_timer!(|| "Accumulating elements");
755 let mut combined_values = E::Fr::zero();
757
758 ensure!(commitments.len() == values.len());
761 for (labeled_comm, value) in commitments.into_iter().zip_eq(values) {
762 let acc_timer = start_timer!(|| format!("Accumulating {}", labeled_comm.label()));
763 let curr_challenge = fs_rng.squeeze_short_nonnative_field_element::<E::Fr>();
764
765 combined_values += &(value * curr_challenge);
766
767 let comm = labeled_comm.commitment();
768 let degree_bound = labeled_comm.degree_bound();
769
770 let coeff = randomizer.unwrap_or_else(E::Fr::one) * curr_challenge;
772 let comm_with_challenge: E::G1Projective = comm.0.mul(coeff);
773
774 *combined_comms.entry(degree_bound).or_insert_with(E::G1Projective::zero) += &comm_with_challenge;
776 end_timer!(acc_timer);
777 }
778
779 let mut bases = vec![vk.vk.g, -proof.w];
782 let mut coeffs = vec![combined_values, point];
783 if let Some(random_v) = proof.random_v {
784 bases.push(vk.vk.gamma_g);
785 coeffs.push(random_v);
786 }
787 *combined_witness += if let Some(randomizer) = randomizer {
788 coeffs.iter_mut().for_each(|c| *c *= randomizer);
789 proof.w.mul(randomizer)
790 } else {
791 proof.w.to_projective()
792 };
793 let coeffs = coeffs.into_iter().map(|c| c.into()).collect::<Vec<_>>();
794 *combined_adjusted_witness += VariableBase::msm(&bases, &coeffs);
795 end_timer!(acc_time);
796 Ok(())
797 }
798
799 fn check_elems(
800 vk: &UniversalVerifier<E>,
801 combined_comms: BTreeMap<Option<usize>, E::G1Projective>,
802 combined_witness: E::G1Projective,
803 combined_adjusted_witness: E::G1Projective,
804 ) -> Result<bool> {
805 let check_time = start_timer!(|| "Checking elems");
806 let mut g1_projective_elems = Vec::with_capacity(combined_comms.len() + 2);
807 let mut g2_prepared_elems = Vec::with_capacity(combined_comms.len() + 2);
808
809 for (degree_bound, comm) in combined_comms.into_iter() {
810 let shift_power = if let Some(degree_bound) = degree_bound {
811 vk.prepared_negative_powers_of_beta_h
813 .get(°ree_bound)
814 .cloned()
815 .ok_or(PCError::UnsupportedDegreeBound(degree_bound))?
816 } else {
817 vk.vk.prepared_h.clone()
818 };
819
820 g1_projective_elems.push(comm);
821 g2_prepared_elems.push(shift_power);
822 }
823
824 g1_projective_elems.push(-combined_adjusted_witness);
825 g2_prepared_elems.push(vk.vk.prepared_h.clone());
826
827 g1_projective_elems.push(-combined_witness);
828 g2_prepared_elems.push(vk.vk.prepared_beta_h.clone());
829
830 let g1_prepared_elems_iter = E::G1Projective::batch_normalization_into_affine(g1_projective_elems)
831 .into_iter()
832 .map(|a| a.prepare())
833 .collect::<Vec<_>>();
834
835 ensure!(g1_prepared_elems_iter.len() == g2_prepared_elems.len());
836 let g1_g2_prepared = g1_prepared_elems_iter.iter().zip_eq(g2_prepared_elems.iter());
837 let is_one: bool = E::product_of_pairings(g1_g2_prepared).is_one();
838 end_timer!(check_time);
839 Ok(is_one)
840 }
841}
842
843#[cfg(test)]
844mod tests {
845 #![allow(non_camel_case_types)]
846
847 use super::{Bases, CommitterKey, CommitterUnionKey, SonicKZG10};
848 use crate::{crypto_hash::PoseidonSponge, polycommit::test_templates::*};
849 use snarkvm_curves::bls12_377::{Bls12_377, Fq};
850 use snarkvm_utilities::{FromBytes, ToBytes, rand::TestRng};
851
852 use itertools::Itertools;
853 use rand::distr::Distribution;
854
855 type Sponge = PoseidonSponge<Fq, 2, 1>;
856 type PC_Bls12_377 = SonicKZG10<Bls12_377, Sponge>;
857
858 #[test]
859 fn test_committer_key_serialization() {
860 let rng = &mut TestRng::default();
861 let max_degree = rand::distr::Uniform::new_inclusive(8, 64).unwrap().sample(rng);
862 let supported_degree = rand::distr::Uniform::new_inclusive(1, max_degree).unwrap().sample(rng);
863
864 let lagrange_size = |d: usize| if d.is_power_of_two() { d } else { d.next_power_of_two() >> 1 };
865
866 let pp = PC_Bls12_377::load_srs(max_degree).unwrap();
867
868 let (ck, _vk) = PC_Bls12_377::trim(&pp, supported_degree, [lagrange_size(supported_degree)], 0, None).unwrap();
869
870 let ck_bytes = ck.to_bytes_le().unwrap();
871 let ck_recovered: CommitterKey<Bls12_377> = FromBytes::read_le(&ck_bytes[..]).unwrap();
872 let ck_recovered_bytes = ck_recovered.to_bytes_le().unwrap();
873
874 assert_eq!(&ck_bytes, &ck_recovered_bytes);
875 }
876
877 #[test]
880 fn trim_shares_its_bases_with_the_srs() {
881 let pp = PC_Bls12_377::load_srs((1 << 12) - 1).unwrap();
882 let (ck, _) = PC_Bls12_377::trim(&pp, 1000, [], 1, Some(&[500])).unwrap();
883 let top = pp.max_degree();
884
885 assert!(matches!(ck.powers_of_beta_g, Bases::Shared { .. }));
886 assert_eq!(ck.powers_of_beta_g.owned_capacity(), 0, "a shared prefix owns nothing");
887 assert_eq!(&*ck.powers_of_beta_g, pp.powers_of_beta_g(0, 1001).unwrap().as_slice());
888
889 let shifted = ck.shifted_powers_of_beta_g.as_ref().unwrap();
890 assert!(matches!(shifted, Bases::Shared { .. }));
891 assert_eq!(shifted.owned_capacity(), 0, "a shared suffix owns nothing");
892 assert_eq!(&**shifted, pp.powers_of_beta_g(top - 500, top + 1).unwrap().as_slice());
893
894 let bytes = ck.to_bytes_le().unwrap();
896 let read: CommitterKey<Bls12_377> = FromBytes::read_le(&bytes[..]).unwrap();
897 assert!(matches!(read.powers_of_beta_g, Bases::Owned(_)), "a key read from bytes owns its bases");
898 assert_eq!(read.to_bytes_le().unwrap(), bytes);
899 }
900
901 #[test]
904 fn a_key_keeps_its_snapshot_across_a_growth() {
905 let pp = PC_Bls12_377::load_srs((1 << 15) - 1).unwrap();
906 let (before, _) = PC_Bls12_377::trim(&pp, 1000, [], 1, Some(&[500])).unwrap();
907 let points = before.powers_of_beta_g.to_vec();
908
909 pp.download_powers_for(0..(1 << 16)).unwrap();
910 let (after, _) = PC_Bls12_377::trim(&pp, 1000, [], 1, Some(&[500])).unwrap();
911
912 assert_eq!(&*before.powers_of_beta_g, points.as_slice(), "the older snapshot changed");
913 assert_eq!(&*after.powers_of_beta_g, points.as_slice());
914 assert_ne!(
915 before.powers_of_beta_g.as_ptr(),
916 after.powers_of_beta_g.as_ptr(),
917 "a key trimmed after the growth should share the new snapshot"
918 );
919 }
920
921 #[test]
924 fn trim_shares_the_snapshot_left_after_its_own_downloads() {
925 let pp = PC_Bls12_377::load_srs((1 << 15) - 1).unwrap();
926 let (ck, _) = PC_Bls12_377::trim(&pp, 1000, [1 << 16], 1, Some(&[500])).unwrap();
927 let (current, _) = pp.shared_powers_of_beta_g(0, 1).unwrap();
928 match &ck.powers_of_beta_g {
929 Bases::Shared { store, .. } => {
930 assert!(std::sync::Arc::ptr_eq(store, ¤t), "the key shares a snapshot its own trim replaced")
931 }
932 Bases::Owned(_) => panic!("trim should share"),
933 }
934 }
935
936 #[test]
939 fn an_empty_range_leaves_the_snapshot_alone() {
940 let pp = PC_Bls12_377::load_srs((1 << 15) - 1).unwrap();
941 let (before, _) = pp.shared_powers_of_beta_g(0, 1).unwrap();
942 let held = 1 << 15;
943 assert!(pp.powers_of_beta_g(held, held).unwrap().is_empty());
944 assert!(pp.powers_of_beta_g(0, 0).unwrap().is_empty());
945 let (after, _) = pp.shared_powers_of_beta_g(0, 1).unwrap();
946 assert!(std::sync::Arc::ptr_eq(&before, &after), "an empty range replaced the snapshot");
947 }
948
949 #[test]
952 fn a_rejected_trim_downloads_nothing() {
953 let pp = PC_Bls12_377::load_srs((1 << 15) - 1).unwrap();
954 let (before, _) = pp.shared_powers_of_beta_g(0, 1).unwrap();
955 let grown = 1 << 16;
956 assert!(PC_Bls12_377::trim(&pp, grown - 1, [], 1, Some(&[grown])).is_err());
958 assert!(PC_Bls12_377::trim(&pp, grown - 1, [3], 1, None).is_err());
960 assert!(PC_Bls12_377::trim(&pp, grown - 1, [1 << 29], 1, None).is_err());
961 let (after, _) = pp.shared_powers_of_beta_g(0, 1).unwrap();
962 assert!(std::sync::Arc::ptr_eq(&before, &after), "a rejected trim grew the SRS");
963 }
964
965 #[test]
968 fn editing_a_shared_key_copies_it_first() {
969 use super::Bases;
970 let pp = PC_Bls12_377::load_srs((1 << 12) - 1).unwrap();
971 let (mut ck, _) = PC_Bls12_377::trim(&pp, 1000, [], 1, Some(&[500])).unwrap();
972 ck.powers_of_beta_g.to_mut().swap(0, 1);
973 assert!(matches!(ck.powers_of_beta_g, Bases::Owned(_)));
974 assert_ne!(&*ck.powers_of_beta_g, pp.powers_of_beta_g(0, 1001).unwrap().as_slice());
975 let (fresh, _) = PC_Bls12_377::trim(&pp, 1000, [], 1, Some(&[500])).unwrap();
976 assert_eq!(&*fresh.powers_of_beta_g, pp.powers_of_beta_g(0, 1001).unwrap().as_slice());
977 }
978
979 #[test]
980 fn test_union_takes_each_array_from_the_key_where_it_is_longest() {
981 let pp = PC_Bls12_377::load_srs(32).unwrap();
982 let (long_prefix, _) = PC_Bls12_377::trim(&pp, 16, [], 0, Some(&[4])).unwrap();
985 let (long_suffix, _) = PC_Bls12_377::trim(&pp, 12, [], 0, Some(&[4, 10])).unwrap();
986 let (long_hiding, _) = PC_Bls12_377::trim(&pp, 8, [], 1, Some(&[4])).unwrap();
987 let shifted_hiding = |ck: &CommitterKey<Bls12_377>, bound| {
988 ck.shifted_powers_of_beta_times_gamma_g.as_ref().unwrap()[&bound].clone()
989 };
990
991 for keys in [&long_prefix, &long_suffix, &long_hiding].into_iter().permutations(3) {
992 let union = CommitterUnionKey::union(keys);
993 assert_eq!(&*union.powers().powers_of_beta_g, &*long_prefix.powers_of_beta_g);
994 assert_eq!(union.powers().powers_of_beta_times_gamma_g, long_hiding.powers_of_beta_times_gamma_g);
995 for (bound, hiding) in [(4, shifted_hiding(&long_hiding, 4)), (10, shifted_hiding(&long_suffix, 10))] {
996 let shifted = union.shifted_powers_of_beta_g(bound).unwrap();
997 let top = pp.max_degree();
998 assert_eq!(
999 shifted.powers_of_beta_g,
1000 pp.powers_of_beta_g(top - bound, top + 1).unwrap(),
1001 "bound {bound}"
1002 );
1003 assert_eq!(shifted.powers_of_beta_times_gamma_g, hiding, "bound {bound}");
1004 }
1005 }
1006 }
1007
1008 #[test]
1009 fn test_single_poly() {
1010 single_poly_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1011 }
1012
1013 #[test]
1014 fn test_quadratic_poly_degree_bound_multiple_queries() {
1015 quadratic_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1016 }
1017
1018 #[test]
1019 fn test_linear_poly_degree_bound() {
1020 linear_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1021 }
1022
1023 #[test]
1024 fn test_single_poly_degree_bound() {
1025 single_poly_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1026 }
1027
1028 #[test]
1029 fn test_single_poly_degree_bound_multiple_queries() {
1030 single_poly_degree_bound_multiple_queries_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1031 }
1032
1033 #[test]
1034 fn test_two_polys_degree_bound_single_query() {
1035 two_polys_degree_bound_single_query_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1036 }
1037
1038 #[test]
1039 fn test_full_end_to_end() {
1040 full_end_to_end_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1041 println!("Finished bls12-377");
1042 }
1043
1044 #[test]
1045 fn test_single_equation() {
1046 single_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1047 println!("Finished bls12-377");
1048 }
1049
1050 #[test]
1051 fn test_two_equation() {
1052 two_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1053 println!("Finished bls12-377");
1054 }
1055
1056 #[test]
1057 fn test_two_equation_degree_bound() {
1058 two_equation_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1059 println!("Finished bls12-377");
1060 }
1061
1062 #[test]
1063 fn test_full_end_to_end_equation() {
1064 full_end_to_end_equation_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1065 println!("Finished bls12-377");
1066 }
1067
1068 #[test]
1069 #[should_panic]
1070 fn test_bad_degree_bound() {
1071 bad_degree_bound_test::<Bls12_377, Sponge>().expect("test failed for bls12-377");
1072 println!("Finished bls12-377");
1073 }
1074
1075 #[test]
1076 fn test_lagrange_commitment() {
1077 crate::polycommit::test_templates::lagrange_test_template::<Bls12_377, Sponge>()
1078 .expect("test failed for bls12-377");
1079 println!("Finished bls12-377");
1080 }
1081}