1use super::Certificate;
17use crate::{
18 AlgebraicSponge,
19 SNARK,
20 SNARKError,
21 fft::EvaluationDomain,
22 polycommit::sonic_pc::{
23 Commitment,
24 CommitterUnionKey,
25 Evaluations,
26 LabeledCommitment,
27 QuerySet,
28 Randomness,
29 SonicKZG10,
30 },
31 r1cs::{ConstraintSynthesizer, SynthesisError},
32 snark::varuna::{
33 CircuitProvingKey,
34 CircuitVerifyingKey,
35 Proof,
36 SNARKMode,
37 UniversalSRS,
38 VarunaVersion,
39 ahp::{AHPError, AHPForR1CS, CircuitId, EvaluationsProvider},
40 proof,
41 prover,
42 witness_label,
43 },
44 srs::UniversalVerifier,
45};
46use snarkvm_curves::PairingEngine;
47use snarkvm_fields::{One, PrimeField, ToConstraintField, Zero};
48use snarkvm_utilities::{ToBytes, dev_eprintln, dev_println, to_bytes_le};
49
50use anyhow::{Result, anyhow, bail, ensure};
51use core::marker::PhantomData;
52use itertools::Itertools;
53use rand::{CryptoRng, Rng};
54use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc};
55
56use crate::srs::UniversalProver;
57
58#[derive(Clone, Debug)]
60pub struct VarunaSNARK<E: PairingEngine, FS: AlgebraicSponge<E::Fq, 2>, SM: SNARKMode>(
61 #[doc(hidden)] PhantomData<(E, FS, SM)>,
62);
63
64impl<E: PairingEngine, FS: AlgebraicSponge<E::Fq, 2>, SM: SNARKMode> VarunaSNARK<E, FS, SM> {
65 pub const PROTOCOL_NAME: &'static [u8] = b"VARUNA-2023";
68
69 pub fn batch_circuit_setup<C: ConstraintSynthesizer<E::Fr>>(
73 universal_srs: &UniversalSRS<E>,
74 circuits: &[&C],
75 ) -> Result<Vec<(CircuitProvingKey<E, SM>, CircuitVerifyingKey<E>)>> {
76 let index_time = start_timer!(|| "Varuna::CircuitSetup");
77
78 let universal_prover = &universal_srs.to_universal_prover()?;
79
80 let mut circuit_keys = Vec::with_capacity(circuits.len());
81 for circuit in circuits {
82 let mut indexed_circuit = AHPForR1CS::<_, SM>::index(*circuit)?;
83 universal_srs.download_powers_for(0..indexed_circuit.max_degree()?).map_err(|e| {
86 anyhow!("Failed to download powers for degree {}: {e}", indexed_circuit.max_degree().unwrap())
87 })?;
88 let coefficient_support = AHPForR1CS::<E::Fr, SM>::get_degree_bounds(&indexed_circuit.index_info)?;
89
90 let supported_hiding_bound = 1;
92 let supported_lagrange_sizes = [].into_iter(); let (committer_key, _) = SonicKZG10::<E, FS>::trim(
94 universal_srs,
95 indexed_circuit.max_degree()?,
96 supported_lagrange_sizes,
97 supported_hiding_bound,
98 Some(coefficient_support.as_slice()),
99 )?;
100
101 let ck = CommitterUnionKey::union(std::iter::once(&committer_key));
102
103 let commit_time = start_timer!(|| format!("Commit to index polynomials for {}", indexed_circuit.id));
104 let setup_rng = None::<&mut dyn Rng>; let (mut circuit_commitments, commitment_randomnesses): (_, _) = SonicKZG10::<E, FS>::commit(
107 universal_prover,
108 &ck,
109 indexed_circuit.interpolate_matrix_evals()?.map(Into::into),
110 setup_rng,
111 )?;
112 let empty_randomness = Randomness::<E>::empty();
113 ensure!(commitment_randomnesses.iter().all(|r| r == &empty_randomness));
114 end_timer!(commit_time);
115
116 circuit_commitments.sort_by(|c1, c2| c1.label().cmp(c2.label()));
117 let circuit_commitments = circuit_commitments.into_iter().map(|c| *c.commitment()).collect();
118 indexed_circuit.prune_row_col_evals();
119 let circuit_verifying_key = CircuitVerifyingKey {
120 circuit_info: indexed_circuit.index_info,
121 circuit_commitments,
122 id: indexed_circuit.id,
123 };
124 let circuit_proving_key = CircuitProvingKey {
125 circuit_verifying_key: circuit_verifying_key.clone(),
126 circuit: Arc::new(indexed_circuit),
127 committer_key: Arc::new(committer_key),
128 };
129 circuit_keys.push((circuit_proving_key, circuit_verifying_key));
130 }
131
132 end_timer!(index_time);
133 Ok(circuit_keys)
134 }
135
136 fn init_sponge<'a>(
137 fs_parameters: &FS::Parameters,
138 inputs_and_batch_sizes: &BTreeMap<CircuitId, (usize, &[Vec<E::Fr>])>,
139 circuit_commitments: impl Iterator<Item = &'a [crate::polycommit::sonic_pc::Commitment<E>]>,
140 varuna_version: VarunaVersion,
141 ) -> FS {
142 let mut sponge = FS::new_with_parameters(fs_parameters);
143
144 sponge.absorb_bytes(match varuna_version {
145 VarunaVersion::V1 | VarunaVersion::V2 => Self::PROTOCOL_NAME,
146 VarunaVersion::V3 => b"VARUNA-2026-V3",
147 });
148 for (batch_size, inputs) in inputs_and_batch_sizes.values() {
149 sponge.absorb_bytes(&(*batch_size as u64).to_le_bytes());
150 for input in inputs.iter() {
151 sponge.absorb_nonnative_field_elements(input.iter().copied());
152 }
153 }
154 for circuit_specific_commitments in circuit_commitments {
155 sponge.absorb_native_field_elements(circuit_specific_commitments);
156 }
157 sponge
158 }
159
160 fn init_sponge_for_certificate(
161 fs_parameters: &FS::Parameters,
162 verifying_key: &CircuitVerifyingKey<E>,
163 ) -> Result<FS> {
164 let mut sponge = FS::new_with_parameters(fs_parameters);
165 sponge.absorb_bytes(&to_bytes_le![&Self::PROTOCOL_NAME]?);
166 sponge.absorb_bytes(&verifying_key.circuit_info.to_bytes_le()?);
167 sponge.absorb_native_field_elements(&verifying_key.circuit_commitments);
168 sponge.absorb_bytes(&verifying_key.id.0);
169 Ok(sponge)
170 }
171
172 fn absorb_labeled_with_sums(
173 comms: &[LabeledCommitment<Commitment<E>>],
174 sums: &[prover::MatrixSums<E::Fr>],
175 sponge: &mut FS,
176 ) {
177 let commitments: Vec<_> = comms.iter().map(|c| *c.commitment()).collect();
178 Self::absorb_with_sums(&commitments, sums, sponge)
179 }
180
181 fn absorb_labeled(comms: &[LabeledCommitment<Commitment<E>>], sponge: &mut FS) {
182 let commitments: Vec<_> = comms.iter().map(|c| *c.commitment()).collect();
183 Self::absorb(&commitments, sponge);
184 }
185
186 fn absorb(commitments: &[Commitment<E>], sponge: &mut FS) {
187 let sponge_time = start_timer!(|| "Absorbing commitments");
188 sponge.absorb_native_field_elements(commitments);
189 end_timer!(sponge_time);
190 }
191
192 fn absorb_with_sums(commitments: &[Commitment<E>], sums: &[prover::MatrixSums<E::Fr>], sponge: &mut FS) {
193 let sponge_time = start_timer!(|| "Absorbing commitments and message");
194 Self::absorb(commitments, sponge);
195 Self::absorb_sums(sums, sponge);
196 end_timer!(sponge_time);
197 }
198
199 fn absorb_sums(sums: &[prover::MatrixSums<E::Fr>], sponge: &mut FS) {
200 for sum in sums.iter() {
201 sponge.absorb_nonnative_field_elements([sum.sum_a, sum.sum_b, sum.sum_c]);
202 }
203 }
204}
205
206impl<E: PairingEngine, FS, SM> SNARK for VarunaSNARK<E, FS, SM>
207where
208 E::Fr: PrimeField,
209 E::Fq: PrimeField,
210 FS: AlgebraicSponge<E::Fq, 2>,
211 SM: SNARKMode,
212{
213 type BaseField = E::Fq;
214 type Certificate = Certificate<E>;
215 type FSParameters = FS::Parameters;
216 type FiatShamirRng = FS;
217 type Proof = Proof<E>;
218 type ProvingKey = CircuitProvingKey<E, SM>;
219 type ScalarField = E::Fr;
220 type UniversalProver = UniversalProver<E>;
221 type UniversalSRS = UniversalSRS<E>;
222 type UniversalVerifier = UniversalVerifier<E>;
223 type VerifierInput = [E::Fr];
224 type VerifyingKey = CircuitVerifyingKey<E>;
225
226 fn universal_setup(max_degree: usize) -> Result<Self::UniversalSRS> {
227 let setup_time = start_timer!(|| { format!("Varuna::UniversalSetup with max_degree {max_degree}",) });
228 let srs = SonicKZG10::<E, FS>::load_srs(max_degree).map_err(Into::into);
229 end_timer!(setup_time);
230 srs
231 }
232
233 fn circuit_setup<C: ConstraintSynthesizer<E::Fr>>(
236 universal_srs: &Self::UniversalSRS,
237 circuit: &C,
238 ) -> Result<(Self::ProvingKey, Self::VerifyingKey)> {
239 let mut circuit_keys = Self::batch_circuit_setup::<C>(universal_srs, &[circuit])?;
240 ensure!(circuit_keys.len() == 1);
241 Ok(circuit_keys.pop().unwrap())
242 }
243
244 fn prove_vk(
247 universal_prover: &Self::UniversalProver,
248 fs_parameters: &Self::FSParameters,
249 verifying_key: &Self::VerifyingKey,
250 proving_key: &Self::ProvingKey,
251 ) -> Result<Self::Certificate> {
252 let mut sponge = Self::init_sponge_for_certificate(fs_parameters, verifying_key)?;
254 let mut challenges = sponge.squeeze_nonnative_field_elements(verifying_key.circuit_commitments.len());
259 let point = challenges.pop().ok_or(anyhow!("Failed to squeeze random element"))?;
260 let one = E::Fr::one();
261 let linear_combination_challenges = core::iter::once(&one).chain(challenges.iter());
262
263 let circuit_id = std::iter::once(&verifying_key.id);
264 let circuit_poly_info = AHPForR1CS::<E::Fr, SM>::index_polynomial_info(circuit_id);
265
266 let mut lc = crate::polycommit::sonic_pc::LinearCombination::empty("circuit_check");
269 for (label, &c) in circuit_poly_info.keys().zip(linear_combination_challenges) {
270 lc.add(c, label.clone());
271 }
272
273 let query_set = QuerySet::from_iter([("circuit_check".into(), ("challenge".into(), point))]);
274 let committer_key = CommitterUnionKey::union(std::iter::once(proving_key.committer_key.as_ref()));
275
276 let empty_randomness = vec![Randomness::<E>::empty(); 12];
277 let certificate = SonicKZG10::<E, FS>::open_combinations(
278 universal_prover,
279 &committer_key,
280 &[lc],
281 proving_key.circuit.interpolate_matrix_evals()?,
282 &empty_randomness,
283 &query_set,
284 &mut sponge,
285 )?;
286
287 Ok(Self::Certificate::new(certificate))
288 }
289
290 fn verify_vk<C: ConstraintSynthesizer<Self::ScalarField>>(
294 universal_verifier: &Self::UniversalVerifier,
295 fs_parameters: &Self::FSParameters,
296 circuit: &C,
297 verifying_key: &Self::VerifyingKey,
298 certificate: &Self::Certificate,
299 ) -> Result<bool> {
300 let circuit_id = &verifying_key.id;
302 let state = AHPForR1CS::<E::Fr, SM>::index_helper(circuit)?;
303 if state.index_info != verifying_key.circuit_info {
304 bail!("Circuit info mismatch, expected {:?}, got {:?}", verifying_key.circuit_info, state.index_info);
305 }
306 if state.id != *circuit_id {
307 bail!("Circuit ID mismatch, expected {:?}, got {:?}.", circuit_id, state.id);
308 }
309
310 if certificate.pc_proof.is_hiding() {
312 bail!("Certificate should not be hiding");
313 }
314
315 let mut sponge = Self::init_sponge_for_certificate(fs_parameters, verifying_key)?;
317
318 let mut challenges = sponge.squeeze_nonnative_field_elements(verifying_key.circuit_commitments.len());
323 let point = challenges.pop().ok_or(anyhow!("Failed to squeeze random element"))?;
324 let combiners = core::iter::once(E::Fr::one()).chain(challenges);
325
326 let (lc, evaluation) =
329 AHPForR1CS::<E::Fr, SM>::evaluate_index_polynomials(state, circuit_id, point, combiners)?;
330
331 ensure!(verifying_key.circuit_commitments.len() == lc.terms.len());
332 let commitments = verifying_key
333 .iter()
334 .cloned()
335 .zip_eq(lc.terms.keys())
336 .map(|(c, label)| LabeledCommitment::new(format!("{label:?}"), c, None))
337 .collect_vec();
338 let evaluations = Evaluations::from_iter([(("circuit_check".into(), point), evaluation)]);
339 let query_set = QuerySet::from_iter([("circuit_check".into(), ("challenge".into(), point))]);
340
341 SonicKZG10::<E, FS>::check_combinations(
342 universal_verifier,
343 &[lc],
344 &commitments,
345 &query_set,
346 &evaluations,
347 &certificate.pc_proof,
348 &mut sponge,
349 )
350 }
351
352 fn prove_batch<C: ConstraintSynthesizer<E::Fr>, R: Rng + CryptoRng>(
356 universal_prover: &Self::UniversalProver,
357 fs_parameters: &Self::FSParameters,
358 varuna_version: VarunaVersion,
359 keys_to_constraints: &BTreeMap<&CircuitProvingKey<E, SM>, &[C]>,
360 zk_rng: &mut R,
361 ) -> Result<Self::Proof> {
362 let prover_time = start_timer!(|| "Varuna::Prover");
363 if keys_to_constraints.is_empty() {
364 bail!(SNARKError::EmptyBatch);
365 }
366
367 let mut circuits_to_constraints = BTreeMap::new();
368 for (pk, constraints) in keys_to_constraints {
369 circuits_to_constraints.insert(pk.circuit.deref(), *constraints);
370 }
371 let prover_state = AHPForR1CS::<_, SM>::init_prover(&circuits_to_constraints, zk_rng)?;
372
373 let mut batch_sizes = BTreeMap::new();
376 let mut circuit_infos = BTreeMap::new();
377 let mut inputs_and_batch_sizes = BTreeMap::new();
378 let mut total_instances = 0usize;
379 let mut public_inputs = BTreeMap::new(); let num_unique_circuits = keys_to_constraints.len();
381 let mut circuit_ids = Vec::with_capacity(num_unique_circuits);
382
383 #[cfg(feature = "snark-print")]
384 {
385 let batch_sizes = keys_to_constraints
387 .keys()
388 .map(|pk| {
389 prover_state
390 .batch_size(&pk.circuit)
391 .ok_or(anyhow!("[Varuna::prove_batch] Batch not found for circuit {:?}", pk.circuit.id))
392 })
393 .collect::<Result<Vec<_>>>()?;
394
395 println!("[Varuna::prove_batch] Batch sizes: {batch_sizes:?}\n");
396
397 for (i, (key, batch_size)) in keys_to_constraints.keys().zip(batch_sizes.iter()).enumerate() {
398 println!(" - Circuit {i}: {} ({batch_size} instance(s))\n", key.circuit_verifying_key.id);
399 for (j, public_input) in prover_state.public_inputs(&key.circuit).unwrap().iter().enumerate() {
402 println!(" - Instance {j}");
403 println!(" - 0: {}", E::Fr::one());
406 for (k, value) in public_input.iter().enumerate() {
407 println!(" - {}: {value}", k + 1);
408 }
409 println!();
410 }
411 }
412 }
413
414 for pk in keys_to_constraints.keys() {
415 let batch_size = prover_state.batch_size(&pk.circuit).ok_or(anyhow!("Batch size not found."))?;
416 let public_input = prover_state.public_inputs(&pk.circuit).ok_or(anyhow!("Public input not found."))?;
417
418 let padded_public_input =
419 prover_state.padded_public_inputs(&pk.circuit).ok_or(anyhow!("Padded public input not found."))?;
420
421 let circuit_id = pk.circuit.id;
422 batch_sizes.insert(circuit_id, batch_size);
423 circuit_infos.insert(circuit_id, &pk.circuit_verifying_key.circuit_info);
424 inputs_and_batch_sizes.insert(circuit_id, (batch_size, padded_public_input));
425 public_inputs.insert(circuit_id, public_input);
426 total_instances = total_instances.saturating_add(batch_size);
427
428 circuit_ids.push(circuit_id);
429 }
430 ensure!(prover_state.total_instances == total_instances);
431
432 let committer_key = CommitterUnionKey::union(keys_to_constraints.keys().map(|pk| pk.committer_key.deref()));
433
434 let circuit_commitments =
435 keys_to_constraints.keys().map(|pk| pk.circuit_verifying_key.circuit_commitments.as_slice());
436 dev_println!("inputs_and_batch_sizes: {inputs_and_batch_sizes:?}");
437 let mut sponge =
438 Self::init_sponge(fs_parameters, &inputs_and_batch_sizes, circuit_commitments.clone(), varuna_version);
439
440 let prover_state = AHPForR1CS::<_, SM>::prover_first_round(prover_state, zk_rng)?;
444
445 let first_round_comm_time = start_timer!(|| "Committing to first round polys");
446 let (first_commitments, first_commitment_randomnesses) = {
447 let first_round_oracles = prover_state.first_round_oracles.as_ref().unwrap();
448 SonicKZG10::<E, FS>::commit(
449 universal_prover,
450 &committer_key,
451 first_round_oracles.iter().map(Into::into),
452 SM::ZK.then_some(zk_rng),
453 )?
454 };
455 end_timer!(first_round_comm_time);
456
457 Self::absorb_labeled(&first_commitments, &mut sponge);
458
459 let (verifier_first_message, verifier_state) = AHPForR1CS::<_, SM>::verifier_first_round(
460 &batch_sizes,
461 &circuit_infos,
462 prover_state.max_constraint_domain,
463 prover_state.max_variable_domain,
464 prover_state.max_non_zero_domain,
465 &mut sponge,
466 )?;
467 let (second_oracles, prover_state) =
473 AHPForR1CS::<_, SM>::prover_second_round(&verifier_first_message, prover_state, zk_rng)?;
474
475 let second_round_comm_time = start_timer!(|| "Committing to second round polys");
476 let (second_commitments, second_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
477 universal_prover,
478 &committer_key,
479 second_oracles.iter().map(Into::into),
480 SM::ZK.then_some(zk_rng),
481 )?;
482 end_timer!(second_round_comm_time);
483
484 Self::absorb_labeled(&second_commitments, &mut sponge);
485
486 let (verifier_second_msg, verifier_state) =
487 AHPForR1CS::<_, SM>::verifier_second_round(verifier_state, &mut sponge, varuna_version)?;
488 let (prover_prepare_third_message, prover_state, verifier_prepare_third_msg, verifier_state) = {
494 match varuna_version {
495 VarunaVersion::V1 => (None, prover_state, None, verifier_state),
496 VarunaVersion::V2 | VarunaVersion::V3 => {
497 let (prover_prepare_third_message, prover_state) = AHPForR1CS::<_, SM>::prover_prepare_third_round(
498 &verifier_first_message,
499 &verifier_second_msg,
500 prover_state,
501 zk_rng,
502 )?;
503
504 Self::absorb_sums(
505 &prover_prepare_third_message.sums.clone().into_iter().flatten().collect_vec(),
506 &mut sponge,
507 );
508
509 let (verifier_prepare_third_msg, verifier_state) =
510 AHPForR1CS::<_, SM>::verifier_prepare_third_round(
511 verifier_state,
512 &batch_sizes,
513 &circuit_infos,
514 &mut sponge,
515 varuna_version,
516 )?;
517
518 (Some(prover_prepare_third_message), prover_state, Some(verifier_prepare_third_msg), verifier_state)
519 }
520 }
521 };
522 let (prover_third_message, third_oracles, prover_state) = AHPForR1CS::<_, SM>::prover_third_round(
528 &verifier_first_message,
529 &verifier_second_msg,
530 &verifier_prepare_third_msg,
531 prover_state,
532 zk_rng,
533 varuna_version,
534 )?;
535
536 let third_round_comm_time = start_timer!(|| "Committing to third round polys");
537 let (third_commitments, third_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
538 universal_prover,
539 &committer_key,
540 third_oracles.iter().map(Into::into),
541 SM::ZK.then_some(zk_rng),
542 )?;
543 end_timer!(third_round_comm_time);
544
545 match varuna_version {
546 VarunaVersion::V1 => {
547 let prover_third_message = prover_third_message
548 .clone()
549 .ok_or_else(|| anyhow!("Expected prover to contribute sums in the third round."))?;
550 if prover_prepare_third_message.is_some() {
551 return Err(anyhow!("Expected prover to not contribute sums in the prepare third round."))?;
552 }
553 Self::absorb_labeled_with_sums(
554 &third_commitments,
555 &prover_third_message.sums.into_iter().flatten().collect_vec(),
556 &mut sponge,
557 );
558 }
559 VarunaVersion::V2 | VarunaVersion::V3 => {
560 if prover_third_message.is_some() {
561 return Err(anyhow!("Expected prover to not contribute sums in the third round."))?;
562 }
563 Self::absorb_labeled(&third_commitments, &mut sponge);
564 }
565 }
566
567 let prover_third_message = match varuna_version {
569 VarunaVersion::V1 => prover_third_message,
570 VarunaVersion::V2 | VarunaVersion::V3 => prover_prepare_third_message,
571 }
572 .ok_or_else(|| anyhow!("Prover did not contribute sums in the expected round."))?;
573
574 let (verifier_third_msg, verifier_state) =
575 AHPForR1CS::<_, SM>::verifier_third_round(verifier_state, &mut sponge)?;
576 let (prover_fourth_message, fourth_oracles, mut prover_state) =
582 AHPForR1CS::<_, SM>::prover_fourth_round(&verifier_second_msg, &verifier_third_msg, prover_state, zk_rng)?;
583
584 let fourth_round_comm_time = start_timer!(|| "Committing to fourth round polys");
585 let (fourth_commitments, fourth_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
586 universal_prover,
587 &committer_key,
588 fourth_oracles.iter().map(Into::into),
589 SM::ZK.then_some(zk_rng),
590 )?;
591 end_timer!(fourth_round_comm_time);
592
593 Self::absorb_labeled_with_sums(&fourth_commitments, &prover_fourth_message.sums, &mut sponge);
594
595 let (verifier_fourth_msg, verifier_state) =
596 AHPForR1CS::<_, SM>::verifier_fourth_round(verifier_state, &mut sponge)?;
597 let first_round_oracles = prover_state.first_round_oracles.take().unwrap();
601 let index_a_polys =
602 prover_state.circuit_specific_states.values_mut().flat_map(|s| s.a_polys.take().unwrap()).collect_vec();
603 let index_b_polys =
604 prover_state.circuit_specific_states.values_mut().flat_map(|s| s.b_polys.take().unwrap()).collect_vec();
605
606 let fifth_oracles = AHPForR1CS::<_, SM>::prover_fifth_round(verifier_fourth_msg, prover_state, zk_rng)?;
609
610 let fifth_round_comm_time = start_timer!(|| "Committing to fifth round polys");
611 let (fifth_commitments, fifth_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
612 universal_prover,
613 &committer_key,
614 fifth_oracles.iter().map(Into::into),
615 SM::ZK.then_some(zk_rng),
616 )?;
617 end_timer!(fifth_round_comm_time);
618
619 Self::absorb_labeled(&fifth_commitments, &mut sponge);
620
621 let verifier_state = AHPForR1CS::<_, SM>::verifier_fifth_round(verifier_state, &mut sponge)?;
622 let polynomials: Vec<_> = index_a_polys
626 .into_iter()
627 .chain(index_b_polys)
628 .chain(first_round_oracles.into_iter())
629 .chain(second_oracles.into_iter())
630 .chain(third_oracles.into_iter())
631 .chain(fourth_oracles.into_iter())
632 .chain(fifth_oracles.into_iter())
633 .collect();
634 ensure!(
635 polynomials.len()
636 == num_unique_circuits * 6 + AHPForR1CS::<E::Fr, SM>::num_first_round_oracles(total_instances) +
638 AHPForR1CS::<E::Fr, SM>::num_second_round_oracles() +
639 AHPForR1CS::<E::Fr, SM>::num_third_round_oracles() +
640 AHPForR1CS::<E::Fr, SM>::num_fourth_round_oracles(num_unique_circuits) +
641 AHPForR1CS::<E::Fr, SM>::num_fifth_round_oracles()
642 );
643
644 let witness_comm_len = if SM::ZK { first_commitments.len() - 1 } else { first_commitments.len() };
646 let mask_poly = SM::ZK.then(|| *first_commitments[witness_comm_len].commitment());
647 let witness_commitments = first_commitments[..witness_comm_len]
648 .iter()
649 .map(|c| proof::WitnessCommitments { w: *c.commitment() })
650 .collect_vec();
651 let fourth_commitments_chunked = fourth_commitments.chunks_exact(3);
652 let (g_a_commitments, g_b_commitments, g_c_commitments) = fourth_commitments_chunked
653 .map(|c| (*c[0].commitment(), *c[1].commitment(), *c[2].commitment()))
654 .multiunzip();
655
656 #[rustfmt::skip]
657 let commitments = proof::Commitments {
658 witness_commitments,
659 mask_poly,
660 h_0: *second_commitments[0].commitment(),
661 g_1: *third_commitments[0].commitment(),
662 h_1: *third_commitments[1].commitment(),
663 g_a_commitments,
664 g_b_commitments,
665 g_c_commitments,
666 h_2: *fifth_commitments[0].commitment(),
667 };
668
669 let indexer_randomness = vec![Randomness::<E>::empty(); 6 * num_unique_circuits];
671 let commitment_randomnesses: Vec<Randomness<E>> = indexer_randomness
672 .into_iter()
673 .chain(first_commitment_randomnesses)
674 .chain(second_commitment_randomnesses)
675 .chain(third_commitment_randomnesses)
676 .chain(fourth_commitment_randomnesses)
677 .chain(fifth_commitment_randomnesses)
678 .collect();
679
680 let empty_randomness = Randomness::<E>::empty();
681 if SM::ZK {
682 ensure!(commitment_randomnesses.iter().any(|r| r != &empty_randomness));
683 } else {
684 ensure!(commitment_randomnesses.iter().all(|r| r == &empty_randomness));
685 }
686
687 let (query_set, verifier_state) = AHPForR1CS::<_, SM>::verifier_query_set(verifier_state);
689 dev_println!("Final challenge gamma: {:?}", verifier_state.gamma);
690 let lc_s = AHPForR1CS::<_, SM>::construct_linear_combinations(
691 &public_inputs,
692 &polynomials,
693 &prover_third_message,
694 &prover_fourth_message,
695 &verifier_state,
696 varuna_version,
697 )?;
698
699 let eval_time = start_timer!(|| "Evaluating linear combinations over query set");
700 let mut evaluations = std::collections::BTreeMap::new();
701 for (label, (_, point)) in query_set.to_set() {
702 if !AHPForR1CS::<E::Fr, SM>::LC_WITH_ZERO_EVAL.contains(&label.as_str()) {
703 let lc = lc_s.get(&label).ok_or_else(|| AHPError::MissingEval(label.to_string()))?;
704 let evaluation = polynomials.get_lc_eval(lc, point)?;
705 evaluations.insert(label, evaluation);
706 }
707 }
708
709 let evaluations = proof::Evaluations::from_map(&evaluations, batch_sizes.clone());
710 end_timer!(eval_time);
711
712 sponge.absorb_nonnative_field_elements(evaluations.to_field_elements());
713
714 let pc_proof = SonicKZG10::<E, FS>::open_combinations(
715 universal_prover,
716 &committer_key,
717 lc_s.values(),
718 polynomials,
719 &commitment_randomnesses,
720 &query_set.to_set(),
721 &mut sponge,
722 )?;
723
724 let proof = Proof::<E>::new(
725 batch_sizes,
726 commitments,
727 evaluations,
728 prover_third_message,
729 prover_fourth_message,
730 pc_proof,
731 )?;
732 proof.check_batch_sizes()?;
733 ensure!(proof.pc_proof.is_hiding() == SM::ZK);
734
735 end_timer!(prover_time);
736 Ok(proof)
737 }
738
739 fn verify_batch<B: Borrow<Self::VerifierInput>>(
743 universal_verifier: &Self::UniversalVerifier,
744 fs_parameters: &Self::FSParameters,
745 varuna_version: VarunaVersion,
746 keys_to_inputs: &BTreeMap<&Self::VerifyingKey, &[B]>,
747 proof: &Self::Proof,
748 ) -> Result<bool> {
749 if keys_to_inputs.is_empty() {
750 bail!(SNARKError::EmptyBatch);
751 }
752
753 proof.check_batch_sizes()?;
754 let batch_sizes_vec = proof.batch_sizes();
755 let mut batch_sizes = BTreeMap::new();
756 ensure!(
757 keys_to_inputs.len() == batch_sizes_vec.len(),
758 "[verify batch] Expected {} keys to inputs, but {} were provided.",
759 batch_sizes_vec.len(),
760 keys_to_inputs.len()
761 );
762 for (i, (vk, public_inputs_i)) in keys_to_inputs.iter().enumerate() {
763 batch_sizes.insert(vk.id, batch_sizes_vec[i]);
764
765 if public_inputs_i.is_empty() {
766 bail!(SNARKError::EmptyBatch);
767 }
768
769 if public_inputs_i.len() != batch_sizes_vec[i] {
770 bail!(SNARKError::BatchSizeMismatch);
771 }
772 }
773
774 let mut max_num_constraints = 0;
776 let mut max_num_variables = 0;
777 let mut max_non_zero_domain = None;
778 let mut public_inputs = BTreeMap::new();
779 let mut padded_public_vec = Vec::with_capacity(keys_to_inputs.len());
780 let mut inputs_and_batch_sizes = BTreeMap::new();
781 let mut input_domains = BTreeMap::new();
782 let mut circuit_infos = BTreeMap::new();
783 let mut circuit_ids = Vec::with_capacity(keys_to_inputs.len());
784
785 #[cfg(feature = "snark-print")]
786 {
787 println!(
789 "[Varuna::verify_batch] Batch sizes: {:?}\n",
790 keys_to_inputs.values().map(|instances| instances.len()).collect_vec()
791 );
792
793 for (i, (circuit, public_inputs)) in keys_to_inputs.iter().enumerate() {
794 println!(" - Circuit {i}: {} ({} instance(s))\n", circuit.id, public_inputs.len());
795 for (j, public_input) in public_inputs.iter().enumerate() {
796 let public_input = public_input.borrow().to_field_elements()?;
797 println!(" - Instance {j}");
798 for (k, value) in public_input.iter().enumerate() {
799 println!(" - {k}: {value}");
800 }
801 println!("\n");
802 }
803 }
804 }
805
806 for (&vk, &public_inputs_i) in keys_to_inputs.iter() {
807 max_num_constraints = max_num_constraints.max(vk.circuit_info.num_constraints);
808 max_num_variables = max_num_variables.max(vk.circuit_info.num_public_and_private_variables);
809
810 let non_zero_domains = AHPForR1CS::<_, SM>::cmp_non_zero_domains(&vk.circuit_info, max_non_zero_domain)?;
811 max_non_zero_domain = non_zero_domains.max_non_zero_domain;
812
813 let input_domain = EvaluationDomain::<E::Fr>::new(vk.circuit_info.num_public_inputs)
814 .ok_or(anyhow!("Failed to create EvaluationDomain from num_public_inputs"))?;
815 input_domains.insert(vk.id, input_domain);
816
817 let input_fields = public_inputs_i
818 .iter()
819 .map(|input| {
820 let input = input.borrow().to_field_elements()?;
821 ensure!(input.len() > 0);
822 ensure!(input[0] == E::Fr::one());
823 if input.len() > input_domain.size() {
824 bail!(SNARKError::PublicInputSizeMismatch);
825 }
826 Ok(input)
827 })
828 .collect::<Result<Vec<_>, _>>()?;
829
830 let (padded_public_inputs_i, parsed_public_inputs_i): (Vec<_>, Vec<_>) = {
831 input_fields
832 .iter()
833 .map(|input| {
834 let input_len = input.len().max(input_domain.size());
835 let mut new_input = Vec::with_capacity(input_len);
836 new_input.extend_from_slice(input);
837 new_input.resize(input_len, E::Fr::zero());
838 dev_println!("[verify Batch] Number of padded public variables: {}", new_input.len());
839 let unformatted = prover::ConstraintSystem::unformat_public_input(&new_input);
840 (new_input, unformatted)
841 })
842 .unzip()
843 };
844
845 let circuit_id = vk.id;
846 public_inputs.insert(circuit_id, parsed_public_inputs_i);
847
848 padded_public_vec.push(padded_public_inputs_i);
849
850 circuit_infos.insert(circuit_id, &vk.circuit_info);
851 circuit_ids.push(circuit_id);
852 }
853 for (i, (vk, &batch_size)) in keys_to_inputs.keys().zip(batch_sizes.values()).enumerate() {
854 inputs_and_batch_sizes.insert(vk.id, (batch_size, padded_public_vec[i].as_slice()));
855 }
856 let max_constraint_domain =
857 EvaluationDomain::<E::Fr>::new(max_num_constraints).ok_or(SynthesisError::PolyTooLarge)?;
858 let max_variable_domain =
859 EvaluationDomain::<E::Fr>::new(max_num_variables).ok_or(SynthesisError::PolyTooLarge)?;
860 let max_non_zero_domain = max_non_zero_domain.ok_or(SynthesisError::PolyTooLarge)?;
861
862 let comms = &proof.commitments;
863 let proof_has_correct_zk_mode = if SM::ZK {
864 proof.pc_proof.is_hiding() & comms.mask_poly.is_some()
865 } else {
866 !proof.pc_proof.is_hiding() & comms.mask_poly.is_none()
867 };
868 if !proof_has_correct_zk_mode {
869 dev_eprintln!(
870 "Found `mask_poly` in the first round when not expected, or proof has incorrect hiding mode ({})",
871 proof.pc_proof.is_hiding()
872 );
873 return Ok(false);
874 }
875
876 let verifier_time = start_timer!(|| format!("Varuna::Verify with batch sizes: {batch_sizes:?}"));
877
878 let first_round_info = AHPForR1CS::<E::Fr, SM>::first_round_polynomial_info(batch_sizes.iter());
879
880 let mut first_comms_consumed = 0;
881 let mut first_commitments = batch_sizes
882 .iter()
883 .flat_map(|(circuit_id, &batch_size)| {
884 let first_comms = comms.witness_commitments[first_comms_consumed..][..batch_size]
885 .iter()
886 .enumerate()
887 .map(|(j, w_comm)| {
888 LabeledCommitment::new_with_info(
889 &first_round_info[&witness_label(*circuit_id, "w", j)],
890 w_comm.w,
891 )
892 });
893 first_comms_consumed += batch_size;
894 first_comms
895 })
896 .collect_vec();
897
898 if SM::ZK {
899 first_commitments.push(LabeledCommitment::new_with_info(
900 first_round_info.get("mask_poly").ok_or(anyhow!("Missing mask_poly"))?,
901 comms.mask_poly.ok_or(anyhow!("Missing mask_poly"))?,
902 ));
903 }
904
905 let second_round_info = AHPForR1CS::<E::Fr, SM>::second_round_polynomial_info();
906 let second_commitments = [LabeledCommitment::new_with_info(&second_round_info["h_0"], comms.h_0)];
907
908 let third_round_info = AHPForR1CS::<E::Fr, SM>::third_round_polynomial_info(max_variable_domain.size());
909 let third_commitments = [
910 LabeledCommitment::new_with_info(&third_round_info["g_1"], comms.g_1),
911 LabeledCommitment::new_with_info(&third_round_info["h_1"], comms.h_1),
912 ];
913
914 ensure!(
915 comms.g_a_commitments.len() == comms.g_b_commitments.len(),
916 "[verify Batch] Expected {} g_a commitments to match {} g_b commitments.",
917 comms.g_b_commitments.len(),
918 comms.g_a_commitments.len()
919 );
920 ensure!(
921 comms.g_a_commitments.len() == comms.g_c_commitments.len(),
922 "[verify Batch] Expected {} g_a commitments to match {} g_c commitments.",
923 comms.g_c_commitments.len(),
924 comms.g_a_commitments.len()
925 );
926 ensure!(
927 comms.g_a_commitments.len() == circuit_ids.len(),
928 "[verify Batch] Expected {} g_a commitments to match {} circuit ids.",
929 circuit_ids.len(),
930 comms.g_a_commitments.len()
931 );
932 let fourth_round_info =
933 AHPForR1CS::<E::Fr, SM>::fourth_round_polynomial_info(circuit_infos.clone().into_iter());
934 let fourth_commitments = comms
935 .g_a_commitments
936 .iter()
937 .zip_eq(comms.g_b_commitments.iter())
938 .zip_eq(comms.g_c_commitments.iter())
939 .zip_eq(circuit_ids.iter())
940 .flat_map(|(((g_a, g_b), g_c), circuit_id)| {
941 [
942 LabeledCommitment::new_with_info(&fourth_round_info[&witness_label(*circuit_id, "g_a", 0)], *g_a),
943 LabeledCommitment::new_with_info(&fourth_round_info[&witness_label(*circuit_id, "g_b", 0)], *g_b),
944 LabeledCommitment::new_with_info(&fourth_round_info[&witness_label(*circuit_id, "g_c", 0)], *g_c),
945 ]
946 })
947 .collect_vec();
948
949 let fifth_round_info = AHPForR1CS::<E::Fr, SM>::fifth_round_polynomial_info();
950 let fifth_commitments = [LabeledCommitment::new_with_info(&fifth_round_info["h_2"], comms.h_2)];
951
952 let circuit_commitments = keys_to_inputs.keys().map(|vk| vk.circuit_commitments.as_slice());
953 dev_println!("inputs_and_batch_sizes: {inputs_and_batch_sizes:?}");
954 let mut sponge =
955 Self::init_sponge(fs_parameters, &inputs_and_batch_sizes, circuit_commitments.clone(), varuna_version);
956
957 let first_round_time = start_timer!(|| "First round");
960 Self::absorb_labeled(&first_commitments, &mut sponge);
961 let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_first_round(
962 &batch_sizes,
963 &circuit_infos,
964 max_constraint_domain,
965 max_variable_domain,
966 max_non_zero_domain,
967 &mut sponge,
968 )?;
969 end_timer!(first_round_time);
970 let second_round_time = start_timer!(|| "Second round");
975 Self::absorb_labeled(&second_commitments, &mut sponge);
976 let (_, verifier_state) =
977 AHPForR1CS::<_, SM>::verifier_second_round(verifier_state, &mut sponge, varuna_version)?;
978 end_timer!(second_round_time);
979 let verifier_state = {
984 match varuna_version {
985 VarunaVersion::V1 => verifier_state,
986 VarunaVersion::V2 | VarunaVersion::V3 => {
987 let prepare_third_round_time = start_timer!(|| "Prep third round");
988 Self::absorb_sums(&proof.third_msg.sums.clone().into_iter().flatten().collect_vec(), &mut sponge);
989 let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_prepare_third_round(
990 verifier_state,
991 &batch_sizes,
992 &circuit_infos,
993 &mut sponge,
994 varuna_version,
995 )?;
996 end_timer!(prepare_third_round_time);
997 verifier_state
998 }
999 }
1000 };
1001 let third_round_time = start_timer!(|| "Third round");
1006 match varuna_version {
1007 VarunaVersion::V1 => {
1008 Self::absorb_labeled_with_sums(
1009 &third_commitments,
1010 &proof.third_msg.sums.clone().into_iter().flatten().collect_vec(),
1011 &mut sponge,
1012 );
1013 }
1014 VarunaVersion::V2 | VarunaVersion::V3 => {
1015 Self::absorb_labeled(&third_commitments, &mut sponge);
1016 }
1017 }
1018 let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_third_round(verifier_state, &mut sponge)?;
1019 end_timer!(third_round_time);
1020 let fourth_round_time = start_timer!(|| "Fourth round");
1025
1026 Self::absorb_labeled_with_sums(&fourth_commitments, &proof.fourth_msg.sums, &mut sponge);
1027 let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_fourth_round(verifier_state, &mut sponge)?;
1028 end_timer!(fourth_round_time);
1029 let fifth_round_time = start_timer!(|| "Fifth round");
1034
1035 Self::absorb_labeled(&fifth_commitments, &mut sponge);
1036 let verifier_state = AHPForR1CS::<_, SM>::verifier_fifth_round(verifier_state, &mut sponge)?;
1037 end_timer!(fifth_round_time);
1038 ensure!(
1045 circuit_commitments.len() == circuit_ids.len(),
1046 "[verify Batch] Expected {} circuit commitments, but {} were provided.",
1047 circuit_ids.len(),
1048 circuit_commitments.len()
1049 );
1050 let commitments: Vec<_> = circuit_commitments
1051 .into_iter()
1052 .flatten()
1053 .zip_eq(AHPForR1CS::<E::Fr, SM>::index_polynomial_info(circuit_ids.iter()).values())
1054 .map(|(c, info)| LabeledCommitment::new_with_info(info, *c))
1055 .chain(first_commitments)
1056 .chain(second_commitments)
1057 .chain(third_commitments)
1058 .chain(fourth_commitments)
1059 .chain(fifth_commitments)
1060 .collect();
1061
1062 let query_set_time = start_timer!(|| "Constructing query set");
1063 let (query_set, verifier_state) = AHPForR1CS::<_, SM>::verifier_query_set(verifier_state);
1064 end_timer!(query_set_time);
1065
1066 sponge.absorb_nonnative_field_elements(proof.evaluations.to_field_elements());
1067
1068 let mut evaluations = Evaluations::new();
1069
1070 let mut current_circuit_id = "".to_string();
1071 let mut circuit_index: i64 = -1;
1072
1073 for (label, (_point_name, q)) in query_set.to_set() {
1074 if AHPForR1CS::<E::Fr, SM>::LC_WITH_ZERO_EVAL.contains(&label.as_ref()) {
1075 evaluations.insert((label, q), E::Fr::zero());
1076 } else {
1077 if label != "g_1" {
1078 let circuit_id = CircuitId::from_witness_label(&label).to_string();
1079 if circuit_id != current_circuit_id {
1080 circuit_index += 1;
1081 current_circuit_id = circuit_id;
1082 }
1083 }
1084 let eval = proof
1085 .evaluations
1086 .get(circuit_index as usize, &label)
1087 .ok_or_else(|| AHPError::MissingEval(label.clone()))?;
1088 evaluations.insert((label, q), eval);
1089 }
1090 }
1091
1092 let lc_time = start_timer!(|| "Constructing linear combinations");
1093 let lc_s = AHPForR1CS::<_, SM>::construct_linear_combinations(
1094 &public_inputs,
1095 &evaluations,
1096 &proof.third_msg,
1097 &proof.fourth_msg,
1098 &verifier_state,
1099 varuna_version,
1100 )?;
1101 end_timer!(lc_time);
1102
1103 let pc_time = start_timer!(|| "Checking linear combinations with PC");
1104 let evaluations_are_correct = SonicKZG10::<E, FS>::check_combinations(
1105 universal_verifier,
1106 lc_s.values(),
1107 &commitments,
1108 &query_set.to_set(),
1109 &evaluations,
1110 &proof.pc_proof,
1111 &mut sponge,
1112 )?;
1113 end_timer!(pc_time);
1114
1115 if !evaluations_are_correct {
1116 dev_eprintln!("SonicKZG10::Check failed using final challenge gamma: {:?}", verifier_state.gamma);
1117 }
1118
1119 end_timer!(verifier_time, || format!(
1120 " SonicKZG10::Check for AHP Verifier linear equations: {}",
1121 evaluations_are_correct & proof_has_correct_zk_mode
1122 ));
1123 Ok(evaluations_are_correct & proof_has_correct_zk_mode)
1124 }
1125}