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