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    #[cfg_attr(miri, ignore)] // long running
399    fn verification_fails_with_incorrect_evaluation() {
400        let num_vars = 10;
401
402        {
403            let (pp, comm, point_f, eval_f, mut transcript) =
404                setup_full_protocol::<F, N, K, M>(num_vars);
405            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
406            let tampered = eval_f + F::one_with_cfg(&field_cfg);
407
408            let result = TestZip::verify::<_, CHECKED>(
409                &mut transcript,
410                &pp,
411                &comm,
412                &field_cfg,
413                &point_f,
414                &tampered,
415            );
416
417            assert!(result.is_err());
418        }
419
420        {
421            let (pp, comm, point_f, eval_f, mut transcript) =
422                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
423            let field_cfg = get_field_cfg::<PolyZt, F>(&mut transcript.fs_transcript);
424            let tampered = eval_f + F::one_with_cfg(&field_cfg);
425
426            let result = TestPolyZip::verify::<_, CHECKED>(
427                &mut transcript,
428                &pp,
429                &comm,
430                &field_cfg,
431                &point_f,
432                &tampered,
433            );
434
435            assert!(result.is_err());
436        }
437    }
438
439    #[test]
440    #[cfg_attr(miri, ignore)] // long running
441    fn verification_fails_with_tampered_proof() {
442        fn tamper(mut proof: PcsVerifierTranscript) -> PcsVerifierTranscript {
443            let original_f0: F = proof.clone().read_field_elements(1).unwrap().remove(0);
444            // Skipping over LENGTH_NUM_BYTES prefix for b field elements, and the modulus
445            // bytes, to flip a byte in the VALUE part of the first b element.
446            type Mod = <F as Field>::Modulus;
447            let offset = Mod::LENGTH_NUM_BYTES + Mod::NUM_BYTES;
448            proof.stream.get_mut()[offset] ^= 0x01;
449
450            // Sanity check that we didn't mess up the tampering
451            let tampered_f0: F = proof.clone().read_field_elements(1).unwrap().remove(0);
452            assert_eq!(original_f0.modulus(), tampered_f0.modulus());
453            assert_ne!(original_f0, tampered_f0);
454
455            proof
456        }
457        let num_vars = 10;
458
459        {
460            let (pp, comm, point_f, eval_f, proof) = setup_full_protocol::<F, N, K, M>(num_vars);
461            let mut tampered = tamper(proof);
462            let field_cfg = get_field_cfg::<Zt, F>(&mut tampered.fs_transcript);
463            let result = TestZip::verify::<_, CHECKED>(
464                &mut tampered,
465                &pp,
466                &comm,
467                &field_cfg,
468                &point_f,
469                &eval_f,
470            );
471            assert!(result.is_err());
472        }
473
474        {
475            let (pp, comm, point_f, eval_f, proof) =
476                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
477            let mut tampered = tamper(proof);
478            let field_cfg = get_field_cfg::<PolyZt, F>(&mut tampered.fs_transcript);
479            let result = TestPolyZip::verify::<_, CHECKED>(
480                &mut tampered,
481                &pp,
482                &comm,
483                &field_cfg,
484                &point_f,
485                &eval_f,
486            );
487            assert!(result.is_err());
488        }
489    }
490
491    #[test]
492    #[cfg_attr(miri, ignore)] // long running
493    fn verification_fails_with_wrong_commitment() {
494        let num_vars = 10;
495        {
496            let (pp, _comm_poly1, point_f, eval_f, mut transcript) =
497                setup_full_protocol::<F, N, K, M>(num_vars);
498            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
499
500            let poly2: DenseMultilinearExtension<_> =
501                (20..(20 + (1 << num_vars))).map(Int::from).collect();
502
503            let (_, comm_poly2) = TestZip::commit_single(&pp, &poly2).unwrap();
504
505            let result = TestZip::verify::<_, CHECKED>(
506                &mut transcript,
507                &pp,
508                &comm_poly2,
509                &field_cfg,
510                &point_f,
511                &eval_f,
512            );
513
514            assert!(result.is_err());
515        }
516
517        {
518            let (pp, _comm_poly1, point_f, eval_f, mut transcript) =
519                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
520            let field_cfg = get_field_cfg::<PolyZt, F>(&mut transcript.fs_transcript);
521
522            let different_evals = {
523                let different_eval_coeffs: Vec<_> = (1..=((1 << num_vars) * (DEGREE_PLUS_ONE - 1)))
524                    .map(|x| (x % 3 == 0).into())
525                    .collect_vec();
526                different_eval_coeffs
527                    .chunks_exact(DEGREE_PLUS_ONE - 1)
528                    .map(BinaryPoly::new)
529                    .collect_vec()
530            };
531
532            let poly2 = DenseMultilinearExtension::from_evaluations_vec(
533                num_vars,
534                different_evals,
535                Zero::zero(),
536            );
537            let (_, comm_poly2) = TestPolyZip::commit_single(&pp, &poly2).unwrap();
538
539            let result = TestPolyZip::verify::<_, CHECKED>(
540                &mut transcript,
541                &pp,
542                &comm_poly2,
543                &field_cfg,
544                &point_f,
545                &eval_f,
546            );
547
548            assert!(result.is_err());
549        }
550    }
551
552    #[test]
553    #[cfg_attr(miri, ignore)] // long running
554    fn verification_fails_with_invalid_point_size() {
555        let num_vars = 10;
556
557        let make_invalid_point = |cfg: &<F as PrimeField>::Config| {
558            let mut invalid_point = vec![];
559            for i in 0..=num_vars {
560                invalid_point.push(F::from_with_cfg(100 + i as i32, cfg));
561            }
562            invalid_point
563        };
564
565        {
566            let (pp, comm, _point_f, eval_f, mut transcript) =
567                setup_full_protocol_poly::<F, N, K, M, DEGREE_PLUS_ONE>(num_vars);
568            let field_cfg = get_field_cfg::<PolyZt, F>(&mut transcript.fs_transcript);
569            let invalid_point = make_invalid_point(eval_f.cfg());
570
571            let result = TestPolyZip::verify::<_, CHECKED>(
572                &mut transcript,
573                &pp,
574                &comm,
575                &field_cfg,
576                &invalid_point,
577                &eval_f,
578            );
579
580            assert!(matches!(result, Err(..)));
581        }
582
583        {
584            let (pp, comm, _point_f, eval_f, mut transcript) =
585                setup_full_protocol::<F, N, K, M>(num_vars);
586            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
587            let invalid_point = make_invalid_point(eval_f.cfg());
588
589            let result = TestZip::verify::<_, CHECKED>(
590                &mut transcript,
591                &pp,
592                &comm,
593                &field_cfg,
594                &invalid_point,
595                &eval_f,
596            );
597
598            assert!(matches!(result, Err(..)));
599        }
600    }
601
602    #[test]
603    #[cfg_attr(miri, ignore)] // long running
604    fn verification_fails_due_to_incorrect_polynomial() {
605        let num_vars = 10;
606        let (pp, mle1) = setup_test_params(num_vars);
607        let poly_size = 1 << num_vars;
608
609        let (hint, comm) = TestZip::commit_single(&pp, &mle1).unwrap();
610
611        let mle2: DenseMultilinearExtension<_> = (20..20 + poly_size).map(Int::from).collect();
612
613        let point: Vec<<Zt as ZipTypes>::Pt> =
614            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
615
616        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
617        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
618
619        let _eval_f = TestZip::prove_single::<F, CHECKED>(
620            &mut prover_transcript,
621            &pp,
622            &mle2,
623            &point,
624            &hint,
625            &field_cfg,
626        )
627        .unwrap();
628
629        let eval_mle1 = mle1
630            .evaluate(&point, Zero::zero())
631            .expect("Failed to evaluate polynomial");
632
633        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
634        let eval_mle1_f = eval_mle1.into_with_cfg(&field_cfg);
635
636        let mut verifier_transcript = prover_transcript.into_verification_transcript();
637        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
638        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
639
640        let verification_result = TestZip::verify::<_, CHECKED>(
641            &mut verifier_transcript,
642            &pp,
643            &comm,
644            &field_cfg,
645            &point_f,
646            &eval_mle1_f,
647        );
648        assert!(verification_result.is_err());
649    }
650
651    #[test]
652    #[cfg_attr(miri, ignore)] // long running
653    fn verification_fails_due_to_a_hint_that_is_not_close() {
654        let num_vars = 10;
655        let (pp, mle) = setup_test_params(num_vars);
656
657        let (original_hint, comm) = TestZip::commit_single(&pp, &mle).unwrap();
658
659        let mut corrupted_data = original_hint.cw_matrices[0].clone();
660        {
661            let mut corrupted_rows = corrupted_data.to_rows_slices_mut();
662            let codeword_len = pp.linear_code.codeword_len();
663            let corruption_count = codeword_len / 2 + 1;
664            for i in corrupted_rows[0].iter_mut().take(corruption_count) {
665                *i += Int::ONE;
666            }
667        }
668
669        let corrupted_merkle_tree = MerkleTree::new(&corrupted_data.to_rows_slices());
670        let corrupted_hint = ZipPlusHint::new(vec![corrupted_data], corrupted_merkle_tree);
671
672        let point: Vec<<Zt as ZipTypes>::Pt> =
673            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
674
675        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
676        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
677
678        let eval_f = TestZip::prove_single::<F, CHECKED>(
679            &mut prover_transcript,
680            &pp,
681            &mle,
682            &point,
683            &corrupted_hint,
684            &field_cfg,
685        )
686        .unwrap();
687
688        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
689
690        let mut verifier_transcript = prover_transcript.into_verification_transcript();
691        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
692        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
693
694        let verification_result = TestZip::verify::<_, CHECKED>(
695            &mut verifier_transcript,
696            &pp,
697            &comm,
698            &field_cfg,
699            &point_f,
700            &eval_f,
701        );
702
703        assert!(verification_result.is_err());
704    }
705
706    #[test]
707    #[cfg_attr(miri, ignore)] // long running
708    fn verification_fails_due_to_incorrect_evaluation() {
709        let num_vars = 10;
710        let (pp, mle) = setup_test_params(num_vars);
711
712        let (hint, comm) = TestZip::commit_single(&pp, &mle).unwrap();
713
714        let point: Vec<<Zt as ZipTypes>::Pt> =
715            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
716
717        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
718        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
719
720        let eval_f = TestZip::prove_single::<F, CHECKED>(
721            &mut prover_transcript,
722            &pp,
723            &mle,
724            &point,
725            &hint,
726            &field_cfg,
727        )
728        .unwrap();
729
730        let incorrect_eval_f = eval_f + F::one_with_cfg(&field_cfg);
731        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
732
733        let mut verifier_transcript = prover_transcript.into_verification_transcript();
734        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
735        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
736
737        let verification_result = TestZip::verify::<_, CHECKED>(
738            &mut verifier_transcript,
739            &pp,
740            &comm,
741            &field_cfg,
742            &point_f,
743            &incorrect_eval_f, // Use the wrong evaluation here
744        );
745
746        assert!(verification_result.is_err());
747    }
748
749    #[test]
750    #[cfg_attr(miri, ignore)] // long running
751    fn verification_fails_if_proximity_check_is_invalid() {
752        let num_vars = 10;
753        let poly_size: usize = 1 << num_vars;
754
755        let pp = TestZip::setup(poly_size, C.clone());
756
757        let mle: DenseMultilinearExtension<_> = (0..poly_size as i32)
758            .map(<Zt as ZipTypes>::Eval::from)
759            .collect();
760
761        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
762
763        let point = vec![ConstOne::ONE; num_vars];
764
765        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
766        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
767
768        let eval_f = TestZip::prove_single::<F, CHECKED>(
769            &mut prover_transcript,
770            &pp,
771            &mle,
772            &point,
773            &hint,
774            &field_cfg,
775        )
776        .unwrap();
777
778        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
779
780        // New transcript layout: [b field elems] [combined_row] [column openings...]
781        // To trigger "Proximity failure", corrupt a column value (past b +
782        // combined_row).
783        let row_len = pp.linear_code.row_len();
784        let num_bytes_f = eval_f.inner().get_num_bytes();
785        let b_section_size = 1 + pp.num_rows * 2 * num_bytes_f;
786        let bytes_per_comb_r = M * size_of::<crypto_bigint::Word>();
787        let combined_row_size = row_len * bytes_per_comb_r;
788        let column_values_start = b_section_size + combined_row_size;
789        let bytes_per_cw = K * size_of::<crypto_bigint::Word>();
790
791        let mut verifier_transcript = prover_transcript.into_verification_transcript();
792        assert!(
793            column_values_start + bytes_per_cw <= verifier_transcript.stream.get_ref().len(),
794            "proof too small to tamper column values"
795        );
796
797        let flip_at = column_values_start + bytes_per_cw / 2;
798        verifier_transcript.stream.get_mut()[flip_at] ^= 0x01;
799
800        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
801        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
802
803        let res = TestZip::verify::<_, CHECKED>(
804            &mut verifier_transcript,
805            &pp,
806            &comm,
807            &field_cfg,
808            &point_f,
809            &eval_f,
810        );
811
812        match res {
813            Err(ZipError::InvalidPcsOpen(msg)) => {
814                assert_eq!(msg, "Proximity failure");
815            }
816            Ok(()) => panic!("verification unexpectedly succeeded"),
817            Err(e) => panic!("unexpected error: {e:?}"),
818        }
819    }
820
821    #[test]
822    #[cfg_attr(miri, ignore)] // long running
823    fn verification_fails_if_evaluation_consistency_check_is_invalid() {
824        let num_vars = 10;
825        let poly_size: usize = 1 << num_vars;
826        let pp = TestZip::setup(poly_size, C.clone());
827
828        let mle: DenseMultilinearExtension<_> =
829            (0..poly_size as i32).map(Int::<INT_LIMBS>::from).collect();
830
831        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
832
833        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
834
835        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
836        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
837
838        let eval_f = TestZip::prove_single::<F, CHECKED>(
839            &mut prover_transcript,
840            &pp,
841            &mle,
842            &point,
843            &hint,
844            &field_cfg,
845        )
846        .unwrap();
847
848        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
849
850        // New transcript starts with b field elements: [1-byte prefix][modulus|value
851        // per elem]. Flip a byte inside the first b element's VALUE to corrupt
852        // eval consistency.
853        let num_bytes_f_mod = eval_f.modulus().get_num_bytes();
854        let num_bytes_f_val = eval_f.inner().get_num_bytes();
855        let flip_at = 1 + num_bytes_f_mod + num_bytes_f_val / 4;
856
857        let mut verifier_transcript = prover_transcript.into_verification_transcript();
858        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
859        get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
860        assert!(
861            flip_at < verifier_transcript.stream.get_ref().len(),
862            "proof too small to tamper b section"
863        );
864        verifier_transcript.stream.get_mut()[flip_at] ^= 0x01;
865
866        let res = TestZip::verify::<_, CHECKED>(
867            &mut verifier_transcript,
868            &pp,
869            &comm,
870            &field_cfg,
871            &point_f,
872            &eval_f,
873        );
874
875        match res {
876            Err(ZipError::InvalidPcsOpen(msg)) => {
877                assert_eq!(msg, "Evaluation consistency failure");
878            }
879            Ok(()) => panic!("verification unexpectedly succeeded"),
880            Err(e) => panic!("unexpected error: {e:?}"),
881        }
882    }
883
884    #[test]
885    fn verification_succeeds_for_zero_polynomial() {
886        let num_vars = 10;
887        let poly_size: usize = 1 << num_vars;
888        let pp = TestZip::setup(poly_size, C.clone());
889
890        let mle: DenseMultilinearExtension<_> = vec![Zero::zero(); poly_size].into_iter().collect();
891
892        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
893
894        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
895
896        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
897        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
898
899        let eval_f = TestZip::prove_single::<F, CHECKED>(
900            &mut prover_transcript,
901            &pp,
902            &mle,
903            &point,
904            &hint,
905            &field_cfg,
906        )
907        .unwrap();
908
909        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
910
911        let mut verifier_transcript = prover_transcript.into_verification_transcript();
912        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
913        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
914
915        let res = TestZip::verify::<_, CHECKED>(
916            &mut verifier_transcript,
917            &pp,
918            &comm,
919            &field_cfg,
920            &point_f,
921            &eval_f,
922        );
923        assert!(res.is_ok());
924    }
925
926    #[test]
927    #[cfg_attr(miri, ignore)] // long running
928    fn verification_succeeds_at_zero_point() {
929        let num_vars = 10;
930        let poly_size: usize = 1 << num_vars;
931        let pp = TestZip::setup(poly_size, C.clone());
932
933        let mle: DenseMultilinearExtension<_> =
934            (1..=poly_size as i32).map(Int::<INT_LIMBS>::from).collect();
935
936        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
937
938        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
939
940        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
941        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
942
943        let eval_f = TestZip::prove_single::<F, CHECKED>(
944            &mut prover_transcript,
945            &pp,
946            &mle,
947            &point,
948            &hint,
949            &field_cfg,
950        )
951        .unwrap();
952
953        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
954
955        let mut verifier_transcript = prover_transcript.into_verification_transcript();
956        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
957        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
958
959        let res = TestZip::verify::<_, CHECKED>(
960            &mut verifier_transcript,
961            &pp,
962            &comm,
963            &field_cfg,
964            &point_f,
965            &eval_f,
966        );
967        assert!(res.is_ok());
968    }
969
970    #[test]
971    #[cfg_attr(miri, ignore)] // long running
972    fn verification_succeeds_when_polynomial_coefficients_are_max_bit_size() {
973        let num_vars = 10;
974        let (pp, _) = setup_test_params(num_vars);
975
976        let mut evals: Vec<<Zt as ZipTypes>::Eval> =
977            (0..1 << num_vars as i32).map(Int::from).collect();
978        evals[1] = Int::from(i64::MAX);
979        let poly = DenseMultilinearExtension::from_evaluations_vec(num_vars, evals, Zero::zero());
980
981        let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
982
983        let mut point = vec![<Zt as ZipTypes>::Pt::ZERO; num_vars];
984        point[0] = <Zt as ZipTypes>::Pt::ONE;
985
986        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
987        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
988
989        let eval_f = TestZip::prove_single::<F, CHECKED>(
990            &mut prover_transcript,
991            &pp,
992            &poly,
993            &point,
994            &hint,
995            &field_cfg,
996        )
997        .unwrap();
998
999        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1000
1001        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1002        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1003        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1004
1005        let verification_result = TestZip::verify::<_, CHECKED>(
1006            &mut verifier_transcript,
1007            &pp,
1008            &comm,
1009            &field_cfg,
1010            &point_f,
1011            &eval_f,
1012        );
1013
1014        assert!(
1015            verification_result.is_ok(),
1016            "Verification failed: {verification_result:?}",
1017        );
1018    }
1019
1020    #[test]
1021    #[cfg_attr(miri, ignore)] // long running
1022    fn verification_succeeds_with_minimal_polynomial_size_mu_is_8() {
1023        let num_vars = 10;
1024        let (pp, poly) = setup_test_params(num_vars);
1025
1026        let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
1027
1028        let point: Vec<<Zt as ZipTypes>::Pt> = (1..=num_vars as i32).map(Int::from).collect_vec();
1029
1030        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1031        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
1032
1033        let eval_f = TestZip::prove_single::<F, CHECKED>(
1034            &mut prover_transcript,
1035            &pp,
1036            &poly,
1037            &point,
1038            &hint,
1039            &field_cfg,
1040        )
1041        .unwrap();
1042
1043        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1044
1045        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1046        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1047        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1048
1049        let verification_result = TestZip::verify::<_, CHECKED>(
1050            &mut verifier_transcript,
1051            &pp,
1052            &comm,
1053            &field_cfg,
1054            &point_f,
1055            &eval_f,
1056        );
1057
1058        assert!(verification_result.is_ok());
1059    }
1060
1061    #[test]
1062    #[cfg_attr(miri, ignore)] // long running
1063    fn verification_succeeds_for_code_row_length_of_1() {
1064        let num_vars = 8;
1065        macro_rules! make_code {
1066            () => {
1067                IprsCode::new(1, 0).unwrap()
1068            };
1069        }
1070        {
1071            let (pp, comm, point_f, eval_f, mut transcript) =
1072                setup_full_protocol_inner::<Zt, C, F, N>(
1073                    num_vars,
1074                    |num_vars| {
1075                        setup_test_params_inner(num_vars, make_code!(), |poly_size| {
1076                            (1..=poly_size as i32).map(Int::from).collect()
1077                        })
1078                    },
1079                    || (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect(),
1080                );
1081            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
1082
1083            let result = TestZip::verify::<_, CHECKED>(
1084                &mut transcript,
1085                &pp,
1086                &comm,
1087                &field_cfg,
1088                &point_f,
1089                &eval_f,
1090            );
1091            assert!(result.is_ok(), "Verification failed: {result:?}")
1092        };
1093        {
1094            let (pp, comm, point_f, eval_f, mut transcript) =
1095                setup_full_protocol_inner::<PolyZt, PolyC, F, N>(
1096                    num_vars,
1097                    |num_vars| {
1098                        setup_test_params_inner(num_vars, make_code!(), |poly_size| {
1099                            let degree = DEGREE_PLUS_ONE - 1;
1100                            let eval_coeffs: Vec<_> = (1..=(poly_size * degree) as i64)
1101                                .map(|v| v.is_odd().into())
1102                                .collect_vec();
1103                            eval_coeffs
1104                                .chunks_exact(degree)
1105                                .map(BinaryPoly::new)
1106                                .collect_vec()
1107                        })
1108                    },
1109                    || (0..num_vars).map(|i| i as i128 + 2).collect(),
1110                );
1111            let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
1112
1113            let result = TestPolyZip::verify::<_, CHECKED>(
1114                &mut transcript,
1115                &pp,
1116                &comm,
1117                &field_cfg,
1118                &point_f,
1119                &eval_f,
1120            );
1121            assert!(result.is_ok(), "Verification failed: {result:?}")
1122        }
1123    }
1124
1125    #[test]
1126    #[cfg_attr(miri, ignore)] // long running
1127    fn verification_fails_at_proximity_link_check_if_combined_row_is_corrupted() {
1128        let num_vars = 10;
1129        let poly_size: usize = 1 << num_vars;
1130        let pp = TestZip::setup(poly_size, C.clone());
1131
1132        let mle: DenseMultilinearExtension<_> = (1..=poly_size as i32).map(Int::from).collect();
1133
1134        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
1135
1136        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
1137
1138        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1139        let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
1140
1141        let eval_f = TestZip::prove_single::<F, CHECKED>(
1142            &mut prover_transcript,
1143            &pp,
1144            &mle,
1145            &point,
1146            &hint,
1147            &field_cfg,
1148        )
1149        .unwrap();
1150
1151        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1152
1153        // Offset past b section to reach combined_row (CombR = Int<M>).
1154        let num_bytes_f = eval_f.inner().get_num_bytes();
1155        let b_section_size = 1 + pp.num_rows * 2 * num_bytes_f;
1156        let bytes_to_corrupt = M * size_of::<crypto_bigint::Word>();
1157
1158        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1159        assert!(
1160            b_section_size + bytes_to_corrupt <= verifier_transcript.stream.get_ref().len(),
1161            "proof too small to tamper combined_row"
1162        );
1163
1164        for b in &mut verifier_transcript.stream.get_mut()
1165            [b_section_size..b_section_size + bytes_to_corrupt]
1166        {
1167            *b = 0xFF;
1168        }
1169
1170        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1171        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1172
1173        let res = TestZip::verify::<_, CHECKED>(
1174            &mut verifier_transcript,
1175            &pp,
1176            &comm,
1177            &field_cfg,
1178            &point_f,
1179            &eval_f,
1180        );
1181        assert!(res.is_err());
1182    }
1183
1184    /// Mirrors: `Zip/Verify: RandomField<4>, poly_size = 2^12 (Int limbs = 1)`
1185    #[test]
1186    #[cfg_attr(miri, ignore)] // long running
1187    fn bench_p12_verify() {
1188        fn inner<const P: usize>() {
1189            let mut rng = ThreadRng::default();
1190            // Match the benchmark's transcript usage for linear code construction
1191            let poly_size: usize = 1 << P;
1192            let pp = TestZip::setup(poly_size, C.clone());
1193
1194            let mle = DenseMultilinearExtension::rand(P, &mut rng);
1195            let (hint, commitment) = TestZip::commit_single(&pp, &mle).expect("commit");
1196
1197            let point = vec![1i64; P].iter().map(|v| v.into()).collect_vec();
1198
1199            let mut prover_transcript = PcsProverTranscript::new_from_commitment(&commitment);
1200            let field_cfg = get_field_cfg::<Zt, F>(&mut prover_transcript.fs_transcript);
1201
1202            let eval_f = TestZip::prove_single::<F, CHECKED>(
1203                &mut prover_transcript,
1204                &pp,
1205                &mle,
1206                &point,
1207                &hint,
1208                &field_cfg,
1209            )
1210            .unwrap();
1211
1212            let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1213
1214            let mut verifier_transcript = prover_transcript.into_verification_transcript();
1215            verifier_transcript
1216                .fs_transcript
1217                .absorb_slice(&commitment.root);
1218            let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1219
1220            let zero_f = F::zero_with_cfg(&field_cfg);
1221            let mle_f = DenseMultilinearExtension::from_evaluations_vec(
1222                P,
1223                mle.iter().map(|c| c.into_with_cfg(&field_cfg)).collect(),
1224                zero_f.clone(),
1225            );
1226            let expected_eval_f = mle_f.evaluate(&point_f, zero_f).unwrap();
1227            assert_eq!(eval_f, expected_eval_f, "prover returned wrong eval");
1228
1229            TestZip::verify::<_, CHECKED>(
1230                &mut verifier_transcript,
1231                &pp,
1232                &commitment,
1233                &field_cfg,
1234                &point_f,
1235                &eval_f,
1236            )
1237            .expect("verify");
1238        }
1239
1240        inner::<12>();
1241    }
1242
1243    /// Mirrors: `Zip+/Verify` for `poly_size=2^12`
1244    #[test]
1245    #[cfg_attr(miri, ignore)] // long running
1246    fn bench_p12_verify_poly() {
1247        fn inner<const P: usize>() {
1248            let mut rng = ThreadRng::default();
1249            // Match the benchmark's transcript usage for linear code construction
1250            let poly_size: usize = 1 << P;
1251            let pp = TestPolyZip::setup(poly_size, POLY_C.clone());
1252
1253            let mle = DenseMultilinearExtension::rand(P, &mut rng);
1254            let (hint, comm) = TestPolyZip::commit_single(&pp, &mle).expect("commit");
1255
1256            let point = vec![1i64; P].iter().map(|v| (*v).into()).collect_vec();
1257
1258            let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1259            let field_cfg = get_field_cfg::<PolyZt, F>(&mut prover_transcript.fs_transcript);
1260
1261            let eval_f = TestPolyZip::prove_single::<F, CHECKED>(
1262                &mut prover_transcript,
1263                &pp,
1264                &mle,
1265                &point,
1266                &hint,
1267                &field_cfg,
1268            )
1269            .unwrap();
1270
1271            let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1272
1273            let mut verifier_transcript = prover_transcript.into_verification_transcript();
1274            verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1275            let field_cfg = get_field_cfg::<PolyZt, F>(&mut verifier_transcript.fs_transcript);
1276
1277            // Verifier replays verification from the same proof (also like the bench)
1278            TestPolyZip::verify::<_, CHECKED>(
1279                &mut verifier_transcript,
1280                &pp,
1281                &comm,
1282                &field_cfg,
1283                &point_f,
1284                &eval_f,
1285            )
1286            .expect("verify");
1287        }
1288
1289        inner::<12>();
1290    }
1291
1292    fn batched_prove_verify_inner<const BATCH: usize>(num_vars: usize) {
1293        let poly_size = 1 << num_vars;
1294        let pp = TestZip::setup(poly_size, C.clone());
1295
1296        let polys: Vec<DenseMultilinearExtension<_>> = (0..BATCH)
1297            .map(|b| {
1298                let base = (b * poly_size) as i32;
1299                (base + 1..=base + poly_size as i32)
1300                    .map(Int::<INT_LIMBS>::from)
1301                    .collect()
1302            })
1303            .collect();
1304
1305        let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
1306        let point: Vec<<Zt as ZipTypes>::Pt> =
1307            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
1308
1309        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1310        let field_cfg = get_field_cfg::<PolyZt, F>(&mut prover_transcript.fs_transcript);
1311
1312        let eval_f = TestZip::prove::<F, CHECKED>(
1313            &mut prover_transcript,
1314            &pp,
1315            &polys,
1316            &point,
1317            &hint,
1318            &field_cfg,
1319        )
1320        .unwrap();
1321
1322        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1323
1324        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1325        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1326        let field_cfg = get_field_cfg::<PolyZt, F>(&mut verifier_transcript.fs_transcript);
1327
1328        let res = TestZip::verify::<_, CHECKED>(
1329            &mut verifier_transcript,
1330            &pp,
1331            &comm,
1332            &field_cfg,
1333            &point_f,
1334            &eval_f,
1335        );
1336        assert!(
1337            res.is_ok(),
1338            "Batched verify (batch={BATCH}) failed: {res:?}"
1339        );
1340    }
1341
1342    #[test]
1343    fn batched_prove_verify_batch_2() {
1344        batched_prove_verify_inner::<2>(10);
1345    }
1346
1347    #[test]
1348    #[cfg_attr(miri, ignore)] // long running
1349    fn batched_prove_verify_batch_5() {
1350        batched_prove_verify_inner::<5>(10);
1351    }
1352
1353    #[test]
1354    #[cfg_attr(miri, ignore)] // long running
1355    fn batched_prove_verify_batch_1_roundtrip() {
1356        batched_prove_verify_inner::<1>(10);
1357    }
1358
1359    #[test]
1360    #[cfg_attr(miri, ignore)] // long running
1361    fn batched_verify_fails_with_tampered_eval() {
1362        let num_vars = 10;
1363        let poly_size = 1 << num_vars;
1364        let pp = TestZip::setup(poly_size, C.clone());
1365
1366        let polys: Vec<DenseMultilinearExtension<_>> = vec![
1367            (1..=poly_size as i32).map(Int::from).collect(),
1368            (17..=16 + poly_size as i32).map(Int::from).collect(),
1369        ];
1370
1371        let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
1372        let point: Vec<<Zt as ZipTypes>::Pt> = (0..num_vars).map(|i| Int::from(i + 2)).collect();
1373
1374        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1375        let field_cfg = get_field_cfg::<PolyZt, F>(&mut prover_transcript.fs_transcript);
1376
1377        let eval_f = TestZip::prove::<F, CHECKED>(
1378            &mut prover_transcript,
1379            &pp,
1380            &polys,
1381            &point,
1382            &hint,
1383            &field_cfg,
1384        )
1385        .unwrap();
1386        let tampered_eval = eval_f + F::one_with_cfg(&field_cfg);
1387
1388        let point_f: Vec<F> = point.iter().map(|v| v.into_with_cfg(&field_cfg)).collect();
1389
1390        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1391        verifier_transcript.fs_transcript.absorb_slice(&comm.root);
1392        let field_cfg = get_field_cfg::<Zt, F>(&mut verifier_transcript.fs_transcript);
1393
1394        let res = TestZip::verify::<_, CHECKED>(
1395            &mut verifier_transcript,
1396            &pp,
1397            &comm,
1398            &field_cfg,
1399            &point_f,
1400            &tampered_eval,
1401        );
1402        assert!(res.is_err(), "Should fail when eval is tampered");
1403    }
1404}