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::{BaseFieldConfig, ProjectElementWithConfig};
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::{ConstTranscribable, Transcript},
19};
20use zinc_utils::{
21    UNCHECKED, add, cfg_into_iter,
22    from_ref::FromRef,
23    inner_product::{FieldInnerProduct, InnerProduct, NativeInnerProduct},
24};
25
26impl<Zt: ZipTypes, Lc: LinearCode<Zt>> ZipPlus<Zt, Lc> {
27    /// Verifies an opening proof for one or more committed multilinear
28    /// polynomials at an evaluation point, using the Zip+ protocol.
29    ///
30    /// This replaces the old two-phase (verify_testing + verify_evaluation)
31    /// approach. The old protocol performed two proximity checks (one in CombR,
32    /// one in F via a separate `projecting_element` γ) and one eval consistency
33    /// check. The merged protocol eliminates the F-domain proximity check
34    /// entirely, replacing it with a coherence check between `b` and `w`
35    /// that ties the single CombR proximity check to the evaluation claim.
36    ///
37    /// # Verification checks (4 total)
38    ///
39    /// 1. **Eval consistency**: `<q_0, b> == eval_f`. Ensures the claimed
40    ///    evaluation matches the `b` vector written by the prover, where `b_j =
41    ///    sum_i(<w'_ij, q_1>)` and `w'_ij` is the j-th decoded row of poly i
42    ///    after taking the random linear combination `<entry, alphas_i>` of
43    ///    every entry.
44    ///
45    /// 2. **Coherence** (b-w): `<w, q_1> == <s, b>`. Ensures `b` and `w` are
46    ///    derived from the same underlying rows `w'_j`, tying the
47    ///    proximity-tested `w` to the eval-tested `b`.
48    ///
49    /// 3. **Proximity** (per opened column, batched across polys): `Enc(w)[col]
50    ///    == sum_i(sum_j(s_j * <v_ij[col], alphas_i>))`. For each poly i and
51    ///    row j, takes the random linear combination `<v_ij[col], alphas_i>` of
52    ///    the Cw column entry to get a CombR value, combines rows with
53    ///    coefficients `s`, sums across polys. Compares against the encoded
54    ///    combined row.
55    ///
56    /// 4. **Merkle proof** (per opened column): verifies the column values
57    ///    against `comm.root`, ensuring that the data matches what was
58    ///    committed.
59    ///
60    /// Chain of trust: Merkle (check 4) → column data authentic → proximity
61    /// (check 3) → `w` is a valid codeword consistent with columns →
62    /// coherence (check 2) → `b` is consistent with `w` →
63    /// eval consistency (check 1) → `eval_f` is correct.
64    ///
65    /// # Algorithm
66    /// 1. Computes `(q_0, q_1) = point_to_tensor(point_f)`.
67    /// 2. Per polynomial, re-derives `alphas` from the transcript.
68    /// 3. Reads `b` (length `num_rows`) from the transcript.
69    /// 4. **Check 1**: asserts `<q_0, b> == eval_f`.
70    /// 5. Re-derives combination coefficients `s` (or `[1]` when `num_rows ==
71    ///    1`).
72    /// 6. Reads combined row `w` (CombR, length `row_len`) and encodes it.
73    /// 7. **Check 2**: asserts `<w, q_1> == <s, b>`.
74    /// 8. For each of `NUM_COLUMN_OPENINGS`: a. Squeezes column index, reads
75    ///    per-poly column values + Merkle proof. b. **Check 3**:
76    ///    `verify_column_testing_batched`. c. **Check 4**:
77    ///    `proof.verify(comm.root, column_values, col)`.
78    ///
79    /// # Parameters
80    /// - `vp`: Public parameters (same as prover's `pp`).
81    /// - `comm`: The `ZipPlusCommitment` (Merkle root + batch size) from the
82    ///   commit phase.
83    /// - `point_f`: The evaluation point in field `F` (length `num_vars`).
84    /// - `eval_f`: The claimed combined evaluation `<q_0, b>`.
85    /// - `proof`: The `ZipPlusProof` produced by `prove`.
86    ///
87    /// # Returns
88    /// `Ok(())` if all four checks pass.
89    ///
90    /// # Errors
91    /// - `ZipError::InvalidPcsParam` if inputs are malformed.
92    /// - `ZipError::InvalidPcsOpen("Evaluation consistency failure")` if check
93    ///   1 fails.
94    /// - `ZipError::InvalidPcsOpen("Coherence failure")` if check 2 fails.
95    /// - `ZipError::InvalidPcsOpen("Proximity failure")` if check 3 fails.
96    /// - `ZipError::InvalidPcsOpen("Column opening verification failed: ...")`
97    ///   if check 4 (Merkle) fails.
98    #[allow(clippy::arithmetic_side_effects, clippy::type_complexity)]
99    pub fn verify<C, const CHECK_FOR_OVERFLOW: bool>(
100        transcript: &mut PcsVerifierTranscript,
101        vp: &ZipPlusParams<Zt, Lc>,
102        comm: &ZipPlusCommitment,
103        field_cfg: &C,
104        point_f: &[C::Element],
105        eval_f: &C::Element,
106    ) -> Result<(), ZipError>
107    where
108        C: BaseFieldConfig
109            + ProjectElementWithConfig<Zt::CombR>
110            + ProjectElementWithConfig<Zt::Chal>,
111        C::Integer: ConstTranscribable,
112    {
113        let per_poly_alphas = Self::sample_alphas(&mut transcript.fs_transcript, comm.batch_size);
114        Self::verify_with_alphas::<C, CHECK_FOR_OVERFLOW>(
115            transcript,
116            vp,
117            comm,
118            field_cfg,
119            point_f,
120            eval_f,
121            &per_poly_alphas,
122        )
123    }
124
125    /// Like [`Self::verify`], but accepts pre-sampled alpha challenges.
126    ///
127    /// The caller is responsible for sampling `per_poly_alphas` from the
128    /// transcript before calling this (typically via [`Self::sample_alphas`]).
129    /// This allows computing `eval_f` externally from the alphas (e.g. via
130    /// alpha-projection of polynomial MLE evaluations in the lift-and-project
131    /// flow).
132    #[allow(clippy::arithmetic_side_effects, clippy::type_complexity)]
133    pub fn verify_with_alphas<C, const CHECK_FOR_OVERFLOW: bool>(
134        transcript: &mut PcsVerifierTranscript,
135        vp: &ZipPlusParams<Zt, Lc>,
136        comm: &ZipPlusCommitment,
137        field_cfg: &C,
138        point_f: &[C::Element],
139        eval_f: &C::Element,
140        per_poly_alphas: &[Vec<Zt::Chal>],
141    ) -> Result<(), ZipError>
142    where
143        C: BaseFieldConfig
144            + ProjectElementWithConfig<Zt::CombR>
145            + ProjectElementWithConfig<Zt::Chal>,
146        C::Integer: ConstTranscribable,
147    {
148        let batch_size = comm.batch_size;
149        validate_input::<Zt, Lc, _>(
150            "verify",
151            vp.num_vars,
152            vp.linear_code.row_len(),
153            batch_size,
154            &[],
155            &[point_f],
156        )?;
157
158        let num_rows = vp.num_rows;
159        let row_len = vp.linear_code.row_len();
160
161        // TODO Lift q0, q1 back to int and take following dot products on ints instead
162        // of MBSInnerProduct in field (see combined_row)
163        let (q_0, q_1) = point_to_tensor(field_cfg, point_f, vp.num_rows)?;
164        let zero_f = field_cfg.zero();
165
166        let b: Vec<C::Element> = transcript.read_field_elements(field_cfg, num_rows)?;
167
168        // Check 1: <q_0, b> == eval_f
169        let q_0_dot_b =
170            NativeInnerProduct::inner_product::<UNCHECKED>(field_cfg, &q_0, &b, zero_f.clone())?;
171        if q_0_dot_b != *eval_f {
172            return Err(ZipError::InvalidPcsOpen(
173                "Evaluation consistency failure".into(),
174            ));
175        }
176
177        let coeffs: Vec<Zt::Chal> = if num_rows == 1 {
178            vec![Zt::Chal::ONE]
179        } else {
180            transcript.fs_transcript.get_challenges(num_rows)
181        };
182
183        let combined_row: Vec<Zt::CombR> = transcript.read_const_many(row_len)?;
184        let encoded_combined_row: Vec<Zt::CombR> = vp.linear_code.encode_wide(&combined_row);
185
186        // Check 2: <w, q_1> == <s, b>
187        // Ensures b and w are derived from the same underlying rows w'_j.
188        // NOTE: CombR entries (Int<M>) can exceed the field's bit-width, so the
189        // CombR->F projection must reduce mod p before truncating limbs.
190        let lhs = FieldInnerProduct::inner_product::<UNCHECKED>(
191            field_cfg,
192            &q_1,
193            &combined_row,
194            zero_f.clone(),
195        )?;
196        let rhs = FieldInnerProduct::inner_product::<UNCHECKED>(field_cfg, &b, &coeffs, zero_f)?;
197
198        if lhs != rhs {
199            return Err(ZipError::InvalidPcsOpen("Coherence failure".into()));
200        }
201
202        let columns_and_proofs: Vec<_> = (0..Zt::NUM_COLUMN_OPENINGS)
203            .map(|_| -> Result<_, ZipError> {
204                let column_idx = transcript.squeeze_challenge_idx(vp.linear_code.codeword_len());
205                let column_values = transcript.read_const_many(batch_size * vp.num_rows)?;
206                let proof = transcript.read_merkle_proof().map_err(|e| {
207                    ZipError::InvalidPcsOpen(format!("Failed to read Merkle a proof: {e}"))
208                })?;
209
210                Ok((column_idx, column_values, proof))
211            })
212            .try_collect()?;
213
214        cfg_into_iter!(columns_and_proofs).try_for_each(
215            |(column_idx, column_values, proof)| -> Result<(), ZipError> {
216                Self::verify_column_testing_batched::<CHECK_FOR_OVERFLOW>(
217                    per_poly_alphas,
218                    &coeffs,
219                    &encoded_combined_row,
220                    &column_values,
221                    column_idx,
222                    vp.num_rows,
223                    batch_size,
224                )?;
225
226                proof
227                    .verify(&comm.root, &column_values, column_idx)
228                    .map_err(|e| {
229                        ZipError::InvalidPcsOpen(format!("Column opening verification failed: {e}"))
230                    })?;
231
232                Ok(())
233            },
234        )?;
235
236        Ok(())
237    }
238
239    /// Samples per-polynomial alpha challenges from the transcript.
240    ///
241    /// This is the same sampling logic used internally by [`Self::verify`].
242    /// Exposed so that callers (e.g. the Zinc+ PIOP verifier) can sample
243    /// alphas, perform alpha-projection externally, and then call
244    /// [`Self::verify_with_alphas`].
245    pub fn sample_alphas(
246        transcript: &mut Blake3Transcript,
247        batch_size: usize,
248    ) -> Vec<Vec<Zt::Chal>> {
249        let degree_bound = Zt::Comb::DEGREE_BOUND;
250        (0..batch_size)
251            .map(|_| {
252                if degree_bound.is_zero() {
253                    vec![Zt::Chal::ONE]
254                } else {
255                    transcript.get_challenges(add!(degree_bound, 1))
256                }
257            })
258            .collect()
259    }
260
261    // Check 3: Enc(w)[col] == sum_i( sum_j( s_j * <v_ij[col], alphas_i> ) )
262    // For each poly i and row j, takes the random linear combination
263    // <v_ij[col], alphas_i> of the Cw column entry to CombR,
264    // combines rows with coefficients s, sums across polys.
265    pub(super) fn verify_column_testing_batched<const CHECK_FOR_OVERFLOW: bool>(
266        per_poly_alphas: &[Vec<Zt::Chal>],
267        coeffs: &[Zt::Chal],
268        encoded_combined_row: &[Zt::CombR],
269        all_column_entries: &[Zt::Cw],
270        column: usize,
271        num_rows: usize,
272        batch_size: usize,
273    ) -> Result<(), ZipError> {
274        #[allow(clippy::arithmetic_side_effects)]
275        let all_column_entries_comb =
276            (0..batch_size).try_fold(Zt::CombR::ZERO, |acc, i| -> Result<_, ZipError> {
277                let column_entries: Vec<_> = all_column_entries[i * num_rows..(i + 1) * num_rows]
278                    .iter()
279                    .map(Zt::Comb::from_ref)
280                    .map(|p| {
281                        Zt::CombDotChal::inner_product::<CHECK_FOR_OVERFLOW>(
282                            &(),
283                            &p,
284                            &per_poly_alphas[i],
285                            Zt::CombR::ZERO,
286                        )
287                    })
288                    .try_collect()?;
289
290                Ok(acc
291                    + Zt::ArrCombRDotChal::inner_product::<CHECK_FOR_OVERFLOW>(
292                        &(),
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_primitives::{
325        FixedConfig, IntSemiring, ProjectElementWithConfig, SemiringConfig, WithAssociatedInteger,
326        crypto_bigint_int::Int,
327        crypto_bigint_monty::{MontyField, MontyFieldElement},
328        crypto_bigint_uint::{U64, Uint},
329    };
330    use itertools::Itertools;
331    use num_traits::{ConstOne, ConstZero, Zero};
332    use rand::prelude::*;
333    use std::{mem::size_of, sync::LazyLock};
334    use zinc_poly::{
335        mle::{DenseMultilinearExtension, MultilinearExtensionRand},
336        univariate::binary::BinaryPoly,
337    };
338    use zinc_transcript::traits::{ConstTranscribable, Transcript};
339    use zinc_utils::CHECKED;
340
341    const INT_LIMBS: usize = U64::LIMBS;
342
343    const N: usize = INT_LIMBS;
344    const K: usize = INT_LIMBS * 4;
345    const M: usize = INT_LIMBS * 8;
346    const DEGREE_PLUS_ONE: usize = 3;
347
348    type Cfg = MontyField<K>;
349    type F = MontyFieldElement<K>;
350
351    type Zt = TestZipTypes<N, K, M>;
352    type C = IprsCode<Zt, TestIprsConfig, REP_FACTOR, CHECKED>;
353    static C: LazyLock<C> = LazyLock::new(|| C::new(IPRS_ROW_LEN, IPRS_DEPTH).unwrap());
354
355    type PolyZt = TestBinPolyZipTypes<K, M, DEGREE_PLUS_ONE>;
356    type PolyC = IprsCode<PolyZt, TestIprsConfig, REP_FACTOR, CHECKED>;
357    static POLY_C: LazyLock<PolyC> =
358        LazyLock::new(|| PolyC::new(IPRS_ROW_LEN, IPRS_DEPTH).unwrap());
359
360    type TestZip = ZipPlus<Zt, C>;
361    type TestPolyZip = ZipPlus<PolyZt, PolyC>;
362
363    #[test]
364    fn successful_verification_of_valid_proof() {
365        let num_vars = 10;
366        {
367            let (pp, comm, point_f, eval_f, mut transcript) =
368                setup_full_protocol::<Cfg, N, K, M>(num_vars);
369            let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
370
371            let result = TestZip::verify::<_, CHECKED>(
372                &mut transcript,
373                &pp,
374                &comm,
375                &field_cfg,
376                &point_f,
377                &eval_f,
378            );
379            assert!(result.is_ok(), "Verification failed: {result:?}")
380        };
381        {
382            let (pp, comm, point_f, eval_f, mut transcript) =
383                setup_full_protocol_poly::<Cfg, N, K, M, DEGREE_PLUS_ONE>(num_vars);
384            let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut transcript.fs_transcript);
385
386            let result = TestPolyZip::verify::<_, CHECKED>(
387                &mut transcript,
388                &pp,
389                &comm,
390                &field_cfg,
391                &point_f,
392                &eval_f,
393            );
394
395            assert!(result.is_ok(), "Verification failed: {result:?}");
396        }
397    }
398
399    #[test]
400    #[cfg_attr(miri, ignore)] // long running
401    fn verification_fails_with_incorrect_evaluation() {
402        let num_vars = 10;
403
404        {
405            let (pp, comm, point_f, eval_f, mut transcript) =
406                setup_full_protocol::<Cfg, N, K, M>(num_vars);
407            let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
408            let tampered = field_cfg.add(&eval_f, &field_cfg.one());
409
410            let result = TestZip::verify::<_, CHECKED>(
411                &mut transcript,
412                &pp,
413                &comm,
414                &field_cfg,
415                &point_f,
416                &tampered,
417            );
418
419            assert!(result.is_err());
420        }
421
422        {
423            let (pp, comm, point_f, eval_f, mut transcript) =
424                setup_full_protocol_poly::<Cfg, N, K, M, DEGREE_PLUS_ONE>(num_vars);
425            let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut transcript.fs_transcript);
426            let tampered = field_cfg.add(&eval_f, &field_cfg.one());
427
428            let result = TestPolyZip::verify::<_, CHECKED>(
429                &mut transcript,
430                &pp,
431                &comm,
432                &field_cfg,
433                &point_f,
434                &tampered,
435            );
436
437            assert!(result.is_err());
438        }
439    }
440
441    #[test]
442    #[cfg_attr(miri, ignore)] // long running
443    fn verification_fails_with_tampered_proof() {
444        fn tamper(mut proof: PcsVerifierTranscript) -> PcsVerifierTranscript {
445            let original_i0: Uint<K> = proof.clone().read_const_many(1).unwrap().remove(0);
446            // The b field elements are transcribed as canonical lifted
447            // integers (no length prefix, no modulus); flip a byte in the
448            // first element's value.
449            proof.stream.get_mut()[0] ^= 0x01;
450
451            // Sanity check that we didn't mess up the tampering
452            let tampered_i0: Uint<K> = proof.clone().read_const_many(1).unwrap().remove(0);
453            assert_ne!(original_i0, tampered_i0);
454
455            proof
456        }
457        let num_vars = 10;
458
459        {
460            let (pp, comm, point_f, eval_f, proof) = setup_full_protocol::<Cfg, N, K, M>(num_vars);
461            let mut tampered = tamper(proof);
462            let field_cfg = get_field_cfg::<Zt, Cfg>(&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::<Cfg, N, K, M, DEGREE_PLUS_ONE>(num_vars);
477            let mut tampered = tamper(proof);
478            let field_cfg = get_field_cfg::<PolyZt, Cfg>(&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::<Cfg, N, K, M>(num_vars);
498            let field_cfg = get_field_cfg::<Zt, Cfg>(&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::<Cfg, N, K, M, DEGREE_PLUS_ONE>(num_vars);
520            let field_cfg = get_field_cfg::<PolyZt, Cfg>(&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_padded)
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: &Cfg| {
558            let mut invalid_point = vec![];
559            for i in 0..=num_vars {
560                invalid_point.push(cfg.project(&Int::<N>::from(100 + i as i32)));
561            }
562            invalid_point
563        };
564
565        {
566            let (pp, comm, _point_f, eval_f, mut transcript) =
567                setup_full_protocol_poly::<Cfg, N, K, M, DEGREE_PLUS_ONE>(num_vars);
568            let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut transcript.fs_transcript);
569            let invalid_point = make_invalid_point(&field_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::<Cfg, N, K, M>(num_vars);
586            let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
587            let invalid_point = make_invalid_point(&field_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, Cfg>(&mut prover_transcript.fs_transcript);
618
619        let _eval_f = TestZip::prove_single::<Cfg, 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(&FixedConfig::default(), &point)
631            .expect("Failed to evaluate polynomial");
632
633        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
634        let eval_mle1_f = field_cfg.project(&eval_mle1);
635
636        let mut verifier_transcript = prover_transcript.into_verification_transcript();
637        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
638        let field_cfg = get_field_cfg::<Zt, Cfg>(&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, Cfg>(&mut prover_transcript.fs_transcript);
677
678        let eval_f = TestZip::prove_single::<Cfg, 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| field_cfg.project(v)).collect();
689
690        let mut verifier_transcript = prover_transcript.into_verification_transcript();
691        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
692        let field_cfg = get_field_cfg::<Zt, Cfg>(&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, Cfg>(&mut prover_transcript.fs_transcript);
719
720        let eval_f = TestZip::prove_single::<Cfg, 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 = field_cfg.add(&eval_f, &field_cfg.one());
731        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
732
733        let mut verifier_transcript = prover_transcript.into_verification_transcript();
734        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
735        let field_cfg = get_field_cfg::<Zt, Cfg>(&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, Cfg>(&mut prover_transcript.fs_transcript);
767
768        let eval_f = TestZip::prove_single::<Cfg, 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| field_cfg.project(v)).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 b_section_size = pp.num_rows * <Cfg as WithAssociatedInteger>::Integer::NUM_BYTES;
785        let bytes_per_comb_r = M * size_of::<crypto_bigint::Word>();
786        let combined_row_size = row_len * bytes_per_comb_r;
787        let column_values_start = b_section_size + combined_row_size;
788        let bytes_per_cw = K * size_of::<crypto_bigint::Word>();
789
790        let mut verifier_transcript = prover_transcript.into_verification_transcript();
791        assert!(
792            column_values_start + bytes_per_cw <= verifier_transcript.stream.get_ref().len(),
793            "proof too small to tamper column values"
794        );
795
796        let flip_at = column_values_start + bytes_per_cw / 2;
797        verifier_transcript.stream.get_mut()[flip_at] ^= 0x01;
798
799        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
800        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
801
802        let res = TestZip::verify::<_, CHECKED>(
803            &mut verifier_transcript,
804            &pp,
805            &comm,
806            &field_cfg,
807            &point_f,
808            &eval_f,
809        );
810
811        match res {
812            Err(ZipError::InvalidPcsOpen(msg)) => {
813                assert_eq!(msg, "Proximity failure");
814            }
815            Ok(()) => panic!("verification unexpectedly succeeded"),
816            Err(e) => panic!("unexpected error: {e:?}"),
817        }
818    }
819
820    #[test]
821    #[cfg_attr(miri, ignore)] // long running
822    fn verification_fails_if_evaluation_consistency_check_is_invalid() {
823        let num_vars = 10;
824        let poly_size: usize = 1 << num_vars;
825        let pp = TestZip::setup(poly_size, C.clone());
826
827        let mle: DenseMultilinearExtension<_> =
828            (0..poly_size as i32).map(Int::<INT_LIMBS>::from).collect();
829
830        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
831
832        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
833
834        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
835        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut prover_transcript.fs_transcript);
836
837        let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
838            &mut prover_transcript,
839            &pp,
840            &mle,
841            &point,
842            &hint,
843            &field_cfg,
844        )
845        .unwrap();
846
847        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
848
849        // The transcript starts with the raw b field elements. Flip a byte
850        // inside the first b element's value to corrupt eval consistency.
851        let flip_at = <Cfg as WithAssociatedInteger>::Integer::NUM_BYTES / 4;
852
853        let mut verifier_transcript = prover_transcript.into_verification_transcript();
854        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
855        get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
856        assert!(
857            flip_at < verifier_transcript.stream.get_ref().len(),
858            "proof too small to tamper b section"
859        );
860        verifier_transcript.stream.get_mut()[flip_at] ^= 0x01;
861
862        let res = TestZip::verify::<_, CHECKED>(
863            &mut verifier_transcript,
864            &pp,
865            &comm,
866            &field_cfg,
867            &point_f,
868            &eval_f,
869        );
870
871        match res {
872            Err(ZipError::InvalidPcsOpen(msg)) => {
873                assert_eq!(msg, "Evaluation consistency failure");
874            }
875            Ok(()) => panic!("verification unexpectedly succeeded"),
876            Err(e) => panic!("unexpected error: {e:?}"),
877        }
878    }
879
880    #[test]
881    fn verification_succeeds_for_zero_polynomial() {
882        let num_vars = 10;
883        let poly_size: usize = 1 << num_vars;
884        let pp = TestZip::setup(poly_size, C.clone());
885
886        let mle: DenseMultilinearExtension<_> = vec![Zero::zero(); poly_size].into_iter().collect();
887
888        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
889
890        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
891
892        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
893        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut prover_transcript.fs_transcript);
894
895        let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
896            &mut prover_transcript,
897            &pp,
898            &mle,
899            &point,
900            &hint,
901            &field_cfg,
902        )
903        .unwrap();
904
905        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
906
907        let mut verifier_transcript = prover_transcript.into_verification_transcript();
908        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
909        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
910
911        let res = TestZip::verify::<_, CHECKED>(
912            &mut verifier_transcript,
913            &pp,
914            &comm,
915            &field_cfg,
916            &point_f,
917            &eval_f,
918        );
919        assert!(res.is_ok());
920    }
921
922    #[test]
923    #[cfg_attr(miri, ignore)] // long running
924    fn verification_succeeds_at_zero_point() {
925        let num_vars = 10;
926        let poly_size: usize = 1 << num_vars;
927        let pp = TestZip::setup(poly_size, C.clone());
928
929        let mle: DenseMultilinearExtension<_> =
930            (1..=poly_size as i32).map(Int::<INT_LIMBS>::from).collect();
931
932        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
933
934        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
935
936        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
937        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut prover_transcript.fs_transcript);
938
939        let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
940            &mut prover_transcript,
941            &pp,
942            &mle,
943            &point,
944            &hint,
945            &field_cfg,
946        )
947        .unwrap();
948
949        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
950
951        let mut verifier_transcript = prover_transcript.into_verification_transcript();
952        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
953        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
954
955        let res = TestZip::verify::<_, CHECKED>(
956            &mut verifier_transcript,
957            &pp,
958            &comm,
959            &field_cfg,
960            &point_f,
961            &eval_f,
962        );
963        assert!(res.is_ok());
964    }
965
966    #[test]
967    #[cfg_attr(miri, ignore)] // long running
968    fn verification_succeeds_when_polynomial_coefficients_are_max_bit_size() {
969        let num_vars = 10;
970        let (pp, _) = setup_test_params(num_vars);
971
972        let mut evals: Vec<<Zt as ZipTypes>::Eval> =
973            (0..1 << num_vars as i32).map(Int::from).collect();
974        evals[1] = Int::from(i64::MAX);
975        let poly = DenseMultilinearExtension::from_evaluations_vec(num_vars, evals, Zero::zero());
976
977        let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
978
979        let mut point = vec![<Zt as ZipTypes>::Pt::ZERO; num_vars];
980        point[0] = <Zt as ZipTypes>::Pt::ONE;
981
982        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
983        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut prover_transcript.fs_transcript);
984
985        let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
986            &mut prover_transcript,
987            &pp,
988            &poly,
989            &point,
990            &hint,
991            &field_cfg,
992        )
993        .unwrap();
994
995        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
996
997        let mut verifier_transcript = prover_transcript.into_verification_transcript();
998        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
999        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
1000
1001        let verification_result = TestZip::verify::<_, CHECKED>(
1002            &mut verifier_transcript,
1003            &pp,
1004            &comm,
1005            &field_cfg,
1006            &point_f,
1007            &eval_f,
1008        );
1009
1010        assert!(
1011            verification_result.is_ok(),
1012            "Verification failed: {verification_result:?}",
1013        );
1014    }
1015
1016    #[test]
1017    #[cfg_attr(miri, ignore)] // long running
1018    fn verification_succeeds_with_minimal_polynomial_size_mu_is_8() {
1019        let num_vars = 10;
1020        let (pp, poly) = setup_test_params(num_vars);
1021
1022        let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
1023
1024        let point: Vec<<Zt as ZipTypes>::Pt> = (1..=num_vars as i32).map(Int::from).collect_vec();
1025
1026        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1027        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut prover_transcript.fs_transcript);
1028
1029        let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
1030            &mut prover_transcript,
1031            &pp,
1032            &poly,
1033            &point,
1034            &hint,
1035            &field_cfg,
1036        )
1037        .unwrap();
1038
1039        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
1040
1041        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1042        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
1043        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
1044
1045        let verification_result = TestZip::verify::<_, CHECKED>(
1046            &mut verifier_transcript,
1047            &pp,
1048            &comm,
1049            &field_cfg,
1050            &point_f,
1051            &eval_f,
1052        );
1053
1054        assert!(verification_result.is_ok());
1055    }
1056
1057    #[test]
1058    #[cfg_attr(miri, ignore)] // long running
1059    fn verification_succeeds_for_code_row_length_of_1() {
1060        let num_vars = 8;
1061        macro_rules! make_code {
1062            () => {
1063                IprsCode::new(1, 0).unwrap()
1064            };
1065        }
1066        {
1067            let (pp, comm, point_f, eval_f, mut transcript) =
1068                setup_full_protocol_inner::<Zt, C, Cfg, N>(
1069                    num_vars,
1070                    |num_vars| {
1071                        setup_test_params_inner(num_vars, make_code!(), |poly_size| {
1072                            (1..=poly_size as i32).map(Int::from).collect()
1073                        })
1074                    },
1075                    || (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect(),
1076                );
1077            let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
1078
1079            let result = TestZip::verify::<_, CHECKED>(
1080                &mut transcript,
1081                &pp,
1082                &comm,
1083                &field_cfg,
1084                &point_f,
1085                &eval_f,
1086            );
1087            assert!(result.is_ok(), "Verification failed: {result:?}")
1088        };
1089        {
1090            let (pp, comm, point_f, eval_f, mut transcript) =
1091                setup_full_protocol_inner::<PolyZt, PolyC, Cfg, N>(
1092                    num_vars,
1093                    |num_vars| {
1094                        setup_test_params_inner(num_vars, make_code!(), |poly_size| {
1095                            let degree = DEGREE_PLUS_ONE - 1;
1096                            let eval_coeffs: Vec<_> = (1..=(poly_size * degree) as i64)
1097                                .map(|v| v.is_odd().into())
1098                                .collect_vec();
1099                            eval_coeffs
1100                                .chunks_exact(degree)
1101                                .map(BinaryPoly::new_padded)
1102                                .collect_vec()
1103                        })
1104                    },
1105                    || (0..num_vars).map(|i| i as i128 + 2).collect(),
1106                );
1107            let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
1108
1109            let result = TestPolyZip::verify::<_, CHECKED>(
1110                &mut transcript,
1111                &pp,
1112                &comm,
1113                &field_cfg,
1114                &point_f,
1115                &eval_f,
1116            );
1117            assert!(result.is_ok(), "Verification failed: {result:?}")
1118        }
1119    }
1120
1121    #[test]
1122    #[cfg_attr(miri, ignore)] // long running
1123    fn verification_fails_at_proximity_link_check_if_combined_row_is_corrupted() {
1124        let num_vars = 10;
1125        let poly_size: usize = 1 << num_vars;
1126        let pp = TestZip::setup(poly_size, C.clone());
1127
1128        let mle: DenseMultilinearExtension<_> = (1..=poly_size as i32).map(Int::from).collect();
1129
1130        let (hint, comm) = TestZip::commit_single(&pp, &mle).expect("commit should succeed");
1131
1132        let point: Vec<<Zt as ZipTypes>::Pt> = vec![Zero::zero(); num_vars];
1133
1134        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1135        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut prover_transcript.fs_transcript);
1136
1137        let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
1138            &mut prover_transcript,
1139            &pp,
1140            &mle,
1141            &point,
1142            &hint,
1143            &field_cfg,
1144        )
1145        .unwrap();
1146
1147        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
1148
1149        // Offset past b section to reach combined_row (CombR = Int<M>).
1150        let b_section_size = pp.num_rows * <Cfg as WithAssociatedInteger>::Integer::NUM_BYTES;
1151        let bytes_to_corrupt = M * size_of::<crypto_bigint::Word>();
1152
1153        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1154        assert!(
1155            b_section_size + bytes_to_corrupt <= verifier_transcript.stream.get_ref().len(),
1156            "proof too small to tamper combined_row"
1157        );
1158
1159        for b in &mut verifier_transcript.stream.get_mut()
1160            [b_section_size..b_section_size + bytes_to_corrupt]
1161        {
1162            *b = 0xFF;
1163        }
1164
1165        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
1166        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
1167
1168        let res = TestZip::verify::<_, CHECKED>(
1169            &mut verifier_transcript,
1170            &pp,
1171            &comm,
1172            &field_cfg,
1173            &point_f,
1174            &eval_f,
1175        );
1176        assert!(res.is_err());
1177    }
1178
1179    /// Mirrors: `Zip/Verify: RandomField<4>, poly_size = 2^12 (Int limbs = 1)`
1180    #[test]
1181    #[cfg_attr(miri, ignore)] // long running
1182    fn bench_p12_verify() {
1183        fn inner<const P: usize>() {
1184            let mut rng = ThreadRng::default();
1185            // Match the benchmark's transcript usage for linear code construction
1186            let poly_size: usize = 1 << P;
1187            let pp = TestZip::setup(poly_size, C.clone());
1188
1189            let mle = DenseMultilinearExtension::rand(P, &mut rng);
1190            let (hint, commitment) = TestZip::commit_single(&pp, &mle).expect("commit");
1191
1192            let point = vec![1i64; P].iter().map(|v| v.into()).collect_vec();
1193
1194            let mut prover_transcript = PcsProverTranscript::new_from_commitment(&commitment);
1195            let field_cfg = get_field_cfg::<Zt, Cfg>(&mut prover_transcript.fs_transcript);
1196
1197            let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
1198                &mut prover_transcript,
1199                &pp,
1200                &mle,
1201                &point,
1202                &hint,
1203                &field_cfg,
1204            )
1205            .unwrap();
1206
1207            let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
1208
1209            let mut verifier_transcript = prover_transcript.into_verification_transcript();
1210            verifier_transcript
1211                .fs_transcript
1212                .absorb_bytes(&commitment.root);
1213            let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
1214
1215            let zero_f = field_cfg.zero();
1216            let mle_f = DenseMultilinearExtension::from_evaluations_vec(
1217                P,
1218                mle.iter().map(|c| field_cfg.project(c)).collect(),
1219                zero_f,
1220            );
1221            let expected_eval_f = mle_f.evaluate(&field_cfg, &point_f).unwrap();
1222            assert_eq!(eval_f, expected_eval_f, "prover returned wrong eval");
1223
1224            TestZip::verify::<_, CHECKED>(
1225                &mut verifier_transcript,
1226                &pp,
1227                &commitment,
1228                &field_cfg,
1229                &point_f,
1230                &eval_f,
1231            )
1232            .expect("verify");
1233        }
1234
1235        inner::<12>();
1236    }
1237
1238    /// Mirrors: `Zip+/Verify` for `poly_size=2^12`
1239    #[test]
1240    #[cfg_attr(miri, ignore)] // long running
1241    fn bench_p12_verify_poly() {
1242        fn inner<const P: usize>() {
1243            let mut rng = ThreadRng::default();
1244            // Match the benchmark's transcript usage for linear code construction
1245            let poly_size: usize = 1 << P;
1246            let pp = TestPolyZip::setup(poly_size, POLY_C.clone());
1247
1248            let mle = DenseMultilinearExtension::rand(P, &mut rng);
1249            let (hint, comm) = TestPolyZip::commit_single(&pp, &mle).expect("commit");
1250
1251            let point = vec![1i64; P].iter().map(|v| (*v).into()).collect_vec();
1252
1253            let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1254            let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut prover_transcript.fs_transcript);
1255
1256            let eval_f = TestPolyZip::prove_single::<Cfg, CHECKED>(
1257                &mut prover_transcript,
1258                &pp,
1259                &mle,
1260                &point,
1261                &hint,
1262                &field_cfg,
1263            )
1264            .unwrap();
1265
1266            let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
1267
1268            let mut verifier_transcript = prover_transcript.into_verification_transcript();
1269            verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
1270            let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut verifier_transcript.fs_transcript);
1271
1272            // Verifier replays verification from the same proof (also like the bench)
1273            TestPolyZip::verify::<_, CHECKED>(
1274                &mut verifier_transcript,
1275                &pp,
1276                &comm,
1277                &field_cfg,
1278                &point_f,
1279                &eval_f,
1280            )
1281            .expect("verify");
1282        }
1283
1284        inner::<12>();
1285    }
1286
1287    fn batched_prove_verify_inner<const BATCH: usize>(num_vars: usize) {
1288        let poly_size = 1 << num_vars;
1289        let pp = TestZip::setup(poly_size, C.clone());
1290
1291        let polys: Vec<DenseMultilinearExtension<_>> = (0..BATCH)
1292            .map(|b| {
1293                let base = (b * poly_size) as i32;
1294                (base + 1..=base + poly_size as i32)
1295                    .map(Int::<INT_LIMBS>::from)
1296                    .collect()
1297            })
1298            .collect();
1299
1300        let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
1301        let point: Vec<<Zt as ZipTypes>::Pt> =
1302            (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect();
1303
1304        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1305        let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut prover_transcript.fs_transcript);
1306
1307        let eval_f = TestZip::prove::<Cfg, CHECKED>(
1308            &mut prover_transcript,
1309            &pp,
1310            &polys,
1311            &point,
1312            &hint,
1313            &field_cfg,
1314        )
1315        .unwrap();
1316
1317        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
1318
1319        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1320        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
1321        let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut verifier_transcript.fs_transcript);
1322
1323        let res = TestZip::verify::<_, CHECKED>(
1324            &mut verifier_transcript,
1325            &pp,
1326            &comm,
1327            &field_cfg,
1328            &point_f,
1329            &eval_f,
1330        );
1331        assert!(
1332            res.is_ok(),
1333            "Batched verify (batch={BATCH}) failed: {res:?}"
1334        );
1335    }
1336
1337    #[test]
1338    fn batched_prove_verify_batch_2() {
1339        batched_prove_verify_inner::<2>(10);
1340    }
1341
1342    #[test]
1343    #[cfg_attr(miri, ignore)] // long running
1344    fn batched_prove_verify_batch_5() {
1345        batched_prove_verify_inner::<5>(10);
1346    }
1347
1348    #[test]
1349    #[cfg_attr(miri, ignore)] // long running
1350    fn batched_prove_verify_batch_1_roundtrip() {
1351        batched_prove_verify_inner::<1>(10);
1352    }
1353
1354    #[test]
1355    #[cfg_attr(miri, ignore)] // long running
1356    fn batched_verify_fails_with_tampered_eval() {
1357        let num_vars = 10;
1358        let poly_size = 1 << num_vars;
1359        let pp = TestZip::setup(poly_size, C.clone());
1360
1361        let polys: Vec<DenseMultilinearExtension<_>> = vec![
1362            (1..=poly_size as i32).map(Int::from).collect(),
1363            (17..=16 + poly_size as i32).map(Int::from).collect(),
1364        ];
1365
1366        let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
1367        let point: Vec<<Zt as ZipTypes>::Pt> = (0..num_vars).map(|i| Int::from(i + 2)).collect();
1368
1369        let mut prover_transcript = PcsProverTranscript::new_from_commitment(&comm);
1370        let field_cfg = get_field_cfg::<PolyZt, Cfg>(&mut prover_transcript.fs_transcript);
1371
1372        let eval_f = TestZip::prove::<Cfg, CHECKED>(
1373            &mut prover_transcript,
1374            &pp,
1375            &polys,
1376            &point,
1377            &hint,
1378            &field_cfg,
1379        )
1380        .unwrap();
1381        let tampered_eval = field_cfg.add(&eval_f, &field_cfg.one());
1382
1383        let point_f: Vec<F> = point.iter().map(|v| field_cfg.project(v)).collect();
1384
1385        let mut verifier_transcript = prover_transcript.into_verification_transcript();
1386        verifier_transcript.fs_transcript.absorb_bytes(&comm.root);
1387        let field_cfg = get_field_cfg::<Zt, Cfg>(&mut verifier_transcript.fs_transcript);
1388
1389        let res = TestZip::verify::<_, CHECKED>(
1390            &mut verifier_transcript,
1391            &pp,
1392            &comm,
1393            &field_cfg,
1394            &point_f,
1395            &tampered_eval,
1396        );
1397        assert!(res.is_err(), "Should fail when eval is tampered");
1398    }
1399}