noah_bulletproofs/r1cs/
verifier.rs

1#![allow(non_snake_case)]
2
3use alloc::{boxed::Box, vec, vec::Vec};
4use core::borrow::BorrowMut;
5use core::mem;
6use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
7use curve25519_dalek::scalar::Scalar;
8use curve25519_dalek::traits::{Identity, MultiscalarMul, VartimeMultiscalarMul};
9use merlin::Transcript;
10use rand_core::{CryptoRng, RngCore};
11
12use super::{
13    ConstraintSystem, LinearCombination, R1CSProof, RandomizableConstraintSystem,
14    RandomizedConstraintSystem, Variable,
15};
16
17use crate::errors::R1CSError;
18use crate::generators::{BulletproofGens, PedersenGens};
19use crate::transcript::TranscriptProtocol;
20
21/// A [`ConstraintSystem`] implementation for use by the verifier.
22///
23/// The verifier adds high-level variable commitments to the transcript,
24/// allocates low-level variables and creates constraints in terms of these
25/// high-level variables and low-level variables.
26///
27/// When all constraints are added, the verifying code calls `verify`
28/// which consumes the `Verifier` instance, samples random challenges
29/// that instantiate the randomized constraints, and verifies the proof.
30pub struct Verifier<T: BorrowMut<Transcript>> {
31    transcript: T,
32    constraints: Vec<LinearCombination>,
33
34    /// Records the number of low-level variables allocated in the
35    /// constraint system.
36    ///
37    /// Because the `VerifierCS` only keeps the constraints
38    /// themselves, it doesn't record the assignments (they're all
39    /// `Missing`), so the `num_vars` isn't kept implicitly in the
40    /// variable assignments.
41    num_vars: usize,
42    V: Vec<CompressedRistretto>,
43
44    /// This list holds closures that will be called in the second phase of the protocol,
45    /// when non-randomized variables are committed.
46    /// After that, the option will flip to None and additional calls to `randomize_constraints`
47    /// will invoke closures immediately.
48    deferred_constraints: Vec<Box<dyn Fn(&mut RandomizingVerifier<T>) -> Result<(), R1CSError>>>,
49
50    /// Index of a pending multiplier that's not fully assigned yet.
51    pending_multiplier: Option<usize>,
52}
53
54/// Verifier in the randomizing phase.
55///
56/// Note: this type is exported because it is used to specify the associated type
57/// in the public impl of a trait `ConstraintSystem`, which boils down to allowing compiler to
58/// monomorphize the closures for the proving and verifying code.
59/// However, this type cannot be instantiated by the user and therefore can only be used within
60/// the callback provided to `specify_randomized_constraints`.
61pub struct RandomizingVerifier<T: BorrowMut<Transcript>> {
62    verifier: Verifier<T>,
63}
64
65impl<T: BorrowMut<Transcript>> ConstraintSystem for Verifier<T> {
66    fn transcript(&mut self) -> &mut Transcript {
67        self.transcript.borrow_mut()
68    }
69
70    fn multiply(
71        &mut self,
72        mut left: LinearCombination,
73        mut right: LinearCombination,
74    ) -> (Variable, Variable, Variable) {
75        let var = self.num_vars;
76        self.num_vars += 1;
77
78        // Create variables for l,r,o
79        let l_var = Variable::MultiplierLeft(var);
80        let r_var = Variable::MultiplierRight(var);
81        let o_var = Variable::MultiplierOutput(var);
82
83        // Constrain l,r,o:
84        left.terms.push((l_var, -Scalar::one()));
85        right.terms.push((r_var, -Scalar::one()));
86        self.constrain(left);
87        self.constrain(right);
88
89        (l_var, r_var, o_var)
90    }
91
92    fn allocate(&mut self, _: Option<Scalar>) -> Result<Variable, R1CSError> {
93        match self.pending_multiplier {
94            None => {
95                let i = self.num_vars;
96                self.num_vars += 1;
97                self.pending_multiplier = Some(i);
98                Ok(Variable::MultiplierLeft(i))
99            }
100            Some(i) => {
101                self.pending_multiplier = None;
102                Ok(Variable::MultiplierRight(i))
103            }
104        }
105    }
106
107    fn allocate_multiplier(
108        &mut self,
109        _: Option<(Scalar, Scalar)>,
110    ) -> Result<(Variable, Variable, Variable), R1CSError> {
111        let var = self.num_vars;
112        self.num_vars += 1;
113
114        // Create variables for l,r,o
115        let l_var = Variable::MultiplierLeft(var);
116        let r_var = Variable::MultiplierRight(var);
117        let o_var = Variable::MultiplierOutput(var);
118
119        Ok((l_var, r_var, o_var))
120    }
121
122    fn multipliers_len(&self) -> usize {
123        self.num_vars
124    }
125
126    fn constrain(&mut self, lc: LinearCombination) {
127        // TODO: check that the linear combinations are valid
128        // (e.g. that variables are valid, that the linear combination
129        // evals to 0 for prover, etc).
130        self.constraints.push(lc);
131    }
132}
133
134impl<T: BorrowMut<Transcript>> RandomizableConstraintSystem for Verifier<T> {
135    type RandomizedCS = RandomizingVerifier<T>;
136
137    fn specify_randomized_constraints<F>(&mut self, callback: F) -> Result<(), R1CSError>
138    where
139        F: 'static + Fn(&mut Self::RandomizedCS) -> Result<(), R1CSError>,
140    {
141        self.deferred_constraints.push(Box::new(callback));
142        Ok(())
143    }
144}
145
146impl<T: BorrowMut<Transcript>> ConstraintSystem for RandomizingVerifier<T> {
147    fn transcript(&mut self) -> &mut Transcript {
148        self.verifier.transcript.borrow_mut()
149    }
150
151    fn multiply(
152        &mut self,
153        left: LinearCombination,
154        right: LinearCombination,
155    ) -> (Variable, Variable, Variable) {
156        self.verifier.multiply(left, right)
157    }
158
159    fn allocate(&mut self, assignment: Option<Scalar>) -> Result<Variable, R1CSError> {
160        self.verifier.allocate(assignment)
161    }
162
163    fn allocate_multiplier(
164        &mut self,
165        input_assignments: Option<(Scalar, Scalar)>,
166    ) -> Result<(Variable, Variable, Variable), R1CSError> {
167        self.verifier.allocate_multiplier(input_assignments)
168    }
169
170    fn multipliers_len(&self) -> usize {
171        self.verifier.multipliers_len()
172    }
173
174    fn constrain(&mut self, lc: LinearCombination) {
175        self.verifier.constrain(lc)
176    }
177}
178
179impl<T: BorrowMut<Transcript>> RandomizedConstraintSystem for RandomizingVerifier<T> {
180    fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar {
181        self.verifier
182            .transcript
183            .borrow_mut()
184            .challenge_scalar(label)
185    }
186}
187
188impl<T: BorrowMut<Transcript>> Verifier<T> {
189    /// Construct an empty constraint system with specified external
190    /// input variables.
191    ///
192    /// # Inputs
193    ///
194    /// The `transcript` parameter is a Merlin proof transcript.  The
195    /// `VerifierCS` holds onto the `&mut Transcript` until it consumes
196    /// itself during [`VerifierCS::verify`], releasing its borrow of the
197    /// transcript.  This ensures that the transcript cannot be
198    /// altered except by the `VerifierCS` before proving is complete.
199    ///
200    /// The `commitments` parameter is a list of Pedersen commitments
201    /// to the external variables for the constraint system.  All
202    /// external variables must be passed up-front, so that challenges
203    /// produced by [`ConstraintSystem::challenge_scalar`] are bound
204    /// to the external variables.
205    ///
206    /// # Returns
207    ///
208    /// Returns a tuple `(cs, vars)`.
209    ///
210    /// The first element is the newly constructed constraint system.
211    ///
212    /// The second element is a list of [`Variable`]s corresponding to
213    /// the external inputs, which can be used to form constraints.
214    pub fn new(mut transcript: T) -> Self {
215        transcript.borrow_mut().r1cs_domain_sep();
216
217        Verifier {
218            transcript,
219            num_vars: 0,
220            V: Vec::new(),
221            constraints: Vec::new(),
222            deferred_constraints: Vec::new(),
223            pending_multiplier: None,
224        }
225    }
226
227    /// Creates commitment to a high-level variable and adds it to the transcript.
228    ///
229    /// # Inputs
230    ///
231    /// The `commitment` parameter is a Pedersen commitment
232    /// to the external variable for the constraint system.  All
233    /// external variables must be passed up-front, so that challenges
234    /// produced by [`ConstraintSystem::challenge_scalar`] are bound
235    /// to the external variables.
236    ///
237    /// # Returns
238    ///
239    /// Returns a pair of a Pedersen commitment (as a compressed Ristretto point),
240    /// and a [`Variable`] corresponding to it, which can be used to form constraints.
241    pub fn commit(&mut self, commitment: CompressedRistretto) -> Variable {
242        let i = self.V.len();
243        self.V.push(commitment);
244
245        // Add the commitment to the transcript.
246        self.transcript.borrow_mut().append_point(b"V", &commitment);
247
248        Variable::Committed(i)
249    }
250
251    /// Use a challenge, `z`, to flatten the constraints in the
252    /// constraint system into vectors used for proving and
253    /// verification.
254    ///
255    /// # Output
256    ///
257    /// Returns a tuple of
258    /// ```text
259    /// (wL, wR, wO, wV, wc)
260    /// ```
261    /// where `w{L,R,O}` is \\( z \cdot z^Q \cdot W_{L,R,O} \\).
262    ///
263    /// This has the same logic as `ProverCS::flattened_constraints()`
264    /// but also computes the constant terms (which the prover skips
265    /// because they're not needed to construct the proof).
266    fn flattened_constraints(
267        &mut self,
268        z: &Scalar,
269    ) -> (Vec<Scalar>, Vec<Scalar>, Vec<Scalar>, Vec<Scalar>, Scalar) {
270        let n = self.num_vars;
271        let m = self.V.len();
272
273        let mut wL = vec![Scalar::zero(); n];
274        let mut wR = vec![Scalar::zero(); n];
275        let mut wO = vec![Scalar::zero(); n];
276        let mut wV = vec![Scalar::zero(); m];
277        let mut wc = Scalar::zero();
278
279        let mut exp_z = *z;
280        for lc in self.constraints.iter() {
281            for (var, coeff) in &lc.terms {
282                match var {
283                    Variable::MultiplierLeft(i) => {
284                        wL[*i] += exp_z * coeff;
285                    }
286                    Variable::MultiplierRight(i) => {
287                        wR[*i] += exp_z * coeff;
288                    }
289                    Variable::MultiplierOutput(i) => {
290                        wO[*i] += exp_z * coeff;
291                    }
292                    Variable::Committed(i) => {
293                        wV[*i] -= exp_z * coeff;
294                    }
295                    Variable::One() => {
296                        wc -= exp_z * coeff;
297                    }
298                }
299            }
300            exp_z *= z;
301        }
302
303        (wL, wR, wO, wV, wc)
304    }
305
306    /// Calls all remembered callbacks with an API that
307    /// allows generating challenge scalars.
308    fn create_randomized_constraints(mut self) -> Result<Self, R1CSError> {
309        // Clear the pending multiplier (if any) because it was committed into A_L/A_R/S.
310        self.pending_multiplier = None;
311
312        if self.deferred_constraints.len() == 0 {
313            self.transcript.borrow_mut().r1cs_1phase_domain_sep();
314            Ok(self)
315        } else {
316            self.transcript.borrow_mut().r1cs_2phase_domain_sep();
317            // Note: the wrapper could've used &mut instead of ownership,
318            // but specifying lifetimes for boxed closures is not going to be nice,
319            // so we move the self into wrapper and then move it back out afterwards.
320            let mut callbacks = mem::replace(&mut self.deferred_constraints, Vec::new());
321            let mut wrapped_self = RandomizingVerifier { verifier: self };
322            for callback in callbacks.drain(..) {
323                callback(&mut wrapped_self)?;
324            }
325            Ok(wrapped_self.verifier)
326        }
327    }
328
329    // Get scalars for single multiexponentiation verification
330    // Order is
331    // pc_gens.B
332    // pc_gens.B_blinding
333    // gens.G_vec
334    // gens.H_vec
335    // proof.A_I1
336    // proof.A_O1
337    // proof.S1
338    // proof.A_I2
339    // proof.A_O2
340    // proof.S2
341    // self.V
342    // T_1, T3, T4, T5, T6
343    // proof.ipp_proof.L_vec.iter().map(|L_i| L_i.decompress()))
344    // proof.ipp_proof.R_vec
345    pub(super) fn verification_scalars<R: RngCore + CryptoRng>(
346        mut self,
347        rng: &mut R,
348        proof: &R1CSProof,
349        bp_gens: &BulletproofGens,
350    ) -> Result<(Self, Vec<Scalar>), R1CSError> {
351        // Commit a length _suffix_ for the number of high-level variables.
352        // We cannot do this in advance because user can commit variables one-by-one,
353        // but this suffix provides safe disambiguation because each variable
354        // is prefixed with a separate label.
355        let transcript = self.transcript.borrow_mut();
356        transcript.append_u64(b"m", self.V.len() as u64);
357
358        let n1 = self.num_vars;
359        transcript.validate_and_append_point(b"A_I1", &proof.A_I1)?;
360        transcript.validate_and_append_point(b"A_O1", &proof.A_O1)?;
361        transcript.validate_and_append_point(b"S1", &proof.S1)?;
362
363        // Process the remaining constraints.
364        self = self.create_randomized_constraints()?;
365
366        let transcript = self.transcript.borrow_mut();
367
368        // If the number of multiplications is not 0 or a power of 2, then pad the circuit.
369        let n = self.num_vars;
370        let n2 = n - n1;
371        let padded_n = self.num_vars.next_power_of_two();
372        let pad = padded_n - n;
373
374        use crate::inner_product_proof::inner_product;
375        use crate::util;
376        use core::iter;
377
378        if bp_gens.gens_capacity < padded_n {
379            return Err(R1CSError::InvalidGeneratorsLength);
380        }
381
382        // These points are the identity in the 1-phase unrandomized case.
383        transcript.append_point(b"A_I2", &proof.A_I2);
384        transcript.append_point(b"A_O2", &proof.A_O2);
385        transcript.append_point(b"S2", &proof.S2);
386
387        let y = transcript.challenge_scalar(b"y");
388        let z = transcript.challenge_scalar(b"z");
389
390        transcript.validate_and_append_point(b"T_1", &proof.T_1)?;
391        transcript.validate_and_append_point(b"T_3", &proof.T_3)?;
392        transcript.validate_and_append_point(b"T_4", &proof.T_4)?;
393        transcript.validate_and_append_point(b"T_5", &proof.T_5)?;
394        transcript.validate_and_append_point(b"T_6", &proof.T_6)?;
395
396        let u = transcript.challenge_scalar(b"u");
397        let x = transcript.challenge_scalar(b"x");
398
399        transcript.append_scalar(b"t_x", &proof.t_x);
400        transcript.append_scalar(b"t_x_blinding", &proof.t_x_blinding);
401        transcript.append_scalar(b"e_blinding", &proof.e_blinding);
402
403        let w = transcript.challenge_scalar(b"w");
404
405        let (wL, wR, wO, wV, wc) = self.flattened_constraints(&z);
406
407        // Get IPP variables
408        let (u_sq, u_inv_sq, s) = proof
409            .ipp_proof
410            .verification_scalars(padded_n, self.transcript.borrow_mut())
411            .map_err(|_| R1CSError::VerificationError)?;
412
413        let a = proof.ipp_proof.a;
414        let b = proof.ipp_proof.b;
415
416        let y_inv = y.invert();
417        let y_inv_vec = util::exp_iter(y_inv)
418            .take(padded_n)
419            .collect::<Vec<Scalar>>();
420        let yneg_wR = wR
421            .into_iter()
422            .zip(y_inv_vec.iter())
423            .map(|(wRi, exp_y_inv)| wRi * exp_y_inv)
424            .chain(iter::repeat(Scalar::zero()).take(pad))
425            .collect::<Vec<Scalar>>();
426
427        let delta = inner_product(&yneg_wR[0..n], &wL);
428
429        let u_for_g = iter::repeat(Scalar::one())
430            .take(n1)
431            .chain(iter::repeat(u).take(n2 + pad));
432        let u_for_h = u_for_g.clone();
433
434        // define parameters for P check
435        let g_scalars: Vec<_> = yneg_wR
436            .iter()
437            .zip(u_for_g)
438            .zip(s.iter().take(padded_n))
439            .map(|((yneg_wRi, u_or_1), s_i)| u_or_1 * (x * yneg_wRi - a * s_i))
440            .collect();
441
442        let h_scalars: Vec<_> = y_inv_vec
443            .iter()
444            .zip(u_for_h)
445            .zip(s.iter().rev().take(padded_n))
446            .zip(wL.into_iter().chain(iter::repeat(Scalar::zero()).take(pad)))
447            .zip(wO.into_iter().chain(iter::repeat(Scalar::zero()).take(pad)))
448            .map(|((((y_inv_i, u_or_1), s_i_inv), wLi), wOi)| {
449                u_or_1 * (y_inv_i * (x * wLi + wOi - b * s_i_inv) - Scalar::one())
450            })
451            .collect();
452
453        // Create a `TranscriptRng` from the transcript. The verifier
454        // has no witness data to commit, so this just mixes external
455        // randomness into the existing transcript.
456        let mut rng = self.transcript.borrow_mut().build_rng().finalize(rng);
457        let r = Scalar::random(&mut rng);
458
459        let xx = x * x;
460        let rxx = r * xx;
461        let xxx = x * xx;
462
463        // group the T_scalars and T_points together
464        let T_scalars = [r * x, rxx * x, rxx * xx, rxx * xxx, rxx * xx * xx];
465
466        let mut scalars: Vec<Scalar> = vec![];
467        scalars.push(w * (proof.t_x - a * b) + r * (xx * (wc + delta) - proof.t_x));
468        scalars.push(-proof.e_blinding - r * proof.t_x_blinding);
469        scalars.extend_from_slice(&g_scalars);
470        scalars.extend_from_slice(&h_scalars);
471        scalars.extend_from_slice(&[x, xx, xxx, u * x, u * xx, u * xxx]);
472        for wVi in wV.iter() {
473            scalars.push(wVi * rxx);
474        }
475        scalars.extend_from_slice(&T_scalars);
476        scalars.extend_from_slice(&u_sq);
477        scalars.extend_from_slice(&u_inv_sq);
478        Ok((self, scalars))
479    }
480
481    /// Consume this `VerifierCS` and attempt to verify the supplied `proof`.
482    /// The `pc_gens` and `bp_gens` are generators for Pedersen commitments and
483    /// Bulletproofs vector commitments, respectively.  The
484    /// [`BulletproofGens`] should have `gens_capacity` greater than
485    /// the number of multiplication constraints that will eventually
486    /// be added into the constraint system.
487    pub fn verify<R: RngCore + CryptoRng>(
488        self,
489        rng: &mut R,
490        proof: &R1CSProof,
491        pc_gens: &PedersenGens,
492        bp_gens: &BulletproofGens,
493    ) -> Result<(), R1CSError> {
494        self.verify_and_return_transcript(rng, proof, pc_gens, bp_gens)
495            .map(|_| ())
496    }
497    /// Same as `verify`, but also returns the transcript back to the user.
498    pub fn verify_and_return_transcript<R: RngCore + CryptoRng>(
499        mut self,
500        rng: &mut R,
501        proof: &R1CSProof,
502        pc_gens: &PedersenGens,
503        bp_gens: &BulletproofGens,
504    ) -> Result<T, R1CSError> {
505        let (verifier, scalars) = self.verification_scalars(rng, proof, bp_gens)?;
506        self = verifier;
507        let T_points = [proof.T_1, proof.T_3, proof.T_4, proof.T_5, proof.T_6];
508
509        // We are performing a single-party circuit proof, so party index is 0.
510        let gens = bp_gens.share(0);
511
512        let padded_n = self.num_vars.next_power_of_two();
513
514        use core::iter;
515        let mega_check = RistrettoPoint::optional_multiscalar_mul(
516            scalars,
517            iter::once(Some(pc_gens.B))
518                .chain(iter::once(Some(pc_gens.B_blinding)))
519                .chain(gens.G(padded_n).map(|&G_i| Some(G_i)))
520                .chain(gens.H(padded_n).map(|&H_i| Some(H_i)))
521                .chain(iter::once(proof.A_I1.decompress()))
522                .chain(iter::once(proof.A_O1.decompress()))
523                .chain(iter::once(proof.S1.decompress()))
524                .chain(iter::once(proof.A_I2.decompress()))
525                .chain(iter::once(proof.A_O2.decompress()))
526                .chain(iter::once(proof.S2.decompress()))
527                .chain(self.V.iter().map(|V_i| V_i.decompress()))
528                .chain(T_points.iter().map(|T_i| T_i.decompress()))
529                .chain(proof.ipp_proof.L_vec.iter().map(|L_i| L_i.decompress()))
530                .chain(proof.ipp_proof.R_vec.iter().map(|R_i| R_i.decompress())),
531        )
532        .ok_or_else(|| R1CSError::VerificationError)?;
533
534        use curve25519_dalek::traits::IsIdentity;
535
536        if !mega_check.is_identity() {
537            return Err(R1CSError::VerificationError);
538        }
539
540        Ok(self.transcript)
541    }
542}
543
544/// Batch verification of R1CS proofs
545pub fn batch_verify<'a, I, R: CryptoRng + RngCore>(
546    prng: &mut R,
547    instances: I,
548    pc_gens: &PedersenGens,
549    bp_gens: &BulletproofGens,
550) -> Result<(), R1CSError>
551where
552    I: IntoIterator<Item = (Verifier<&'a mut Transcript>, &'a R1CSProof)>,
553{
554    let mut max_n_padded = 0;
555    let mut verifiers: Vec<Verifier<_>> = vec![];
556    let mut proofs: Vec<&R1CSProof> = vec![];
557    let mut verification_scalars = vec![];
558    for (verifier, proof) in instances.into_iter() {
559        // verification_scalars method is mutable, need to run before obtaining verifier.num_vars
560        let (verifier, scalars) = verifier.verification_scalars(prng, proof, bp_gens)?;
561        let n = verifier.num_vars.next_power_of_two();
562        if n > max_n_padded {
563            max_n_padded = n;
564        }
565        verification_scalars.push(scalars);
566        verifiers.push(verifier);
567        proofs.push(proof);
568    }
569    let mut all_scalars = vec![];
570    let mut all_elems = vec![];
571
572    for _ in 0..(2 * max_n_padded + 2) {
573        all_scalars.push(Scalar::zero());
574    }
575    all_elems.push(pc_gens.B);
576    all_elems.push(pc_gens.B_blinding);
577    let gens = bp_gens.share(0);
578    for G in gens.G(max_n_padded) {
579        all_elems.push(*G);
580    }
581    for H in gens.H(max_n_padded) {
582        all_elems.push(*H);
583    }
584
585    for ((verifier, proof), scalars) in verifiers
586        .into_iter()
587        .zip(proofs.iter())
588        .zip(verification_scalars.iter())
589    {
590        let alpha = Scalar::random(prng);
591        let scaled_scalars: Vec<Scalar> = scalars.into_iter().map(|s| alpha * s).collect();
592        let padded_n = verifier.num_vars.next_power_of_two();
593        all_scalars[0] += scaled_scalars[0]; // B
594        all_scalars[1] += scaled_scalars[1]; // B_blinding
595                                             // g values
596        for (i, s) in (&scaled_scalars[2..2 + padded_n]).iter().enumerate() {
597            all_scalars[i + 2] += *s;
598        }
599        // h values
600        for (i, s) in (&scaled_scalars[2 + padded_n..2 + 2 * padded_n])
601            .iter()
602            .enumerate()
603        {
604            all_scalars[2 + max_n_padded + i] += *s;
605        }
606
607        for s in (&scaled_scalars[2 + 2 * padded_n..]).iter() {
608            all_scalars.push(*s);
609        }
610        all_elems.push(proof.A_I1.decompress().unwrap());
611        all_elems.push(proof.A_O1.decompress().unwrap());
612        all_elems.push(proof.S1.decompress().unwrap());
613        all_elems.push(proof.A_I2.decompress().unwrap());
614        all_elems.push(proof.A_O2.decompress().unwrap());
615        all_elems.push(proof.S2.decompress().unwrap());
616        let V: Vec<_> = verifier
617            .V
618            .iter()
619            .map(|Vi| Vi.decompress().unwrap())
620            .collect();
621        all_elems.extend_from_slice(V.as_slice());
622        all_elems.push(proof.T_1.decompress().unwrap());
623        all_elems.push(proof.T_3.decompress().unwrap());
624        all_elems.push(proof.T_4.decompress().unwrap());
625        all_elems.push(proof.T_5.decompress().unwrap());
626        all_elems.push(proof.T_6.decompress().unwrap());
627        let L_vec: Vec<_> = proof
628            .ipp_proof
629            .L_vec
630            .iter()
631            .map(|L| L.decompress().unwrap())
632            .collect();
633        let R_vec: Vec<_> = proof
634            .ipp_proof
635            .R_vec
636            .iter()
637            .map(|R| R.decompress().unwrap())
638            .collect();
639        all_elems.extend_from_slice(&L_vec);
640        all_elems.extend_from_slice(&R_vec);
641    }
642
643    let multi_exp = RistrettoPoint::multiscalar_mul(all_scalars, all_elems);
644    if multi_exp != RistrettoPoint::identity() {
645        Err(R1CSError::VerificationError)
646    } else {
647        Ok(())
648    }
649}