Skip to main content

zip_plus/pcs/
phase_verify.rs

1use crate::{
2    ZipError,
3    code::LinearCode,
4    pcs::{
5        structs::{ZipPlus, ZipPlusCommitment, ZipPlusParams, ZipTypes},
6        utils::{point_to_tensor, validate_input},
7    },
8    pcs_transcript::PcsVerifierTranscript,
9};
10use crypto_primitives::{FromPrimitiveWithConfig, FromWithConfig};
11use itertools::Itertools;
12use num_traits::{ConstOne, ConstZero, Zero};
13#[cfg(feature = "parallel")]
14use rayon::prelude::*;
15use zinc_poly::Polynomial;
16use zinc_transcript::{
17    Blake3Transcript,
18    traits::{Transcribable, Transcript},
19};
20use zinc_utils::{
21    UNCHECKED, add, cfg_into_iter,
22    from_ref::FromRef,
23    inner_product::{InnerProduct, MBSInnerProduct},
24    mul_by_scalar::MulByScalar,
25};
26
27impl<Zt: ZipTypes, Lc: LinearCode<Zt>> ZipPlus<Zt, Lc> {
28    /// Verifies an opening proof for one or more committed multilinear
29    /// polynomials at an evaluation point, using the Zip+ protocol.
30    ///
31    /// This replaces the old two-phase (verify_testing + verify_evaluation)
32    /// approach. The old protocol performed two proximity checks (one in CombR,
33    /// one in F via a separate `projecting_element` γ) and one eval consistency
34    /// check. The merged protocol eliminates the F-domain proximity check
35    /// entirely, replacing it with a coherence check between `b` and `w`
36    /// that ties the single CombR proximity check to the evaluation claim.
37    ///
38    /// # Verification checks (4 total)
39    ///
40    /// 1. **Eval consistency**: `<q_0, b> == eval_f`. Ensures the claimed
41    ///    evaluation matches the `b` vector written by the prover, where `b_j =
42    ///    sum_i(<w'_ij, q_1>)` and `w'_ij` is the j-th decoded row of poly i
43    ///    after taking the random linear combination `<entry, alphas_i>` of
44    ///    every entry.
45    ///
46    /// 2. **Coherence** (b-w): `<w, q_1> == <s, b>`. Ensures `b` and `w` are
47    ///    derived from the same underlying rows `w'_j`, tying the
48    ///    proximity-tested `w` to the eval-tested `b`.
49    ///
50    /// 3. **Proximity** (per opened column, batched across polys): `Enc(w)[col]
51    ///    == sum_i(sum_j(s_j * <v_ij[col], alphas_i>))`. For each poly i and
52    ///    row j, takes the random linear combination `<v_ij[col], alphas_i>` of
53    ///    the Cw column entry to get a CombR value, combines rows with
54    ///    coefficients `s`, sums across polys. Compares against the encoded
55    ///    combined row.
56    ///
57    /// 4. **Merkle proof** (per opened column): verifies the column values
58    ///    against `comm.root`, ensuring that the data matches what was
59    ///    committed.
60    ///
61    /// Chain of trust: Merkle (check 4) → column data authentic → proximity
62    /// (check 3) → `w` is a valid codeword consistent with columns →
63    /// coherence (check 2) → `b` is consistent with `w` →
64    /// eval consistency (check 1) → `eval_f` is correct.
65    ///
66    /// # Algorithm
67    /// 1. Computes `(q_0, q_1) = point_to_tensor(point_f)`.
68    /// 2. Per polynomial, re-derives `alphas` from the transcript.
69    /// 3. Reads `b` (length `num_rows`) from the transcript.
70    /// 4. **Check 1**: asserts `<q_0, b> == eval_f`.
71    /// 5. Re-derives combination coefficients `s` (or `[1]` when `num_rows ==
72    ///    1`).
73    /// 6. Reads combined row `w` (CombR, length `row_len`) and encodes it.
74    /// 7. **Check 2**: asserts `<w, q_1> == <s, b>`.
75    /// 8. For each of `NUM_COLUMN_OPENINGS`: a. Squeezes column index, reads
76    ///    per-poly column values + Merkle proof. b. **Check 3**:
77    ///    `verify_column_testing_batched`. c. **Check 4**:
78    ///    `proof.verify(comm.root, column_values, col)`.
79    ///
80    /// # Parameters
81    /// - `vp`: Public parameters (same as prover's `pp`).
82    /// - `comm`: The `ZipPlusCommitment` (Merkle root + batch size) from the
83    ///   commit phase.
84    /// - `point_f`: The evaluation point in field `F` (length `num_vars`).
85    /// - `eval_f`: The claimed combined evaluation `<q_0, b>`.
86    /// - `proof`: The `ZipPlusProof` produced by `prove`.
87    ///
88    /// # Returns
89    /// `Ok(())` if all four checks pass.
90    ///
91    /// # Errors
92    /// - `ZipError::InvalidPcsParam` if inputs are malformed.
93    /// - `ZipError::InvalidPcsOpen("Evaluation consistency failure")` if check
94    ///   1 fails.
95    /// - `ZipError::InvalidPcsOpen("Coherence failure")` if check 2 fails.
96    /// - `ZipError::InvalidPcsOpen("Proximity failure")` if check 3 fails.
97    /// - `ZipError::InvalidPcsOpen("Column opening verification failed: ...")`
98    ///   if check 4 (Merkle) fails.
99    #[allow(clippy::arithmetic_side_effects, clippy::type_complexity)]
100    pub fn verify<F, const CHECK_FOR_OVERFLOW: bool>(
101        transcript: &mut PcsVerifierTranscript,
102        vp: &ZipPlusParams<Zt, Lc>,
103        comm: &ZipPlusCommitment,
104        field_cfg: &F::Config,
105        point_f: &[F],
106        eval_f: &F,
107    ) -> Result<(), ZipError>
108    where
109        F: FromPrimitiveWithConfig
110            + FromRef<F>
111            + for<'a> FromWithConfig<&'a Zt::CombR>
112            + for<'a> FromWithConfig<&'a Zt::Chal>
113            + for<'a> MulByScalar<&'a F>,
114        F::Inner: Transcribable,
115        F::Modulus: FromRef<Zt::Fmod> + Transcribable,
116    {
117        let per_poly_alphas = Self::sample_alphas(&mut transcript.fs_transcript, comm.batch_size);
118        Self::verify_with_alphas::<F, CHECK_FOR_OVERFLOW>(
119            transcript,
120            vp,
121            comm,
122            field_cfg,
123            point_f,
124            eval_f,
125            &per_poly_alphas,
126        )
127    }
128
129    /// Like [`Self::verify`], but accepts pre-sampled alpha challenges.
130    ///
131    /// The caller is responsible for sampling `per_poly_alphas` from the
132    /// transcript before calling this (typically via [`Self::sample_alphas`]).
133    /// This allows computing `eval_f` externally from the alphas (e.g. via
134    /// alpha-projection of polynomial MLE evaluations in the lift-and-project
135    /// flow).
136    #[allow(clippy::arithmetic_side_effects, clippy::type_complexity)]
137    pub fn verify_with_alphas<F, const CHECK_FOR_OVERFLOW: bool>(
138        transcript: &mut PcsVerifierTranscript,
139        vp: &ZipPlusParams<Zt, Lc>,
140        comm: &ZipPlusCommitment,
141        field_cfg: &F::Config,
142        point_f: &[F],
143        eval_f: &F,
144        per_poly_alphas: &[Vec<Zt::Chal>],
145    ) -> Result<(), ZipError>
146    where
147        F: FromPrimitiveWithConfig
148            + FromRef<F>
149            + for<'a> FromWithConfig<&'a Zt::CombR>
150            + for<'a> FromWithConfig<&'a Zt::Chal>
151            + for<'a> MulByScalar<&'a F>,
152        F::Inner: Transcribable,
153        F::Modulus: FromRef<Zt::Fmod> + Transcribable,
154    {
155        let batch_size = comm.batch_size;
156        validate_input::<Zt, Lc, _>(
157            "verify",
158            vp.num_vars,
159            vp.linear_code.row_len(),
160            batch_size,
161            &[],
162            &[point_f],
163        )?;
164
165        let num_rows = vp.num_rows;
166        let row_len = vp.linear_code.row_len();
167
168        // TODO Lift q0, q1 back to int and take following dot products on ints instead
169        // of MBSInnerProduct in field (see combined_row)
170        let (q_0, q_1) = point_to_tensor(vp.num_rows, point_f, field_cfg)?;
171        let zero_f = F::zero_with_cfg(field_cfg);
172
173        let b: Vec<F> = transcript.read_field_elements(num_rows)?;
174
175        // Check 1: <q_0, b> == eval_f
176        if MBSInnerProduct::inner_product::<UNCHECKED>(&q_0, &b, zero_f.clone())? != *eval_f {
177            return Err(ZipError::InvalidPcsOpen(
178                "Evaluation consistency failure".into(),
179            ));
180        }
181
182        let coeffs: Vec<Zt::Chal> = if num_rows == 1 {
183            vec![Zt::Chal::ONE]
184        } else {
185            transcript.fs_transcript.get_challenges(num_rows)
186        };
187
188        let combined_row: Vec<Zt::CombR> = transcript.read_const_many(row_len)?;
189        let encoded_combined_row: Vec<Zt::CombR> = vp.linear_code.encode_wide(&combined_row);
190
191        // Check 2: <w, q_1> == <s, b>
192        // Ensures b and w are derived from the same underlying rows w'_j.
193        // NOTE: CombR entries (Int<M>) can exceed the field's bit-width, so the
194        // CombR→F lift must reduce mod p before truncating limbs.
195        // MontyField's FromWithConfig does this; BoxedMontyField's does not and will
196        // panic.
197        let lhs = MBSInnerProduct::inner_product_field(&combined_row, &q_1, zero_f.clone())?;
198        let rhs = MBSInnerProduct::inner_product_field(&coeffs, &b, zero_f.clone())?;
199
200        if lhs != rhs {
201            return Err(ZipError::InvalidPcsOpen("Coherence failure".into()));
202        }
203
204        let columns_and_proofs: Vec<_> = (0..Zt::NUM_COLUMN_OPENINGS)
205            .map(|_| -> Result<_, ZipError> {
206                let column_idx = transcript.squeeze_challenge_idx(vp.linear_code.codeword_len());
207                let column_values = transcript.read_const_many(batch_size * vp.num_rows)?;
208                let proof = transcript.read_merkle_proof().map_err(|e| {
209                    ZipError::InvalidPcsOpen(format!("Failed to read Merkle a proof: {e}"))
210                })?;
211
212                Ok((column_idx, column_values, proof))
213            })
214            .try_collect()?;
215
216        cfg_into_iter!(columns_and_proofs).try_for_each(
217            |(column_idx, column_values, proof)| -> Result<(), ZipError> {
218                Self::verify_column_testing_batched::<CHECK_FOR_OVERFLOW>(
219                    per_poly_alphas,
220                    &coeffs,
221                    &encoded_combined_row,
222                    &column_values,
223                    column_idx,
224                    vp.num_rows,
225                    batch_size,
226                )?;
227
228                proof
229                    .verify(&comm.root, &column_values, column_idx)
230                    .map_err(|e| {
231                        ZipError::InvalidPcsOpen(format!("Column opening verification failed: {e}"))
232                    })?;
233
234                Ok(())
235            },
236        )?;
237
238        Ok(())
239    }
240
241    /// Samples per-polynomial alpha challenges from the transcript.
242    ///
243    /// This is the same sampling logic used internally by [`Self::verify`].
244    /// Exposed so that callers (e.g. the Zinc+ PIOP verifier) can sample
245    /// alphas, perform alpha-projection externally, and then call
246    /// [`Self::verify_with_alphas`].
247    pub fn sample_alphas(
248        transcript: &mut Blake3Transcript,
249        batch_size: usize,
250    ) -> Vec<Vec<Zt::Chal>> {
251        let degree_bound = Zt::Comb::DEGREE_BOUND;
252        (0..batch_size)
253            .map(|_| {
254                if degree_bound.is_zero() {
255                    vec![Zt::Chal::ONE]
256                } else {
257                    transcript.get_challenges(add!(degree_bound, 1))
258                }
259            })
260            .collect()
261    }
262
263    // Check 3: Enc(w)[col] == sum_i( sum_j( s_j * <v_ij[col], alphas_i> ) )
264    // For each poly i and row j, takes the random linear combination
265    // <v_ij[col], alphas_i> of the Cw column entry to CombR,
266    // combines rows with coefficients s, sums across polys.
267    pub(super) fn verify_column_testing_batched<const CHECK_FOR_OVERFLOW: bool>(
268        per_poly_alphas: &[Vec<Zt::Chal>],
269        coeffs: &[Zt::Chal],
270        encoded_combined_row: &[Zt::CombR],
271        all_column_entries: &[Zt::Cw],
272        column: usize,
273        num_rows: usize,
274        batch_size: usize,
275    ) -> Result<(), ZipError> {
276        #[allow(clippy::arithmetic_side_effects)]
277        let all_column_entries_comb =
278            (0..batch_size).try_fold(Zt::CombR::ZERO, |acc, i| -> Result<_, ZipError> {
279                let column_entries: Vec<_> = all_column_entries[i * num_rows..(i + 1) * num_rows]
280                    .iter()
281                    .map(Zt::Comb::from_ref)
282                    .map(|p| {
283                        Zt::CombDotChal::inner_product::<CHECK_FOR_OVERFLOW>(
284                            &p,
285                            &per_poly_alphas[i],
286                            Zt::CombR::ZERO,
287                        )
288                    })
289                    .try_collect()?;
290
291                Ok(acc
292                    + Zt::ArrCombRDotChal::inner_product::<CHECK_FOR_OVERFLOW>(
293                        &column_entries,
294                        coeffs,
295                        Zt::CombR::ZERO,
296                    )?)
297            })?;
298
299        if all_column_entries_comb != encoded_combined_row[column] {
300            return Err(ZipError::InvalidPcsOpen("Proximity failure".into()));
301        }
302
303        Ok(())
304    }
305}
306
307#[cfg(test)]
308#[allow(
309    clippy::arithmetic_side_effects,
310    clippy::cast_possible_truncation,
311    clippy::cast_possible_wrap
312)]
313mod tests {
314    use crate::{
315        ZipError,
316        code::{LinearCode, iprs::IprsCode},
317        merkle::MerkleTree,
318        pcs::{
319            structs::{ZipPlus, ZipPlusHint, ZipTypes},
320            test_utils::*,
321        },
322        pcs_transcript::{PcsProverTranscript, PcsVerifierTranscript},
323    };
324    use crypto_bigint::U64;
325    use crypto_primitives::{
326        Field, FromWithConfig, IntSemiring, IntoWithConfig, PrimeField, crypto_bigint_int::Int,
327        crypto_bigint_monty::MontyField,
328    };
329    use itertools::Itertools;
330    use num_traits::{ConstOne, ConstZero, Zero};
331    use rand::prelude::*;
332    use std::{mem::size_of, sync::LazyLock};
333    use zinc_poly::{
334        mle::{DenseMultilinearExtension, MultilinearExtensionRand},
335        univariate::binary::BinaryPoly,
336    };
337    use zinc_transcript::traits::{ConstTranscribable, Transcribable, Transcript};
338    use zinc_utils::CHECKED;
339
340    const INT_LIMBS: usize = U64::LIMBS;
341
342    const N: usize = INT_LIMBS;
343    const K: usize = INT_LIMBS * 4;
344    const M: usize = INT_LIMBS * 8;
345    const DEGREE_PLUS_ONE: usize = 3;
346
347    type F = MontyField<K>;
348
349    type Zt = TestZipTypes<N, K, M>;
350    type C = IprsCode<Zt, TestIprsConfig, REP_FACTOR, CHECKED>;
351    static C: LazyLock<C> = LazyLock::new(|| C::new(IPRS_ROW_LEN, IPRS_DEPTH).unwrap());
352
353    type PolyZt = TestBinPolyZipTypes<K, M, DEGREE_PLUS_ONE>;
354    type PolyC = IprsCode<PolyZt, TestIprsConfig, REP_FACTOR, CHECKED>;
355    static POLY_C: LazyLock<PolyC> =
356        LazyLock::new(|| PolyC::new(IPRS_ROW_LEN, IPRS_DEPTH).unwrap());
357
358    type TestZip = ZipPlus<Zt, C>;
359    type TestPolyZip = ZipPlus<PolyZt, PolyC>;
360
361    #[test]
362    fn successful_verification_of_valid_proof() {
363        let num_vars = 10;
364        {
365            let (pp, comm, point_f, eval_f, mut transcript) =
366                setup_full_protocol::<F, N, K, M>(num_vars);
367            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
368
369            let result = TestZip::verify::<_, CHECKED>(
370                &mut transcript,
371                &pp,
372                &comm,
373                &field_cfg,
374                &point_f,
375                &eval_f,
376            );
377            assert!(result.is_ok(), "Verification failed: {result:?}")
378        };
379        {
380            let (pp, comm, point_f, eval_f, mut transcript) =
381                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
382            let field_cfg = get_field_cfg::<PolyZt, F>(&mut transcript.fs_transcript);
383
384            let result = TestPolyZip::verify::<_, CHECKED>(
385                &mut transcript,
386                &pp,
387                &comm,
388                &field_cfg,
389                &point_f,
390                &eval_f,
391            );
392
393            assert!(result.is_ok(), "Verification failed: {result:?}");
394        }
395    }
396
397    #[test]
398    fn verification_fails_with_incorrect_evaluation() {
399        let num_vars = 10;
400
401        {
402            let (pp, comm, point_f, eval_f, mut transcript) =
403                setup_full_protocol::<F, N, K, M>(num_vars);
404            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
405            let tampered = eval_f + F::one_with_cfg(&field_cfg);
406
407            let result = TestZip::verify::<_, CHECKED>(
408                &mut transcript,
409                &pp,
410                &comm,
411                &field_cfg,
412                &point_f,
413                &tampered,
414            );
415
416            assert!(result.is_err());
417        }
418
419        {
420            let (pp, comm, point_f, eval_f, mut transcript) =
421                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
422            let field_cfg = get_field_cfg::<PolyZt, F>(&mut transcript.fs_transcript);
423            let tampered = eval_f + F::one_with_cfg(&field_cfg);
424
425            let result = TestPolyZip::verify::<_, CHECKED>(
426                &mut transcript,
427                &pp,
428                &comm,
429                &field_cfg,
430                &point_f,
431                &tampered,
432            );
433
434            assert!(result.is_err());
435        }
436    }
437
438    #[test]
439    fn verification_fails_with_tampered_proof() {
440        fn tamper(mut proof: PcsVerifierTranscript) -> PcsVerifierTranscript {
441            let original_f0: F = proof.clone().read_field_elements(1).unwrap().remove(0);
442            // Skipping over LENGTH_NUM_BYTES prefix for b field elements, and the modulus
443            // bytes, to flip a byte in the VALUE part of the first b element.
444            type Mod = <F as Field>::Modulus;
445            let offset = Mod::LENGTH_NUM_BYTES + Mod::NUM_BYTES;
446            proof.stream.get_mut()[offset] ^= 0x01;
447
448            // Sanity check that we didn't mess up the tampering
449            let tampered_f0: F = proof.clone().read_field_elements(1).unwrap().remove(0);
450            assert_eq!(original_f0.modulus(), tampered_f0.modulus());
451            assert_ne!(original_f0, tampered_f0);
452
453            proof
454        }
455        let num_vars = 10;
456
457        {
458            let (pp, comm, point_f, eval_f, proof) = setup_full_protocol::<F, N, K, M>(num_vars);
459            let mut tampered = tamper(proof);
460            let field_cfg = get_field_cfg::<Zt, F>(&mut tampered.fs_transcript);
461            let result = TestZip::verify::<_, CHECKED>(
462                &mut tampered,
463                &pp,
464                &comm,
465                &field_cfg,
466                &point_f,
467                &eval_f,
468            );
469            assert!(result.is_err());
470        }
471
472        {
473            let (pp, comm, point_f, eval_f, proof) =
474                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
475            let mut tampered = tamper(proof);
476            let field_cfg = get_field_cfg::<PolyZt, F>(&mut tampered.fs_transcript);
477            let result = TestPolyZip::verify::<_, CHECKED>(
478                &mut tampered,
479                &pp,
480                &comm,
481                &field_cfg,
482                &point_f,
483                &eval_f,
484            );
485            assert!(result.is_err());
486        }
487    }
488
489    #[test]
490    fn verification_fails_with_wrong_commitment() {
491        let num_vars = 10;
492        {
493            let (pp, _comm_poly1, point_f, eval_f, mut transcript) =
494                setup_full_protocol::<F, N, K, M>(num_vars);
495            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
496
497            let poly2: DenseMultilinearExtension<_> =
498                (20..(20 + (1 << num_vars))).map(Int::from).collect();
499
500            let (_, comm_poly2) = TestZip::commit_single(&pp, &poly2).unwrap();
501
502            let result = TestZip::verify::<_, CHECKED>(
503                &mut transcript,
504                &pp,
505                &comm_poly2,
506                &field_cfg,
507                &point_f,
508                &eval_f,
509            );
510
511            assert!(result.is_err());
512        }
513
514        {
515            let (pp, _comm_poly1, point_f, eval_f, mut transcript) =
516                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
517            let field_cfg = get_field_cfg::<PolyZt, F>(&mut transcript.fs_transcript);
518
519            let different_evals = {
520                let different_eval_coeffs: Vec<_> = (1..=((1 << num_vars) * (DEGREE_PLUS_ONE - 1)))
521                    .map(|x| (x % 3 == 0).into())
522                    .collect_vec();
523                different_eval_coeffs
524                    .chunks_exact(DEGREE_PLUS_ONE - 1)
525                    .map(BinaryPoly::new)
526                    .collect_vec()
527            };
528
529            let poly2 = DenseMultilinearExtension::from_evaluations_vec(
530                num_vars,
531                different_evals,
532                Zero::zero(),
533            );
534            let (_, comm_poly2) = TestPolyZip::commit_single(&pp, &poly2).unwrap();
535
536            let result = TestPolyZip::verify::<_, CHECKED>(
537                &mut transcript,
538                &pp,
539                &comm_poly2,
540                &field_cfg,
541                &point_f,
542                &eval_f,
543            );
544
545            assert!(result.is_err());
546        }
547    }
548
549    #[test]
550    fn verification_fails_with_invalid_point_size() {
551        let num_vars = 10;
552
553        let make_invalid_point = |cfg: &<F as PrimeField>::Config| {
554            let mut invalid_point = vec![];
555            for i in 0..=num_vars {
556                invalid_point.push(F::from_with_cfg(100 + i as i32, cfg));
557            }
558            invalid_point
559        };
560
561        {
562            let (pp, comm, _point_f, eval_f, mut transcript) =
563                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
564            let field_cfg = get_field_cfg::<PolyZt, F>(&mut transcript.fs_transcript);
565            let invalid_point = make_invalid_point(eval_f.cfg());
566
567            let result = TestPolyZip::verify::<_, CHECKED>(
568                &mut transcript,
569                &pp,
570                &comm,
571                &field_cfg,
572                &invalid_point,
573                &eval_f,
574            );
575
576            assert!(matches!(result, Err(..)));
577        }
578
579        {
580            let (pp, comm, _point_f, eval_f, mut transcript) =
581                setup_full_protocol::<F, N, K, M>(num_vars);
582            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
583            let invalid_point = make_invalid_point(eval_f.cfg());
584
585            let result = TestZip::verify::<_, CHECKED>(
586                &mut transcript,
587                &pp,
588                &comm,
589                &field_cfg,
590                &invalid_point,
591                &eval_f,
592            );
593
594            assert!(matches!(result, Err(..)));
595        }
596    }
597
598    #[test]
599    fn verification_fails_due_to_incorrect_polynomial() {
600        let num_vars = 10;
601        let (pp, mle1) = setup_test_params(num_vars);
602        let poly_size = 1 << num_vars;
603
604        let (hint, comm) = TestZip::commit_single(&pp, &mle1).unwrap();
605
606        let mle2: DenseMultilinearExtension<_> = (20..20 + poly_size).map(Int::from).collect();
607
608        let point: Vec<<Zt as ZipTypes>::Pt> =
609            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
610
611        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
612        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
613
614        let _eval_f = TestZip::prove_single::<F, CHECKED>(
615            &mut prover_transcript,
616            &pp,
617            &mle2,
618            &point,
619            &hint,
620            &field_cfg,
621        )
622        .unwrap();
623
624        let eval_mle1 = mle1
625            .evaluate(&point, Zero::zero())
626            .expect("Failed to evaluate polynomial");
627
628        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
629        let eval_mle1_f = eval_mle1.into_with_cfg(&field_cfg);
630
631        let mut verifier_transcript = prover_transcript.into_verification_transcript();
632        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
633        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
634
635        let verification_result = TestZip::verify::<_, CHECKED>(
636            &mut verifier_transcript,
637            &pp,
638            &comm,
639            &field_cfg,
640            &point_f,
641            &eval_mle1_f,
642        );
643        assert!(verification_result.is_err());
644    }
645
646    #[test]
647    fn verification_fails_due_to_a_hint_that_is_not_close() {
648        let num_vars = 10;
649        let (pp, mle) = setup_test_params(num_vars);
650
651        let (original_hint, comm) = TestZip::commit_single(&pp, &mle).unwrap();
652
653        let mut corrupted_data = original_hint.cw_matrices[0].clone();
654        {
655            let mut corrupted_rows = corrupted_data.to_rows_slices_mut();
656            let codeword_len = pp.linear_code.codeword_len();
657            let corruption_count = codeword_len / 2 + 1;
658            for i in corrupted_rows[0].iter_mut().take(corruption_count) {
659                *i += Int::ONE;
660            }
661        }
662
663        let corrupted_merkle_tree = MerkleTree::new(&corrupted_data.to_rows_slices());
664        let corrupted_hint = ZipPlusHint::new(vec![corrupted_data], corrupted_merkle_tree);
665
666        let point: Vec<<Zt as ZipTypes>::Pt> =
667            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
668
669        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
670        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
671
672        let eval_f = TestZip::prove_single::<F, CHECKED>(
673            &mut prover_transcript,
674            &pp,
675            &mle,
676            &point,
677            &corrupted_hint,
678            &field_cfg,
679        )
680        .unwrap();
681
682        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
683
684        let mut verifier_transcript = prover_transcript.into_verification_transcript();
685        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
686        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
687
688        let verification_result = TestZip::verify::<_, CHECKED>(
689            &mut verifier_transcript,
690            &pp,
691            &comm,
692            &field_cfg,
693            &point_f,
694            &eval_f,
695        );
696
697        assert!(verification_result.is_err());
698    }
699
700    #[test]
701    fn verification_fails_due_to_incorrect_evaluation() {
702        let num_vars = 10;
703        let (pp, mle) = setup_test_params(num_vars);
704
705        let (hint, comm) = TestZip::commit_single(&pp, &mle).unwrap();
706
707        let point: Vec<<Zt as ZipTypes>::Pt> =
708            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
709
710        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
711        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
712
713        let eval_f = TestZip::prove_single::<F, CHECKED>(
714            &mut prover_transcript,
715            &pp,
716            &mle,
717            &point,
718            &hint,
719            &field_cfg,
720        )
721        .unwrap();
722
723        let incorrect_eval_f = eval_f + F::one_with_cfg(&field_cfg);
724        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
725
726        let mut verifier_transcript = prover_transcript.into_verification_transcript();
727        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
728        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
729
730        let verification_result = TestZip::verify::<_, CHECKED>(
731            &mut verifier_transcript,
732            &pp,
733            &comm,
734            &field_cfg,
735            &point_f,
736            &incorrect_eval_f, // Use the wrong evaluation here
737        );
738
739        assert!(verification_result.is_err());
740    }
741
742    #[test]
743    fn verification_fails_if_proximity_check_is_invalid() {
744        let num_vars = 10;
745        let poly_size: usize = 1 << num_vars;
746
747        let pp = TestZip::setup(poly_size, C.clone());
748
749        let mle: DenseMultilinearExtension<_> = (0..poly_size as i32)
750            .map(<Zt as ZipTypes>::Eval::from)
751            .collect();
752
753        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
754
755        let point = vec![ConstOne::ONE; num_vars];
756
757        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
758        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
759
760        let eval_f = TestZip::prove_single::<F, CHECKED>(
761            &mut prover_transcript,
762            &pp,
763            &mle,
764            &point,
765            &hint,
766            &field_cfg,
767        )
768        .unwrap();
769
770        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
771
772        // New transcript layout: [b field elems] [combined_row] [column openings...]
773        // To trigger "Proximity failure", corrupt a column value (past b +
774        // combined_row).
775        let row_len = pp.linear_code.row_len();
776        let num_bytes_f = eval_f.inner().get_num_bytes();
777        let b_section_size = 1 + pp.num_rows * 2 * num_bytes_f;
778        let bytes_per_comb_r = M * size_of::<crypto_bigint::Word>();
779        let combined_row_size = row_len * bytes_per_comb_r;
780        let column_values_start = b_section_size + combined_row_size;
781        let bytes_per_cw = K * size_of::<crypto_bigint::Word>();
782
783        let mut verifier_transcript = prover_transcript.into_verification_transcript();
784        assert!(
785            column_values_start + bytes_per_cw <= verifier_transcript.stream.get_ref().len(),
786            "proof too small to tamper column values"
787        );
788
789        let flip_at = column_values_start + bytes_per_cw / 2;
790        verifier_transcript.stream.get_mut()[flip_at] ^= 0x01;
791
792        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
793        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
794
795        let res = TestZip::verify::<_, CHECKED>(
796            &mut verifier_transcript,
797            &pp,
798            &comm,
799            &field_cfg,
800            &point_f,
801            &eval_f,
802        );
803
804        match res {
805            Err(ZipError::InvalidPcsOpen(msg)) => {
806                assert_eq!(msg, "Proximity failure");
807            }
808            Ok(()) => panic!("verification unexpectedly succeeded"),
809            Err(e) => panic!("unexpected error: {e:?}"),
810        }
811    }
812
813    #[test]
814    fn verification_fails_if_evaluation_consistency_check_is_invalid() {
815        let num_vars = 10;
816        let poly_size: usize = 1 << num_vars;
817        let pp = TestZip::setup(poly_size, C.clone());
818
819        let mle: DenseMultilinearExtension<_> =
820            (0..poly_size as i32).map(Int::<INT_LIMBS>::from).collect();
821
822        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
823
824        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
825
826        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
827        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
828
829        let eval_f = TestZip::prove_single::<F, CHECKED>(
830            &mut prover_transcript,
831            &pp,
832            &mle,
833            &point,
834            &hint,
835            &field_cfg,
836        )
837        .unwrap();
838
839        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
840
841        // New transcript starts with b field elements: [1-byte prefix][modulus|value
842        // per elem]. Flip a byte inside the first b element's VALUE to corrupt
843        // eval consistency.
844        let num_bytes_f_mod = eval_f.modulus().get_num_bytes();
845        let num_bytes_f_val = eval_f.inner().get_num_bytes();
846        let flip_at = 1 + num_bytes_f_mod + num_bytes_f_val / 4;
847
848        let mut verifier_transcript = prover_transcript.into_verification_transcript();
849        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
850        get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
851        assert!(
852            flip_at < verifier_transcript.stream.get_ref().len(),
853            "proof too small to tamper b section"
854        );
855        verifier_transcript.stream.get_mut()[flip_at] ^= 0x01;
856
857        let res = TestZip::verify::<_, CHECKED>(
858            &mut verifier_transcript,
859            &pp,
860            &comm,
861            &field_cfg,
862            &point_f,
863            &eval_f,
864        );
865
866        match res {
867            Err(ZipError::InvalidPcsOpen(msg)) => {
868                assert_eq!(msg, "Evaluation consistency failure");
869            }
870            Ok(()) => panic!("verification unexpectedly succeeded"),
871            Err(e) => panic!("unexpected error: {e:?}"),
872        }
873    }
874
875    #[test]
876    fn verification_succeeds_for_zero_polynomial() {
877        let num_vars = 10;
878        let poly_size: usize = 1 << num_vars;
879        let pp = TestZip::setup(poly_size, C.clone());
880
881        let mle: DenseMultilinearExtension<_> = vec![Zero::zero(); poly_size].into_iter().collect();
882
883        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
884
885        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
886
887        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
888        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
889
890        let eval_f = TestZip::prove_single::<F, CHECKED>(
891            &mut prover_transcript,
892            &pp,
893            &mle,
894            &point,
895            &hint,
896            &field_cfg,
897        )
898        .unwrap();
899
900        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
901
902        let mut verifier_transcript = prover_transcript.into_verification_transcript();
903        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
904        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
905
906        let res = TestZip::verify::<_, CHECKED>(
907            &mut verifier_transcript,
908            &pp,
909            &comm,
910            &field_cfg,
911            &point_f,
912            &eval_f,
913        );
914        assert!(res.is_ok());
915    }
916
917    #[test]
918    fn verification_succeeds_at_zero_point() {
919        let num_vars = 10;
920        let poly_size: usize = 1 << num_vars;
921        let pp = TestZip::setup(poly_size, C.clone());
922
923        let mle: DenseMultilinearExtension<_> =
924            (1..=poly_size as i32).map(Int::<INT_LIMBS>::from).collect();
925
926        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
927
928        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
929
930        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
931        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
932
933        let eval_f = TestZip::prove_single::<F, CHECKED>(
934            &mut prover_transcript,
935            &pp,
936            &mle,
937            &point,
938            &hint,
939            &field_cfg,
940        )
941        .unwrap();
942
943        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
944
945        let mut verifier_transcript = prover_transcript.into_verification_transcript();
946        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
947        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
948
949        let res = TestZip::verify::<_, CHECKED>(
950            &mut verifier_transcript,
951            &pp,
952            &comm,
953            &field_cfg,
954            &point_f,
955            &eval_f,
956        );
957        assert!(res.is_ok());
958    }
959
960    #[test]
961    fn verification_succeeds_when_polynomial_coefficients_are_max_bit_size() {
962        let num_vars = 10;
963        let (pp, _) = setup_test_params(num_vars);
964
965        let mut evals: Vec<<Zt as ZipTypes>::Eval> =
966            (0..1 << num_vars as i32).map(Int::from).collect();
967        evals[1] = Int::from(i64::MAX);
968        let poly = DenseMultilinearExtension::from_evaluations_vec(num_vars, evals, Zero::zero());
969
970        let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
971
972        let mut point = vec![<Zt as ZipTypes>::Pt::ZERO; num_vars];
973        point[0] = <Zt as ZipTypes>::Pt::ONE;
974
975        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
976        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
977
978        let eval_f = TestZip::prove_single::<F, CHECKED>(
979            &mut prover_transcript,
980            &pp,
981            &poly,
982            &point,
983            &hint,
984            &field_cfg,
985        )
986        .unwrap();
987
988        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
989
990        let mut verifier_transcript = prover_transcript.into_verification_transcript();
991        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
992        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
993
994        let verification_result = TestZip::verify::<_, CHECKED>(
995            &mut verifier_transcript,
996            &pp,
997            &comm,
998            &field_cfg,
999            &point_f,
1000            &eval_f,
1001        );
1002
1003        assert!(
1004            verification_result.is_ok(),
1005            "Verification failed: {verification_result:?}",
1006        );
1007    }
1008
1009    #[test]
1010    fn verification_succeeds_with_minimal_polynomial_size_mu_is_8() {
1011        let num_vars = 10;
1012        let (pp, poly) = setup_test_params(num_vars);
1013
1014        let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
1015
1016        let point: Vec<<Zt as ZipTypes>::Pt> = (1..=num_vars as i32).map(Int::from).collect_vec();
1017
1018        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1019        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
1020
1021        let eval_f = TestZip::prove_single::<F, CHECKED>(
1022            &mut prover_transcript,
1023            &pp,
1024            &poly,
1025            &point,
1026            &hint,
1027            &field_cfg,
1028        )
1029        .unwrap();
1030
1031        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1032
1033        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1034        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1035        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1036
1037        let verification_result = TestZip::verify::<_, CHECKED>(
1038            &mut verifier_transcript,
1039            &pp,
1040            &comm,
1041            &field_cfg,
1042            &point_f,
1043            &eval_f,
1044        );
1045
1046        assert!(verification_result.is_ok());
1047    }
1048
1049    #[test]
1050    fn verification_succeeds_for_code_row_length_of_1() {
1051        let num_vars = 8;
1052        macro_rules! make_code {
1053            () => {
1054                IprsCode::new(1, 0).unwrap()
1055            };
1056        }
1057        {
1058            let (pp, comm, point_f, eval_f, mut transcript) =
1059                setup_full_protocol_inner::<Zt, C, F, N>(
1060                    num_vars,
1061                    |num_vars| {
1062                        setup_test_params_inner(num_vars, make_code!(), |poly_size| {
1063                            (1..=poly_size as i32).map(Int::from).collect()
1064                        })
1065                    },
1066                    || (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect(),
1067                );
1068            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
1069
1070            let result = TestZip::verify::<_, CHECKED>(
1071                &mut transcript,
1072                &pp,
1073                &comm,
1074                &field_cfg,
1075                &point_f,
1076                &eval_f,
1077            );
1078            assert!(result.is_ok(), "Verification failed: {result:?}")
1079        };
1080        {
1081            let (pp, comm, point_f, eval_f, mut transcript) =
1082                setup_full_protocol_inner::<PolyZt, PolyC, F, N>(
1083                    num_vars,
1084                    |num_vars| {
1085                        setup_test_params_inner(num_vars, make_code!(), |poly_size| {
1086                            let degree = DEGREE_PLUS_ONE - 1;
1087                            let eval_coeffs: Vec<_> = (1..=(poly_size * degree) as i64)
1088                                .map(|v| v.is_odd().into())
1089                                .collect_vec();
1090                            eval_coeffs
1091                                .chunks_exact(degree)
1092                                .map(BinaryPoly::new)
1093                                .collect_vec()
1094                        })
1095                    },
1096                    || (0..num_vars).map(|i| i as i128 + 2).collect(),
1097                );
1098            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
1099
1100            let result = TestPolyZip::verify::<_, CHECKED>(
1101                &mut transcript,
1102                &pp,
1103                &comm,
1104                &field_cfg,
1105                &point_f,
1106                &eval_f,
1107            );
1108            assert!(result.is_ok(), "Verification failed: {result:?}")
1109        }
1110    }
1111
1112    #[test]
1113    fn verification_fails_at_proximity_link_check_if_combined_row_is_corrupted() {
1114        let num_vars = 10;
1115        let poly_size: usize = 1 << num_vars;
1116        let pp = TestZip::setup(poly_size, C.clone());
1117
1118        let mle: DenseMultilinearExtension<_> = (1..=poly_size as i32).map(Int::from).collect();
1119
1120        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
1121
1122        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
1123
1124        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1125        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
1126
1127        let eval_f = TestZip::prove_single::<F, CHECKED>(
1128            &mut prover_transcript,
1129            &pp,
1130            &mle,
1131            &point,
1132            &hint,
1133            &field_cfg,
1134        )
1135        .unwrap();
1136
1137        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1138
1139        // Offset past b section to reach combined_row (CombR = Int<M>).
1140        let num_bytes_f = eval_f.inner().get_num_bytes();
1141        let b_section_size = 1 + pp.num_rows * 2 * num_bytes_f;
1142        let bytes_to_corrupt = M * size_of::<crypto_bigint::Word>();
1143
1144        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1145        assert!(
1146            b_section_size + bytes_to_corrupt <= verifier_transcript.stream.get_ref().len(),
1147            "proof too small to tamper combined_row"
1148        );
1149
1150        for b in &mut verifier_transcript.stream.get_mut()
1151            [b_section_size..b_section_size + bytes_to_corrupt]
1152        {
1153            *b = 0xFF;
1154        }
1155
1156        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1157        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1158
1159        let res = TestZip::verify::<_, CHECKED>(
1160            &mut verifier_transcript,
1161            &pp,
1162            &comm,
1163            &field_cfg,
1164            &point_f,
1165            &eval_f,
1166        );
1167        assert!(res.is_err());
1168    }
1169
1170    /// Mirrors: `Zip/Verify: RandomField<4>, poly_size = 2^12 (Int limbs = 1)`
1171    #[test]
1172    fn bench_p12_verify() {
1173        fn inner<const P: usize>() {
1174            let mut rng = ThreadRng::default();
1175            // Match the benchmark's transcript usage for linear code construction
1176            let poly_size: usize = 1 << P;
1177            let pp = TestZip::setup(poly_size, C.clone());
1178
1179            let mle = DenseMultilinearExtension::rand(P, &mut rng);
1180            let (hint, commitment) = TestZip::commit_single(&pp, &mle).expect("commit");
1181
1182            let point = vec![1i64; P].iter().map(|v| v.into()).collect_vec();
1183
1184            let mut prover_transcript = PcsProverTranscript::new_from_commitment(&commitment);
1185            let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
1186
1187            let eval_f = TestZip::prove_single::<F, CHECKED>(
1188                &mut prover_transcript,
1189                &pp,
1190                &mle,
1191                &point,
1192                &hint,
1193                &field_cfg,
1194            )
1195            .unwrap();
1196
1197            let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1198
1199            let mut verifier_transcript = prover_transcript.into_verification_transcript();
1200            verifier_transcript
1201                .fs_transcript
1202                .absorb_slice(&commitment.root);
1203            let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1204
1205            let zero_f = F::zero_with_cfg(&field_cfg);
1206            let mle_f = DenseMultilinearExtension::from_evaluations_vec(
1207                P,
1208                mle.iter().map(|c| c.into_with_cfg(&field_cfg)).collect(),
1209                zero_f.clone(),
1210            );
1211            let expected_eval_f = mle_f.evaluate(&point_f, zero_f).unwrap();
1212            assert_eq!(eval_f, expected_eval_f, "prover returned wrong eval");
1213
1214            TestZip::verify::<_, CHECKED>(
1215                &mut verifier_transcript,
1216                &pp,
1217                &commitment,
1218                &field_cfg,
1219                &point_f,
1220                &eval_f,
1221            )
1222            .expect("verify");
1223        }
1224
1225        inner::<12>();
1226    }
1227
1228    /// Mirrors: `Zip+/Verify` for `poly_size=2^12`
1229    #[test]
1230    fn bench_p12_verify_poly() {
1231        fn inner<const P: usize>() {
1232            let mut rng = ThreadRng::default();
1233            // Match the benchmark's transcript usage for linear code construction
1234            let poly_size: usize = 1 << P;
1235            let pp = TestPolyZip::setup(poly_size, POLY_C.clone());
1236
1237            let mle = DenseMultilinearExtension::rand(P, &mut rng);
1238            let (hint, comm) = TestPolyZip::commit_single(&pp, &mle).expect("commit");
1239
1240            let point = vec![1i64; P].iter().map(|v| (*v).into()).collect_vec();
1241
1242            let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1243            let field_cfg = get_field_cfg::<PolyZt, F>(&mut prover_transcript.fs_transcript);
1244
1245            let eval_f = TestPolyZip::prove_single::<F, CHECKED>(
1246                &mut prover_transcript,
1247                &pp,
1248                &mle,
1249                &point,
1250                &hint,
1251                &field_cfg,
1252            )
1253            .unwrap();
1254
1255            let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1256
1257            let mut verifier_transcript = prover_transcript.into_verification_transcript();
1258            verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1259            let field_cfg = get_field_cfg::<PolyZt, F>(&mut verifier_transcript.fs_transcript);
1260
1261            // Verifier replays verification from the same proof (also like the bench)
1262            TestPolyZip::verify::<_, CHECKED>(
1263                &mut verifier_transcript,
1264                &pp,
1265                &comm,
1266                &field_cfg,
1267                &point_f,
1268                &eval_f,
1269            )
1270            .expect("verify");
1271        }
1272
1273        inner::<12>();
1274    }
1275
1276    fn batched_prove_verify_inner<const BATCH: usize>(num_vars: usize) {
1277        let poly_size = 1 << num_vars;
1278        let pp = TestZip::setup(poly_size, C.clone());
1279
1280        let polys: Vec<DenseMultilinearExtension<_>> = (0..BATCH)
1281            .map(|b| {
1282                let base = (b * poly_size) as i32;
1283                (base + 1..=base + poly_size as i32)
1284                    .map(Int::<INT_LIMBS>::from)
1285                    .collect()
1286            })
1287            .collect();
1288
1289        let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
1290        let point: Vec<<Zt as ZipTypes>::Pt> =
1291            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
1292
1293        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1294        let field_cfg = get_field_cfg::<PolyZt, F>(&mut prover_transcript.fs_transcript);
1295
1296        let eval_f = TestZip::prove::<F, CHECKED>(
1297            &mut prover_transcript,
1298            &pp,
1299            &polys,
1300            &point,
1301            &hint,
1302            &field_cfg,
1303        )
1304        .unwrap();
1305
1306        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1307
1308        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1309        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1310        let field_cfg = get_field_cfg::<PolyZt, F>(&mut verifier_transcript.fs_transcript);
1311
1312        let res = TestZip::verify::<_, CHECKED>(
1313            &mut verifier_transcript,
1314            &pp,
1315            &comm,
1316            &field_cfg,
1317            &point_f,
1318            &eval_f,
1319        );
1320        assert!(
1321            res.is_ok(),
1322            "Batched verify (batch={BATCH}) failed: {res:?}"
1323        );
1324    }
1325
1326    #[test]
1327    fn batched_prove_verify_batch_2() {
1328        batched_prove_verify_inner::<2>(10);
1329    }
1330
1331    #[test]
1332    fn batched_prove_verify_batch_5() {
1333        batched_prove_verify_inner::<5>(10);
1334    }
1335
1336    #[test]
1337    fn batched_prove_verify_batch_1_roundtrip() {
1338        batched_prove_verify_inner::<1>(10);
1339    }
1340
1341    #[test]
1342    fn batched_verify_fails_with_tampered_eval() {
1343        let num_vars = 10;
1344        let poly_size = 1 << num_vars;
1345        let pp = TestZip::setup(poly_size, C.clone());
1346
1347        let polys: Vec<DenseMultilinearExtension<_>> = vec![
1348            (1..=poly_size as i32).map(Int::from).collect(),
1349            (17..=16 + poly_size as i32).map(Int::from).collect(),
1350        ];
1351
1352        let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
1353        let point: Vec<<Zt as ZipTypes>::Pt> = (0..num_vars).map(|i| Int::from(i + 2)).collect();
1354
1355        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1356        let field_cfg = get_field_cfg::<PolyZt, F>(&mut prover_transcript.fs_transcript);
1357
1358        let eval_f = TestZip::prove::<F, CHECKED>(
1359            &mut prover_transcript,
1360            &pp,
1361            &polys,
1362            &point,
1363            &hint,
1364            &field_cfg,
1365        )
1366        .unwrap();
1367        let tampered_eval = eval_f + F::one_with_cfg(&field_cfg);
1368
1369        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1370
1371        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1372        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1373        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1374
1375        let res = TestZip::verify::<_, CHECKED>(
1376            &mut verifier_transcript,
1377            &pp,
1378            &comm,
1379            &field_cfg,
1380            &point_f,
1381            &tampered_eval,
1382        );
1383        assert!(res.is_err(), "Should fail when eval is tampered");
1384    }
1385}