Skip to main content

zinc_protocol/
verifier.rs

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