Skip to main content

zinc_protocol/
prover.rs

1use super::*;
2use crypto_primitives::{ConstIntSemiring, FromPrimitiveWithConfig, FromWithConfig};
3use num_traits::Zero;
4use std::{collections::HashMap, fmt::Debug};
5use zinc_piop::{
6    combined_poly_resolver::CombinedPolyResolver,
7    ideal_check::IdealCheckProtocol,
8    multipoint_eval::{MultipointEval, Proof as MultipointEvalProof},
9    projections::{
10        ColumnMajorTrace, ProjectedTrace, RowMajorTrace, evaluate_trace_to_column_mles,
11        project_scalars, project_scalars_to_field, project_trace_coeffs_column_major,
12        project_trace_coeffs_row_major,
13    },
14    sumcheck::multi_degree::MultiDegreeSumcheck,
15};
16use zinc_poly::univariate::dynamic::over_field::DynamicPolynomialF;
17use zinc_transcript::traits::{ConstTranscribable, Transcript};
18use zinc_uair::{
19    Uair, UairSignature, UairTrace, constraint_counter::count_constraints,
20    degree_counter::count_max_degree,
21};
22use zinc_utils::{
23    add, cfg_join, from_ref::FromRef, inner_transparent_field::InnerTransparentField,
24    mul_by_scalar::MulByScalar, projectable_to_field::ProjectableToField,
25};
26use zip_plus::{
27    pcs::structs::{ZipPlus, ZipPlusHint, ZipPlusParams, ZipTypes},
28    pcs_transcript::PcsProverTranscript,
29};
30
31//
32// Shared base
33//
34
35/// Persistent prover infrastructure carried across every step: the
36/// Fiat-Shamir transcript, PCS parameters/hints/commitments, and trace
37/// reference.
38#[derive(Clone, Debug)]
39pub struct ProverBase<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
40    num_vars: usize,
41    uair_signature: UairSignature,
42    pcs_transcript: PcsProverTranscript,
43    trace: &'a UairTrace<'static, Zt::Int, Zt::Int, D>,
44
45    // Commitment info
46    pp_bin: &'a ZipPlusParams<Zt::BinaryZt, Zt::BinaryLc>,
47    pp_arb: &'a ZipPlusParams<Zt::ArbitraryZt, Zt::ArbitraryLc>,
48    pp_int: &'a ZipPlusParams<Zt::IntZt, Zt::IntLc>,
49    hint_bin: Option<ZipPlusHint<<Zt::BinaryZt as ZipTypes>::Cw>>,
50    hint_arb: Option<ZipPlusHint<<Zt::ArbitraryZt as ZipTypes>::Cw>>,
51    hint_int: Option<ZipPlusHint<<Zt::IntZt as ZipTypes>::Cw>>,
52    commitment_bin: ZipPlusCommitment,
53    commitment_arb: ZipPlusCommitment,
54    commitment_int: ZipPlusCommitment,
55
56    _phantom: PhantomData<(U, F)>,
57}
58
59//
60// Type-state structs
61//
62
63/// After step 1 via [`step1_combined`](ProverCommitted::step1_combined)
64/// (row-major / "combined" projection). `project_scalar` has been consumed.
65#[derive(Clone, Debug)]
66pub struct ProverProjectedCombined<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
67    base: ProverBase<'a, Zt, U, F, D>,
68    field_cfg: F::Config,
69    projected_trace: RowMajorTrace<F>,
70    projected_scalars_fx: HashMap<U::Scalar, DynamicPolynomialF<F>>,
71}
72
73/// After step 1 via [`step1_mle_first`](ProverCommitted::step1_mle_first)
74/// (column-major / MLE-first projection). `project_scalar` has been consumed.
75#[derive(Clone, Debug)]
76pub struct ProverProjectedMleFirst<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
77    base: ProverBase<'a, Zt, U, F, D>,
78    field_cfg: F::Config,
79    projected_trace: ColumnMajorTrace<F>,
80    projected_scalars_fx: HashMap<U::Scalar, DynamicPolynomialF<F>>,
81}
82
83/// After step 2 (ideal check).
84#[derive(Clone, Debug)]
85pub struct ProverIdealChecked<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
86    base: ProverBase<'a, Zt, U, F, D>,
87    field_cfg: F::Config,
88    projected_trace: ProjectedTrace<F>,
89    projected_scalars_fx: HashMap<U::Scalar, DynamicPolynomialF<F>>,
90
91    // New
92    ic_proof: IdealCheckProof<F>,
93    ic_eval_point: Vec<F>,
94}
95
96/// After step 3 (eval projection). `projected_scalars_fx` has been consumed.
97#[derive(Clone, Debug)]
98pub struct ProverEvalProjected<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
99    base: ProverBase<'a, Zt, U, F, D>,
100    field_cfg: F::Config,
101    projected_trace: ProjectedTrace<F>,
102    ic_proof: IdealCheckProof<F>,
103    ic_eval_point: Vec<F>,
104
105    // New
106    projected_trace_f: Vec<DenseMultilinearExtension<F::Inner>>,
107    projected_scalars_f: HashMap<U::Scalar, F>,
108}
109
110/// After step 4 (sumcheck).
111#[allow(clippy::type_complexity)]
112#[derive(Clone, Debug)]
113pub struct ProverSumchecked<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
114    base: ProverBase<'a, Zt, U, F, D>,
115    field_cfg: F::Config,
116    projected_trace: ProjectedTrace<F>,
117    ic_proof: IdealCheckProof<F>,
118    projected_trace_f: Vec<DenseMultilinearExtension<F::Inner>>,
119
120    // New
121    cpr_proof: CombinedPolyResolverProof<F>,
122    cpr_eval_point: Vec<F>,
123    combined_sumcheck: MultiDegreeSumcheckProof<F>,
124    lookup_proof: Option<BatchedLookupProof<F>>,
125}
126
127/// After step 5 (multipoint eval).
128#[derive(Clone, Debug)]
129pub struct ProverMultipointEvaled<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
130    base: ProverBase<'a, Zt, U, F, D>,
131    field_cfg: F::Config,
132    projected_trace: ProjectedTrace<F>,
133    ic_proof: IdealCheckProof<F>,
134    cpr_proof: CombinedPolyResolverProof<F>,
135    combined_sumcheck: MultiDegreeSumcheckProof<F>,
136    lookup_proof: Option<BatchedLookupProof<F>>,
137
138    // New
139    mp_proof: MultipointEvalProof<F>,
140    r_0: Vec<F>,
141}
142
143/// After step 6 (lift-and-project).
144#[derive(Clone, Debug)]
145pub struct ProverLifted<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
146    base: ProverBase<'a, Zt, U, F, D>,
147    field_cfg: F::Config,
148    ic_proof: IdealCheckProof<F>,
149    cpr_proof: CombinedPolyResolverProof<F>,
150    combined_sumcheck: MultiDegreeSumcheckProof<F>,
151    lookup_proof: Option<BatchedLookupProof<F>>,
152    mp_proof: MultipointEvalProof<F>,
153    r_0: Vec<F>,
154
155    // New
156    lifted_evals: Vec<DynamicPolynomialF<F>>,
157}
158
159/// After step 7 (PCS open). No new fields are added here, but the PCS
160/// transcript has been updated with the opening proof.
161/// Ready for generating the final proof object in
162/// [`finish`](ProverPcsOpened::finish).
163#[derive(Clone, Debug)]
164pub struct ProverPcsOpened<'a, Zt: ZincTypes<D>, U: Uair, F: PrimeField, const D: usize> {
165    base: ProverBase<'a, Zt, U, F, D>,
166    ic_proof: IdealCheckProof<F>,
167    cpr_proof: CombinedPolyResolverProof<F>,
168    combined_sumcheck: MultiDegreeSumcheckProof<F>,
169    lookup_proof: Option<BatchedLookupProof<F>>,
170    mp_proof: MultipointEvalProof<F>,
171    lifted_evals: Vec<DynamicPolynomialF<F>>,
172}
173
174//
175// Step implementations
176//
177
178/// Prover uses common type bounds across all steps, so we use a helper macro to
179/// define them
180macro_rules! impl_with_type_bounds {
181    ($type_name:ident { $($code:tt)* }) => {
182        impl<'a, Zt, U, F, const D: usize> $type_name<'a, Zt, U, F, D>
183        where
184            Zt: ZincTypes<D>,
185            Zt::Int: ProjectableToField<F>,
186            <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
187            U: Uair + 'static,
188            F: InnerTransparentField
189                + FromPrimitiveWithConfig
190                + for<'b> FromWithConfig<&'b Zt::Int>
191                + for<'b> FromWithConfig<&'b Zt::CombR>
192                + for<'b> FromWithConfig<&'b Zt::Chal>
193                + for<'b> MulByScalar<&'b F>
194                + FromRef<F>
195                + Send
196                + Sync
197                + 'static,
198            F::Inner:
199                ConstIntSemiring + ConstTranscribable + FromRef<Zt::Fmod> + Send + Sync + Zero + Default,
200            F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
201        {
202            $($code)*
203        }
204    };
205}
206
207impl<Zt, U, F, const D: usize> ZincPlusPiop<Zt, U, F, D>
208where
209    Zt: ZincTypes<D>,
210    U: Uair,
211    F: PrimeField,
212    F::Inner: ConstTranscribable,
213{
214    /// Step 0: Prover entry point.
215    /// Commit *witness* columns via Zip+ PCS, absorb roots and public
216    /// data into the Fiat-Shamir transcript.
217    #[allow(clippy::type_complexity)]
218    pub fn step0_commit<'a>(
219        (pp_bin, pp_arb, pp_int): &'a (
220            ZipPlusParams<Zt::BinaryZt, Zt::BinaryLc>,
221            ZipPlusParams<Zt::ArbitraryZt, Zt::ArbitraryLc>,
222            ZipPlusParams<Zt::IntZt, Zt::IntLc>,
223        ),
224        trace: &'a UairTrace<'static, Zt::Int, Zt::Int, D>,
225        num_vars: usize,
226    ) -> Result<ProverBase<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
227        let uair_signature = U::signature();
228        let public_trace = trace.public(&uair_signature);
229        let witness_trace = trace.witness(&uair_signature);
230
231        let (res_bin, (res_arb, res_int)) = cfg_join!(
232            commit_optionally(pp_bin, &witness_trace.binary_poly),
233            commit_optionally(pp_arb, &witness_trace.arbitrary_poly),
234            commit_optionally(pp_int, &witness_trace.int),
235        );
236        let (hint_bin, commitment_bin) = res_bin?;
237        let (hint_arb, commitment_arb) = res_arb?;
238        let (hint_int, commitment_int) = res_int?;
239
240        let mut pcs_transcript = PcsProverTranscript::new_from_commitments(
241            [&commitment_bin, &commitment_arb, &commitment_int].into_iter(),
242        );
243
244        absorb_public_columns(&mut pcs_transcript.fs_transcript, &public_trace.binary_poly);
245        absorb_public_columns(
246            &mut pcs_transcript.fs_transcript,
247            &public_trace.arbitrary_poly,
248        );
249        absorb_public_columns(&mut pcs_transcript.fs_transcript, &public_trace.int);
250
251        Ok(ProverBase {
252            num_vars,
253            uair_signature,
254            pcs_transcript,
255            trace,
256            pp_bin,
257            pp_arb,
258            pp_int,
259            hint_bin,
260            hint_arb,
261            hint_int,
262            commitment_bin,
263            commitment_arb,
264            commitment_int,
265            _phantom: PhantomData,
266        })
267    }
268}
269
270impl_with_type_bounds!(ProverBase
271{
272    #[allow(clippy::type_complexity)]
273    fn project_common<S: Fn(&U::Scalar, &F::Config) -> DynamicPolynomialF<F>>(
274        &mut self,
275        project_scalar: S,
276    ) -> Result<(F::Config, HashMap<U::Scalar, DynamicPolynomialF<F>>), ProtocolError<F, U::Ideal>>
277    {
278        let field_cfg = self
279            .pcs_transcript
280            .fs_transcript
281            .get_random_field_cfg::<F, Zt::Fmod, Zt::PrimeTest>();
282
283        let projected_scalars_fx = project_scalars::<F, U>(|s| project_scalar(s, &field_cfg));
284        Ok((field_cfg, projected_scalars_fx))
285    }
286
287    /// Step 1 (combined / row-major): Prime projection
288    /// (`\phi_q`: `Z[X] -> F_q[X]`). Samples a random prime, projects the
289    /// full trace and scalars using the row-major layout.
290    /// Works for both linear and non-linear constraints.
291    pub fn step1_combined<S: Fn(&U::Scalar, &F::Config) -> DynamicPolynomialF<F>>(
292        mut self,
293        project_scalar: S,
294    ) -> Result<ProverProjectedCombined<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
295        let (field_cfg, projected_scalars_fx) = self.project_common(project_scalar)?;
296
297        let projected_trace = project_trace_coeffs_row_major(self.trace, &field_cfg);
298        Ok(ProverProjectedCombined {
299            base: self,
300            field_cfg,
301            projected_trace,
302            projected_scalars_fx,
303        })
304    }
305
306    /// Step 1 (MLE-first / column-major): Prime projection
307    /// (`\phi_q`: `Z[X] -> F_q[X]`). Samples a random prime, projects the
308    /// full trace and scalars using the column-major layout.
309    /// Only suitable for linear constraints.
310    pub fn step1_mle_first<S: Fn(&U::Scalar, &F::Config) -> DynamicPolynomialF<F>>(
311        mut self,
312        project_scalar: S,
313    ) -> Result<ProverProjectedMleFirst<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
314        let (field_cfg, projected_scalars_fx) = self.project_common(project_scalar)?;
315
316        let projected_trace = project_trace_coeffs_column_major(self.trace, &field_cfg);
317        Ok(ProverProjectedMleFirst {
318            base: self,
319            field_cfg,
320            projected_trace,
321            projected_scalars_fx,
322        })
323    }
324});
325
326impl_with_type_bounds!(ProverProjectedCombined
327{
328    /// Step 2 (combined): Ideal check via `prove_combined` on the row-major
329    /// trace. Works for both linear and non-linear constraints.
330    pub fn step2_ideal_check(
331        mut self,
332    ) -> Result<ProverIdealChecked<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
333        let num_constraints = count_constraints::<U>();
334
335        let (ic_proof, ic_prover_state) = U::prove_combined(
336            &mut self.base.pcs_transcript.fs_transcript,
337            &self.projected_trace,
338            &self.projected_scalars_fx,
339            num_constraints,
340            self.base.num_vars,
341            &self.field_cfg,
342        )?;
343
344        Ok(ProverIdealChecked {
345            base: self.base,
346            field_cfg: self.field_cfg,
347            projected_trace: ProjectedTrace::RowMajor(self.projected_trace),
348            projected_scalars_fx: self.projected_scalars_fx,
349            ic_proof,
350            ic_eval_point: ic_prover_state.evaluation_point,
351        })
352    }
353});
354
355impl_with_type_bounds!(ProverProjectedMleFirst
356{
357    /// Step 2 (MLE-first): Ideal check via `prove_linear` on the column-major
358    /// trace. Only suitable for linear constraints.
359    pub fn step2_ideal_check(
360        mut self,
361    ) -> Result<ProverIdealChecked<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
362        let num_constraints = count_constraints::<U>();
363
364        let (ic_proof, ic_prover_state) = U::prove_linear(
365            &mut self.base.pcs_transcript.fs_transcript,
366            &self.projected_trace,
367            &self.projected_scalars_fx,
368            num_constraints,
369            self.base.num_vars,
370            &self.field_cfg,
371        )?;
372
373        Ok(ProverIdealChecked {
374            base: self.base,
375            field_cfg: self.field_cfg,
376            projected_trace: ProjectedTrace::ColumnMajor(self.projected_trace),
377            projected_scalars_fx: self.projected_scalars_fx,
378            ic_proof,
379            ic_eval_point: ic_prover_state.evaluation_point,
380        })
381    }
382});
383
384impl_with_type_bounds!(ProverIdealChecked
385{
386    /// Step 3: Evaluation projection (`\psi_a`: `F_q[X] -> F_q`). Samples
387    /// `a in F_q`, evaluates polynomials at `X = a`.
388    pub fn step3_eval_projection(
389        mut self,
390    ) -> Result<ProverEvalProjected<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
391        let projecting_element: Zt::Chal = self.base.pcs_transcript.fs_transcript.get_challenge();
392        let projecting_element_f: F = F::from_with_cfg(&projecting_element, &self.field_cfg);
393
394        let projected_trace_f =
395            evaluate_trace_to_column_mles(&self.projected_trace, &projecting_element_f);
396
397        let projected_scalars_f =
398            project_scalars_to_field(self.projected_scalars_fx, &projecting_element_f)
399                .map_err(|(_s, _f, e)| ProtocolError::ScalarProjection(e))?;
400
401        Ok(ProverEvalProjected {
402            base: self.base,
403            field_cfg: self.field_cfg,
404            projected_trace: self.projected_trace,
405            ic_proof: self.ic_proof,
406            ic_eval_point: self.ic_eval_point,
407            projected_trace_f,
408            projected_scalars_f,
409        })
410    }
411});
412
413impl_with_type_bounds!(ProverEvalProjected
414{
415    /// Step 4: Combined CPR + Lookup multi-degree sumcheck over F_q.
416    /// Batches the CPR constraint claim (degree `max_deg+2`) with lookup groups
417    /// (one per table type) into a single sumcheck sharing one evaluation point `r*`.
418    /// Produces `up_evals` and `down_evals` (CPR) and lookup auxiliary witnesses at `r*`.
419    pub fn step4_sumcheck(
420        mut self,
421    ) -> Result<ProverSumchecked<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
422        let num_constraints = count_constraints::<U>();
423        let max_degree = count_max_degree::<U>();
424
425        let (cpr_group, cpr_ancillary) = CombinedPolyResolver::prepare_sumcheck_group::<U>(
426            &mut self.base.pcs_transcript.fs_transcript,
427            self.projected_trace_f.clone(),
428            &self.ic_eval_point,
429            &self.projected_scalars_f,
430            num_constraints,
431            self.base.num_vars,
432            max_degree,
433            &self.field_cfg,
434        )?;
435
436        // 4b: Lookup prepare — placeholder
437        let lookup_specs = &self.base.uair_signature;
438        let groups = vec![cpr_group];
439        // TODO: for each LookupGroup from group_lookup_specs(lookup_specs):
440        //   - call prepare_batched_lookup_group(transcript, instance, &field_cfg)
441        //   - push triple into groups, collect pending proofs + metas
442        let _ = lookup_specs; // suppress unused warning until logup is implemented
443
444        // 4c: Multi-degree sumcheck width CPR group and lookup groups
445        let (combined_sumcheck, md_states) = MultiDegreeSumcheck::prove_as_subprotocol(
446            &mut self.base.pcs_transcript.fs_transcript,
447            groups,
448            self.base.num_vars,
449            &self.field_cfg,
450        );
451        // 4c: Finalize up_evals, down_evals
452        let (cpr_proof, cpr_prover_state) = CombinedPolyResolver::finalize_prover(
453            &mut self.base.pcs_transcript.fs_transcript,
454            md_states.into_iter().next().expect("one CPR group"),
455            cpr_ancillary,
456            &self.field_cfg,
457        )?;
458
459        // TODO: build BatchedLookupProof from collected lookup_proofs + lookup_metas
460        let lookup_proof = None;
461
462        Ok(ProverSumchecked {
463            base: self.base,
464            field_cfg: self.field_cfg,
465            projected_trace: self.projected_trace,
466            ic_proof: self.ic_proof,
467            projected_trace_f: self.projected_trace_f,
468            cpr_proof,
469            cpr_eval_point: cpr_prover_state.evaluation_point,
470            combined_sumcheck,
471            lookup_proof,
472        })
473    }
474});
475
476impl_with_type_bounds!(ProverSumchecked
477{
478    /// Step 5: Multi-point evaluation sumcheck. Combines `up_evals` and
479    /// `down_evals` at `r'` into a single evaluation point `r_0`.
480    /// Only the sumcheck proof is sent; scalar evaluations at `r_0` are derived from the
481    /// polynomial-valued `lifted_evals` in Step 6
482    pub fn step5_multipoint_eval(
483        mut self,
484    ) -> Result<ProverMultipointEvaled<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
485        let (mp_proof, mp_prover_state) = MultipointEval::prove_as_subprotocol(
486            &mut self.base.pcs_transcript.fs_transcript,
487            &self.projected_trace_f,
488            &self.cpr_eval_point,
489            &self.cpr_proof.up_evals,
490            &self.cpr_proof.down_evals,
491            self.base.uair_signature.shifts(),
492            &self.field_cfg,
493        )?;
494
495        Ok(ProverMultipointEvaled {
496            base: self.base,
497            field_cfg: self.field_cfg,
498            projected_trace: self.projected_trace,
499            ic_proof: self.ic_proof,
500            cpr_proof: self.cpr_proof,
501            combined_sumcheck: self.combined_sumcheck,
502            lookup_proof: self.lookup_proof,
503            mp_proof,
504            r_0: mp_prover_state.eval_point,
505        })
506    }
507});
508
509impl_with_type_bounds!(ProverMultipointEvaled
510{
511    /// Step 6: Lift-and-project. Computes per-column polynomial MLE
512    /// evaluations at `r_0` in `F_q[X]` and absorbs them into the transcript.
513    pub fn step6_lift_and_project(
514        mut self,
515    ) -> Result<ProverLifted<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
516        // Compute per-column polynomial MLE evaluations at r_0 in F_q[X]
517        // (after \phi_q but before \psi_a). The verifier derives the scalar
518        // open_evals via \psi_a for the sumcheck consistency check, and
519        // supplies these to the Zip+ PCS for alpha-projection.
520        let lifted_evals = compute_lifted_evals::<F, D>(
521            &self.r_0,
522            &self.base.trace.binary_poly,
523            &self.projected_trace,
524            &self.field_cfg,
525        );
526
527        let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
528        for bar_u in &lifted_evals {
529            self.base
530                .pcs_transcript
531                .fs_transcript
532                .absorb_random_field_slice(&bar_u.coeffs, &mut transcription_buf);
533        }
534
535        Ok(ProverLifted {
536            base: self.base,
537            field_cfg: self.field_cfg,
538            ic_proof: self.ic_proof,
539            cpr_proof: self.cpr_proof,
540            combined_sumcheck: self.combined_sumcheck,
541            lookup_proof: self.lookup_proof,
542            mp_proof: self.mp_proof,
543            r_0: self.r_0,
544            lifted_evals,
545        })
546    }
547});
548
549impl_with_type_bounds!(ProverLifted
550{
551    /// Step 7: PCS open at `r_0` (witness columns only).
552    pub fn step7_pcs_open<const CHECK_FOR_OVERFLOW: bool>(
553        mut self,
554    ) -> Result<ProverPcsOpened<'a, Zt, U, F, D>, ProtocolError<F, U::Ideal>> {
555        let witness_trace = self.base.trace.witness(&self.base.uair_signature);
556
557        if let Some(hint_bin) = &self.base.hint_bin {
558            let _ = ZipPlus::<Zt::BinaryZt, Zt::BinaryLc>::prove_f::<_, CHECK_FOR_OVERFLOW>(
559                &mut self.base.pcs_transcript,
560                self.base.pp_bin,
561                &witness_trace.binary_poly,
562                &self.r_0,
563                hint_bin,
564                &self.field_cfg,
565            )?;
566        }
567        if let Some(hint_arb) = &self.base.hint_arb {
568            let _ = ZipPlus::<Zt::ArbitraryZt, Zt::ArbitraryLc>::prove_f::<_, CHECK_FOR_OVERFLOW>(
569                &mut self.base.pcs_transcript,
570                self.base.pp_arb,
571                &witness_trace.arbitrary_poly,
572                &self.r_0,
573                hint_arb,
574                &self.field_cfg,
575            )?;
576        }
577        if let Some(hint_int) = &self.base.hint_int {
578            let _ = ZipPlus::<Zt::IntZt, Zt::IntLc>::prove_f::<_, CHECK_FOR_OVERFLOW>(
579                &mut self.base.pcs_transcript,
580                self.base.pp_int,
581                &witness_trace.int,
582                &self.r_0,
583                hint_int,
584                &self.field_cfg,
585            )?;
586        }
587
588        Ok(ProverPcsOpened {
589            base: self.base,
590            ic_proof: self.ic_proof,
591            cpr_proof: self.cpr_proof,
592            combined_sumcheck: self.combined_sumcheck,
593            lookup_proof: self.lookup_proof,
594            mp_proof: self.mp_proof,
595            lifted_evals: self.lifted_evals,
596        })
597    }
598});
599
600impl_with_type_bounds!(ProverPcsOpened
601{
602    /// Assemble the final proof from accumulated state.
603    pub fn finish(self) -> Result<Proof<F>, ProtocolError<F, U::Ideal>> {
604        let sig = self.base.uair_signature;
605        let zip_proof = self.base.pcs_transcript.stream.into_inner();
606        let commitments = (
607            self.base.commitment_bin,
608            self.base.commitment_arb,
609            self.base.commitment_int,
610        );
611
612        let lifted_evals = self.lifted_evals;
613
614        // Extract witness-only lifted evals (public columns come first in trace).
615        let pub_cols = sig.public_cols();
616        let num_pub_bin = pub_cols.num_binary_poly_cols();
617        let num_pub_arb = pub_cols.num_arbitrary_poly_cols();
618        let num_pub_int = pub_cols.num_int_cols();
619        let total = sig.total_cols();
620        let num_total_bin = total.num_binary_poly_cols();
621        let num_total_arb = total.num_arbitrary_poly_cols();
622        let witness = sig.witness_cols();
623        let witness_arb_offset = add!(num_total_bin, num_pub_arb);
624        let witness_arb_end = add!(witness_arb_offset, witness.num_arbitrary_poly_cols());
625        let witness_int_offset = add!(add!(num_total_bin, num_total_arb), num_pub_int);
626        let witness_lifted_evals: Vec<_> = lifted_evals[num_pub_bin..num_total_bin]
627            .iter()
628            .chain(&lifted_evals[witness_arb_offset..witness_arb_end])
629            .chain(&lifted_evals[witness_int_offset..])
630            .cloned()
631            .collect();
632
633        Ok(Proof {
634            commitments,
635            ideal_check: self.ic_proof,
636            resolver: self.cpr_proof,
637            combined_sumcheck: self.combined_sumcheck,
638            multipoint_eval: self.mp_proof,
639            zip: zip_proof,
640            witness_lifted_evals,
641            lookup_proof: self.lookup_proof,
642        })
643    }
644});
645
646//
647// prove() wrapper
648//
649
650impl<Zt, U, F, const D: usize> ZincPlusPiop<Zt, U, F, D>
651where
652    Zt: ZincTypes<D>,
653    Zt::Int: ProjectableToField<F>,
654    <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
655    F: InnerTransparentField
656        + FromPrimitiveWithConfig
657        + for<'a> FromWithConfig<&'a Zt::Int>
658        + for<'a> FromWithConfig<&'a Zt::CombR>
659        + for<'a> FromWithConfig<&'a Zt::Chal>
660        + for<'a> FromWithConfig<&'a Zt::Pt>
661        + for<'a> MulByScalar<&'a F>
662        + FromRef<F>
663        + Send
664        + Sync
665        + 'static,
666    F::Inner:
667        ConstIntSemiring + ConstTranscribable + FromRef<Zt::Fmod> + Send + Sync + Zero + Default,
668    F::Modulus: ConstTranscribable + FromRef<Zt::Fmod>,
669    U: Uair + 'static,
670{
671    /// Zinc+ full PIOP prover.
672    ///
673    /// Runs all protocol steps in sequence and returns the assembled proof.
674    /// For per-step control, start with [`Self::step0_commit`] and chain the
675    /// individual `stepN_*` methods.
676    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
677    pub fn prove<const MLE_FIRST: bool, const CHECK_FOR_OVERFLOW: bool>(
678        pp: &(
679            ZipPlusParams<Zt::BinaryZt, Zt::BinaryLc>,
680            ZipPlusParams<Zt::ArbitraryZt, Zt::ArbitraryLc>,
681            ZipPlusParams<Zt::IntZt, Zt::IntLc>,
682        ),
683        trace: &UairTrace<'static, Zt::Int, Zt::Int, D>,
684        num_vars: usize,
685        project_scalar: impl Fn(&U::Scalar, &F::Config) -> DynamicPolynomialF<F>,
686    ) -> Result<Proof<F>, ProtocolError<F, U::Ideal>> {
687        let committed = Self::step0_commit(pp, trace, num_vars)?;
688
689        let ideal_checked = if MLE_FIRST {
690            committed
691                .step1_mle_first(project_scalar)?
692                .step2_ideal_check()?
693        } else {
694            committed
695                .step1_combined(project_scalar)?
696                .step2_ideal_check()?
697        };
698
699        ideal_checked
700            .step3_eval_projection()?
701            .step4_sumcheck()?
702            .step5_multipoint_eval()?
703            .step6_lift_and_project()?
704            .step7_pcs_open::<CHECK_FOR_OVERFLOW>()?
705            .finish()
706    }
707}
708
709#[allow(clippy::type_complexity)]
710fn commit_optionally<Zt: ZipTypes, Lc: LinearCode<Zt>>(
711    pp: &ZipPlusParams<Zt, Lc>,
712    trace: &[DenseMultilinearExtension<Zt::Eval>],
713) -> Result<(Option<ZipPlusHint<Zt::Cw>>, ZipPlusCommitment), ZipError> {
714    if trace.is_empty() {
715        Ok((
716            None,
717            ZipPlusCommitment {
718                root: Default::default(),
719                batch_size: 0,
720            },
721        ))
722    } else {
723        let (hint, commitment) = ZipPlus::commit(pp, trace)?;
724        Ok((Some(hint), commitment))
725    }
726}