snarkvm_algorithms/snark/varuna/
varuna.rs

1// Copyright 2024 Aleo Network Foundation
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 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        ahp::{AHPError, AHPForR1CS, CircuitId, EvaluationsProvider},
39        proof,
40        prover,
41        witness_label,
42    },
43    srs::UniversalVerifier,
44};
45use rand::RngCore;
46use snarkvm_curves::PairingEngine;
47use snarkvm_fields::{One, PrimeField, ToConstraintField, Zero};
48use snarkvm_utilities::{ToBytes, 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;
57use snarkvm_utilities::println;
58
59/// The Varuna proof system.
60#[derive(Clone, Debug)]
61pub struct VarunaSNARK<E: PairingEngine, FS: AlgebraicSponge<E::Fq, 2>, SM: SNARKMode>(
62    #[doc(hidden)] PhantomData<(E, FS, SM)>,
63);
64
65impl<E: PairingEngine, FS: AlgebraicSponge<E::Fq, 2>, SM: SNARKMode> VarunaSNARK<E, FS, SM> {
66    /// The personalization string for this protocol.
67    /// Used to personalize the Fiat-Shamir RNG.
68    pub const PROTOCOL_NAME: &'static [u8] = b"VARUNA-2023";
69
70    // TODO: implement optimizations resulting from batching
71    //       (e.g. computing a common set of Lagrange powers, FFT precomputations, etc)
72    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            // TODO: Add check that c is in the correct mode.
84            // Ensure the universal SRS supports the circuit size.
85            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            // Varuna only needs degree 2 random polynomials.
91            let supported_hiding_bound = 1;
92            let supported_lagrange_sizes = [].into_iter(); // TODO: consider removing lagrange_bases_at_beta_g from CommitterKey
93            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 RngCore>; // We do not randomize the commitments
105
106            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    ) -> FS {
141        let mut sponge = FS::new_with_parameters(fs_parameters);
142        sponge.absorb_bytes(Self::PROTOCOL_NAME);
143        for (batch_size, inputs) in inputs_and_batch_sizes.values() {
144            sponge.absorb_bytes(&(*batch_size as u64).to_le_bytes());
145            for input in inputs.iter() {
146                sponge.absorb_nonnative_field_elements(input.iter().copied());
147            }
148        }
149        for circuit_specific_commitments in circuit_commitments {
150            sponge.absorb_native_field_elements(circuit_specific_commitments);
151        }
152        sponge
153    }
154
155    fn init_sponge_for_certificate(
156        fs_parameters: &FS::Parameters,
157        verifying_key: &CircuitVerifyingKey<E>,
158    ) -> Result<FS> {
159        let mut sponge = FS::new_with_parameters(fs_parameters);
160        sponge.absorb_bytes(&to_bytes_le![&Self::PROTOCOL_NAME]?);
161        sponge.absorb_bytes(&verifying_key.circuit_info.to_bytes_le()?);
162        sponge.absorb_native_field_elements(&verifying_key.circuit_commitments);
163        sponge.absorb_bytes(&verifying_key.id.0);
164        Ok(sponge)
165    }
166
167    fn absorb_labeled_with_sums(
168        comms: &[LabeledCommitment<Commitment<E>>],
169        sums: &[prover::MatrixSums<E::Fr>],
170        sponge: &mut FS,
171    ) {
172        let commitments: Vec<_> = comms.iter().map(|c| *c.commitment()).collect();
173        Self::absorb_with_sums(&commitments, sums, sponge)
174    }
175
176    fn absorb_labeled(comms: &[LabeledCommitment<Commitment<E>>], sponge: &mut FS) {
177        let commitments: Vec<_> = comms.iter().map(|c| *c.commitment()).collect();
178        Self::absorb(&commitments, sponge);
179    }
180
181    fn absorb(commitments: &[Commitment<E>], sponge: &mut FS) {
182        let sponge_time = start_timer!(|| "Absorbing commitments");
183        sponge.absorb_native_field_elements(commitments);
184        end_timer!(sponge_time);
185    }
186
187    fn absorb_with_sums(commitments: &[Commitment<E>], sums: &[prover::MatrixSums<E::Fr>], sponge: &mut FS) {
188        let sponge_time = start_timer!(|| "Absorbing commitments and message");
189        Self::absorb(commitments, sponge);
190        for sum in sums.iter() {
191            sponge.absorb_nonnative_field_elements([sum.sum_a, sum.sum_b, sum.sum_c]);
192        }
193        end_timer!(sponge_time);
194    }
195}
196
197impl<E: PairingEngine, FS, SM> SNARK for VarunaSNARK<E, FS, SM>
198where
199    E::Fr: PrimeField,
200    E::Fq: PrimeField,
201    FS: AlgebraicSponge<E::Fq, 2>,
202    SM: SNARKMode,
203{
204    type BaseField = E::Fq;
205    type Certificate = Certificate<E>;
206    type FSParameters = FS::Parameters;
207    type FiatShamirRng = FS;
208    type Proof = Proof<E>;
209    type ProvingKey = CircuitProvingKey<E, SM>;
210    type ScalarField = E::Fr;
211    type UniversalProver = UniversalProver<E>;
212    type UniversalSRS = UniversalSRS<E>;
213    type UniversalVerifier = UniversalVerifier<E>;
214    type VerifierInput = [E::Fr];
215    type VerifyingKey = CircuitVerifyingKey<E>;
216
217    fn universal_setup(max_degree: usize) -> Result<Self::UniversalSRS> {
218        let setup_time = start_timer!(|| { format!("Varuna::UniversalSetup with max_degree {max_degree}",) });
219        let srs = SonicKZG10::<E, FS>::load_srs(max_degree).map_err(Into::into);
220        end_timer!(setup_time);
221        srs
222    }
223
224    /// Generates the circuit proving and verifying keys.
225    /// This is a deterministic algorithm that anyone can rerun.
226    fn circuit_setup<C: ConstraintSynthesizer<E::Fr>>(
227        universal_srs: &Self::UniversalSRS,
228        circuit: &C,
229    ) -> Result<(Self::ProvingKey, Self::VerifyingKey)> {
230        let mut circuit_keys = Self::batch_circuit_setup::<C>(universal_srs, &[circuit])?;
231        ensure!(circuit_keys.len() == 1);
232        Ok(circuit_keys.pop().unwrap())
233    }
234
235    /// Prove that the verifying key commitments commit to the indexed circuit's polynomials
236    fn prove_vk(
237        universal_prover: &Self::UniversalProver,
238        fs_parameters: &Self::FSParameters,
239        verifying_key: &Self::VerifyingKey,
240        proving_key: &Self::ProvingKey,
241    ) -> Result<Self::Certificate> {
242        // Initialize sponge
243        let mut sponge = Self::init_sponge_for_certificate(fs_parameters, verifying_key)?;
244        // Compute challenges for linear combination, and the point to evaluate the polynomials at.
245        // The linear combination requires `num_polynomials - 1` coefficients
246        // (since the first coeff is 1), and so we squeeze out `num_polynomials` points.
247        let mut challenges = sponge.squeeze_nonnative_field_elements(verifying_key.circuit_commitments.len());
248        let point = challenges.pop().ok_or(anyhow!("Failed to squeeze random element"))?;
249        let one = E::Fr::one();
250        let linear_combination_challenges = core::iter::once(&one).chain(challenges.iter());
251
252        let circuit_id = std::iter::once(&verifying_key.id);
253        let circuit_poly_info = AHPForR1CS::<E::Fr, SM>::index_polynomial_info(circuit_id);
254
255        // We will construct a linear combination and provide a proof of evaluation of the lc at `point`.
256        let mut lc = crate::polycommit::sonic_pc::LinearCombination::empty("circuit_check");
257        for (label, &c) in circuit_poly_info.keys().zip(linear_combination_challenges) {
258            lc.add(c, label.clone());
259        }
260
261        let query_set = QuerySet::from_iter([("circuit_check".into(), ("challenge".into(), point))]);
262        let committer_key = CommitterUnionKey::union(std::iter::once(proving_key.committer_key.as_ref()));
263
264        let empty_randomness = vec![Randomness::<E>::empty(); 12];
265        let certificate = SonicKZG10::<E, FS>::open_combinations(
266            universal_prover,
267            &committer_key,
268            &[lc],
269            proving_key.circuit.interpolate_matrix_evals()?,
270            &empty_randomness,
271            &query_set,
272            &mut sponge,
273        )?;
274
275        Ok(Self::Certificate::new(certificate))
276    }
277
278    /// Verify that the verifying key commitments commit to the indexed circuit's polynomials
279    /// Verify that the verifying key's circuit_info is correct
280    fn verify_vk<C: ConstraintSynthesizer<Self::ScalarField>>(
281        universal_verifier: &Self::UniversalVerifier,
282        fs_parameters: &Self::FSParameters,
283        circuit: &C,
284        verifying_key: &Self::VerifyingKey,
285        certificate: &Self::Certificate,
286    ) -> Result<bool> {
287        // Ensure the VerifyingKey encodes the expected circuit.
288        let circuit_id = &verifying_key.id;
289        let state = AHPForR1CS::<E::Fr, SM>::index_helper(circuit)?;
290        if state.index_info != verifying_key.circuit_info {
291            bail!(SNARKError::CircuitNotFound);
292        }
293        if state.id != *circuit_id {
294            bail!(SNARKError::CircuitNotFound);
295        }
296
297        // Initialize sponge.
298        let mut sponge = Self::init_sponge_for_certificate(fs_parameters, verifying_key)?;
299
300        // Compute challenges for linear combination, and the point to evaluate the polynomials at.
301        // The linear combination requires `num_polynomials - 1` coefficients
302        // (since the first coeff is 1), and so we squeeze out `num_polynomials` points.
303        let mut challenges = sponge.squeeze_nonnative_field_elements(verifying_key.circuit_commitments.len());
304        let point = challenges.pop().ok_or(anyhow!("Failed to squeeze random element"))?;
305        let combiners = core::iter::once(E::Fr::one()).chain(challenges);
306
307        // We will construct a linear combination and provide a proof of evaluation of the lc at `point`.
308        let (lc, evaluation) =
309            AHPForR1CS::<E::Fr, SM>::evaluate_index_polynomials(state, circuit_id, point, combiners)?;
310
311        ensure!(verifying_key.circuit_commitments.len() == lc.terms.len());
312        let commitments = verifying_key
313            .iter()
314            .cloned()
315            .zip_eq(lc.terms.keys())
316            .map(|(c, label)| LabeledCommitment::new(format!("{label:?}"), c, None))
317            .collect_vec();
318        let evaluations = Evaluations::from_iter([(("circuit_check".into(), point), evaluation)]);
319        let query_set = QuerySet::from_iter([("circuit_check".into(), ("challenge".into(), point))]);
320
321        SonicKZG10::<E, FS>::check_combinations(
322            universal_verifier,
323            &[lc],
324            &commitments,
325            &query_set,
326            &evaluations,
327            &certificate.pc_proof,
328            &mut sponge,
329        )
330        .map_err(Into::into)
331    }
332
333    /// This is the main entrypoint for creating proofs.
334    /// You can find a specification of the prover algorithm in:
335    /// https://github.com/ProvableHQ/protocol-docs
336    fn prove_batch<C: ConstraintSynthesizer<E::Fr>, R: Rng + CryptoRng>(
337        universal_prover: &Self::UniversalProver,
338        fs_parameters: &Self::FSParameters,
339        keys_to_constraints: &BTreeMap<&CircuitProvingKey<E, SM>, &[C]>,
340        zk_rng: &mut R,
341    ) -> Result<Self::Proof> {
342        let prover_time = start_timer!(|| "Varuna::Prover");
343        if keys_to_constraints.is_empty() {
344            bail!(SNARKError::EmptyBatch);
345        }
346
347        let mut circuits_to_constraints = BTreeMap::new();
348        for (pk, constraints) in keys_to_constraints {
349            circuits_to_constraints.insert(pk.circuit.deref(), *constraints);
350        }
351        let prover_state = AHPForR1CS::<_, SM>::init_prover(&circuits_to_constraints, zk_rng)?;
352
353        // extract information from the prover key and state to consume in further calculations
354        let mut batch_sizes = BTreeMap::new();
355        let mut circuit_infos = BTreeMap::new();
356        let mut inputs_and_batch_sizes = BTreeMap::new();
357        let mut total_instances = 0usize;
358        let mut public_inputs = BTreeMap::new(); // inputs need to live longer than the rest of prover_state
359        let num_unique_circuits = keys_to_constraints.len();
360        let mut circuit_ids = Vec::with_capacity(num_unique_circuits);
361        for pk in keys_to_constraints.keys() {
362            let batch_size = prover_state.batch_size(&pk.circuit).ok_or(SNARKError::CircuitNotFound)?;
363            let public_input = prover_state.public_inputs(&pk.circuit).ok_or(SNARKError::CircuitNotFound)?;
364            let padded_public_input =
365                prover_state.padded_public_inputs(&pk.circuit).ok_or(SNARKError::CircuitNotFound)?;
366            let circuit_id = pk.circuit.id;
367            batch_sizes.insert(circuit_id, batch_size);
368            circuit_infos.insert(circuit_id, &pk.circuit_verifying_key.circuit_info);
369            inputs_and_batch_sizes.insert(circuit_id, (batch_size, padded_public_input));
370            public_inputs.insert(circuit_id, public_input);
371            total_instances = total_instances.saturating_add(batch_size);
372
373            circuit_ids.push(circuit_id);
374        }
375        ensure!(prover_state.total_instances == total_instances);
376
377        let committer_key = CommitterUnionKey::union(keys_to_constraints.keys().map(|pk| pk.committer_key.deref()));
378
379        let circuit_commitments =
380            keys_to_constraints.keys().map(|pk| pk.circuit_verifying_key.circuit_commitments.as_slice());
381
382        let mut sponge = Self::init_sponge(fs_parameters, &inputs_and_batch_sizes, circuit_commitments.clone());
383
384        // --------------------------------------------------------------------
385        // First round
386
387        let prover_state = AHPForR1CS::<_, SM>::prover_first_round(prover_state, zk_rng)?;
388
389        let first_round_comm_time = start_timer!(|| "Committing to first round polys");
390        let (first_commitments, first_commitment_randomnesses) = {
391            let first_round_oracles = prover_state.first_round_oracles.as_ref().unwrap();
392            SonicKZG10::<E, FS>::commit(
393                universal_prover,
394                &committer_key,
395                first_round_oracles.iter().map(Into::into),
396                SM::ZK.then_some(zk_rng),
397            )?
398        };
399        end_timer!(first_round_comm_time);
400
401        Self::absorb_labeled(&first_commitments, &mut sponge);
402
403        let (verifier_first_message, verifier_state) = AHPForR1CS::<_, SM>::verifier_first_round(
404            &batch_sizes,
405            &circuit_infos,
406            prover_state.max_constraint_domain,
407            prover_state.max_variable_domain,
408            prover_state.max_non_zero_domain,
409            &mut sponge,
410        )?;
411        // --------------------------------------------------------------------
412
413        // --------------------------------------------------------------------
414        // Second round
415
416        let (second_oracles, prover_state) =
417            AHPForR1CS::<_, SM>::prover_second_round(&verifier_first_message, prover_state, zk_rng)?;
418
419        let second_round_comm_time = start_timer!(|| "Committing to second round polys");
420        let (second_commitments, second_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
421            universal_prover,
422            &committer_key,
423            second_oracles.iter().map(Into::into),
424            SM::ZK.then_some(zk_rng),
425        )?;
426        end_timer!(second_round_comm_time);
427
428        Self::absorb_labeled(&second_commitments, &mut sponge);
429
430        let (verifier_second_msg, verifier_state) =
431            AHPForR1CS::<_, SM>::verifier_second_round(verifier_state, &mut sponge)?;
432        // --------------------------------------------------------------------
433
434        // --------------------------------------------------------------------
435        // Third round
436
437        let (prover_third_message, third_oracles, prover_state) = AHPForR1CS::<_, SM>::prover_third_round(
438            &verifier_first_message,
439            &verifier_second_msg,
440            prover_state,
441            zk_rng,
442        )?;
443
444        let third_round_comm_time = start_timer!(|| "Committing to third round polys");
445        let (third_commitments, third_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
446            universal_prover,
447            &committer_key,
448            third_oracles.iter().map(Into::into),
449            SM::ZK.then_some(zk_rng),
450        )?;
451        end_timer!(third_round_comm_time);
452
453        Self::absorb_labeled_with_sums(
454            &third_commitments,
455            &prover_third_message.sums.clone().into_iter().flatten().collect_vec(),
456            &mut sponge,
457        );
458
459        let (verifier_third_msg, verifier_state) =
460            AHPForR1CS::<_, SM>::verifier_third_round(verifier_state, &mut sponge)?;
461        // --------------------------------------------------------------------
462
463        // --------------------------------------------------------------------
464        // Fourth round
465
466        let (prover_fourth_message, fourth_oracles, mut prover_state) =
467            AHPForR1CS::<_, SM>::prover_fourth_round(&verifier_second_msg, &verifier_third_msg, prover_state, zk_rng)?;
468
469        let fourth_round_comm_time = start_timer!(|| "Committing to fourth round polys");
470        let (fourth_commitments, fourth_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
471            universal_prover,
472            &committer_key,
473            fourth_oracles.iter().map(Into::into),
474            SM::ZK.then_some(zk_rng),
475        )?;
476        end_timer!(fourth_round_comm_time);
477
478        Self::absorb_labeled_with_sums(&fourth_commitments, &prover_fourth_message.sums, &mut sponge);
479
480        let (verifier_fourth_msg, verifier_state) =
481            AHPForR1CS::<_, SM>::verifier_fourth_round(verifier_state, &mut sponge)?;
482        // --------------------------------------------------------------------
483
484        // We take out values from state before they are consumed.
485        let first_round_oracles = prover_state.first_round_oracles.take().unwrap();
486        let index_a_polys =
487            prover_state.circuit_specific_states.values_mut().flat_map(|s| s.a_polys.take().unwrap()).collect_vec();
488        let index_b_polys =
489            prover_state.circuit_specific_states.values_mut().flat_map(|s| s.b_polys.take().unwrap()).collect_vec();
490
491        // --------------------------------------------------------------------
492        // Fifth round
493        let fifth_oracles = AHPForR1CS::<_, SM>::prover_fifth_round(verifier_fourth_msg, prover_state, zk_rng)?;
494
495        let fifth_round_comm_time = start_timer!(|| "Committing to fifth round polys");
496        let (fifth_commitments, fifth_commitment_randomnesses) = SonicKZG10::<E, FS>::commit(
497            universal_prover,
498            &committer_key,
499            fifth_oracles.iter().map(Into::into),
500            SM::ZK.then_some(zk_rng),
501        )?;
502        end_timer!(fifth_round_comm_time);
503
504        Self::absorb_labeled(&fifth_commitments, &mut sponge);
505
506        let verifier_state = AHPForR1CS::<_, SM>::verifier_fifth_round(verifier_state, &mut sponge)?;
507        // --------------------------------------------------------------------
508
509        // Gather prover polynomials in one vector.
510        let polynomials: Vec<_> = index_a_polys
511            .into_iter()
512            .chain(index_b_polys)
513            .chain(first_round_oracles.into_iter())
514            .chain(second_oracles.into_iter())
515            .chain(third_oracles.into_iter())
516            .chain(fourth_oracles.into_iter())
517            .chain(fifth_oracles.into_iter())
518            .collect();
519        ensure!(
520            polynomials.len()
521                == num_unique_circuits * 6 + // numerator and denominator for each matrix sumcheck
522            AHPForR1CS::<E::Fr, SM>::num_first_round_oracles(total_instances) +
523            AHPForR1CS::<E::Fr, SM>::num_second_round_oracles() +
524            AHPForR1CS::<E::Fr, SM>::num_third_round_oracles() +
525            AHPForR1CS::<E::Fr, SM>::num_fourth_round_oracles(num_unique_circuits) +
526            AHPForR1CS::<E::Fr, SM>::num_fifth_round_oracles()
527        );
528
529        // Gather commitments in one vector.
530        let witness_comm_len = if SM::ZK { first_commitments.len() - 1 } else { first_commitments.len() };
531        let mask_poly = SM::ZK.then(|| *first_commitments[witness_comm_len].commitment());
532        let witness_commitments = first_commitments[..witness_comm_len]
533            .iter()
534            .map(|c| proof::WitnessCommitments { w: *c.commitment() })
535            .collect_vec();
536        let fourth_commitments_chunked = fourth_commitments.chunks_exact(3);
537        let (g_a_commitments, g_b_commitments, g_c_commitments) = fourth_commitments_chunked
538            .map(|c| (*c[0].commitment(), *c[1].commitment(), *c[2].commitment()))
539            .multiunzip();
540
541        #[rustfmt::skip]
542        let commitments = proof::Commitments {
543            witness_commitments,
544            mask_poly,
545            h_0: *second_commitments[0].commitment(),
546            g_1: *third_commitments[0].commitment(),
547            h_1: *third_commitments[1].commitment(),
548            g_a_commitments,
549            g_b_commitments,
550            g_c_commitments,
551            h_2: *fifth_commitments[0].commitment(),
552        };
553
554        // Gather commitment randomness together.
555        let indexer_randomness = vec![Randomness::<E>::empty(); 6 * num_unique_circuits];
556        let commitment_randomnesses: Vec<Randomness<E>> = indexer_randomness
557            .into_iter()
558            .chain(first_commitment_randomnesses)
559            .chain(second_commitment_randomnesses)
560            .chain(third_commitment_randomnesses)
561            .chain(fourth_commitment_randomnesses)
562            .chain(fifth_commitment_randomnesses)
563            .collect();
564
565        let empty_randomness = Randomness::<E>::empty();
566        if SM::ZK {
567            ensure!(commitment_randomnesses.iter().any(|r| r != &empty_randomness));
568        } else {
569            ensure!(commitment_randomnesses.iter().all(|r| r == &empty_randomness));
570        }
571
572        // Compute the AHP verifier's query set.
573        let (query_set, verifier_state) = AHPForR1CS::<_, SM>::verifier_query_set(verifier_state);
574        let lc_s = AHPForR1CS::<_, SM>::construct_linear_combinations(
575            &public_inputs,
576            &polynomials,
577            &prover_third_message,
578            &prover_fourth_message,
579            &verifier_state,
580        )?;
581
582        let eval_time = start_timer!(|| "Evaluating linear combinations over query set");
583        let mut evaluations = std::collections::BTreeMap::new();
584        for (label, (_, point)) in query_set.to_set() {
585            if !AHPForR1CS::<E::Fr, SM>::LC_WITH_ZERO_EVAL.contains(&label.as_str()) {
586                let lc = lc_s.get(&label).ok_or_else(|| AHPError::MissingEval(label.to_string()))?;
587                let evaluation = polynomials.get_lc_eval(lc, point)?;
588                evaluations.insert(label, evaluation);
589            }
590        }
591
592        let evaluations = proof::Evaluations::from_map(&evaluations, batch_sizes.clone());
593        end_timer!(eval_time);
594
595        sponge.absorb_nonnative_field_elements(evaluations.to_field_elements());
596
597        let pc_proof = SonicKZG10::<E, FS>::open_combinations(
598            universal_prover,
599            &committer_key,
600            lc_s.values(),
601            polynomials,
602            &commitment_randomnesses,
603            &query_set.to_set(),
604            &mut sponge,
605        )?;
606
607        let proof = Proof::<E>::new(
608            batch_sizes,
609            commitments,
610            evaluations,
611            prover_third_message,
612            prover_fourth_message,
613            pc_proof,
614        )?;
615        proof.check_batch_sizes()?;
616        ensure!(proof.pc_proof.is_hiding() == SM::ZK);
617
618        end_timer!(prover_time);
619        Ok(proof)
620    }
621
622    /// This is the main entrypoint for verifying proofs.
623    /// You can find a specification of the verifier algorithm in:
624    /// https://github.com/ProvableHQ/protocol-docs
625    fn verify_batch<B: Borrow<Self::VerifierInput>>(
626        universal_verifier: &Self::UniversalVerifier,
627        fs_parameters: &Self::FSParameters,
628        keys_to_inputs: &BTreeMap<&Self::VerifyingKey, &[B]>,
629        proof: &Self::Proof,
630    ) -> Result<bool> {
631        if keys_to_inputs.is_empty() {
632            bail!(SNARKError::EmptyBatch);
633        }
634
635        proof.check_batch_sizes()?;
636        let batch_sizes_vec = proof.batch_sizes();
637        let mut batch_sizes = BTreeMap::new();
638        for (i, (vk, public_inputs_i)) in keys_to_inputs.iter().enumerate() {
639            batch_sizes.insert(vk.id, batch_sizes_vec[i]);
640
641            if public_inputs_i.is_empty() {
642                bail!(SNARKError::EmptyBatch);
643            }
644
645            if public_inputs_i.len() != batch_sizes_vec[i] {
646                bail!(SNARKError::BatchSizeMismatch);
647            }
648        }
649
650        // collect values into structures for our calculations
651        let mut max_num_constraints = 0;
652        let mut max_num_variables = 0;
653        let mut max_non_zero_domain = None;
654        let mut public_inputs = BTreeMap::new();
655        let mut padded_public_vec = Vec::with_capacity(keys_to_inputs.len());
656        let mut inputs_and_batch_sizes = BTreeMap::new();
657        let mut input_domains = BTreeMap::new();
658        let mut circuit_infos = BTreeMap::new();
659        let mut circuit_ids = Vec::with_capacity(keys_to_inputs.len());
660        for (&vk, &public_inputs_i) in keys_to_inputs.iter() {
661            max_num_constraints = max_num_constraints.max(vk.circuit_info.num_constraints);
662            max_num_variables = max_num_variables.max(vk.circuit_info.num_public_and_private_variables);
663
664            let non_zero_domains = AHPForR1CS::<_, SM>::cmp_non_zero_domains(&vk.circuit_info, max_non_zero_domain)?;
665            max_non_zero_domain = non_zero_domains.max_non_zero_domain;
666
667            let input_domain = EvaluationDomain::<E::Fr>::new(vk.circuit_info.num_public_inputs)
668                .ok_or(anyhow!("Failed to create EvaluationDomain from num_public_inputs"))?;
669            input_domains.insert(vk.id, input_domain);
670
671            let input_fields = public_inputs_i
672                .iter()
673                .map(|input| {
674                    let input = input.borrow().to_field_elements()?;
675                    ensure!(input.len() > 0);
676                    ensure!(input[0] == E::Fr::one());
677                    if input.len() > input_domain.size() {
678                        bail!(SNARKError::PublicInputSizeMismatch);
679                    }
680                    Ok(input)
681                })
682                .collect::<Result<Vec<_>, _>>()?;
683
684            let (padded_public_inputs_i, parsed_public_inputs_i): (Vec<_>, Vec<_>) = {
685                input_fields
686                    .iter()
687                    .map(|input| {
688                        let input_len = input.len().max(input_domain.size());
689                        let mut new_input = Vec::with_capacity(input_len);
690                        new_input.extend_from_slice(input);
691                        new_input.resize(input_len, E::Fr::zero());
692                        if cfg!(debug_assertions) {
693                            println!("Number of padded public variables: {}", new_input.len());
694                        }
695                        let unformatted = prover::ConstraintSystem::unformat_public_input(&new_input);
696                        (new_input, unformatted)
697                    })
698                    .unzip()
699            };
700            let circuit_id = vk.id;
701            public_inputs.insert(circuit_id, parsed_public_inputs_i);
702            padded_public_vec.push(padded_public_inputs_i);
703            circuit_infos.insert(circuit_id, &vk.circuit_info);
704            circuit_ids.push(circuit_id);
705        }
706        for (i, (vk, &batch_size)) in keys_to_inputs.keys().zip(batch_sizes.values()).enumerate() {
707            inputs_and_batch_sizes.insert(vk.id, (batch_size, padded_public_vec[i].as_slice()));
708        }
709        let max_constraint_domain =
710            EvaluationDomain::<E::Fr>::new(max_num_constraints).ok_or(SynthesisError::PolyTooLarge)?;
711        let max_variable_domain =
712            EvaluationDomain::<E::Fr>::new(max_num_variables).ok_or(SynthesisError::PolyTooLarge)?;
713        let max_non_zero_domain = max_non_zero_domain.ok_or(SynthesisError::PolyTooLarge)?;
714
715        let comms = &proof.commitments;
716        let proof_has_correct_zk_mode = if SM::ZK {
717            proof.pc_proof.is_hiding() & comms.mask_poly.is_some()
718        } else {
719            !proof.pc_proof.is_hiding() & comms.mask_poly.is_none()
720        };
721        if !proof_has_correct_zk_mode {
722            eprintln!(
723                "Found `mask_poly` in the first round when not expected, or proof has incorrect hiding mode ({})",
724                proof.pc_proof.is_hiding()
725            );
726            return Ok(false);
727        }
728
729        let verifier_time = start_timer!(|| format!("Varuna::Verify with batch sizes: {:?}", batch_sizes));
730
731        let first_round_info = AHPForR1CS::<E::Fr, SM>::first_round_polynomial_info(batch_sizes.iter());
732
733        let mut first_comms_consumed = 0;
734        let mut first_commitments = batch_sizes
735            .iter()
736            .flat_map(|(circuit_id, &batch_size)| {
737                let first_comms = comms.witness_commitments[first_comms_consumed..][..batch_size]
738                    .iter()
739                    .enumerate()
740                    .map(|(j, w_comm)| {
741                        LabeledCommitment::new_with_info(
742                            &first_round_info[&witness_label(*circuit_id, "w", j)],
743                            w_comm.w,
744                        )
745                    });
746                first_comms_consumed += batch_size;
747                first_comms
748            })
749            .collect_vec();
750
751        if SM::ZK {
752            first_commitments.push(LabeledCommitment::new_with_info(
753                first_round_info.get("mask_poly").ok_or(anyhow!("Missing mask_poly"))?,
754                comms.mask_poly.ok_or(anyhow!("Missing mask_poly"))?,
755            ));
756        }
757
758        let second_round_info = AHPForR1CS::<E::Fr, SM>::second_round_polynomial_info();
759        let second_commitments = [LabeledCommitment::new_with_info(&second_round_info["h_0"], comms.h_0)];
760
761        let third_round_info = AHPForR1CS::<E::Fr, SM>::third_round_polynomial_info(max_variable_domain.size());
762        let third_commitments = [
763            LabeledCommitment::new_with_info(&third_round_info["g_1"], comms.g_1),
764            LabeledCommitment::new_with_info(&third_round_info["h_1"], comms.h_1),
765        ];
766
767        let fourth_round_info =
768            AHPForR1CS::<E::Fr, SM>::fourth_round_polynomial_info(circuit_infos.clone().into_iter());
769        let fourth_commitments = comms
770            .g_a_commitments
771            .iter()
772            .zip_eq(comms.g_b_commitments.iter())
773            .zip_eq(comms.g_c_commitments.iter())
774            .zip_eq(circuit_ids.iter())
775            .flat_map(|(((g_a, g_b), g_c), circuit_id)| {
776                [
777                    LabeledCommitment::new_with_info(&fourth_round_info[&witness_label(*circuit_id, "g_a", 0)], *g_a),
778                    LabeledCommitment::new_with_info(&fourth_round_info[&witness_label(*circuit_id, "g_b", 0)], *g_b),
779                    LabeledCommitment::new_with_info(&fourth_round_info[&witness_label(*circuit_id, "g_c", 0)], *g_c),
780                ]
781            })
782            .collect_vec();
783
784        let fifth_round_info = AHPForR1CS::<E::Fr, SM>::fifth_round_polynomial_info();
785        let fifth_commitments = [LabeledCommitment::new_with_info(&fifth_round_info["h_2"], comms.h_2)];
786
787        let circuit_commitments = keys_to_inputs.keys().map(|vk| vk.circuit_commitments.as_slice());
788        let mut sponge = Self::init_sponge(fs_parameters, &inputs_and_batch_sizes, circuit_commitments.clone());
789
790        // --------------------------------------------------------------------
791        // First round
792        let first_round_time = start_timer!(|| "First round");
793        Self::absorb_labeled(&first_commitments, &mut sponge);
794        let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_first_round(
795            &batch_sizes,
796            &circuit_infos,
797            max_constraint_domain,
798            max_variable_domain,
799            max_non_zero_domain,
800            &mut sponge,
801        )?;
802        end_timer!(first_round_time);
803        // --------------------------------------------------------------------
804
805        // --------------------------------------------------------------------
806        // Second round
807        let second_round_time = start_timer!(|| "Second round");
808        Self::absorb_labeled(&second_commitments, &mut sponge);
809        let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_second_round(verifier_state, &mut sponge)?;
810        end_timer!(second_round_time);
811        // --------------------------------------------------------------------
812
813        // --------------------------------------------------------------------
814        // Third round
815        let third_round_time = start_timer!(|| "Third round");
816        Self::absorb_labeled_with_sums(
817            &third_commitments,
818            &proof.third_msg.sums.clone().into_iter().flatten().collect_vec(),
819            &mut sponge,
820        );
821        let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_third_round(verifier_state, &mut sponge)?;
822        end_timer!(third_round_time);
823        // --------------------------------------------------------------------
824
825        // --------------------------------------------------------------------
826        // Fourth round
827        let fourth_round_time = start_timer!(|| "Fourth round");
828
829        Self::absorb_labeled_with_sums(&fourth_commitments, &proof.fourth_msg.sums, &mut sponge);
830        let (_, verifier_state) = AHPForR1CS::<_, SM>::verifier_fourth_round(verifier_state, &mut sponge)?;
831        end_timer!(fourth_round_time);
832        // --------------------------------------------------------------------
833
834        // --------------------------------------------------------------------
835        // Fifth round
836        let fifth_round_time = start_timer!(|| "Fifth round");
837
838        Self::absorb_labeled(&fifth_commitments, &mut sponge);
839        let verifier_state = AHPForR1CS::<_, SM>::verifier_fifth_round(verifier_state, &mut sponge)?;
840        end_timer!(fifth_round_time);
841        // --------------------------------------------------------------------
842
843        // Collect degree bounds for commitments. Indexed polynomials have *no*
844        // degree bounds because we know the committed index polynomial has the
845        // correct degree.
846
847        let commitments: Vec<_> = circuit_commitments
848            .into_iter()
849            .flatten()
850            .zip_eq(AHPForR1CS::<E::Fr, SM>::index_polynomial_info(circuit_ids.iter()).values())
851            .map(|(c, info)| LabeledCommitment::new_with_info(info, *c))
852            .chain(first_commitments)
853            .chain(second_commitments)
854            .chain(third_commitments)
855            .chain(fourth_commitments)
856            .chain(fifth_commitments)
857            .collect();
858
859        let query_set_time = start_timer!(|| "Constructing query set");
860        let (query_set, verifier_state) = AHPForR1CS::<_, SM>::verifier_query_set(verifier_state);
861        end_timer!(query_set_time);
862
863        sponge.absorb_nonnative_field_elements(proof.evaluations.to_field_elements());
864
865        let mut evaluations = Evaluations::new();
866
867        let mut current_circuit_id = "".to_string();
868        let mut circuit_index: i64 = -1;
869
870        for (label, (_point_name, q)) in query_set.to_set() {
871            if AHPForR1CS::<E::Fr, SM>::LC_WITH_ZERO_EVAL.contains(&label.as_ref()) {
872                evaluations.insert((label, q), E::Fr::zero());
873            } else {
874                if label != "g_1" {
875                    let circuit_id = CircuitId::from_witness_label(&label).to_string();
876                    if circuit_id != current_circuit_id {
877                        circuit_index += 1;
878                        current_circuit_id = circuit_id;
879                    }
880                }
881                let eval = proof
882                    .evaluations
883                    .get(circuit_index as usize, &label)
884                    .ok_or_else(|| AHPError::MissingEval(label.clone()))?;
885                evaluations.insert((label, q), eval);
886            }
887        }
888
889        let lc_time = start_timer!(|| "Constructing linear combinations");
890        let lc_s = AHPForR1CS::<_, SM>::construct_linear_combinations(
891            &public_inputs,
892            &evaluations,
893            &proof.third_msg,
894            &proof.fourth_msg,
895            &verifier_state,
896        )?;
897        end_timer!(lc_time);
898
899        let pc_time = start_timer!(|| "Checking linear combinations with PC");
900        let evaluations_are_correct = SonicKZG10::<E, FS>::check_combinations(
901            universal_verifier,
902            lc_s.values(),
903            &commitments,
904            &query_set.to_set(),
905            &evaluations,
906            &proof.pc_proof,
907            &mut sponge,
908        )?;
909        end_timer!(pc_time);
910
911        if !evaluations_are_correct {
912            #[cfg(debug_assertions)]
913            eprintln!("SonicKZG10::Check failed using final challenge: {:?}", verifier_state.gamma);
914        }
915
916        end_timer!(verifier_time, || format!(
917            " SonicKZG10::Check for AHP Verifier linear equations: {}",
918            evaluations_are_correct & proof_has_correct_zk_mode
919        ));
920        Ok(evaluations_are_correct & proof_has_correct_zk_mode)
921    }
922}