snarkvm_algorithms/snark/varuna/
varuna.rs

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