1pub mod fold;
30pub mod prover;
31pub mod shared_challenge;
32pub mod verifier;
33
34#[cfg(feature = "parallel")]
35use rayon::prelude::*;
36
37use crate::fold::FoldTrace;
38use crypto_primitives::{
39 BaseFieldConfig, ConstIntRing, ConstIntSemiring, ProjectElementWithConfig,
40 ProjectPrimitiveIntegersWithConfig, Semiring, SetElement, Wrapper,
41};
42use std::{
43 fmt::{Debug, Display},
44 iter,
45 marker::PhantomData,
46};
47use thiserror::Error;
48use zinc_piop::{
49 combined_poly_resolver::{CombinedPolyResolverError, Proof as CombinedPolyResolverProof},
50 ideal_check::{IdealCheckError, Proof as IdealCheckProof},
51 lookup::{
52 BatchedLookupProof, LookupError,
53 booleanity::{BooleanityError, BooleanityProof},
54 },
55 multipoint_eval::{MultipointEvalError, Proof as MultipointEvalProof},
56 projections::ProjectedTrace,
57 sumcheck::multi_degree::MultiDegreeSumcheckProof,
58};
59use zinc_poly::{
60 ConstCoeffBitWidth, EvaluationError as PolyEvaluationError,
61 mle::DenseMultilinearExtension,
62 univariate::{
63 binary::BinaryPoly,
64 dense::DensePolynomial,
65 dynamic::{DynamicPolyVec, DynamicPolynomial, HasDynamicPolynomialConfig},
66 },
67};
68use zinc_primality::PrimalityTest;
69use zinc_transcript::{
70 TranscriptError,
71 traits::{ConstTranscribable, GenTranscribable, Transcribable, Transcript},
72};
73use zinc_uair::{Uair, UairSignature};
74use zinc_utils::{cfg_extend, cfg_into_iter, cfg_iter, from_ref::FromRef, named::Named, powers};
75use zip_plus::{
76 ZipError,
77 code::LinearCode,
78 pcs::structs::{ZipPlusCommitment, ZipTypes},
79};
80
81#[derive(Clone, Debug, PartialEq, Eq)]
96pub struct Proof<F> {
97 pub commitments: (ZipPlusCommitment, ZipPlusCommitment, ZipPlusCommitment),
99 pub zip: Vec<u8>,
101 pub ideal_check: IdealCheckProof<F>,
103 pub cpr_proof: CombinedPolyResolverProof<F>,
105 pub combined_sumcheck: MultiDegreeSumcheckProof<F>,
107 pub multipoint_eval: MultipointEvalProof<F>,
110 pub witness_lifted_evals: Vec<Vec<DynamicPolynomial<F>>>,
130 pub lookup_proof: Option<BatchedLookupProof<F>>,
132 pub booleanity_proof: Option<BooleanityProof<F>>,
136 pub affine_booleanity_proof: Option<BooleanityProof<F>>,
139 pub ideal_checks_fq: Vec<IdealCheckProof<F>>,
143 pub cpr_proofs_fq: Vec<CombinedPolyResolverProof<F>>,
147 pub combined_sumchecks_fq: Vec<MultiDegreeSumcheckProof<F>>,
151 pub multipoint_evals_fq: Vec<MultipointEvalProof<F>>,
155 pub witness_lifted_evals_pp: Option<Vec<DynamicPolynomial<F>>>,
168}
169
170fn read_optional_booleanity_proof<F>(bytes: &[u8]) -> (Option<BooleanityProof<F>>, &[u8])
171where
172 F: ConstTranscribable,
173{
174 let (presence, bytes) = u32::read_transcription_bytes_subset(bytes);
175 if presence == 0 {
176 (None, bytes)
177 } else {
178 let (proof, bytes) = BooleanityProof::read_transcription_bytes_subset(bytes);
179 (Some(proof), bytes)
180 }
181}
182
183fn write_optional_booleanity_proof<'a, F>(
184 proof: &Option<BooleanityProof<F>>,
185 mut buf: &'a mut [u8],
186) -> &'a mut [u8]
187where
188 F: ConstTranscribable,
189{
190 buf = u32::from(proof.is_some()).write_transcription_bytes_subset(buf);
191 if let Some(proof) = proof {
192 buf = proof.write_transcription_bytes_subset(buf);
193 }
194 buf
195}
196
197#[allow(clippy::arithmetic_side_effects)]
198fn optional_booleanity_proof_num_bytes<F>(proof: &Option<BooleanityProof<F>>) -> usize
199where
200 F: ConstTranscribable,
201{
202 proof.as_ref().map_or(0, |proof| {
203 BooleanityProof::<F>::LENGTH_NUM_BYTES + proof.get_num_bytes()
204 })
205}
206
207impl<F> GenTranscribable for Proof<F>
208where
209 F: ConstTranscribable,
210{
211 fn read_transcription_bytes_exact(bytes: &[u8]) -> Self {
212 let (commit0, bytes) = ZipPlusCommitment::read_transcription_bytes_subset(bytes);
213 let (commit1, bytes) = ZipPlusCommitment::read_transcription_bytes_subset(bytes);
214 let (commit2, bytes) = ZipPlusCommitment::read_transcription_bytes_subset(bytes);
215
216 let (zip_len, bytes) = u32::read_transcription_bytes_subset(bytes);
217 let zip_len = usize::try_from(zip_len).expect("zip length must fit into usize");
218 let (zip_bytes, bytes) = bytes.split_at(zip_len);
219 let zip = zip_bytes.to_vec();
220
221 let (ideal_check, bytes) = IdealCheckProof::<F>::read_transcription_bytes_subset(bytes);
222 let (resolver, bytes) =
223 CombinedPolyResolverProof::<F>::read_transcription_bytes_subset(bytes);
224 let (combined_sumcheck, bytes) =
225 MultiDegreeSumcheckProof::<F>::read_transcription_bytes_subset(bytes);
226 let (multipoint_eval, bytes) =
227 MultipointEvalProof::<F>::read_transcription_bytes_subset(bytes);
228
229 let (n_wlf, mut bytes) = u32::read_transcription_bytes_subset(bytes);
233 let n_wlf = usize::try_from(n_wlf).expect("n_wlf must fit into usize");
234 let mut witness_lifted_evals: Vec<Vec<DynamicPolynomial<F>>> = Vec::with_capacity(n_wlf);
235 for _ in 0..n_wlf {
236 let (wv, rest) = DynamicPolyVec::<F>::read_transcription_bytes_subset(bytes);
237 witness_lifted_evals.push(wv.0);
238 bytes = rest;
239 }
240
241 let (booleanity_proof, bytes) = read_optional_booleanity_proof(bytes);
242 let (affine_booleanity_proof, bytes) = read_optional_booleanity_proof(bytes);
243
244 let (n_fq, mut bytes) = u32::read_transcription_bytes_subset(bytes);
247 let n_fq = usize::try_from(n_fq).expect("n_fq must fit into usize");
248 let mut ideal_checks_fq: Vec<IdealCheckProof<F>> = Vec::with_capacity(n_fq);
249 for _ in 0..n_fq {
250 let (ic, rest) = IdealCheckProof::<F>::read_transcription_bytes_subset(bytes);
251 ideal_checks_fq.push(ic);
252 bytes = rest;
253 }
254
255 let (n_cpr_fq, mut bytes) = u32::read_transcription_bytes_subset(bytes);
257 let n_cpr_fq = usize::try_from(n_cpr_fq).expect("n_cpr_fq must fit into usize");
258 let mut cpr_proofs_fq: Vec<CombinedPolyResolverProof<F>> = Vec::with_capacity(n_cpr_fq);
259 for _ in 0..n_cpr_fq {
260 let (cpr, rest) =
261 CombinedPolyResolverProof::<F>::read_transcription_bytes_subset(bytes);
262 cpr_proofs_fq.push(cpr);
263 bytes = rest;
264 }
265
266 let (n_sum_fq, mut bytes) = u32::read_transcription_bytes_subset(bytes);
268 let n_sum_fq = usize::try_from(n_sum_fq).expect("n_sum_fq must fit into usize");
269 let mut combined_sumchecks_fq: Vec<MultiDegreeSumcheckProof<F>> =
270 Vec::with_capacity(n_sum_fq);
271 for _ in 0..n_sum_fq {
272 let (sumcheck, rest) =
273 MultiDegreeSumcheckProof::<F>::read_transcription_bytes_subset(bytes);
274 combined_sumchecks_fq.push(sumcheck);
275 bytes = rest;
276 }
277
278 let (n_mp_fq, mut bytes) = u32::read_transcription_bytes_subset(bytes);
280 let n_mp_fq = usize::try_from(n_mp_fq).expect("n_mp_fq must fit into usize");
281 let mut multipoint_evals_fq: Vec<MultipointEvalProof<F>> = Vec::with_capacity(n_mp_fq);
282 for _ in 0..n_mp_fq {
283 let (mp, rest) = MultipointEvalProof::<F>::read_transcription_bytes_subset(bytes);
284 multipoint_evals_fq.push(mp);
285 bytes = rest;
286 }
287
288 let (presence, bytes) = u32::read_transcription_bytes_subset(bytes);
291 let (witness_lifted_evals_pp, bytes) = if presence != 0 {
292 let (p, rest) = DynamicPolyVec::<F>::read_transcription_bytes_subset(bytes);
293 (Some(p.0), rest)
294 } else {
295 (None, bytes)
296 };
297
298 assert!(bytes.is_empty(), "All bytes should be consumed");
301
302 Self {
303 commitments: (commit0, commit1, commit2),
304 zip,
305 ideal_check,
306 cpr_proof: resolver,
307 combined_sumcheck,
308 multipoint_eval,
309 witness_lifted_evals,
310 lookup_proof: None,
311 booleanity_proof,
312 affine_booleanity_proof,
313 ideal_checks_fq,
314 cpr_proofs_fq,
315 combined_sumchecks_fq,
316 multipoint_evals_fq,
317 witness_lifted_evals_pp,
318 }
319 }
320
321 fn write_transcription_bytes_exact(&self, mut buf: &mut [u8]) {
322 buf = self.commitments.0.write_transcription_bytes_subset(buf);
324 buf = self.commitments.1.write_transcription_bytes_subset(buf);
325 buf = self.commitments.2.write_transcription_bytes_subset(buf);
326
327 let zip_len = u32::try_from(self.zip.len()).expect("zip length must fit into u32");
329 buf = zip_len.write_transcription_bytes_subset(buf);
330 buf[..self.zip.len()].copy_from_slice(&self.zip);
331 buf = &mut buf[self.zip.len()..];
332
333 buf = self.ideal_check.write_transcription_bytes_subset(buf);
335
336 buf = self.cpr_proof.write_transcription_bytes_subset(buf);
338
339 buf = self.combined_sumcheck.write_transcription_bytes_subset(buf);
341
342 buf = self.multipoint_eval.write_transcription_bytes_subset(buf);
344
345 let n_wlf = u32::try_from(self.witness_lifted_evals.len())
350 .expect("witness_lifted_evals length must fit into u32");
351 buf = n_wlf.write_transcription_bytes_subset(buf);
352 for wlf in &self.witness_lifted_evals {
353 buf = DynamicPolyVec::reinterpret(wlf).write_transcription_bytes_subset(buf);
354 }
355
356 buf = write_optional_booleanity_proof(&self.booleanity_proof, buf);
357 buf = write_optional_booleanity_proof(&self.affine_booleanity_proof, buf);
358
359 let n_fq = u32::try_from(self.ideal_checks_fq.len())
361 .expect("ideal_checks_fq length must fit into u32");
362 buf = n_fq.write_transcription_bytes_subset(buf);
363 for ic in &self.ideal_checks_fq {
364 buf = ic.write_transcription_bytes_subset(buf);
365 }
366
367 let n_cpr_fq = u32::try_from(self.cpr_proofs_fq.len())
369 .expect("cpr_proofs_fq length must fit into u32");
370 buf = n_cpr_fq.write_transcription_bytes_subset(buf);
371 for cpr in &self.cpr_proofs_fq {
372 buf = cpr.write_transcription_bytes_subset(buf);
373 }
374
375 let n_sum_fq = u32::try_from(self.combined_sumchecks_fq.len())
377 .expect("combined_sumchecks_fq length must fit into u32");
378 buf = n_sum_fq.write_transcription_bytes_subset(buf);
379 for sumcheck in &self.combined_sumchecks_fq {
380 buf = sumcheck.write_transcription_bytes_subset(buf);
381 }
382
383 let n_mp_fq = u32::try_from(self.multipoint_evals_fq.len())
385 .expect("multipoint_evals_fq length must fit into u32");
386 buf = n_mp_fq.write_transcription_bytes_subset(buf);
387 for mp in &self.multipoint_evals_fq {
388 buf = mp.write_transcription_bytes_subset(buf);
389 }
390
391 let presence = u32::from(self.witness_lifted_evals_pp.is_some());
394 buf = presence.write_transcription_bytes_subset(buf);
395 if let Some(ref lifted_pp) = self.witness_lifted_evals_pp {
396 buf = DynamicPolyVec::reinterpret(lifted_pp).write_transcription_bytes_subset(buf);
397 }
398
399 let _ = buf;
402 }
403}
404
405impl<F> Transcribable for Proof<F>
406where
407 F: ConstTranscribable,
408{
409 #[allow(clippy::arithmetic_side_effects)]
410 fn get_num_bytes(&self) -> usize {
411 let booleanity_bytes = optional_booleanity_proof_num_bytes(&self.booleanity_proof);
412 let affine_booleanity_bytes =
413 optional_booleanity_proof_num_bytes(&self.affine_booleanity_proof);
414 let ideal_checks_fq_bytes: usize = self
415 .ideal_checks_fq
416 .iter()
417 .map(|ic| IdealCheckProof::<F>::LENGTH_NUM_BYTES + ic.get_num_bytes())
418 .sum();
419 let cpr_proofs_fq_bytes: usize = self
420 .cpr_proofs_fq
421 .iter()
422 .map(|cpr| CombinedPolyResolverProof::<F>::LENGTH_NUM_BYTES + cpr.get_num_bytes())
423 .sum();
424 let combined_sumchecks_fq_bytes: usize = self
425 .combined_sumchecks_fq
426 .iter()
427 .map(|sc| MultiDegreeSumcheckProof::<F>::LENGTH_NUM_BYTES + sc.get_num_bytes())
428 .sum();
429 let multipoint_evals_fq_bytes: usize = self
430 .multipoint_evals_fq
431 .iter()
432 .map(|mp| MultipointEvalProof::<F>::LENGTH_NUM_BYTES + mp.get_num_bytes())
433 .sum();
434 let witness_lifted_evals_bytes: usize = self
435 .witness_lifted_evals
436 .iter()
437 .map(|wlf| {
438 DynamicPolyVec::<F>::LENGTH_NUM_BYTES
439 + DynamicPolyVec::reinterpret(wlf).get_num_bytes()
440 })
441 .sum();
442 let witness_lifted_evals_pp_bytes = match &self.witness_lifted_evals_pp {
443 Some(wpp) => {
444 DynamicPolyVec::<F>::LENGTH_NUM_BYTES
445 + DynamicPolyVec::reinterpret(wpp).get_num_bytes()
446 }
447 None => 0,
448 };
449 3 * ZipPlusCommitment::NUM_BYTES
450 + u32::NUM_BYTES
451 + self.zip.len()
452 + IdealCheckProof::<F>::LENGTH_NUM_BYTES
453 + self.ideal_check.get_num_bytes()
454 + CombinedPolyResolverProof::<F>::LENGTH_NUM_BYTES
455 + self.cpr_proof.get_num_bytes()
456 + MultiDegreeSumcheckProof::<F>::LENGTH_NUM_BYTES
457 + self.combined_sumcheck.get_num_bytes()
458 + MultipointEvalProof::<F>::LENGTH_NUM_BYTES
459 + self.multipoint_eval.get_num_bytes()
460 + u32::NUM_BYTES
465 + witness_lifted_evals_bytes
466 + u32::NUM_BYTES
468 + booleanity_bytes
469 + u32::NUM_BYTES
471 + affine_booleanity_bytes
472 + u32::NUM_BYTES
474 + ideal_checks_fq_bytes
475 + u32::NUM_BYTES
477 + cpr_proofs_fq_bytes
478 + u32::NUM_BYTES
480 + combined_sumchecks_fq_bytes
481 + u32::NUM_BYTES
483 + multipoint_evals_fq_bytes
484 + u32::NUM_BYTES
486 + witness_lifted_evals_pp_bytes
487 }
488}
489
490pub trait ZincTypes<const DEGREE_PLUS_ONE: usize, const FOLDED_DEG_PLUS_ONE: usize>:
493 Clone + Debug
494{
495 type Int: Semiring
498 + ConstTranscribable
499 + ConstCoeffBitWidth
500 + Named
501 + Default
502 + Clone
503 + Send
504 + Sync
505 + 'static;
506
507 type Chal: ConstIntRing + ConstTranscribable + Named;
510
511 type Pt: ConstIntRing;
514
515 type CombR;
516
517 type Fmod: ConstIntSemiring
520 + ConstTranscribable
521 + FromRef<Self::Fmod>
522 + Display
523 + Named
524 + Send
525 + Sync;
526
527 type PrimeTest: PrimalityTest<Self::Fmod>;
529
530 type BinaryZt: ZipTypes<
532 Eval = BinaryPoly<FOLDED_DEG_PLUS_ONE>,
533 Chal = Self::Chal,
534 Pt = Self::Pt,
535 CombR = Self::CombR,
536 Fmod = Self::Fmod,
537 PrimeTest = Self::PrimeTest,
538 >;
539
540 type ArbitraryZt: ZipTypes<
542 Eval = DensePolynomial<Self::Int, DEGREE_PLUS_ONE>,
543 Chal = Self::Chal,
544 Pt = Self::Pt,
545 CombR = Self::CombR,
546 Fmod = Self::Fmod,
547 PrimeTest = Self::PrimeTest,
548 >;
549
550 type IntZt: ZipTypes<
552 Eval = Self::Int,
553 Chal = Self::Chal,
554 Pt = Self::Pt,
555 CombR = Self::CombR,
556 Fmod = Self::Fmod,
557 PrimeTest = Self::PrimeTest,
558 >;
559
560 type BinaryFold: FoldTrace<BinaryPoly<DEGREE_PLUS_ONE>, BinaryPoly<FOLDED_DEG_PLUS_ONE>>;
561
562 type BinaryLc: LinearCode<Self::BinaryZt>;
564
565 type ArbitraryLc: LinearCode<Self::ArbitraryZt>;
567
568 type IntLc: LinearCode<Self::IntZt>;
570}
571
572#[derive(Copy, Clone, Default, Debug)]
578pub struct ZincPlusPiop<Zt, U, C, const DEGREE_PLUS_ONE: usize, const FOLDED_DEGREE_PLUS_ONE: usize>(
579 PhantomData<(Zt, U, C)>,
580)
581where
582 Zt: ZincTypes<DEGREE_PLUS_ONE, FOLDED_DEGREE_PLUS_ONE>,
583 U: Uair,
584 C: BaseFieldConfig;
585
586#[derive(Debug, Error)]
589pub enum ProtocolError<F: SetElement> {
590 #[error("ideal check failed: {0}")]
591 IdealCheck(#[from] IdealCheckError<F>),
592 #[error("combined poly resolver failed: {0}")]
593 Resolver(#[from] CombinedPolyResolverError<F>),
594 #[error("scalar projection failed: {0}")]
595 ScalarProjection(PolyEvaluationError),
596 #[error("multi-point evaluation failed: {0}")]
597 MultipointEval(#[from] MultipointEvalError<F>),
598 #[error("lifted eval psi_a projection failed: {0}")]
599 LiftedEvalProjection(PolyEvaluationError),
600 #[error("lookup argument failed: {0}")]
601 Lookup(#[from] LookupError),
602 #[error("booleanity argument failed: {0}")]
603 Booleanity(#[from] BooleanityError<F>),
604 #[error("booleanity proof missing from proof object")]
605 BooleanityProofMissing,
606 #[error("affine virtual source projection failed: {0}")]
607 AffineVirtualSourceProjection(PolyEvaluationError),
608 #[error(
609 "affine virtual bridge mismatch at spec {spec_index}: got {got:?}, expected {expected:?}"
610 )]
611 AffineVirtualBridgeMismatch {
612 spec_index: usize,
613 got: F,
614 expected: F,
615 },
616 #[error("non-canonical proof element: lifted integer >= family modulus")]
617 NonCanonicalElement,
618 #[error("proof family count mismatch: got {got}, expected {expected}")]
619 FamilyCountMismatch { got: usize, expected: usize },
620 #[error("PCS error: {0}")]
621 Pcs(#[from] ZipError),
622 #[error("PCS verification failed at column {0}: {1}")]
623 PcsVerification(usize, ZipError),
624 #[error("transcript failure: {0}")]
627 Transcript(#[from] TranscriptError),
628 #[error("F_q[X] ideal check failed at prime_idx {prime_idx} (q = {q}): {source}")]
629 FqIdealCheck {
630 prime_idx: usize,
631 q: String,
632 source: IdealCheckError<F>,
633 },
634 #[error("q'' witness lifted-evals length mismatch: got {got}, expected {expected}")]
635 WitnessLiftedEvalsPpLengthMismatch { got: usize, expected: usize },
636 #[error(
637 "witness lifted-evals length mismatch at family {family_idx}: got {got}, expected {expected}"
638 )]
639 WitnessLiftedEvalsLengthMismatch {
640 family_idx: usize,
641 got: usize,
642 expected: usize,
643 },
644}
645
646fn absorb_public_columns<T: ConstTranscribable>(
656 transcript: &mut impl Transcript,
657 cols: &[DenseMultilinearExtension<T>],
658) {
659 let mut buf = vec![0u8; T::NUM_BYTES];
660 for col in cols {
661 for entry in col.iter() {
662 entry.write_transcription_bytes_exact(&mut buf);
663 transcript.absorb_bytes(&buf);
664 }
665 }
666}
667
668#[allow(clippy::arithmetic_side_effects)]
677fn compute_lifted_evals<C: BaseFieldConfig, const D: usize>(
678 point: &[C::Element],
679 trace_bin_poly: &[DenseMultilinearExtension<BinaryPoly<D>>],
680 projected_trace: &ProjectedTrace<C::Element>,
681 field_cfg: &C,
682) -> Vec<DynamicPolynomial<C::Element>> {
683 let eq_table = zinc_poly::utils::build_eq_x_r_vec(field_cfg, point)
684 .expect("compute_lifted_evals: eq table build failed");
685
686 let n_bin = trace_bin_poly.len();
687 let zero = field_cfg.zero();
688 let poly_cfg = field_cfg.dyn_poly_cfg();
689
690 let mut result: Vec<DynamicPolynomial<C::Element>> = cfg_iter!(trace_bin_poly)
692 .map(|col| {
693 let mut coeffs = vec![zero.clone(); D];
694 for (b, entry) in col.iter().enumerate() {
695 for (l, coeff) in entry.iter().enumerate() {
696 if *coeff.inner() {
697 field_cfg.add_assign(&mut coeffs[l], &eq_table[b]);
698 }
699 }
700 }
701 poly_cfg.new_trimmed(coeffs)
702 })
703 .collect();
704
705 fn weighted_eq_sum<'a, C2: BaseFieldConfig>(
707 cfg: &C2,
708 col: impl Iterator<Item = &'a DynamicPolynomial<C2::Element>> + Clone,
709 eq_table: &[C2::Element],
710 zero: &C2::Element,
711 ) -> DynamicPolynomial<C2::Element>
712 where
713 C2::Element: 'a,
714 {
715 let num_coeffs = col.clone().map(|e| e.coeffs.len()).max().unwrap_or(0);
716 let mut coeffs = vec![zero.clone(); num_coeffs];
717 for (b, entry) in col.enumerate() {
718 for (l, coeff) in entry.coeffs.iter().enumerate() {
719 let term = cfg.mul(&eq_table[b], coeff);
720 cfg.add_assign(&mut coeffs[l], &term);
721 }
722 }
723 cfg.dyn_poly_cfg().new_trimmed(coeffs)
724 }
725
726 match projected_trace {
727 ProjectedTrace::RowMajor(t) => {
728 let num_cols = t.first().map(|r| r.len()).unwrap_or(0);
729 cfg_extend!(
730 result,
731 cfg_into_iter!(n_bin..num_cols).map(|col_idx| weighted_eq_sum(
732 field_cfg,
733 t.iter().map(|row| &row[col_idx]),
734 &eq_table,
735 &zero,
736 ))
737 );
738 }
739 ProjectedTrace::ColumnMajor(t) => {
740 cfg_extend!(
741 result,
742 cfg_iter!(t[n_bin..]).map(|col_mle| weighted_eq_sum(
743 field_cfg,
744 col_mle.iter(),
745 &eq_table,
746 &zero,
747 ))
748 );
749 }
750 }
751
752 result
753}
754
755#[allow(clippy::arithmetic_side_effects)]
767fn collapse_bit_slice_evals<C: BaseFieldConfig, const D: usize>(
768 bit_slice_evals: &[C::Element],
769 num_cols: usize,
770 alpha_prime: &C::Element,
771 field_cfg: &C,
772) -> Vec<C::Element> {
773 debug_assert_eq!(bit_slice_evals.len(), num_cols * D);
774 let alpha_powers: Vec<C::Element> = powers(field_cfg, alpha_prime, D);
775 bit_slice_evals
776 .chunks_exact(D)
777 .map(|slice| {
778 slice
779 .iter()
780 .zip(&alpha_powers)
781 .fold(field_cfg.zero(), |mut acc, (b, alpha_pow)| {
782 field_cfg.add_assign(&mut acc, &field_cfg.mul(b, alpha_pow));
783 acc
784 })
785 })
786 .collect()
787}
788
789#[allow(clippy::arithmetic_side_effects)]
792fn expected_affine_virtual_bridge_evals<C, P, const D: usize>(
793 signature: &UairSignature<P>,
794 source_bridge_evals: &[C::Element],
795 alpha_prime: &C::Element,
796 field_cfg: &C,
797) -> Vec<C::Element>
798where
799 C: BaseFieldConfig + ProjectPrimitiveIntegersWithConfig,
800 P: Semiring,
801{
802 debug_assert_eq!(
803 source_bridge_evals.len(),
804 signature.total_cols().num_binary_poly_cols()
805 );
806
807 let ones_projection = powers(field_cfg, alpha_prime, D).into_iter().fold(
808 field_cfg.zero(),
809 |mut acc, alpha_power| {
810 field_cfg.add_assign(&mut acc, &alpha_power);
811 acc
812 },
813 );
814
815 signature
816 .affine_virtual_specs()
817 .iter()
818 .map(|spec| {
819 let mut expected = field_cfg.mul(
820 &field_cfg.project(&spec.ones_coefficient()),
821 &ones_projection,
822 );
823 for term in spec.terms() {
824 debug_assert_eq!(term.row_shift(), 0);
825 let term_eval = field_cfg.mul(
826 &field_cfg.project(&term.coefficient()),
827 &source_bridge_evals[term.source_col()],
828 );
829 field_cfg.add_assign(&mut expected, &term_eval);
830 }
831 expected
832 })
833 .collect()
834}
835
836#[allow(clippy::arithmetic_side_effects)]
839fn project_binary_col_at_field<C, const D: usize>(
840 col: &DenseMultilinearExtension<BinaryPoly<D>>,
841 alpha_powers: &[C::Element],
842 field_cfg: &C,
843) -> DenseMultilinearExtension<C::Element>
844where
845 C: BaseFieldConfig,
846{
847 debug_assert_eq!(alpha_powers.len(), D);
848 let zero = field_cfg.zero();
849
850 let evaluations: Vec<C::Element> = col
853 .evaluations
854 .iter()
855 .map(|entry| {
856 let mut acc = zero.clone();
857 for (i, bit) in entry.iter().enumerate() {
858 if *bit.inner() {
859 field_cfg.add_assign(&mut acc, &alpha_powers[i]);
860 }
861 }
862 acc
863 })
864 .collect();
865
866 DenseMultilinearExtension {
867 num_vars: col.num_vars,
868 evaluations,
869 }
870}
871
872pub fn project_scalar_fn<R, C, const D: usize>(
875 scalar: &DensePolynomial<R, D>,
876 field_cfg: &C,
877) -> DynamicPolynomial<C::Element>
878where
879 C: BaseFieldConfig + ProjectElementWithConfig<R>,
880{
881 scalar
882 .iter()
883 .map(|coeff| field_cfg.project(coeff))
884 .collect()
885}
886
887fn project_canonical<C, F>(cfg: &C, int: &C::Integer) -> Result<C::Element, ProtocolError<F>>
891where
892 C: BaseFieldConfig,
893 F: SetElement,
894{
895 if *int < cfg.modulus() {
896 Ok(cfg.project(int))
897 } else {
898 Err(ProtocolError::NonCanonicalElement)
899 }
900}
901
902fn build_all_cfgs<C>(sig: &UairSignature<C::Integer>, qx_cfg: C) -> Vec<C>
914where
915 C: BaseFieldConfig,
916{
917 iter::once(qx_cfg)
918 .chain(
919 sig.primes()
920 .iter()
921 .map(|q| C::new(q).expect("declared prime is assumed prime")),
922 )
923 .collect()
924}
925
926#[cfg(test)]
931#[cfg(not(miri))] #[allow(
933 clippy::arithmetic_side_effects,
934 clippy::result_large_err,
935 clippy::type_complexity,
936 clippy::cast_possible_truncation,
937 clippy::cast_precision_loss,
938 clippy::cast_sign_loss,
939 clippy::clone_on_copy,
940 clippy::redundant_clone
941)]
942mod tests {
943 use super::*;
944 use crate::fold::FoldBinaryTrace4x;
945 use crypto_primitives::{
946 FieldConfig, LiftElementWithConfig, RingConfig, SemiringConfig,
947 crypto_bigint_int::Int,
948 crypto_bigint_monty::{MontyField, MontyFieldElement},
949 crypto_bigint_uint::{U64, Uint},
950 };
951 use num_traits::{ConstOne, WrappingAdd, Zero};
952 use rand::rng;
953 use zinc_piop::{
954 combined_poly_resolver::CombinedPolyResolverError, multipoint_eval::MultipointEvalError,
955 };
956 use zinc_poly::univariate::{binary::BinaryPolyInnerProduct, dense::DensePolyInnerProduct};
957 use zinc_primality::MillerRabin;
958 use zinc_test_uair::{
959 BigLinearUair, BigLinearUairWithPublicInput, BinaryDecompositionUair, GenerateRandomTrace,
960 ShaProxy, TestUairAffineVirtualPublicOnly, TestUairAffineVirtualUnshifted,
961 TestUairBitOpsFqFamily, TestUairFqLargePrime, TestUairMixedShifts,
962 TestUairNoMultiplication, TestUairSimpleMultiplication,
963 };
964 use zinc_uair::{
965 constraint_counter::count_constraints, ideal::DegreeOneIdeal, ideal_collector::IdealOrZero,
966 };
967 use zinc_utils::{
968 CHECKED,
969 inner_product::{MBSInnerProduct, ScalarProduct},
970 projectable_to_field::ProjectableToField,
971 };
972 use zip_plus::{
973 code::{
974 iprs::{IprsCode, PnttConfigF65537},
975 raa::{RaaCode, RaaConfig},
976 },
977 pcs::structs::{ZipPlus, ZipPlusParams},
978 pcs_transcript::PcsProverTranscript,
979 };
980
981 const INT_LIMBS: usize = U64::LIMBS;
982 const FIELD_LIMBS: usize = U64::LIMBS * 3;
983
984 const D: usize = 32;
985 const HALF_D: usize = D / 2;
986 const QUARTER_D: usize = D / 4;
987
988 const K: usize = INT_LIMBS * 4;
991 const M: usize = INT_LIMBS * 8;
992
993 const REP_FACTOR: usize = 8;
994
995 type F = MontyField<FIELD_LIMBS>;
996 type E = MontyFieldElement<FIELD_LIMBS>;
997 type ZtFmod = Uint<FIELD_LIMBS>;
998
999 #[derive(Debug, Clone)]
1000 pub struct BinPolyZipTypes {}
1001 impl ZipTypes for BinPolyZipTypes {
1002 const NUM_COLUMN_OPENINGS: usize = 100;
1003 type Eval = BinaryPoly<QUARTER_D>;
1004 type Cw = DensePolynomial<i64, QUARTER_D>;
1005 type Fmod = ZtFmod;
1006 type PrimeTest = MillerRabin;
1007 type Chal = i128;
1008 type Pt = i128;
1009 type CombR = Int<M>;
1010 type Comb = DensePolynomial<Self::CombR, QUARTER_D>;
1011 type EvalDotChal = BinaryPolyInnerProduct<Self::Chal, QUARTER_D>;
1012 type CombDotChal = DensePolyInnerProduct<
1013 (),
1014 Self::CombR,
1015 Self::Chal,
1016 Self::CombR,
1017 MBSInnerProduct,
1018 QUARTER_D,
1019 >;
1020 type ArrCombRDotChal = MBSInnerProduct;
1021 }
1022
1023 #[derive(Debug, Clone)]
1024 pub struct ArbitraryPolyZipTypesIprs {}
1025 impl ZipTypes for ArbitraryPolyZipTypesIprs {
1026 const NUM_COLUMN_OPENINGS: usize = 100;
1027 type Eval = DensePolynomial<i64, D>;
1028 type Cw = DensePolynomial<i64, D>;
1029 type Fmod = ZtFmod;
1030 type PrimeTest = MillerRabin;
1031 type Chal = i128;
1032 type Pt = i128;
1033 type CombR = Int<M>;
1034 type Comb = DensePolynomial<Self::CombR, D>;
1035 type EvalDotChal =
1036 DensePolyInnerProduct<(), i64, Self::Chal, Self::CombR, MBSInnerProduct, D>;
1037 type CombDotChal =
1038 DensePolyInnerProduct<(), Self::CombR, Self::Chal, Self::CombR, MBSInnerProduct, D>;
1039 type ArrCombRDotChal = MBSInnerProduct;
1040 }
1041
1042 #[derive(Debug, Clone)]
1045 pub struct ArbitraryPolyZipTypesRaa {}
1046 impl ZipTypes for ArbitraryPolyZipTypesRaa {
1047 const NUM_COLUMN_OPENINGS: usize = 100;
1048 type Eval = DensePolynomial<i64, D>;
1049 type Cw = DensePolynomial<Int<K>, D>;
1050 type Fmod = ZtFmod;
1051 type PrimeTest = MillerRabin;
1052 type Chal = i128;
1053 type Pt = i128;
1054 type CombR = Int<M>;
1055 type Comb = DensePolynomial<Self::CombR, D>;
1056 type EvalDotChal =
1057 DensePolyInnerProduct<(), i64, Self::Chal, Self::CombR, MBSInnerProduct, D>;
1058 type CombDotChal =
1059 DensePolyInnerProduct<(), Self::CombR, Self::Chal, Self::CombR, MBSInnerProduct, D>;
1060 type ArrCombRDotChal = MBSInnerProduct;
1061 }
1062
1063 type ZtInt = i64;
1064
1065 #[derive(Debug, Clone)]
1066 pub struct IntZipTypes {}
1067 impl ZipTypes for IntZipTypes {
1068 const NUM_COLUMN_OPENINGS: usize = 100;
1069 type Eval = ZtInt;
1070 type Cw = i128;
1071 type Fmod = ZtFmod;
1072 type PrimeTest = MillerRabin;
1073 type Chal = i128;
1074 type Pt = i128;
1075 type CombR = Int<M>;
1076 type Comb = Self::CombR;
1077 type EvalDotChal = ScalarProduct;
1078 type CombDotChal = ScalarProduct;
1079 type ArrCombRDotChal = MBSInnerProduct;
1080 }
1081
1082 #[derive(Clone, Debug)]
1083 struct TestZincTypesIprs;
1084
1085 impl ZincTypes<D, QUARTER_D> for TestZincTypesIprs {
1086 type Int = ZtInt;
1087 type Chal = i128;
1088 type Pt = i128;
1089 type CombR = Int<M>;
1090 type Fmod = ZtFmod;
1091 type PrimeTest = MillerRabin;
1092
1093 type BinaryZt = BinPolyZipTypes;
1094 type ArbitraryZt = ArbitraryPolyZipTypesIprs;
1095 type IntZt = IntZipTypes;
1096
1097 type BinaryFold = FoldBinaryTrace4x<D, HALF_D, QUARTER_D>;
1098
1099 type BinaryLc = IprsCode<Self::BinaryZt, PnttConfigF65537, REP_FACTOR, CHECKED>;
1100 type ArbitraryLc = IprsCode<Self::ArbitraryZt, PnttConfigF65537, REP_FACTOR, CHECKED>;
1101 type IntLc = IprsCode<Self::IntZt, PnttConfigF65537, REP_FACTOR, CHECKED>;
1102 }
1103
1104 #[derive(Copy, Clone)]
1105 struct TestRaaConfig;
1106 impl RaaConfig for TestRaaConfig {
1107 const PERMUTE_IN_PLACE: bool = false;
1108 const CHECK_FOR_OVERFLOWS: bool = true;
1109 }
1110
1111 #[derive(Clone, Debug)]
1112 struct TestZincTypesRaa;
1113
1114 impl ZincTypes<D, QUARTER_D> for TestZincTypesRaa {
1115 type Int = i64;
1116 type Chal = i128;
1117 type Pt = i128;
1118 type CombR = Int<M>;
1119 type Fmod = ZtFmod;
1120 type PrimeTest = MillerRabin;
1121
1122 type BinaryZt = BinPolyZipTypes;
1123 type ArbitraryZt = ArbitraryPolyZipTypesRaa;
1124 type IntZt = IntZipTypes;
1125
1126 type BinaryFold = FoldBinaryTrace4x<D, HALF_D, QUARTER_D>;
1127
1128 type BinaryLc = RaaCode<Self::BinaryZt, TestRaaConfig, REP_FACTOR>;
1129 type ArbitraryLc = RaaCode<Self::ArbitraryZt, TestRaaConfig, REP_FACTOR>;
1130 type IntLc = RaaCode<Self::IntZt, TestRaaConfig, REP_FACTOR>;
1131 }
1132
1133 fn make_iprs<Zt: ZipTypes>(
1135 num_vars: usize,
1136 ) -> IprsCode<Zt, PnttConfigF65537, REP_FACTOR, CHECKED> {
1137 let poly_size = 1 << num_vars;
1138 IprsCode::new_with_optimal_depth(poly_size).unwrap()
1139 }
1140
1141 fn setup_pp<Zt>(
1143 num_vars: usize,
1144 linear_codes: (Zt::BinaryLc, Zt::ArbitraryLc, Zt::IntLc),
1145 ) -> (
1146 ZipPlusParams<Zt::BinaryZt, Zt::BinaryLc>,
1147 ZipPlusParams<Zt::ArbitraryZt, Zt::ArbitraryLc>,
1148 ZipPlusParams<Zt::IntZt, Zt::IntLc>,
1149 )
1150 where
1151 Zt: ZincTypes<D, QUARTER_D>,
1152 {
1153 let folded_num_vars = num_vars + Zt::BinaryFold::FOLDING_FACTOR.ilog2() as usize;
1154
1155 let poly_size = 1 << num_vars;
1156 let folded_poly_size = 1 << folded_num_vars;
1157 (
1158 ZipPlus::<Zt::BinaryZt, Zt::BinaryLc>::setup(folded_poly_size, linear_codes.0),
1159 ZipPlus::<Zt::ArbitraryZt, Zt::ArbitraryLc>::setup(poly_size, linear_codes.1),
1160 ZipPlus::<Zt::IntZt, Zt::IntLc>::setup(poly_size, linear_codes.2),
1161 )
1162 }
1163
1164 macro_rules! default_project_ideal {
1165 () => {
1166 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i))
1167 };
1168 }
1169
1170 macro_rules! default_project_fq_ideal {
1174 () => {
1175 |_ideal, _cfg| -> IdealOrZero<DegreeOneIdeal<E>> {
1176 unreachable!("this UAIR has no F_q[X] constraints")
1177 }
1178 };
1179 }
1180
1181 fn do_test<Zt, U>(
1182 num_vars: usize,
1183 linear_codes: (Zt::BinaryLc, Zt::ArbitraryLc, Zt::IntLc),
1184 project_ideal: impl Fn(&IdealOrZero<U::Ideal>, &F) -> IdealOrZero<DegreeOneIdeal<E>> + Copy,
1185 project_fq_ideal: impl Fn(&IdealOrZero<U::FqIdeal>, &F) -> IdealOrZero<DegreeOneIdeal<E>> + Copy,
1186 tamper: impl Fn(&mut Proof<ZtFmod>),
1187 check_verification: impl Fn(Result<(), ProtocolError<E>>),
1188 ) where
1189 Zt: ZincTypes<D, QUARTER_D, Fmod = ZtFmod, Int = ZtInt, Chal = i128, CombR = Int<M>>,
1190 Zt::Int: ProjectableToField<F>,
1191 <Zt::ArbitraryZt as ZipTypes>::Eval: ProjectableToField<F>,
1192 U: Uair<Scalar = DensePolynomial<Zt::Int, D>, Prime = Zt::Fmod>
1193 + GenerateRandomTrace<D, PolyCoeff = Zt::Int, Int = Zt::Int>
1194 + 'static,
1195 {
1196 let mut rng = rng();
1197 let pp = setup_pp::<Zt>(num_vars, linear_codes);
1198
1199 let trace = U::generate_random_trace(num_vars, &mut rng);
1200
1201 let sig = U::signature();
1202 let public_trace = trace.public(&sig);
1203
1204 macro_rules! run_protocol {
1205 ($mle_first:ident) => {
1206 let mut proof = ZincPlusPiop::<Zt, U, F, D, QUARTER_D>::prove::<
1207 { $mle_first },
1208 CHECKED,
1209 >(&pp, &trace, num_vars, project_scalar_fn)
1210 .expect("Prover failed");
1211
1212 let mut transcript = PcsProverTranscript::new_from_commitments(std::iter::empty());
1214 transcript.write(&proof).expect("Failed to serialize proof");
1215 let mut transcript = transcript.into_verification_transcript();
1216 let proof_2 = transcript
1217 .read()
1218 .expect("Failed to deserialize proof after serialization");
1219 assert_eq!(proof, proof_2);
1220
1221 tamper(&mut proof);
1222
1223 let verification_result =
1224 ZincPlusPiop::<Zt, U, F, D, QUARTER_D>::verify::<_, CHECKED>(
1225 &pp,
1226 proof,
1227 &public_trace,
1228 num_vars,
1229 project_scalar_fn,
1230 project_ideal,
1231 project_fq_ideal,
1232 );
1233 check_verification(verification_result);
1234 };
1235 }
1236
1237 run_protocol!(false);
1238
1239 run_protocol!(true);
1240 }
1241
1242 #[test]
1247 fn test_e2e_no_multiplication() {
1248 let num_vars = 8;
1249 do_test::<TestZincTypesIprs, TestUairNoMultiplication<ZtInt, ZtFmod>>(
1250 num_vars,
1251 (
1252 make_iprs(num_vars),
1253 make_iprs(num_vars),
1254 make_iprs(num_vars),
1255 ),
1256 default_project_ideal!(),
1257 default_project_fq_ideal!(),
1258 |_| {},
1259 |res| res.unwrap(),
1260 );
1261 }
1262
1263 #[test]
1277 fn test_e2e_simple_multiplication() {
1278 let num_vars = 2;
1279 do_test::<TestZincTypesRaa, TestUairSimpleMultiplication<ZtInt, ZtFmod>>(
1280 num_vars,
1281 (
1282 RaaCode::new(num_vars),
1283 RaaCode::new(num_vars),
1284 RaaCode::new(num_vars),
1285 ),
1286 |_ideal, _field_cfg| IdealOrZero::<DegreeOneIdeal<E>>::zero(),
1287 default_project_fq_ideal!(),
1288 |_| {},
1289 |res| res.unwrap(),
1290 );
1291 }
1292
1293 #[test]
1298 fn test_e2e_mixed_shifts() {
1299 let num_vars = 8;
1300 do_test::<TestZincTypesIprs, TestUairMixedShifts<ZtInt, ZtFmod>>(
1301 num_vars,
1302 (
1303 make_iprs(num_vars),
1304 make_iprs(num_vars),
1305 make_iprs(num_vars),
1306 ),
1307 |_ideal, _field_cfg| IdealOrZero::<DegreeOneIdeal<E>>::zero(),
1308 default_project_fq_ideal!(),
1309 |_| {},
1310 |res| res.unwrap(),
1311 );
1312 }
1313
1314 #[test]
1319 fn test_e2e_binary_decomposition() {
1320 let num_vars = 8;
1321 do_test::<TestZincTypesIprs, BinaryDecompositionUair<ZtInt, ZtFmod>>(
1322 num_vars,
1323 (
1324 make_iprs(num_vars),
1325 make_iprs(num_vars),
1326 make_iprs(num_vars),
1327 ),
1328 default_project_ideal!(),
1329 default_project_fq_ideal!(),
1330 |_| {},
1331 |res| res.unwrap(),
1332 );
1333 }
1334
1335 #[test]
1342 fn test_e2e_fq_large_prime() {
1343 let num_vars = 8;
1344 do_test::<TestZincTypesIprs, TestUairFqLargePrime<ZtInt, ZtFmod>>(
1345 num_vars,
1346 (
1347 make_iprs(num_vars),
1348 make_iprs(num_vars),
1349 make_iprs(num_vars),
1350 ),
1351 |_ideal, _field_cfg| IdealOrZero::<DegreeOneIdeal<E>>::zero(),
1353 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1356 |_| {},
1357 |res| res.unwrap(),
1358 );
1359 }
1360
1361 #[test]
1364 fn test_e2e_bit_ops_with_fq_family() {
1365 let num_vars = 8;
1366 do_test::<TestZincTypesIprs, TestUairBitOpsFqFamily<ZtInt, ZtFmod>>(
1367 num_vars,
1368 (
1369 make_iprs(num_vars),
1370 make_iprs(num_vars),
1371 make_iprs(num_vars),
1372 ),
1373 default_project_ideal!(),
1374 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1375 |_| {},
1376 |res| res.unwrap(),
1377 );
1378 }
1379
1380 #[test]
1382 fn test_e2e_affine_virtual_unshifted() {
1383 let num_vars = 8;
1384 do_test::<TestZincTypesIprs, TestUairAffineVirtualUnshifted<ZtInt, ZtFmod>>(
1385 num_vars,
1386 (
1387 make_iprs(num_vars),
1388 make_iprs(num_vars),
1389 make_iprs(num_vars),
1390 ),
1391 default_project_ideal!(),
1392 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1393 |proof| {
1394 assert_eq!(
1395 proof
1396 .booleanity_proof
1397 .as_ref()
1398 .expect("witness binary columns require a booleanity proof")
1399 .bit_slice_evals
1400 .len(),
1401 3 * D,
1402 );
1403 assert_eq!(
1404 proof
1405 .affine_booleanity_proof
1406 .as_ref()
1407 .expect("affine virtuals require an affine booleanity proof")
1408 .bit_slice_evals
1409 .len(),
1410 2 * D,
1411 );
1412 },
1413 |res| res.unwrap(),
1414 );
1415 }
1416
1417 #[test]
1420 fn test_e2e_affine_virtual_public_only() {
1421 let num_vars = 8;
1422 do_test::<TestZincTypesIprs, TestUairAffineVirtualPublicOnly<ZtInt, ZtFmod>>(
1423 num_vars,
1424 (
1425 make_iprs(num_vars),
1426 make_iprs(num_vars),
1427 make_iprs(num_vars),
1428 ),
1429 default_project_ideal!(),
1430 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1431 |proof| {
1432 assert!(proof.booleanity_proof.is_none());
1433 assert_eq!(
1434 proof
1435 .affine_booleanity_proof
1436 .as_ref()
1437 .expect("affine virtuals require an affine booleanity proof")
1438 .bit_slice_evals
1439 .len(),
1440 2 * D,
1441 );
1442 },
1443 |res| res.unwrap(),
1444 );
1445 }
1446
1447 #[test]
1450 fn test_affine_virtual_truncated_bit_slice_evals() {
1451 let num_vars = 8;
1452 do_test::<TestZincTypesIprs, TestUairAffineVirtualUnshifted<ZtInt, ZtFmod>>(
1453 num_vars,
1454 (
1455 make_iprs(num_vars),
1456 make_iprs(num_vars),
1457 make_iprs(num_vars),
1458 ),
1459 default_project_ideal!(),
1460 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1461 |proof| {
1462 proof
1463 .affine_booleanity_proof
1464 .as_mut()
1465 .expect("affine virtuals require an affine booleanity proof")
1466 .bit_slice_evals
1467 .pop();
1468 },
1469 |res| {
1470 assert!(matches!(
1471 res.unwrap_err(),
1472 ProtocolError::Booleanity(BooleanityError::WrongBitSliceEvalsNumber { .. })
1473 ));
1474 },
1475 );
1476 }
1477
1478 #[test]
1481 fn test_affine_virtual_source_binding_rejects_booleanity_preserving_tamper() {
1482 type U = TestUairAffineVirtualUnshifted<ZtInt, ZtFmod>;
1483 type Piop = ZincPlusPiop<TestZincTypesIprs, U, F, D, QUARTER_D>;
1484 type Ideal = IdealOrZero<DegreeOneIdeal<E>>;
1485
1486 let num_vars = 8;
1487 let pp = setup_pp::<TestZincTypesIprs>(
1488 num_vars,
1489 (
1490 make_iprs(num_vars),
1491 make_iprs(num_vars),
1492 make_iprs(num_vars),
1493 ),
1494 );
1495 let trace = U::generate_random_trace(num_vars, &mut rng());
1496 let public_trace = trace.public(&U::signature());
1497 let mut proof =
1498 Piop::prove::<false, CHECKED>(&pp, &trace, num_vars, project_scalar_fn).expect("prove");
1499
1500 let cfg = Piop::step0_reconstruct_transcript::<Ideal>(
1501 &pp,
1502 proof.clone(),
1503 &public_trace,
1504 num_vars,
1505 )
1506 .and_then(|s| s.step1_prime_projection())
1507 .and_then(|s| {
1508 s.step2_ideal_check(default_project_ideal!(), |ideal, field_cfg| {
1509 ideal.map(|i| DegreeOneIdeal::project(field_cfg, i))
1510 })
1511 })
1512 .and_then(|s| s.step3_eval_projection(project_scalar_fn))
1513 .expect("steps 0..=3")
1514 .field_cfg()
1515 .clone();
1516
1517 let affine_evals = &mut proof
1518 .affine_booleanity_proof
1519 .as_mut()
1520 .expect("affine virtuals require an affine booleanity proof")
1521 .bit_slice_evals;
1522 let one = cfg.one();
1523 let tamper_index = affine_evals
1524 .iter()
1525 .position(|wire_eval| {
1526 let eval: E = cfg.project(wire_eval);
1527 eval != cfg.sub(&one, &eval)
1528 })
1529 .expect("at least one affine endpoint must differ from its complement");
1530 let eval: E = cfg.project(&affine_evals[tamper_index]);
1531 let tampered_eval = cfg.sub(&one, &eval);
1532 assert_eq!(
1533 cfg.mul(&eval, &cfg.sub(&eval, &one)),
1534 cfg.mul(&tampered_eval, &cfg.sub(&tampered_eval, &one)),
1535 "b -> 1-b must preserve the Booleanity residue",
1536 );
1537 affine_evals[tamper_index] = cfg.lift(&tampered_eval);
1538
1539 let err = Piop::verify::<Ideal, CHECKED>(
1540 &pp,
1541 proof,
1542 &public_trace,
1543 num_vars,
1544 project_scalar_fn,
1545 default_project_ideal!(),
1546 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1547 )
1548 .expect_err("the affine source-binding bridge must reject the tamper");
1549 assert!(matches!(
1550 err,
1551 ProtocolError::AffineVirtualBridgeMismatch { .. }
1552 ));
1553 }
1554
1555 #[test]
1565 fn test_e2e_big_linear() {
1566 let num_vars = 8;
1567 do_test::<TestZincTypesIprs, BigLinearUair<ZtInt, ZtFmod>>(
1568 num_vars,
1569 (
1570 make_iprs(num_vars),
1571 make_iprs(num_vars),
1572 make_iprs(num_vars),
1573 ),
1574 default_project_ideal!(),
1575 default_project_fq_ideal!(),
1576 |_| {},
1577 |res| res.unwrap(),
1578 );
1579 }
1580
1581 #[test]
1586 fn test_e2e_big_linear_with_public_input() {
1587 let num_vars = 8;
1588 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1589 num_vars,
1590 (
1591 make_iprs(num_vars),
1592 make_iprs(num_vars),
1593 make_iprs(num_vars),
1594 ),
1595 default_project_ideal!(),
1596 default_project_fq_ideal!(),
1597 |_| {},
1598 |res| res.unwrap(),
1599 );
1600 }
1601
1602 #[test]
1615 fn test_e2e_sha_proxy() {
1616 let num_vars = 8;
1617 do_test::<TestZincTypesIprs, ShaProxy<ZtInt, ZtFmod>>(
1618 num_vars,
1619 (
1620 make_iprs(num_vars),
1621 make_iprs(num_vars),
1622 make_iprs(num_vars),
1623 ),
1624 default_project_ideal!(),
1625 default_project_fq_ideal!(),
1626 |_| {},
1627 |res| res.unwrap(),
1628 );
1629 }
1630
1631 #[test]
1637 fn test_big_linear_tamper_lifted_evals() {
1638 let num_vars = 8;
1639 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1640 num_vars,
1641 (
1642 make_iprs(num_vars),
1643 make_iprs(num_vars),
1644 make_iprs(num_vars),
1645 ),
1646 default_project_ideal!(),
1647 default_project_fq_ideal!(),
1648 |proof| proof.witness_lifted_evals[0].swap(0, 1),
1649 |res| {
1650 assert!(matches!(
1651 res.unwrap_err(),
1652 ProtocolError::MultipointEval(MultipointEvalError::ClaimMismatch { .. })
1653 ));
1654 },
1655 );
1656 }
1657
1658 #[test]
1672 fn test_fq_large_prime_tamper_lifted_evals() {
1673 let num_vars = 8;
1674 do_test::<TestZincTypesIprs, TestUairFqLargePrime<ZtInt, ZtFmod>>(
1675 num_vars,
1676 (
1677 make_iprs(num_vars),
1678 make_iprs(num_vars),
1679 make_iprs(num_vars),
1680 ),
1681 |_ideal, _field_cfg| IdealOrZero::<DegreeOneIdeal<E>>::zero(),
1683 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1684 |proof| {
1685 let lifted = &mut proof.witness_lifted_evals[1][0];
1690 assert!(
1691 lifted.coeffs.len() >= 2,
1692 "lifted polynomial should have at least 2 coefficients to swap"
1693 );
1694 lifted.coeffs.swap(0, 1);
1695 },
1696 |res| {
1697 assert!(matches!(
1698 res.unwrap_err(),
1699 ProtocolError::MultipointEval(MultipointEvalError::ClaimMismatch { .. })
1700 ));
1701 },
1702 );
1703 }
1704
1705 #[test]
1714 fn test_bit_ops_fq_family_tamper_source_lifted_evals() {
1715 let num_vars = 8;
1716 do_test::<TestZincTypesIprs, TestUairBitOpsFqFamily<ZtInt, ZtFmod>>(
1717 num_vars,
1718 (
1719 make_iprs(num_vars),
1720 make_iprs(num_vars),
1721 make_iprs(num_vars),
1722 ),
1723 default_project_ideal!(),
1724 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1725 |proof| {
1726 let sig = TestUairBitOpsFqFamily::<ZtInt, ZtFmod>::signature();
1730 let cfg = F::new(&sig.primes()[0]).expect("declared prime");
1731 let one = cfg.one();
1732 let lifted = &mut proof.witness_lifted_evals[1][0];
1733 if lifted.coeffs.is_empty() {
1734 lifted.coeffs.push(cfg.lift(&one));
1735 } else {
1736 let v = cfg.project(&lifted.coeffs[0]);
1737 lifted.coeffs[0] = cfg.lift(&cfg.add(&v, &one));
1738 }
1739 },
1740 |res| {
1741 assert!(matches!(
1742 res.unwrap_err(),
1743 ProtocolError::MultipointEval(MultipointEvalError::ClaimMismatch { .. })
1744 ));
1745 },
1746 );
1747 }
1748
1749 #[test]
1756 fn test_bit_ops_fq_family_tamper_bit_op_eval() {
1757 let num_vars = 8;
1758 do_test::<TestZincTypesIprs, TestUairBitOpsFqFamily<ZtInt, ZtFmod>>(
1759 num_vars,
1760 (
1761 make_iprs(num_vars),
1762 make_iprs(num_vars),
1763 make_iprs(num_vars),
1764 ),
1765 default_project_ideal!(),
1766 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1767 |proof| {
1768 let sig = TestUairBitOpsFqFamily::<ZtInt, ZtFmod>::signature();
1770 let cfg = F::new(&sig.primes()[0]).expect("declared prime");
1771 let bit_op_eval = &mut proof.cpr_proofs_fq[0].bit_op_evals[0];
1772 let v = cfg.project(&*bit_op_eval);
1773 *bit_op_eval = cfg.lift(&cfg.add(&v, &cfg.one()));
1774 },
1775 |res| {
1776 assert!(
1777 res.is_err(),
1778 "tampered F_q-family bit-op evaluation must be rejected"
1779 );
1780 },
1781 );
1782 }
1783
1784 #[test]
1788 fn test_fq_large_prime_truncated_lifted_evals() {
1789 let num_vars = 8;
1790 do_test::<TestZincTypesIprs, TestUairFqLargePrime<ZtInt, ZtFmod>>(
1791 num_vars,
1792 (
1793 make_iprs(num_vars),
1794 make_iprs(num_vars),
1795 make_iprs(num_vars),
1796 ),
1797 |_ideal, _field_cfg| IdealOrZero::<DegreeOneIdeal<E>>::zero(),
1798 |ideal, field_cfg| ideal.map(|i| DegreeOneIdeal::project(field_cfg, i)),
1799 |proof| proof.witness_lifted_evals[1].clear(),
1802 |res| {
1803 assert!(matches!(
1804 res.unwrap_err(),
1805 ProtocolError::WitnessLiftedEvalsLengthMismatch { family_idx: 1, .. }
1806 ));
1807 },
1808 );
1809 }
1810
1811 #[test]
1819 fn test_tamper_witness_lifted_evals_pp_extra_tail() {
1820 let num_vars = 8;
1821 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1822 num_vars,
1823 (
1824 make_iprs(num_vars),
1825 make_iprs(num_vars),
1826 make_iprs(num_vars),
1827 ),
1828 default_project_ideal!(),
1829 default_project_fq_ideal!(),
1830 |proof| {
1831 let extra = proof.witness_lifted_evals[0][0].clone();
1839 proof.witness_lifted_evals_pp = Some(vec![extra]);
1840 },
1841 |res| {
1842 assert!(matches!(
1843 res.unwrap_err(),
1844 ProtocolError::WitnessLiftedEvalsPpLengthMismatch { .. }
1845 ));
1846 },
1847 );
1848 }
1849
1850 #[test]
1851 fn test_big_linear_tamper_up_evals() {
1852 let num_vars = 8;
1853 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1854 num_vars,
1855 (
1856 make_iprs(num_vars),
1857 make_iprs(num_vars),
1858 make_iprs(num_vars),
1859 ),
1860 default_project_ideal!(),
1861 default_project_fq_ideal!(),
1862 |proof| proof.cpr_proof.up_evals.swap(0, 1),
1863 |res| {
1864 assert!(matches!(
1865 res.unwrap_err(),
1866 ProtocolError::Resolver(
1867 CombinedPolyResolverError::ClaimValueDoesNotMatch { .. }
1868 )
1869 ));
1870 },
1871 );
1872 }
1873
1874 #[test]
1875 fn test_big_linear_tamper_down_evals() {
1876 let num_vars = 8;
1877 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1878 num_vars,
1879 (
1880 make_iprs(num_vars),
1881 make_iprs(num_vars),
1882 make_iprs(num_vars),
1883 ),
1884 default_project_ideal!(),
1885 default_project_fq_ideal!(),
1886 |proof| proof.cpr_proof.down_evals.swap(0, 1),
1887 |res| {
1888 assert!(matches!(
1889 res.unwrap_err(),
1890 ProtocolError::Resolver(
1891 CombinedPolyResolverError::ClaimValueDoesNotMatch { .. }
1892 )
1893 ));
1894 },
1895 );
1896 }
1897
1898 #[test]
1900 fn test_big_linear_tamper_non_canonical_wire_integer() {
1901 let num_vars = 8;
1902 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1903 num_vars,
1904 (
1905 make_iprs(num_vars),
1906 make_iprs(num_vars),
1907 make_iprs(num_vars),
1908 ),
1909 default_project_ideal!(),
1910 default_project_fq_ideal!(),
1911 |proof| proof.cpr_proof.up_evals[0] = ZtFmod::MAX,
1912 |res| {
1913 assert!(matches!(
1914 res.unwrap_err(),
1915 ProtocolError::NonCanonicalElement
1916 ));
1917 },
1918 );
1919 }
1920
1921 #[test]
1927 fn test_big_linear_tamper_trailing_proof_bytes() {
1928 let num_vars = 8;
1929 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1930 num_vars,
1931 (
1932 make_iprs(num_vars),
1933 make_iprs(num_vars),
1934 make_iprs(num_vars),
1935 ),
1936 default_project_ideal!(),
1937 default_project_fq_ideal!(),
1938 |proof| proof.zip.push(0),
1939 |res| {
1940 assert!(res.is_err(), "trailing proof bytes were accepted");
1941 let err = res.unwrap_err();
1942 assert!(
1943 matches!(err, ProtocolError::Transcript(_)),
1944 "trailing proof bytes resulted in {err:?} rather than a transcript error"
1945 );
1946 },
1947 );
1948 }
1949
1950 #[test]
1953 fn test_big_linear_tamper_ideal_check_values() {
1954 let num_vars = 8;
1955 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
1956 num_vars,
1957 (
1958 make_iprs(num_vars),
1959 make_iprs(num_vars),
1960 make_iprs(num_vars),
1961 ),
1962 default_project_ideal!(),
1963 default_project_fq_ideal!(),
1964 |proof| {
1965 let coeffs = &mut proof.ideal_check.combined_mle_values[0].coeffs;
1966 if coeffs.is_empty() {
1967 coeffs.push(ZtFmod::from(1u64));
1968 } else {
1969 if coeffs[0].is_zero() {
1970 coeffs[0] += ZtFmod::ONE;
1971 } else {
1972 coeffs[0] -= ZtFmod::ONE;
1973 }
1974 }
1975 },
1976 |res| {
1977 assert!(matches!(res.unwrap_err(), ProtocolError::IdealCheck(..)));
1978 },
1979 );
1980 }
1981
1982 #[test]
1995 fn test_big_linear_tamper_booleanity_evals() {
1996 use zinc_piop::lookup::booleanity::BooleanityError;
1997 let num_vars = 8;
1998 do_test::<TestZincTypesIprs, BigLinearUair<ZtInt, ZtFmod>>(
1999 num_vars,
2000 (
2001 make_iprs(num_vars),
2002 make_iprs(num_vars),
2003 make_iprs(num_vars),
2004 ),
2005 default_project_ideal!(),
2006 default_project_fq_ideal!(),
2007 |proof| {
2008 let bp = proof
2009 .booleanity_proof
2010 .as_mut()
2011 .expect("BigLinearUair has binary-poly witnesses");
2012 let tampered = bp.bit_slice_evals[0].wrapping_add(&Uint::from(7_u64));
2014 bp.bit_slice_evals[0] = tampered
2015 },
2016 |res| {
2017 assert!(matches!(
2018 res.unwrap_err(),
2019 ProtocolError::Booleanity(BooleanityError::ClaimValueDoesNotMatch { .. })
2020 ));
2021 },
2022 );
2023 }
2024
2025 #[test]
2029 fn test_big_linear_tamper_booleanity_evals_length() {
2030 use zinc_piop::lookup::booleanity::BooleanityError;
2031 let num_vars = 8;
2032 do_test::<TestZincTypesIprs, BigLinearUair<ZtInt, ZtFmod>>(
2033 num_vars,
2034 (
2035 make_iprs(num_vars),
2036 make_iprs(num_vars),
2037 make_iprs(num_vars),
2038 ),
2039 default_project_ideal!(),
2040 default_project_fq_ideal!(),
2041 |proof| {
2042 let bp = proof
2043 .booleanity_proof
2044 .as_mut()
2045 .expect("BigLinearUair has binary-poly witnesses");
2046 bp.bit_slice_evals.pop();
2047 },
2048 |res| {
2049 assert!(matches!(
2050 res.unwrap_err(),
2051 ProtocolError::Booleanity(BooleanityError::WrongBitSliceEvalsNumber { .. })
2052 ));
2053 },
2054 );
2055 }
2056
2057 #[test]
2060 fn test_big_linear_drop_booleanity_proof() {
2061 let num_vars = 8;
2062 do_test::<TestZincTypesIprs, BigLinearUair<ZtInt, ZtFmod>>(
2063 num_vars,
2064 (
2065 make_iprs(num_vars),
2066 make_iprs(num_vars),
2067 make_iprs(num_vars),
2068 ),
2069 default_project_ideal!(),
2070 default_project_fq_ideal!(),
2071 |proof| {
2072 proof.booleanity_proof = None;
2073 },
2074 |res| {
2075 assert!(matches!(
2076 res.unwrap_err(),
2077 ProtocolError::BooleanityProofMissing
2078 ));
2079 },
2080 );
2081 }
2082
2083 #[test]
2100 #[allow(clippy::arithmetic_side_effects)]
2101 fn test_big_linear_alpha_prime_bridge_catches_pin_down_preserving_tamper() {
2102 use zinc_piop::{
2103 combined_poly_resolver::CombinedPolyResolver, lookup::booleanity::BooleanityChecker,
2104 };
2105
2106 type Piop = ZincPlusPiop<TestZincTypesIprs, BigLinearUair<ZtInt, ZtFmod>, F, D, QUARTER_D>;
2107 type Ideal = IdealOrZero<DegreeOneIdeal<E>>;
2108
2109 let num_constraints = count_constraints::<BigLinearUair<ZtInt, ZtFmod>>();
2110
2111 let num_vars = 8;
2112 let iprs = (
2113 make_iprs(num_vars),
2114 make_iprs(num_vars),
2115 make_iprs(num_vars),
2116 );
2117 let pp = setup_pp::<TestZincTypesIprs>(num_vars, iprs);
2118 let trace = BigLinearUair::<ZtInt, ZtFmod>::generate_random_trace(num_vars, &mut rng());
2119 let public_trace = trace.public(&BigLinearUair::<ZtInt, ZtFmod>::signature());
2120 let mut proof =
2121 Piop::prove::<false, CHECKED>(&pp, &trace, num_vars, project_scalar_fn).expect("prove");
2122
2123 let (cfg, a, alpha) = {
2127 let mut v3 = Piop::step0_reconstruct_transcript::<Ideal>(
2128 &pp,
2129 proof.clone(),
2130 &public_trace,
2131 num_vars,
2132 )
2133 .and_then(|s| s.step1_prime_projection())
2134 .and_then(|s| {
2135 s.step2_ideal_check(default_project_ideal!(), default_project_fq_ideal!())
2136 })
2137 .and_then(|s| s.step3_eval_projection(project_scalar_fn))
2138 .expect("steps 0..=3");
2139
2140 let cfg = v3.field_cfg().clone();
2141 let a = v3.projecting_element_f().clone();
2142 let nv = v3.num_vars();
2143 let claimed_sums = v3.proof_combined_sumcheck().claimed_sums().to_vec();
2144 let proof_cpr = v3.proof_cpr().clone();
2145 let ic_subclaim = v3.ic_subclaim().clone();
2146
2147 let sig = v3.uair_signature().clone();
2148 let num_wit_bin =
2149 sig.total_cols().num_binary_poly_cols() - sig.public_cols().num_binary_poly_cols();
2150 let transcript = v3.fs_transcript_mut();
2151
2152 let folding_challenge: E = transcript.get_field_challenge(&cfg);
2153 CombinedPolyResolver::<F>::prepare_verifier::<BigLinearUair<ZtInt, ZtFmod>>(
2154 &proof_cpr,
2155 claimed_sums[0].clone(),
2156 &ic_subclaim,
2157 num_constraints.q,
2158 nv,
2159 &a,
2160 &folding_challenge,
2161 &cfg,
2162 )
2163 .expect("CPR prepare_verifier");
2164
2165 let bool_anc = BooleanityChecker::<F>::prepare_verifier(
2166 transcript,
2167 &claimed_sums[1],
2168 num_wit_bin,
2169 D,
2170 nv,
2171 &cfg,
2172 )
2173 .expect("booleanity prepare_verifier");
2174
2175 (cfg, a, bool_anc.alpha_powers[1].clone())
2176 };
2177
2178 let one = cfg.one();
2180 let two = cfg.add(&one, &one);
2181
2182 let a_inv: E = cfg.inv(&a).expect("a != 0");
2183 let alpha_over_a: E = cfg.mul(&alpha, &a_inv);
2184 let alpha_over_a_sq: E = cfg.mul(&alpha_over_a, &a_inv);
2185
2186 let bp = proof
2187 .booleanity_proof
2188 .as_mut()
2189 .expect("BigLinearUair has binary-poly witnesses");
2190 let b0: E = cfg.project(&bp.bit_slice_evals[0]);
2191 let b1: E = cfg.project(&bp.bit_slice_evals[1]);
2192 let s0: E = cfg.sub(&cfg.mul(&two, &b0), &one); let s1: E = cfg.sub(&cfg.mul(&two, &b1), &one); let denom_inv: E = cfg
2196 .inv(&cfg.add(&one, &alpha_over_a_sq))
2197 .expect("1 + α/a² != 0");
2198 let delta_0: E = cfg.neg(&cfg.mul(&cfg.sub(&s0, &cfg.mul(&alpha_over_a, &s1)), &denom_inv));
2199 let delta_1: E = cfg.neg(&cfg.mul(&a_inv, &delta_0));
2200
2201 assert!(!cfg.is_zero(&delta_0), "tamper must be non-zero");
2203 assert!(
2204 cfg.is_zero(&cfg.add(&delta_0, &cfg.mul(&a, &delta_1))),
2205 "must preserve OLD ψ_a linear pin-down"
2206 );
2207 let residue = cfg.add(
2208 &cfg.add(&cfg.mul(&delta_0, &s0), &cfg.mul(&delta_0, &delta_0)),
2209 &cfg.mul(
2210 &alpha,
2211 &cfg.add(&cfg.mul(&delta_1, &s1), &cfg.mul(&delta_1, &delta_1)),
2212 ),
2213 );
2214 assert!(cfg.is_zero(&residue), "must preserve booleanity residue");
2215
2216 bp.bit_slice_evals[0] = cfg.lift(&cfg.add(&b0, &delta_0));
2217 bp.bit_slice_evals[1] = cfg.lift(&cfg.add(&b1, &delta_1));
2218
2219 let err = Piop::verify::<_, CHECKED>(
2220 &pp,
2221 proof,
2222 &public_trace,
2223 num_vars,
2224 project_scalar_fn,
2225 default_project_ideal!(),
2226 default_project_fq_ideal!(),
2227 )
2228 .expect_err("verifier must reject alpha-prime-tampered proof");
2229 assert!(
2230 matches!(
2231 err,
2232 ProtocolError::MultipointEval(_) | ProtocolError::PcsVerification(..)
2233 ),
2234 "expected MultipointEval / PCS-chain error, got: {err:?}"
2235 );
2236 }
2237
2238 #[test]
2239 fn test_big_linear_tamper_ideal_check() {
2240 let num_vars = 8;
2241 do_test::<TestZincTypesIprs, BigLinearUairWithPublicInput<ZtInt, ZtFmod>>(
2242 num_vars,
2243 (
2244 make_iprs(num_vars),
2245 make_iprs(num_vars),
2246 make_iprs(num_vars),
2247 ),
2248 default_project_ideal!(),
2249 default_project_fq_ideal!(),
2250 |proof| proof.ideal_check.combined_mle_values.swap(0, 1),
2251 |res| {
2252 assert!(matches!(res.unwrap_err(), ProtocolError::IdealCheck(..)));
2253 },
2254 );
2255 }
2256}