Skip to main content

zinc_protocol/
verifier.rs

1use super::*;
2use crypto_primitives::{ConstIntSemiring, FromPrimitiveWithConfig, FromWithConfig};
3use num_traits::Zero;
4use std::{collections::HashMap, io::Cursor};
5use zinc_piop::{
6    combined_poly_resolver::{self, CombinedPolyResolver},
7    ideal_check::{self, IdealCheckProtocol},
8    multipoint_eval::{self, MultipointEval},
9    projections::{
10        ProjectedTrace, project_scalars, project_scalars_to_field, project_trace_coeffs_row_major,
11    },
12    sumcheck::multi_degree::MultiDegreeSumcheck,
13};
14use zinc_poly::{EvaluatablePolynomial, univariate::dynamic::over_field::DynamicPolynomialF};
15use zinc_transcript::{
16    Blake3Transcript,
17    traits::{ConstTranscribable, Transcript},
18};
19use zinc_uair::{
20    Uair, UairSignature, UairTrace,
21    constraint_counter::count_constraints,
22    ideal::{Ideal, IdealCheck},
23    ideal_collector::IdealOrZero,
24};
25use zinc_utils::{
26    add, from_ref::FromRef, inner_transparent_field::InnerTransparentField,
27    mul_by_scalar::MulByScalar, projectable_to_field::ProjectableToField,
28};
29use zip_plus::{
30    pcs::structs::{ZipPlus, ZipPlusParams, ZipTypes},
31    pcs_transcript::PcsVerifierTranscript,
32};
33
34//
35// Shared base
36//
37
38/// Persistent verifier infrastructure carried across every step.
39#[derive(Clone, Debug)]
40pub struct VerifierBase<'a, Zt: ZincTypes<D>, const D: usize> {
41    num_vars: usize,
42    uair_signature: UairSignature,
43    pcs_transcript: PcsVerifierTranscript,
44    public_trace: &'a UairTrace<'a, Zt::Int, Zt::Int, D>,
45
46    // Commitment info
47    vp_bin: &'a ZipPlusParams<Zt::BinaryZt, Zt::BinaryLc>,
48    vp_arb: &'a ZipPlusParams<Zt::ArbitraryZt, Zt::ArbitraryLc>,
49    vp_int: &'a ZipPlusParams<Zt::IntZt, Zt::IntLc>,
50}
51
52//
53// Type-state structs
54//
55
56/// After step 0 (transcript reconstruction).
57#[derive(Clone, Debug)]
58pub struct VerifierTranscriptReconstructed<
59    'a,
60    Zt: ZincTypes<D>,
61    U: Uair,
62    F: PrimeField,
63    IdealOverF,
64    const D: usize,
65> {
66    base: VerifierBase<'a, Zt, D>,
67
68    // Proof leftovers
69    proof_commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
70    proof_ideal_check: IdealCheckProof<F>,
71    proof_resolver: CombinedPolyResolverProof<F>,
72    proof_combined_sumcheck: MultiDegreeSumcheckProof<F>,
73    proof_multipoint_eval: MultipointEvalProof<F>,
74    proof_witness_lifted_evals: Vec<DynamicPolynomialF<F>>,
75    proof_lookup_proof: Option<BatchedLookupProof<F>>,
76    _phantom: PhantomData<(U, IdealOverF)>,
77}
78
79/// After step 1 (prime projection).
80#[derive(Clone, Debug)]
81pub struct VerifierPrimeProjected<
82    'a,
83    Zt: ZincTypes<D>,
84    U: Uair,
85    F: PrimeField,
86    IdealOverF,
87    const D: usize,
88> {
89    base: VerifierBase<'a, Zt, D>,
90    field_cfg: F::Config,
91
92    // Proof leftovers
93    proof_commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
94    proof_ideal_check: IdealCheckProof<F>,
95    proof_resolver: CombinedPolyResolverProof<F>,
96    proof_combined_sumcheck: MultiDegreeSumcheckProof<F>,
97    proof_multipoint_eval: MultipointEvalProof<F>,
98    proof_witness_lifted_evals: Vec<DynamicPolynomialF<F>>,
99    proof_lookup_proof: Option<BatchedLookupProof<F>>,
100    _phantom: PhantomData<(U, IdealOverF)>,
101}
102
103/// After step 2 (ideal check). `project_ideal` has been consumed.
104#[derive(Clone, Debug)]
105pub struct VerifierIdealChecked<
106    'a,
107    Zt: ZincTypes<D>,
108    U: Uair,
109    F: PrimeField,
110    IdealOverF,
111    const D: usize,
112> {
113    base: VerifierBase<'a, Zt, D>,
114    field_cfg: F::Config,
115    ic_subclaim: ideal_check::VerifierSubclaim<F>,
116
117    // Proof leftovers
118    proof_commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
119    proof_resolver: CombinedPolyResolverProof<F>,
120    proof_combined_sumcheck: MultiDegreeSumcheckProof<F>,
121    proof_multipoint_eval: MultipointEvalProof<F>,
122    proof_witness_lifted_evals: Vec<DynamicPolynomialF<F>>,
123    proof_lookup_proof: Option<BatchedLookupProof<F>>,
124    _phantom: PhantomData<(U, IdealOverF)>,
125}
126
127/// After step 3 (eval projection). `project_scalar` has been consumed.
128#[derive(Clone, Debug)]
129pub struct VerifierEvalProjected<
130    'a,
131    Zt: ZincTypes<D>,
132    U: Uair,
133    F: PrimeField,
134    IdealOverF,
135    const D: usize,
136> {
137    base: VerifierBase<'a, Zt, D>,
138    field_cfg: F::Config,
139    ic_subclaim: ideal_check::VerifierSubclaim<F>,
140    projecting_element_f: F,
141    projected_scalars_f: HashMap<U::Scalar, F>,
142
143    // Proof leftovers
144    proof_commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
145    proof_resolver: CombinedPolyResolverProof<F>,
146    proof_combined_sumcheck: MultiDegreeSumcheckProof<F>,
147    proof_multipoint_eval: MultipointEvalProof<F>,
148    proof_witness_lifted_evals: Vec<DynamicPolynomialF<F>>,
149    proof_lookup_proof: Option<BatchedLookupProof<F>>,
150    _phantom: PhantomData<(U, IdealOverF)>,
151}
152
153/// After step 4 (sumcheck verify).
154#[derive(Clone, Debug)]
155pub struct VerifierSumchecked<'a, Zt: ZincTypes<D>, F: PrimeField, IdealOverF, const D: usize> {
156    base: VerifierBase<'a, Zt, D>,
157    field_cfg: F::Config,
158    projecting_element_f: F,
159    cpr_subclaim: combined_poly_resolver::VerifierSubclaim<F>,
160
161    // Proof leftovers
162    proof_commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
163    proof_multipoint_eval: MultipointEvalProof<F>,
164    proof_witness_lifted_evals: Vec<DynamicPolynomialF<F>>,
165    proof_lookup_proof: Option<BatchedLookupProof<F>>,
166    _phantom: PhantomData<IdealOverF>,
167}
168
169/// After step 5 (multi-point eval).
170#[derive(Clone, Debug)]
171pub struct VerifierMultipointEvaled<'a, Zt: ZincTypes<D>, F: PrimeField, IdealOverF, const D: usize>
172{
173    base: VerifierBase<'a, Zt, D>,
174    field_cfg: F::Config,
175    projecting_element_f: F,
176    mp_subclaim: multipoint_eval::Subclaim<F>,
177
178    // Proof leftovers
179    proof_commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
180    proof_witness_lifted_evals: Vec<DynamicPolynomialF<F>>,
181    proof_lookup_proof: Option<BatchedLookupProof<F>>,
182    _phantom: PhantomData<IdealOverF>,
183}
184
185/// After step 6 (lifted evals verification).
186#[derive(Clone, Debug)]
187#[allow(dead_code)]
188pub struct VerifierLiftedEvalsChecked<
189    'a,
190    Zt: ZincTypes<D>,
191    F: PrimeField,
192    IdealOverF,
193    const D: usize,
194> {
195    base: VerifierBase<'a, Zt, D>,
196    field_cfg: F::Config,
197    mp_subclaim: multipoint_eval::Subclaim<F>,
198    all_lifted_evals: Vec<DynamicPolynomialF<F>>,
199
200    // Proof leftovers
201    proof_commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
202    proof_lookup_proof: Option<BatchedLookupProof<F>>,
203    _phantom: PhantomData<IdealOverF>,
204}
205
206/// After step 7 (PCS verify). Ready for
207/// [`finish`](VerifierPcsVerified::finish).
208#[derive(Clone, Debug)]
209pub struct VerifierPcsVerified<IdealOverF> {
210    _phantom: PhantomData<IdealOverF>,
211}
212
213//
214// Step implementations
215//
216
217impl<Zt, U, F, const D: usize> ZincPlusPiop<Zt, U, F, D>
218where
219    Zt: ZincTypes<D>,
220    U: Uair,
221    F: PrimeField,
222    F::Inner: ConstTranscribable,
223{
224    /// Step 0: Verifier entry point.
225    /// Reconstruct Fiat-Shamir transcript from commitments and public data.
226    #[allow(clippy::type_complexity)]
227    pub fn step0_reconstruct_transcript<'a, IdealOverF>(
228        (vp_bin, vp_arb, vp_int): &'a (
229            ZipPlusParams<Zt::BinaryZt, Zt::BinaryLc>,
230            ZipPlusParams<Zt::ArbitraryZt, Zt::ArbitraryLc>,
231            ZipPlusParams<Zt::IntZt, Zt::IntLc>,
232        ),
233        mut proof: Proof<F>,
234        public_trace: &'a UairTrace<'a, Zt::Int, Zt::Int, D>,
235        num_vars: usize,
236    ) -> Result<
237        VerifierTranscriptReconstructed<'a, Zt, U, F, IdealOverF, D>,
238        ProtocolError<F, IdealOverF>,
239    >
240    where
241        IdealOverF: Ideal,
242    {
243        let zip_proof = std::mem::take(&mut proof.zip);
244        let mut base = VerifierBase {
245            num_vars,
246            uair_signature: U::signature(),
247            public_trace,
248            pcs_transcript: PcsVerifierTranscript {
249                fs_transcript: Blake3Transcript::default(),
250                stream: Cursor::new(zip_proof),
251            },
252            vp_bin,
253            vp_arb,
254            vp_int,
255        };
256
257        for comm in [
258            &proof.commitments.0,
259            &proof.commitments.1,
260            &proof.commitments.2,
261        ] {
262            base.pcs_transcript.fs_transcript.absorb_slice(&comm.root);
263        }
264
265        absorb_public_columns(
266            &mut base.pcs_transcript.fs_transcript,
267            &base.public_trace.binary_poly,
268        );
269        absorb_public_columns(
270            &mut base.pcs_transcript.fs_transcript,
271            &base.public_trace.arbitrary_poly,
272        );
273        absorb_public_columns(
274            &mut base.pcs_transcript.fs_transcript,
275            &base.public_trace.int,
276        );
277
278        Ok(VerifierTranscriptReconstructed {
279            base,
280            proof_commitments: proof.commitments,
281            proof_ideal_check: proof.ideal_check,
282            proof_resolver: proof.resolver,
283            proof_combined_sumcheck: proof.combined_sumcheck,
284            proof_multipoint_eval: proof.multipoint_eval,
285            proof_witness_lifted_evals: proof.witness_lifted_evals,
286            proof_lookup_proof: proof.lookup_proof,
287            _phantom: PhantomData,
288        })
289    }
290}
291
292impl<'a, Zt, U, F, IdealOverF, const D: usize>
293    VerifierTranscriptReconstructed<'a, Zt, U, F, IdealOverF, D>
294where
295    Zt: ZincTypes<D>,
296    F: InnerTransparentField + FromPrimitiveWithConfig + FromRef<F> + Send + Sync + 'static,
297    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
298    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
299    U: Uair,
300    IdealOverF: Ideal,
301{
302    /// Step 1: Prime projection. Samples the random field configuration.
303    #[allow(clippy::type_complexity)]
304    pub fn step1_prime_projection(
305        mut self,
306    ) -> Result<VerifierPrimeProjected<'a, Zt, U, F, IdealOverF, D>, ProtocolError<F, IdealOverF>>
307    {
308        let field_cfg = self
309            .base
310            .pcs_transcript
311            .fs_transcript
312            .get_random_field_cfg::<F, Zt::Fmod, Zt::PrimeTest>();
313
314        Ok(VerifierPrimeProjected {
315            base: self.base,
316            field_cfg,
317            proof_commitments: self.proof_commitments,
318            proof_ideal_check: self.proof_ideal_check,
319            proof_resolver: self.proof_resolver,
320            proof_combined_sumcheck: self.proof_combined_sumcheck,
321            proof_multipoint_eval: self.proof_multipoint_eval,
322            proof_witness_lifted_evals: self.proof_witness_lifted_evals,
323            proof_lookup_proof: self.proof_lookup_proof,
324            _phantom: PhantomData,
325        })
326    }
327}
328
329impl<'a, Zt, U, F, IdealOverF, const D: usize> VerifierPrimeProjected<'a, Zt, U, F, IdealOverF, D>
330where
331    Zt: ZincTypes<D>,
332    Zt::Int: ProjectableToField<F>,
333    <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
334    F: InnerTransparentField
335        + FromPrimitiveWithConfig
336        + for<'b> FromWithConfig<&'b Zt::Int>
337        + for<'b> FromWithConfig<&'b Zt::CombR>
338        + for<'b> FromWithConfig<&'b Zt::Chal>
339        + for<'b> MulByScalar<&'b F>
340        + FromRef<F>
341        + Send
342        + Sync
343        + 'static,
344    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
345    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
346    U: Uair + 'static,
347    IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<F>>,
348{
349    /// Step 2: Ideal check verification. Consumes `project_ideal`.
350    #[allow(clippy::type_complexity)]
351    pub fn step2_ideal_check(
352        mut self,
353        project_ideal: impl Fn(&IdealOrZero<U::Ideal>, &F::Config) -> IdealOverF,
354    ) -> Result<VerifierIdealChecked<'a, Zt, U, F, IdealOverF, D>, ProtocolError<F, IdealOverF>>
355    {
356        let num_constraints = count_constraints::<U>();
357
358        let ic_subclaim = U::verify_as_subprotocol::<_, IdealOverF, _>(
359            &mut self.base.pcs_transcript.fs_transcript,
360            self.proof_ideal_check,
361            num_constraints,
362            self.base.num_vars,
363            |ideal| project_ideal(ideal, &self.field_cfg),
364            &self.field_cfg,
365        )?;
366
367        Ok(VerifierIdealChecked {
368            base: self.base,
369            field_cfg: self.field_cfg,
370            ic_subclaim,
371            proof_commitments: self.proof_commitments,
372            proof_resolver: self.proof_resolver,
373            proof_combined_sumcheck: self.proof_combined_sumcheck,
374            proof_multipoint_eval: self.proof_multipoint_eval,
375            proof_witness_lifted_evals: self.proof_witness_lifted_evals,
376            proof_lookup_proof: self.proof_lookup_proof,
377            _phantom: PhantomData,
378        })
379    }
380}
381
382impl<'a, Zt, U, F, IdealOverF, const D: usize> VerifierIdealChecked<'a, Zt, U, F, IdealOverF, D>
383where
384    Zt: ZincTypes<D>,
385    F: InnerTransparentField
386        + for<'b> FromWithConfig<&'b Zt::Chal>
387        + FromRef<F>
388        + Send
389        + Sync
390        + 'static,
391    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
392    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
393    U: Uair + 'static,
394    IdealOverF: Ideal,
395{
396    /// Step 3: Evaluation projection. Consumes `project_scalar`.
397    pub fn step3_eval_projection(
398        mut self,
399        project_scalar: impl Fn(&U::Scalar, &F::Config) -> DynamicPolynomialF<F>,
400    ) -> Result<VerifierEvalProjected<'a, Zt, U, F, IdealOverF, D>, ProtocolError<F, IdealOverF>>
401    {
402        let projecting_element: Zt::Chal = self.base.pcs_transcript.fs_transcript.get_challenge();
403        let projecting_element_f: F = F::from_with_cfg(&projecting_element, &self.field_cfg);
404
405        let projected_scalars_fx = project_scalars::<F, U>(|s| project_scalar(s, &self.field_cfg));
406        let projected_scalars_f =
407            project_scalars_to_field(projected_scalars_fx, &projecting_element_f)
408                .map_err(|(_s, _f, e)| ProtocolError::ScalarProjection(e))?;
409
410        Ok(VerifierEvalProjected {
411            base: self.base,
412            field_cfg: self.field_cfg,
413            ic_subclaim: self.ic_subclaim,
414            projecting_element_f,
415            projected_scalars_f,
416            proof_commitments: self.proof_commitments,
417            proof_resolver: self.proof_resolver,
418            proof_combined_sumcheck: self.proof_combined_sumcheck,
419            proof_multipoint_eval: self.proof_multipoint_eval,
420            proof_witness_lifted_evals: self.proof_witness_lifted_evals,
421            proof_lookup_proof: self.proof_lookup_proof,
422            _phantom: PhantomData,
423        })
424    }
425}
426
427impl<'a, Zt, U, F, IdealOverF, const D: usize> VerifierEvalProjected<'a, Zt, U, F, IdealOverF, D>
428where
429    Zt: ZincTypes<D>,
430    Zt::Int: ProjectableToField<F>,
431    <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
432    F: InnerTransparentField
433        + FromPrimitiveWithConfig
434        + for<'b> FromWithConfig<&'b Zt::Int>
435        + for<'b> FromWithConfig<&'b Zt::CombR>
436        + for<'b> FromWithConfig<&'b Zt::Chal>
437        + for<'b> MulByScalar<&'b F>
438        + FromRef<F>
439        + Send
440        + Sync
441        + 'static,
442    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
443    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
444    U: Uair + 'static,
445    IdealOverF: Ideal,
446{
447    /// Step 4: Sumcheck verification (CPR + lookup groups).
448    pub fn step4_sumcheck_verify(
449        mut self,
450    ) -> Result<VerifierSumchecked<'a, Zt, F, IdealOverF, D>, ProtocolError<F, IdealOverF>> {
451        let num_constraints = count_constraints::<U>();
452
453        let cpr_verifier_ancillary = CombinedPolyResolver::prepare_verifier::<U>(
454            &mut self.base.pcs_transcript.fs_transcript,
455            &self.proof_resolver,
456            self.proof_combined_sumcheck.claimed_sums()[0].clone(),
457            &self.ic_subclaim,
458            num_constraints,
459            self.base.num_vars,
460            &self.projecting_element_f,
461            &self.field_cfg,
462        )?;
463
464        let md_subclaims = MultiDegreeSumcheck::verify_as_subprotocol(
465            &mut self.base.pcs_transcript.fs_transcript,
466            self.base.num_vars,
467            &self.proof_combined_sumcheck,
468            &self.field_cfg,
469        )
470        .map_err(CombinedPolyResolverError::SumcheckError)?;
471
472        let cpr_subclaim = CombinedPolyResolver::finalize_verifier::<U>(
473            &mut self.base.pcs_transcript.fs_transcript,
474            self.proof_resolver,
475            md_subclaims.point().to_vec(),
476            md_subclaims.expected_evaluations()[0].clone(),
477            cpr_verifier_ancillary,
478            &self.projected_scalars_f,
479            &self.field_cfg,
480        )?;
481
482        let _ = &self.proof_lookup_proof;
483
484        Ok(VerifierSumchecked {
485            base: self.base,
486            field_cfg: self.field_cfg,
487            projecting_element_f: self.projecting_element_f,
488            cpr_subclaim,
489            proof_commitments: self.proof_commitments,
490            proof_multipoint_eval: self.proof_multipoint_eval,
491            proof_witness_lifted_evals: self.proof_witness_lifted_evals,
492            proof_lookup_proof: self.proof_lookup_proof,
493            _phantom: PhantomData,
494        })
495    }
496}
497
498impl<'a, Zt, F, IdealOverF, const D: usize> VerifierSumchecked<'a, Zt, F, IdealOverF, D>
499where
500    Zt: ZincTypes<D>,
501    F: InnerTransparentField + FromPrimitiveWithConfig + FromRef<F> + Send + Sync + 'static,
502    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
503    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
504    IdealOverF: Ideal,
505{
506    /// Step 5: Multi-point evaluation sumcheck.
507    pub fn step5_multipoint_eval<U: Uair>(
508        mut self,
509    ) -> Result<VerifierMultipointEvaled<'a, Zt, F, IdealOverF, D>, ProtocolError<F, IdealOverF>>
510    {
511        let mp_subclaim = MultipointEval::verify_as_subprotocol(
512            &mut self.base.pcs_transcript.fs_transcript,
513            self.proof_multipoint_eval,
514            &self.cpr_subclaim.evaluation_point,
515            &self.cpr_subclaim.up_evals,
516            &self.cpr_subclaim.down_evals,
517            self.base.uair_signature.shifts(),
518            self.base.num_vars,
519            &self.field_cfg,
520        )?;
521
522        Ok(VerifierMultipointEvaled {
523            base: self.base,
524            field_cfg: self.field_cfg,
525            projecting_element_f: self.projecting_element_f,
526            mp_subclaim,
527            proof_commitments: self.proof_commitments,
528            proof_witness_lifted_evals: self.proof_witness_lifted_evals,
529            proof_lookup_proof: self.proof_lookup_proof,
530            _phantom: PhantomData,
531        })
532    }
533}
534
535impl<'a, Zt, F, IdealOverF, const D: usize> VerifierMultipointEvaled<'a, Zt, F, IdealOverF, D>
536where
537    Zt: ZincTypes<D>,
538    Zt::Int: ProjectableToField<F>,
539    <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
540    F: InnerTransparentField
541        + FromPrimitiveWithConfig
542        + for<'b> FromWithConfig<&'b Zt::Int>
543        + for<'b> FromWithConfig<&'b Zt::Chal>
544        + for<'b> MulByScalar<&'b F>
545        + FromRef<F>
546        + Send
547        + Sync
548        + 'static,
549    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
550    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
551    IdealOverF: Ideal,
552{
553    /// Step 6: Recompute public lifted_evals, assemble full set, verify
554    /// multipoint eval subclaim, and absorb all lifted_evals into transcript.
555    pub fn step6_lifted_evals<U: Uair>(
556        mut self,
557    ) -> Result<VerifierLiftedEvalsChecked<'a, Zt, F, IdealOverF, D>, ProtocolError<F, IdealOverF>>
558    {
559        let r_0 = &self.mp_subclaim.sumcheck_subclaim.point;
560
561        let pub_cols = self.base.uair_signature.public_cols();
562        let num_pub_bin = pub_cols.num_binary_poly_cols();
563        let num_pub_arb = pub_cols.num_arbitrary_poly_cols();
564        let num_pub_int = pub_cols.num_int_cols();
565
566        let wit_cols = self.base.uair_signature.witness_cols();
567        let num_wit_bin = wit_cols.num_binary_poly_cols();
568        let num_wit_arb = wit_cols.num_arbitrary_poly_cols();
569
570        let public_lifted = if add!(add!(num_pub_bin, num_pub_arb), num_pub_int) > 0 {
571            let projected_public = project_trace_coeffs_row_major::<F, Zt::Int, Zt::Int, D>(
572                self.base.public_trace,
573                &self.field_cfg,
574            );
575            compute_lifted_evals::<F, D>(
576                r_0,
577                &self.base.public_trace.binary_poly,
578                &ProjectedTrace::RowMajor(projected_public),
579                &self.field_cfg,
580            )
581        } else {
582            Vec::new()
583        };
584
585        let witness_lifted_evals = &self.proof_witness_lifted_evals;
586
587        let all_lifted_evals: Vec<_> = public_lifted[..num_pub_bin]
588            .iter()
589            .chain(&witness_lifted_evals[..num_wit_bin])
590            .chain(&public_lifted[num_pub_bin..add!(num_pub_bin, num_pub_arb)])
591            .chain(&witness_lifted_evals[num_wit_bin..add!(num_wit_bin, num_wit_arb)])
592            .chain(&public_lifted[add!(num_pub_bin, num_pub_arb)..])
593            .chain(&witness_lifted_evals[add!(num_wit_bin, num_wit_arb)..])
594            .cloned()
595            .collect();
596
597        let open_evals: Vec<F> = all_lifted_evals
598            .iter()
599            .map(|bar_u| bar_u.evaluate_at_point(&self.projecting_element_f))
600            .collect::<Result<Vec<_>, _>>()
601            .map_err(ProtocolError::LiftedEvalProjection)?;
602
603        MultipointEval::verify_subclaim(
604            &self.mp_subclaim,
605            &open_evals,
606            self.base.uair_signature.shifts(),
607            &self.field_cfg,
608        )?;
609
610        let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
611        for bar_u in &all_lifted_evals {
612            self.base
613                .pcs_transcript
614                .fs_transcript
615                .absorb_random_field_slice(&bar_u.coeffs, &mut transcription_buf);
616        }
617
618        Ok(VerifierLiftedEvalsChecked {
619            base: self.base,
620            field_cfg: self.field_cfg,
621            mp_subclaim: self.mp_subclaim,
622            all_lifted_evals,
623            proof_commitments: self.proof_commitments,
624            proof_lookup_proof: self.proof_lookup_proof,
625            _phantom: PhantomData,
626        })
627    }
628}
629
630impl<'a, Zt, F, IdealOverF, const D: usize> VerifierLiftedEvalsChecked<'a, Zt, F, IdealOverF, D>
631where
632    Zt: ZincTypes<D>,
633    Zt::Int: ProjectableToField<F>,
634    <Zt::BinaryZt as ZipTypes>::Cw: ProjectableToField<F>,
635    <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
636    <Zt::ArbitraryZt as ZipTypes>::Cw: ProjectableToField<F>,
637    <Zt::IntZt as ZipTypes>::Cw: ProjectableToField<F>,
638    F: InnerTransparentField
639        + FromPrimitiveWithConfig
640        + for<'b> FromWithConfig<&'b Zt::Int>
641        + for<'b> FromWithConfig<&'b Zt::CombR>
642        + for<'b> FromWithConfig<&'b Zt::Chal>
643        + for<'b> MulByScalar<&'b F>
644        + FromRef<F>
645        + Send
646        + Sync
647        + 'static,
648    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
649    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
650    IdealOverF: Ideal,
651{
652    /// Step 7: PCS verification at `r_0` (witness columns only).
653    pub fn step7_pcs_verify<U: Uair, const CHECK_FOR_OVERFLOW: bool>(
654        mut self,
655    ) -> Result<VerifierPcsVerified<IdealOverF>, ProtocolError<F, IdealOverF>> {
656        let r_0 = &self.mp_subclaim.sumcheck_subclaim.point;
657        let commitments = &self.proof_commitments;
658
659        let pub_cols = self.base.uair_signature.public_cols();
660        let num_pub_bin = pub_cols.num_binary_poly_cols();
661        let num_pub_arb = pub_cols.num_arbitrary_poly_cols();
662        let num_pub_int = pub_cols.num_int_cols();
663
664        let total = self.base.uair_signature.total_cols();
665        let num_total_bin = total.num_binary_poly_cols();
666        let num_total_arb = total.num_arbitrary_poly_cols();
667
668        let pcs_transcript = &mut self.base.pcs_transcript;
669        let field_cfg = &self.field_cfg;
670        let all_lifted_evals = &self.all_lifted_evals;
671
672        macro_rules! verify_pcs_batch {
673            ($Zt:ty, $Lc:ty, $vp:expr, $idx:tt, [$evals_range:expr]) => {{
674                let comm = &commitments.$idx;
675                if comm.batch_size > 0 {
676                    let per_poly_alphas = ZipPlus::<$Zt, $Lc>::sample_alphas(
677                        &mut pcs_transcript.fs_transcript,
678                        comm.batch_size,
679                    );
680                    let mut eval_f = F::zero_with_cfg(field_cfg);
681                    for (bar_u, alphas) in all_lifted_evals[$evals_range]
682                        .iter()
683                        .zip(per_poly_alphas.iter())
684                    {
685                        for (coeff, alpha) in bar_u.coeffs.iter().zip(alphas.iter()) {
686                            let mut term = F::from_with_cfg(alpha, field_cfg);
687                            term *= coeff;
688                            eval_f += &term;
689                        }
690                    }
691                    ZipPlus::<$Zt, $Lc>::verify_with_alphas::<F, CHECK_FOR_OVERFLOW>(
692                        pcs_transcript,
693                        $vp,
694                        comm,
695                        field_cfg,
696                        r_0,
697                        &eval_f,
698                        &per_poly_alphas,
699                    )
700                    .map_err(|e| ProtocolError::PcsVerification($idx, e))?;
701                }
702            }};
703        }
704
705        verify_pcs_batch!(
706            Zt::BinaryZt,
707            Zt::BinaryLc,
708            self.base.vp_bin,
709            0,
710            [num_pub_bin..num_total_bin]
711        );
712        verify_pcs_batch!(
713            Zt::ArbitraryZt,
714            Zt::ArbitraryLc,
715            self.base.vp_arb,
716            1,
717            [add!(num_total_bin, num_pub_arb)..add!(num_total_bin, num_total_arb)]
718        );
719        verify_pcs_batch!(
720            Zt::IntZt,
721            Zt::IntLc,
722            self.base.vp_int,
723            2,
724            [add!(add!(num_total_bin, num_total_arb), num_pub_int)..]
725        );
726
727        Ok(VerifierPcsVerified {
728            _phantom: PhantomData,
729        })
730    }
731}
732
733impl<IdealOverF: Ideal> VerifierPcsVerified<IdealOverF> {
734    /// Complete verification.
735    pub fn finish<F: PrimeField>(self) -> Result<(), ProtocolError<F, IdealOverF>> {
736        Ok(())
737    }
738}
739
740// ── verify() wrapper ───────────────────────────────────────────────────
741
742impl<Zt, U, F, const D: usize> ZincPlusPiop<Zt, U, F, D>
743where
744    Zt: ZincTypes<D>,
745    Zt::Int: ProjectableToField<F>,
746    <Zt::BinaryZt as ZipTypes>::Cw: ProjectableToField<F>,
747    <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
748    <Zt::ArbitraryZt as ZipTypes>::Cw: ProjectableToField<F>,
749    <Zt::IntZt as ZipTypes>::Cw: ProjectableToField<F>,
750    F: InnerTransparentField
751        + FromPrimitiveWithConfig
752        + for<'a> FromWithConfig<&'a Zt::Int>
753        + for<'a> FromWithConfig<&'a Zt::CombR>
754        + for<'a> FromWithConfig<&'a Zt::Chal>
755        + for<'a> MulByScalar<&'a F>
756        + FromRef<F>
757        + Send
758        + Sync
759        + 'static,
760    F::Inner: ConstIntSemiring + ConstTranscribable + Send + Sync + Zero + Default,
761    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
762    U: Uair + 'static,
763{
764    /// Zinc+ full PIOP verifier.
765    ///
766    /// Runs all verification steps in sequence and returns `Ok(())` on
767    /// success. For per-step control, starts with
768    /// [`Self::step0_reconstruct_transcript`] and chain the individual
769    /// `stepN_*` methods.
770    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
771    pub fn verify<IdealOverF, const CHECK_FOR_OVERFLOW: bool>(
772        vp: &(
773            ZipPlusParams<Zt::BinaryZt, Zt::BinaryLc>,
774            ZipPlusParams<Zt::ArbitraryZt, Zt::ArbitraryLc>,
775            ZipPlusParams<Zt::IntZt, Zt::IntLc>,
776        ),
777        proof: Proof<F>,
778        public_trace: &UairTrace<Zt::Int, Zt::Int, D>,
779        num_vars: usize,
780        project_scalar: impl Fn(&U::Scalar, &F::Config) -> DynamicPolynomialF<F>,
781        project_ideal: impl Fn(&IdealOrZero<U::Ideal>, &F::Config) -> IdealOverF,
782    ) -> Result<(), ProtocolError<F, IdealOverF>>
783    where
784        IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<F>>,
785    {
786        ZincPlusPiop::<Zt, U, F, D>::step0_reconstruct_transcript::<IdealOverF>(
787            vp,
788            proof,
789            public_trace,
790            num_vars,
791        )?
792        .step1_prime_projection()?
793        .step2_ideal_check(project_ideal)?
794        .step3_eval_projection(project_scalar)?
795        .step4_sumcheck_verify()?
796        .step5_multipoint_eval::<U>()?
797        .step6_lifted_evals::<U>()?
798        .step7_pcs_verify::<U, CHECK_FOR_OVERFLOW>()?
799        .finish::<F>()
800    }
801}