1use alloc::vec;
28use alloc::vec::Vec;
29use core::marker::PhantomData;
30
31use p3_binary_dft::{AdditiveNtt, AdditiveRsEncoder, EncodableLevel};
32use p3_binary_field::TowerLevel;
33use p3_challenger::fs::TranscriptField;
34use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
35use p3_commit::{Encoder, Mmcs, MultilinearPcs};
36use p3_field::ExtensionField;
37use p3_multilinear_util::point::Point;
38use p3_multilinear_util::poly::Poly;
39use p3_sumcheck::layout::{Layout, Verifier, Witness, observe_commitment};
40use p3_sumcheck::strategy::Basis;
41use p3_sumcheck::{
42 OpeningBatch, OpeningEvals, OpeningProtocol, PrescribedOpeningSecurity, PrescribedPointPcs,
43 SumcheckData, SumcheckError,
44};
45use p3_util::log2_ceil_usize;
46
47use crate::PcsLayout;
48use crate::error::BinaryPcsError;
49use crate::fold::{ChallengeField, FoldAlphabet};
50use crate::params::{BinaryPcsConfig, BinaryPcsConfigError};
51use crate::proof::BinaryPcsProof;
52use crate::prover::{BinaryPcsProverData, commit, fold_rounds_with, open_queries};
53use crate::transcript::{BinaryPcsProverTranscript, BinaryPcsShape, BinaryPcsVerifierTranscript};
54use crate::verifier::{
55 check_canonical_pow_witness, check_round_and_final_lengths, verify_query_paths,
56};
57
58type Failure<F, MT> = BinaryPcsError<F, <MT as Mmcs<F>>::Error>;
60
61fn batch_size_mismatch<F, MmcsError, T, U>(
66 table_idx: usize,
67 batch: &OpeningBatch<T>,
68 evals: &OpeningBatch<U>,
69) -> BinaryPcsError<F, MmcsError> {
70 BinaryPcsError::OpeningBatchSizeMismatch {
71 table_idx,
72 expected_current: batch.current().len(),
73 expected_next: batch.next().len(),
74 actual_current: evals.current().len(),
75 actual_next: evals.next().len(),
76 }
77}
78
79type Opening<F, EF, MT, MX> = Result<BinaryPcsProof<F, EF, MT, MX>, Failure<F, MT>>;
81
82pub struct BinaryPcs<F: EncodableLevel, EF, MT, MX, E = <F as EncodableLevel>::Encoder> {
94 config: BinaryPcsConfig,
95 mmcs: MT,
96 round_mmcs: MX,
97 encoder: E,
98 _fields: PhantomData<(F, EF)>,
99}
100
101impl<F: EncodableLevel, EF: TowerLevel, MT, MX> BinaryPcs<F, EF, MT, MX> {
102 pub fn new(
125 config: BinaryPcsConfig,
126 mmcs: MT,
127 round_mmcs: MX,
128 ) -> Result<Self, BinaryPcsConfigError> {
129 config.check_alphabets::<F, EF>()?;
130 Ok(Self {
131 config,
132 mmcs,
133 round_mmcs,
134 encoder: F::Encoder::default(),
135 _fields: PhantomData,
136 })
137 }
138
139 #[must_use]
143 pub const fn num_variables(&self) -> usize {
144 self.config.num_variables()
145 }
146}
147
148impl<F, EF, MT, MX, Ntt> BinaryPcs<F, EF, MT, MX, AdditiveRsEncoder<F, Ntt>>
149where
150 F: EncodableLevel,
151 EF: TowerLevel,
152 Ntt: AdditiveNtt<F> + Sync,
153{
154 pub fn with_ntt(
164 config: BinaryPcsConfig,
165 mmcs: MT,
166 round_mmcs: MX,
167 ntt: Ntt,
168 ) -> Result<Self, BinaryPcsConfigError> {
169 config.check_alphabets::<F, EF>()?;
170 Ok(Self {
171 config,
172 mmcs,
173 round_mmcs,
174 encoder: AdditiveRsEncoder::new(ntt),
175 _fields: PhantomData,
176 })
177 }
178}
179
180impl<F, EF, MT, MX, E> BinaryPcs<F, EF, MT, MX, E>
181where
182 F: EncodableLevel + TranscriptField + FoldAlphabet<EF>,
183 EF: ChallengeField<F> + ExtensionField<F> + TowerLevel + FoldAlphabet<EF>,
184 MT: Mmcs<F>,
185 MX: Mmcs<EF, Error = MT::Error>,
186 E: Encoder<F> + Sync,
187{
188 pub fn validate_opening_protocol(
222 &self,
223 protocol: &OpeningProtocol,
224 ) -> Result<(), BinaryPcsError<F, MT::Error>> {
225 let total = protocol.checked_num_cells();
226 if !matches!(total, Some(total) if total > 0 && log2_ceil_usize(total) == self.config.num_variables())
227 {
228 return Err(BinaryPcsError::InvalidOpeningProtocol);
229 }
230 let actual = protocol
231 .checked_num_claims()
232 .ok_or(BinaryPcsError::InvalidOpeningProtocol)?;
233
234 let max = self.config.max_opening_claims();
235 if actual > max {
236 return Err(BinaryPcsError::OpeningClaimCountExceedsSecurityBudget {
237 actual,
238 max,
239 security_level: self.config.security_level(),
240 });
241 }
242 Ok(())
243 }
244
245 #[tracing::instrument(name = "binary pcs open", skip_all)]
248 pub fn try_open<Challenger>(
249 &self,
250 mut prover_data: BinaryPcsProverData<F, EF, MT>,
251 protocol: &OpeningProtocol,
252 challenger: &mut Challenger,
253 ) -> Opening<F, EF, MT, MX>
254 where
255 Challenger: FieldChallenger<F>
256 + GrindingChallenger<Witness = F>
257 + CanSampleUniformBits<F>
258 + CanObserve<MT::Commitment>
259 + CanObserve<MX::Commitment>,
260 {
261 self.validate_opening_protocol(protocol)?;
262 Self::validate_preprocessing_depth(&prover_data)?;
263 let evals = protocol
264 .iter_openings()
265 .map(|(table_idx, batch)| prover_data.layout.eval(table_idx, batch, challenger))
266 .collect();
267 Ok(self.finish_open(prover_data, evals, challenger))
268 }
269
270 #[tracing::instrument(name = "binary pcs open", skip_all)]
277 pub fn try_open_at<Challenger>(
278 &self,
279 mut prover_data: BinaryPcsProverData<F, EF, MT>,
280 protocol: &OpeningProtocol,
281 points: &[Point<EF>],
282 challenger: &mut Challenger,
283 ) -> Opening<F, EF, MT, MX>
284 where
285 Challenger: FieldChallenger<F>
286 + GrindingChallenger<Witness = F>
287 + CanSampleUniformBits<F>
288 + CanObserve<MT::Commitment>
289 + CanObserve<MX::Commitment>,
290 {
291 self.validate_opening_protocol(protocol)?;
292 Self::validate_preprocessing_depth(&prover_data)?;
293 Self::validate_points(protocol, points)?;
294 let evals = protocol
295 .iter_openings()
296 .zip(points)
297 .map(|((table_idx, batch), point)| {
298 prover_data
299 .layout
300 .eval_at(table_idx, batch, point, challenger)
301 })
302 .collect();
303 Ok(self.finish_open(prover_data, evals, challenger))
304 }
305
306 #[tracing::instrument(name = "binary pcs open", skip_all)]
323 pub fn try_open_at_known<Challenger>(
324 &self,
325 mut prover_data: BinaryPcsProverData<F, EF, MT>,
326 protocol: &OpeningProtocol,
327 points: &[Point<EF>],
328 evals: &[OpeningEvals<EF>],
329 challenger: &mut Challenger,
330 ) -> Opening<F, EF, MT, MX>
331 where
332 Challenger: FieldChallenger<F>
333 + GrindingChallenger<Witness = F>
334 + CanSampleUniformBits<F>
335 + CanObserve<MT::Commitment>
336 + CanObserve<MX::Commitment>,
337 {
338 self.validate_opening_protocol(protocol)?;
339 Self::validate_preprocessing_depth(&prover_data)?;
340 Self::validate_points(protocol, points)?;
341 Self::validate_evals(protocol, evals)?;
342 for (((table_idx, batch), point), batch_evals) in
343 protocol.iter_openings().zip(points).zip(evals)
344 {
345 prover_data
346 .layout
347 .eval_at_known(table_idx, batch, point, batch_evals, challenger);
348 }
349 Ok(self.finish_open(prover_data, evals.to_vec(), challenger))
350 }
351
352 fn validate_preprocessing_depth(
358 prover_data: &BinaryPcsProverData<F, EF, MT>,
359 ) -> Result<(), BinaryPcsError<F, MT::Error>> {
360 let folding = prover_data.layout.folding();
361 if folding != 0 {
362 return Err(BinaryPcsError::OpeningPreprocessingDepth { folding });
363 }
364 Ok(())
365 }
366
367 fn validate_points(
368 protocol: &OpeningProtocol,
369 points: &[Point<EF>],
370 ) -> Result<(), BinaryPcsError<F, MT::Error>> {
371 protocol
372 .check_points(points)
373 .map_err(|_| BinaryPcsError::OpeningPointShapeMismatch)
374 }
375
376 fn validate_evals(
378 protocol: &OpeningProtocol,
379 evals: &[OpeningEvals<EF>],
380 ) -> Result<(), BinaryPcsError<F, MT::Error>> {
381 if protocol.num_openings() != evals.len() {
382 return Err(BinaryPcsError::OpeningEvalCountMismatch {
383 expected: protocol.num_openings(),
384 actual: evals.len(),
385 });
386 }
387 for ((table_idx, batch), batch_evals) in protocol.iter_openings().zip(evals) {
388 if !batch.has_same_shape(batch_evals) {
389 return Err(batch_size_mismatch(table_idx, batch, batch_evals));
390 }
391 }
392 Ok(())
393 }
394
395 fn finish_open<Challenger>(
398 &self,
399 prover_data: BinaryPcsProverData<F, EF, MT>,
400 evals: Vec<OpeningEvals<EF>>,
401 challenger: &mut Challenger,
402 ) -> BinaryPcsProof<F, EF, MT, MX>
403 where
404 Challenger: FieldChallenger<F>
405 + GrindingChallenger<Witness = F>
406 + CanSampleUniformBits<F>
407 + CanObserve<MT::Commitment>
408 + CanObserve<MX::Commitment>,
409 {
410 self.finish_open_with::<false, Challenger>(prover_data, evals, challenger)
411 }
412
413 fn finish_open_with<const BIND_EACH_ROUND: bool, Challenger>(
425 &self,
426 prover_data: BinaryPcsProverData<F, EF, MT>,
427 evals: Vec<OpeningEvals<EF>>,
428 challenger: &mut Challenger,
429 ) -> BinaryPcsProof<F, EF, MT, MX>
430 where
431 Challenger: FieldChallenger<F>
432 + GrindingChallenger<Witness = F>
433 + CanSampleUniformBits<F>
434 + CanObserve<MT::Commitment>
435 + CanObserve<MX::Commitment>,
436 {
437 let shape = BinaryPcsShape::new(&self.config);
441 let mut transcript = BinaryPcsProverTranscript::new(challenger, shape);
442
443 let (base_merkle_data, sumcheck_data, rounds, _randomness, final_codeword) =
444 fold_rounds_with::<BIND_EACH_ROUND, F, EF, MT, MX, _>(
445 prover_data,
446 &self.config,
447 &self.mmcs,
448 &self.round_mmcs,
449 &mut transcript,
450 );
451 let query_proofs = open_queries(
452 &self.config,
453 &self.mmcs,
454 &self.round_mmcs,
455 &mut transcript,
456 &base_merkle_data,
457 &rounds,
458 &final_codeword,
459 );
460
461 transcript.finish();
463
464 BinaryPcsProof {
465 sumcheck: sumcheck_data,
466 rounds: query_proofs.rounds,
467 base_opened_values: query_proofs.base_opened_values,
468 base_multi_proof: query_proofs.base_multi_proof,
469 final_codeword: Poly::new(final_codeword),
470 pow_witness: query_proofs.pow_witness,
471 evals,
472 }
473 }
474
475 #[tracing::instrument(name = "binary pcs verify", skip_all)]
526 fn verify_opening<'p, Challenger>(
527 &self,
528 commitment: &MT::Commitment,
529 proof: &'p BinaryPcsProof<F, EF, MT, MX>,
530 protocol: &OpeningProtocol,
531 points: Option<&[Point<EF>]>,
532 challenger: &mut Challenger,
533 ) -> Result<&'p [OpeningEvals<EF>], BinaryPcsError<F, MT::Error>>
534 where
535 Challenger: FieldChallenger<F>
536 + GrindingChallenger<Witness = F>
537 + CanSampleUniformBits<F>
538 + CanObserve<MT::Commitment>
539 + CanObserve<MX::Commitment>,
540 {
541 self.validate_opening_protocol(protocol)?;
542 if protocol.num_openings() != proof.evals.len() {
543 return Err(BinaryPcsError::OpeningBatchCountMismatch {
544 expected: protocol.num_openings(),
545 actual: proof.evals.len(),
546 });
547 }
548
549 let num_fold_rounds = self.config.num_fold_rounds();
550 if proof.sumcheck.num_rounds() != num_fold_rounds {
551 return Err(SumcheckError::RoundCountMismatch {
552 expected: num_fold_rounds,
553 actual: proof.sumcheck.num_rounds(),
554 }
555 .into());
556 }
557
558 if !proof.sumcheck.pow_witnesses.is_empty() {
563 return Err(BinaryPcsError::NonEmptyPowWitnesses {
564 actual: proof.sumcheck.pow_witnesses.len(),
565 });
566 }
567
568 check_round_and_final_lengths(&self.config, proof)?;
569
570 check_canonical_pow_witness(&self.config, proof)?;
573
574 let mut layout_verifier =
577 Verifier::<F, EF>::new(&protocol.table_shapes(), PcsLayout::<F, EF>::strategy());
578
579 for (i, (table_idx, batch)) in protocol.iter_openings().enumerate() {
580 let evals = &proof.evals[i];
581 if !batch.has_same_shape(evals) {
582 return Err(batch_size_mismatch(table_idx, batch, evals));
583 }
584 match points {
585 Some(points) => {
586 layout_verifier
587 .add_claim_at(table_idx, batch, &points[i], evals, challenger)?;
588 }
589 None => {
590 layout_verifier.add_claim(table_idx, batch, evals, challenger)?;
591 }
592 }
593 }
594
595 let shape = BinaryPcsShape::new(&self.config);
606 let mut transcript = BinaryPcsVerifierTranscript::new(challenger, shape);
607
608 let alpha = transcript.fold_batch(|ch| layout_verifier.batching_challenge(ch));
609 let constraint = layout_verifier.constraint(alpha);
610 let mut claimed_sum = EF::ZERO;
611 constraint.combine_evals(&mut claimed_sum);
612
613 let mut betas = Vec::with_capacity(num_fold_rounds);
616 for (batch, (start, arity)) in self.config.fold_batches().enumerate() {
617 for r in start..start + arity {
618 let round_data = SumcheckData {
619 polynomial_evaluations: vec![proof.sumcheck.polynomial_evaluations()[r]],
620 pow_witnesses: Vec::new(),
621 };
622 let round_point = match transcript.fold_batch(|ch| {
624 round_data.verify_rounds(ch, &mut claimed_sum, 1, 0, Basis::Evaluation)
625 }) {
626 Ok(point) => point,
627 Err(error) => {
628 transcript.abort();
629 return Err(error.into());
630 }
631 };
632 betas.push(round_point.as_slice()[0]);
633 }
634 if batch + 1 < self.config.num_fold_batches() {
635 transcript.oracle_commitment(proof.rounds[batch].commitment.clone());
636 }
637 }
638
639 let fold_point = Point::new(betas);
645 let evaluation_of_weights = PcsLayout::<F, EF>::strategy()
646 .variable_order
647 .eval_constraints_poly(core::slice::from_ref(&constraint), &fold_point);
648 let final_value = proof.final_codeword.as_slice()[0];
649 let final_codeword_is_uniform = proof
650 .final_codeword
651 .as_slice()
652 .iter()
653 .all(|&v| v == final_value);
654 if !final_codeword_is_uniform || claimed_sum != evaluation_of_weights * final_value {
655 transcript.abort();
656 return Err(BinaryPcsError::FinalCheck);
657 }
658
659 match verify_query_paths(
660 &self.config,
661 &self.mmcs,
662 &self.round_mmcs,
663 commitment,
664 fold_point.as_slice(),
665 proof,
666 &mut transcript,
667 ) {
668 Ok(()) => transcript.finish(),
669 Err(error) => {
670 transcript.abort();
671 return Err(error);
672 }
673 }
674
675 Ok(&proof.evals)
676 }
677}
678
679impl<F, EF, MT, MX, E, Challenger> MultilinearPcs<EF, Challenger> for BinaryPcs<F, EF, MT, MX, E>
680where
681 F: EncodableLevel + TranscriptField + FoldAlphabet<EF>,
682 EF: ChallengeField<F> + ExtensionField<F> + TowerLevel + FoldAlphabet<EF>,
683 MT: Mmcs<F>,
684 MX: Mmcs<EF, Error = MT::Error>,
685 E: Encoder<F> + Sync,
686 Challenger: FieldChallenger<F>
687 + GrindingChallenger<Witness = F>
688 + CanSampleUniformBits<F>
689 + CanObserve<MT::Commitment>
690 + CanObserve<MX::Commitment>,
691{
692 type Val = F;
693 type Commitment = MT::Commitment;
694 type ProverData = BinaryPcsProverData<F, EF, MT>;
695 type Proof = BinaryPcsProof<F, EF, MT, MX>;
696 type Error = BinaryPcsError<F, MT::Error>;
697 type ProverError = BinaryPcsError<F, MT::Error>;
698 type Witness = Witness<F>;
699 type OpeningProtocol = OpeningProtocol;
700
701 fn num_vars(&self) -> usize {
702 self.config.num_variables()
703 }
704
705 fn commit(
706 &self,
707 witness: Self::Witness,
708 challenger: &mut Challenger,
709 ) -> Result<(Self::Commitment, Self::ProverData), Self::ProverError> {
710 let (commitment, prover_data) =
711 commit::<F, EF, _, _>(&self.config, &self.encoder, &self.mmcs, witness);
712
713 self.observe_commitment(&commitment, challenger);
715
716 Ok((commitment, prover_data))
717 }
718
719 fn observe_commitment(&self, commitment: &Self::Commitment, challenger: &mut Challenger) {
720 observe_commitment::<F, _, _>(challenger, commitment.clone());
721 }
722
723 fn open(
725 &self,
726 prover_data: Self::ProverData,
727 protocol: Self::OpeningProtocol,
728 challenger: &mut Challenger,
729 ) -> Result<Self::Proof, Self::ProverError> {
730 self.try_open(prover_data, &protocol, challenger)
731 }
732
733 fn verify(
734 &self,
735 commitment: &Self::Commitment,
736 proof: &Self::Proof,
737 challenger: &mut Challenger,
738 protocol: Self::OpeningProtocol,
739 ) -> Result<(), Self::Error> {
740 self.observe_commitment(commitment, challenger);
742 self.verify_opening(commitment, proof, &protocol, None, challenger)
743 .map(|_| ())
744 }
745}
746
747impl<F, EF, MT, MX, E, Challenger> PrescribedPointPcs<EF, Challenger>
748 for BinaryPcs<F, EF, MT, MX, E>
749where
750 F: EncodableLevel + TranscriptField + FoldAlphabet<EF>,
751 EF: ChallengeField<F> + ExtensionField<F> + TowerLevel + FoldAlphabet<EF>,
752 MT: Mmcs<F>,
753 MX: Mmcs<EF, Error = MT::Error>,
754 E: Encoder<F> + Sync,
755 Challenger: FieldChallenger<F>
756 + GrindingChallenger<Witness = F>
757 + CanSampleUniformBits<F>
758 + CanObserve<MT::Commitment>
759 + CanObserve<MX::Commitment>,
760{
761 fn prescribed_security(&self, protocol: &OpeningProtocol) -> Option<PrescribedOpeningSecurity> {
762 self.validate_opening_protocol(protocol).ok()?;
763 Some(PrescribedOpeningSecurity {
764 terms: vec![
765 self.config
766 .security_regime()
767 .opening_term(protocol.checked_num_claims()?),
768 ],
769 log2_max_candidates: 0.0,
770 })
771 }
772
773 fn open_at(
776 &self,
777 prover_data: Self::ProverData,
778 protocol: &OpeningProtocol,
779 points: &[Point<EF>],
780 challenger: &mut Challenger,
781 ) -> Result<Self::Proof, Self::ProverError> {
782 self.try_open_at(prover_data, protocol, points, challenger)
783 }
784
785 fn verify_at(
796 &self,
797 commitment: &Self::Commitment,
798 proof: &Self::Proof,
799 protocol: &OpeningProtocol,
800 points: &[Point<EF>],
801 challenger: &mut Challenger,
802 ) -> Result<Vec<OpeningEvals<EF>>, Self::Error> {
803 Self::validate_points(protocol, points)?;
804 self.verify_opening(commitment, proof, protocol, Some(points), challenger)
805 .map(<[_]>::to_vec)
806 }
807}
808
809#[cfg(test)]
810mod tests {
811 use alloc::vec::Vec;
812 use alloc::{format, vec};
813
814 use p3_binary_dft::EncodableLevel;
815 use p3_binary_field::{BinaryField8, BinaryField16, BinaryField64, BinaryField128};
816 use p3_challenger::fs::TranscriptField;
817 use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
818 use p3_commit::{Mmcs, MultilinearPcs};
819 use p3_field::{ExtensionField, PrimeCharacteristicRing};
820 use p3_multilinear_util::point::Point;
821 use p3_sumcheck::layout::{Layout, SuffixProver, Table, Witness};
822 use p3_sumcheck::{OpeningBatch, OpeningProtocol, PrescribedPointPcs, TableShape, TableSpec};
823 use rand::SeedableRng;
824 use rand::distr::{Distribution, StandardUniform};
825 use rand::rngs::SmallRng;
826
827 use super::BinaryPcs;
828 use crate::error::BinaryPcsError;
829 use crate::fold::{ChallengeField, FoldAlphabet};
830 use crate::params::{BinaryPcsConfig, BinaryPcsConfigError, BinaryPcsParams};
831 use crate::proof::BinaryPcsProof;
832 use crate::prover::BinaryPcsProverData;
833 use crate::test_util::{
834 LevelChallenger, LevelMmcs, MyChallenger, MyMmcs, challenger, level_challenger, level_mmcs,
835 mmcs, run_lifecycle,
836 };
837
838 type F = BinaryField128;
839
840 const NUM_VARIABLES: usize = 8;
841
842 #[test]
843 fn constructor_rejects_schedules_derived_for_other_tower_levels() {
844 let high_security = BinaryPcsParams {
850 log_inv_rate: 2,
851 pow_bits: 0,
852 security_level: 100,
853 };
854
855 let wide =
858 BinaryPcsConfig::try_new::<BinaryField128, BinaryField128>(20, high_security).unwrap();
859 let committed = BinaryPcs::<BinaryField64, BinaryField64, (), ()>::new(wide, (), ()).err();
860 assert_eq!(
861 committed,
862 Some(BinaryPcsConfigError::CommittedFieldMismatch {
863 derived: 128,
864 actual: 64,
865 })
866 );
867
868 let mixed = BinaryPcsConfig::try_new::<BinaryField16, BinaryField128>(
870 8,
871 BinaryPcsParams {
872 log_inv_rate: 2,
873 pow_bits: 0,
874 security_level: 40,
875 },
876 )
877 .unwrap();
878 let committed = BinaryPcs::<BinaryField8, BinaryField128, (), ()>::new(mixed, (), ()).err();
879 assert_eq!(
880 committed,
881 Some(BinaryPcsConfigError::CommittedFieldMismatch {
882 derived: 16,
883 actual: 8,
884 })
885 );
886
887 let challenge = BinaryPcs::<BinaryField16, BinaryField64, (), ()>::new(mixed, (), ()).err();
889 assert_eq!(
890 challenge,
891 Some(BinaryPcsConfigError::ChallengeFieldMismatch {
892 derived: 128,
893 actual: 64,
894 })
895 );
896 }
897
898 fn a_single_column_round_trip<A, C>(
906 num_variables: usize,
907 log_folding_factor: usize,
908 log_inv_rate: usize,
909 seed: u64,
910 ) where
911 A: EncodableLevel + TranscriptField + FoldAlphabet<C> + PrimeCharacteristicRing,
912 C: ChallengeField<A> + ExtensionField<A> + FoldAlphabet<C>,
913 StandardUniform: Distribution<A>,
914 LevelMmcs<A>: Mmcs<A>,
915 LevelMmcs<C>: Mmcs<C, Error = <LevelMmcs<A> as Mmcs<A>>::Error>,
916 LevelChallenger<A>: FieldChallenger<A>
917 + GrindingChallenger<Witness = A>
918 + CanSampleUniformBits<A>
919 + CanObserve<<LevelMmcs<A> as Mmcs<A>>::Commitment>
920 + CanObserve<<LevelMmcs<C> as Mmcs<C>>::Commitment>,
921 {
922 let shape = format!(
923 "bits {}/{}, arity {log_folding_factor}",
924 A::bits(),
925 C::bits()
926 );
927 let mut rng = SmallRng::seed_from_u64(seed);
928 let table = Table::<A>::rand(&mut rng, 1, num_variables);
929 let protocol = OpeningProtocol::new(vec![TableSpec::new(
930 TableShape::new(num_variables, 1),
931 vec![OpeningBatch::new(vec![0], Vec::new())],
932 )]);
933
934 let params = BinaryPcsParams {
935 log_inv_rate,
936 pow_bits: 0,
937 security_level: 40,
938 };
939 let config = BinaryPcsConfig::try_new_with_folding::<A, C>(
940 num_variables,
941 params,
942 log_folding_factor,
943 )
944 .unwrap();
945 let pcs: BinaryPcs<A, C, LevelMmcs<A>, LevelMmcs<C>> =
946 BinaryPcs::new(config, level_mmcs(), level_mmcs()).unwrap();
947
948 let mut prover_ch = level_challenger::<A>();
949 let witness = SuffixProver::<A, C>::new_witness(vec![table], 0);
950 let (root, data) = pcs.commit(witness, &mut prover_ch).unwrap();
951 let proof = pcs.open(data, protocol.clone(), &mut prover_ch).unwrap();
952
953 pcs.verify(
954 &root,
955 &proof,
956 &mut level_challenger::<A>(),
957 protocol.clone(),
958 )
959 .unwrap_or_else(|error| panic!("{shape}: {error:?}"));
960
961 let mut tampered = proof;
962 tampered.base_opened_values[0][0] += A::ONE;
963 assert!(
964 pcs.verify(&root, &tampered, &mut level_challenger::<A>(), protocol)
965 .is_err(),
966 "{shape}: a tampered base symbol was accepted"
967 );
968 }
969
970 #[test]
971 fn a_single_column_round_trips_over_every_challenge_field() {
972 for log_folding_factor in 1..=4 {
977 a_single_column_round_trip::<BinaryField8, BinaryField64>(6, log_folding_factor, 2, 1);
978 a_single_column_round_trip::<BinaryField8, F>(6, log_folding_factor, 2, 2);
979 a_single_column_round_trip::<BinaryField64, BinaryField64>(
980 10,
981 log_folding_factor,
982 1,
983 3,
984 );
985 a_single_column_round_trip::<BinaryField64, F>(10, log_folding_factor, 1, 4);
986 a_single_column_round_trip::<F, F>(10, log_folding_factor, 1, 5);
987 }
988 }
989
990 #[test]
994 fn commit_open_verify_round_trips() {
995 let (pcs, commitment, proof, protocol) = run_lifecycle(NUM_VARIABLES);
996
997 let mut verifier_challenger = challenger();
998 pcs.verify(&commitment, &proof, &mut verifier_challenger, protocol)
999 .unwrap();
1000 }
1001
1002 #[test]
1006 fn a_proof_round_trips_through_postcard() {
1007 let (pcs, commitment, proof, protocol) = run_lifecycle(NUM_VARIABLES);
1008
1009 let bytes = postcard::to_allocvec(&proof).unwrap();
1010 let decoded: BinaryPcsProof<F, F, MyMmcs, MyMmcs> = postcard::from_bytes(&bytes).unwrap();
1011 assert_eq!(decoded.rounds.len(), proof.rounds.len());
1012
1013 let mut verifier_challenger = challenger();
1014 pcs.verify(&commitment, &decoded, &mut verifier_challenger, protocol)
1015 .unwrap();
1016 }
1017
1018 fn open_binding_each_round(
1027 pcs: &BinaryPcs<F, F, MyMmcs, MyMmcs>,
1028 mut prover_data: BinaryPcsProverData<F, F, MyMmcs>,
1029 protocol: &OpeningProtocol,
1030 challenger: &mut MyChallenger,
1031 ) -> BinaryPcsProof<F, F, MyMmcs, MyMmcs> {
1032 let evals = protocol
1033 .iter_openings()
1034 .map(|(table_idx, batch)| prover_data.layout.eval(table_idx, batch, challenger))
1035 .collect();
1036
1037 pcs.finish_open_with::<true, MyChallenger>(prover_data, evals, challenger)
1038 }
1039
1040 #[test]
1041 fn fusing_the_binding_into_the_measuring_pass_leaves_the_proof_byte_identical() {
1042 let num_variables = NUM_VARIABLES;
1067 let params = BinaryPcsParams {
1068 log_inv_rate: 2,
1069 pow_bits: 0,
1070 security_level: 40,
1071 };
1072
1073 for log_folding_factor in [1usize, 3] {
1074 let mut rng = SmallRng::seed_from_u64(0x50FA);
1075 let table = Table::rand(&mut rng, 1, num_variables);
1076 let shape = format!("arity {log_folding_factor}");
1077
1078 let protocol = OpeningProtocol::new(vec![TableSpec::new(
1079 TableShape::new(num_variables, 1),
1080 vec![OpeningBatch::new(vec![0], Vec::new())],
1081 )]);
1082
1083 let config = BinaryPcsConfig::try_new_with_folding::<F, F>(
1084 num_variables,
1085 params,
1086 log_folding_factor,
1087 )
1088 .unwrap();
1089 let pcs = BinaryPcs::new(config, mmcs(), mmcs()).unwrap();
1090
1091 let mut got_challenger = challenger();
1093 let (got_commitment, got_data) = pcs
1094 .commit(
1095 SuffixProver::<F, F>::new_witness(vec![table.clone()], 0),
1096 &mut got_challenger,
1097 )
1098 .unwrap();
1099 let got = pcs
1100 .open(got_data, protocol.clone(), &mut got_challenger)
1101 .unwrap();
1102
1103 let mut want_challenger = challenger();
1105 let (want_commitment, want_data) = pcs
1106 .commit(
1107 SuffixProver::<F, F>::new_witness(vec![table], 0),
1108 &mut want_challenger,
1109 )
1110 .unwrap();
1111 let want = open_binding_each_round(&pcs, want_data, &protocol, &mut want_challenger);
1112
1113 assert_eq!(
1115 got.sumcheck.num_rounds(),
1116 want.sumcheck.num_rounds(),
1117 "{shape}: round counts"
1118 );
1119 for (round, (got_msg, want_msg)) in got
1120 .sumcheck
1121 .polynomial_evaluations()
1122 .iter()
1123 .zip(want.sumcheck.polynomial_evaluations())
1124 .enumerate()
1125 {
1126 assert_eq!(got_msg, want_msg, "{shape}: round {round} message");
1127 }
1128
1129 let got_bytes = postcard::to_allocvec(&got).unwrap();
1131 let want_bytes = postcard::to_allocvec(&want).unwrap();
1132 assert_eq!(got_bytes, want_bytes, "{shape}: proof bytes");
1133
1134 assert_eq!(
1140 got_challenger.sample_algebra_element::<F>(),
1141 want_challenger.sample_algebra_element::<F>(),
1142 "{shape}: transcript state after opening"
1143 );
1144
1145 assert_eq!(got_commitment, want_commitment, "{shape}");
1147 pcs.verify(&got_commitment, &got, &mut challenger(), protocol)
1148 .unwrap();
1149 }
1150 }
1151
1152 const fn reproducible_params() -> BinaryPcsParams {
1154 BinaryPcsParams {
1155 log_inv_rate: 2,
1156 pow_bits: 0,
1157 security_level: 40,
1158 }
1159 }
1160
1161 fn one_column_witness() -> Witness<F> {
1163 let mut rng = SmallRng::seed_from_u64(0x0EAF);
1164 SuffixProver::<F, F>::new_witness(vec![Table::rand(&mut rng, 1, NUM_VARIABLES)], 0)
1165 }
1166
1167 fn one_column_protocol() -> OpeningProtocol {
1169 OpeningProtocol::new(vec![TableSpec::new(
1170 TableShape::new(NUM_VARIABLES, 1),
1171 vec![OpeningBatch::new(vec![0], Vec::new())],
1172 )])
1173 }
1174
1175 #[test]
1176 fn supplying_the_evaluation_leaves_the_proof_byte_identical() {
1177 let protocol = one_column_protocol();
1190 let config =
1191 BinaryPcsConfig::try_new::<F, F>(NUM_VARIABLES, reproducible_params()).unwrap();
1192 let pcs = BinaryPcs::new(config, mmcs(), mmcs()).unwrap();
1193
1194 let mut computed_challenger = challenger();
1196 let (commitment, computed_data) = pcs
1197 .commit(one_column_witness(), &mut computed_challenger)
1198 .unwrap();
1199 let sample: F = computed_challenger.sample_algebra_element();
1200 let point = Point::expand_from_univariate(sample, NUM_VARIABLES);
1201 let computed = pcs
1202 .try_open_at(
1203 computed_data,
1204 &protocol,
1205 core::slice::from_ref(&point),
1206 &mut computed_challenger,
1207 )
1208 .unwrap();
1209
1210 let mut supplied_challenger = challenger();
1212 let (same_commitment, supplied_data) = pcs
1213 .commit(one_column_witness(), &mut supplied_challenger)
1214 .unwrap();
1215 let sample: F = supplied_challenger.sample_algebra_element();
1216 let supplied_point = Point::expand_from_univariate(sample, NUM_VARIABLES);
1217 assert_eq!(same_commitment, commitment, "same witness, same commitment");
1218 assert_eq!(supplied_point, point, "same sponge, same point");
1219
1220 let supplied = pcs
1221 .try_open_at_known(
1222 supplied_data,
1223 &protocol,
1224 core::slice::from_ref(&supplied_point),
1225 &computed.evals,
1226 &mut supplied_challenger,
1227 )
1228 .unwrap();
1229
1230 assert_eq!(
1231 postcard::to_allocvec(&supplied).unwrap(),
1232 postcard::to_allocvec(&computed).unwrap(),
1233 "proof bytes"
1234 );
1235
1236 assert_eq!(
1238 supplied_challenger.sample_algebra_element::<F>(),
1239 computed_challenger.sample_algebra_element::<F>(),
1240 "transcript state after opening"
1241 );
1242
1243 let mut verifier_challenger = challenger();
1245 pcs.observe_commitment(&commitment, &mut verifier_challenger);
1246 let verifier_sample: F = verifier_challenger.sample_algebra_element();
1247 let verifier_point = Point::expand_from_univariate(verifier_sample, NUM_VARIABLES);
1248 pcs.verify_at(
1249 &commitment,
1250 &supplied,
1251 &protocol,
1252 core::slice::from_ref(&verifier_point),
1253 &mut verifier_challenger,
1254 )
1255 .unwrap();
1256 }
1257
1258 #[test]
1259 fn a_known_opening_whose_evaluations_miss_the_protocol_is_refused() {
1260 let protocol = one_column_protocol();
1266 let config =
1267 BinaryPcsConfig::try_new::<F, F>(NUM_VARIABLES, reproducible_params()).unwrap();
1268 let pcs = BinaryPcs::new(config, mmcs(), mmcs()).unwrap();
1269
1270 let mut prover_challenger = challenger();
1271 let (_, data) = pcs
1272 .commit(one_column_witness(), &mut prover_challenger)
1273 .unwrap();
1274 let sample: F = prover_challenger.sample_algebra_element();
1275 let point = Point::expand_from_univariate(sample, NUM_VARIABLES);
1276
1277 let mut snapshot = prover_challenger.clone();
1278 let missing = pcs
1279 .try_open_at_known(
1280 data.clone(),
1281 &protocol,
1282 core::slice::from_ref(&point),
1283 &[],
1284 &mut prover_challenger,
1285 )
1286 .err()
1287 .unwrap();
1288 assert!(
1289 matches!(
1290 missing,
1291 BinaryPcsError::OpeningEvalCountMismatch {
1292 expected: 1,
1293 actual: 0,
1294 }
1295 ),
1296 "{missing:?}"
1297 );
1298
1299 let over = pcs
1300 .try_open_at_known(
1301 data.clone(),
1302 &protocol,
1303 core::slice::from_ref(&point),
1304 &[OpeningBatch::new(vec![sample, sample], Vec::new())],
1305 &mut prover_challenger,
1306 )
1307 .err()
1308 .unwrap();
1309 assert!(
1310 matches!(
1311 over,
1312 BinaryPcsError::OpeningBatchSizeMismatch {
1313 table_idx: 0,
1314 expected_current: 1,
1315 expected_next: 0,
1316 actual_current: 2,
1317 actual_next: 0,
1318 }
1319 ),
1320 "{over:?}"
1321 );
1322
1323 let sided = pcs
1324 .try_open_at_known(
1325 data,
1326 &protocol,
1327 core::slice::from_ref(&point),
1328 &[OpeningBatch::new(Vec::new(), vec![sample])],
1329 &mut prover_challenger,
1330 )
1331 .err()
1332 .unwrap();
1333 assert!(
1334 matches!(
1335 sided,
1336 BinaryPcsError::OpeningBatchSizeMismatch {
1337 table_idx: 0,
1338 expected_current: 1,
1339 expected_next: 0,
1340 actual_current: 0,
1341 actual_next: 1,
1342 }
1343 ),
1344 "{sided:?}"
1345 );
1346
1347 assert_eq!(
1348 prover_challenger.sample_algebra_element::<F>(),
1349 snapshot.sample_algebra_element::<F>(),
1350 "a refused opening leaves the transcript alone"
1351 );
1352 }
1353
1354 #[test]
1355 fn an_opening_of_a_layout_that_runs_preprocessing_rounds_is_refused() {
1356 let protocol = one_column_protocol();
1362 let config =
1363 BinaryPcsConfig::try_new::<F, F>(NUM_VARIABLES, reproducible_params()).unwrap();
1364 let pcs = BinaryPcs::new(config, mmcs(), mmcs()).unwrap();
1365
1366 let mut rng = SmallRng::seed_from_u64(0x0EAF);
1367 let witness =
1368 SuffixProver::<F, F>::new_witness(vec![Table::rand(&mut rng, 1, NUM_VARIABLES)], 1);
1369 let mut prover_challenger = challenger();
1370 let (_, data) = pcs.commit(witness, &mut prover_challenger).unwrap();
1371 assert_eq!(data.layout.folding(), 1, "the fixture must carry the depth");
1372
1373 let sample: F = prover_challenger.sample_algebra_element();
1374 let point = Point::expand_from_univariate(sample, NUM_VARIABLES);
1375 let evals = [OpeningBatch::new(vec![sample], Vec::new())];
1376
1377 let mut snapshot = prover_challenger.clone();
1378 let refusals = [
1379 pcs.try_open(data.clone(), &protocol, &mut prover_challenger),
1380 pcs.try_open_at(
1381 data.clone(),
1382 &protocol,
1383 core::slice::from_ref(&point),
1384 &mut prover_challenger,
1385 ),
1386 pcs.try_open_at_known(
1387 data,
1388 &protocol,
1389 core::slice::from_ref(&point),
1390 &evals,
1391 &mut prover_challenger,
1392 ),
1393 ];
1394 for refusal in refusals {
1395 let refusal = refusal.err().unwrap();
1396 assert!(
1397 matches!(
1398 refusal,
1399 BinaryPcsError::OpeningPreprocessingDepth { folding: 1 }
1400 ),
1401 "{refusal:?}"
1402 );
1403 }
1404
1405 assert_eq!(
1406 prover_challenger.sample_algebra_element::<F>(),
1407 snapshot.sample_algebra_element::<F>(),
1408 "a refused opening leaves the transcript alone"
1409 );
1410 }
1411
1412 const fn params() -> BinaryPcsParams {
1413 BinaryPcsParams {
1414 log_inv_rate: 2,
1415 pow_bits: 4,
1416 security_level: 40,
1417 }
1418 }
1419
1420 #[allow(clippy::type_complexity)]
1428 fn open_at_fixture(
1429 seed: u64,
1430 log_folding_factor: usize,
1431 ) -> (
1432 BinaryPcs<F, F, MyMmcs, MyMmcs>,
1433 <MyMmcs as Mmcs<F>>::Commitment,
1434 BinaryPcsProof<F, F, MyMmcs, MyMmcs>,
1435 OpeningProtocol,
1436 Point<F>,
1437 ) {
1438 let mut rng = SmallRng::seed_from_u64(seed);
1439 let table = Table::rand(&mut rng, 1, NUM_VARIABLES);
1440 let witness = SuffixProver::<F, F>::new_witness(vec![table], 0);
1441
1442 let protocol = OpeningProtocol::new(vec![TableSpec::new(
1443 TableShape::new(NUM_VARIABLES, 1),
1444 vec![OpeningBatch::new(vec![0], Vec::new())],
1445 )]);
1446
1447 let config = BinaryPcsConfig::try_new::<F, F>(NUM_VARIABLES, params())
1448 .unwrap()
1449 .try_with_folding(log_folding_factor)
1450 .unwrap();
1451 let pcs = BinaryPcs::new(config, mmcs(), mmcs()).unwrap();
1452
1453 let mut prover_challenger = challenger();
1454 let (commitment, prover_data) = pcs.commit(witness, &mut prover_challenger).unwrap();
1455 let sample: F = prover_challenger.sample_algebra_element();
1456 let point = Point::expand_from_univariate(sample, NUM_VARIABLES);
1457 let proof = pcs
1458 .open_at(
1459 prover_data,
1460 &protocol,
1461 core::slice::from_ref(&point),
1462 &mut prover_challenger,
1463 )
1464 .unwrap();
1465
1466 (pcs, commitment, proof, protocol, point)
1467 }
1468
1469 #[test]
1476 fn verify_at_round_trips_with_transcript_derived_points() {
1477 let (pcs, commitment, proof, protocol, point) = open_at_fixture(0xFEED, 1);
1478
1479 let mut verifier_challenger = challenger();
1482 pcs.observe_commitment(&commitment, &mut verifier_challenger);
1483 let sample: F = verifier_challenger.sample_algebra_element();
1484 let verifier_point = Point::expand_from_univariate(sample, NUM_VARIABLES);
1485 assert_eq!(
1486 verifier_point, point,
1487 "both sides derive the same point from identically-seeded transcripts"
1488 );
1489
1490 pcs.verify_at(
1491 &commitment,
1492 &proof,
1493 &protocol,
1494 core::slice::from_ref(&verifier_point),
1495 &mut verifier_challenger,
1496 )
1497 .unwrap();
1498 }
1499
1500 #[test]
1501 fn batched_verify_at_round_trips_with_transcript_derived_points() {
1502 let (pcs, commitment, proof, protocol, point) = open_at_fixture(0xFEED, 3);
1503 let mut verifier_challenger = challenger();
1504 pcs.observe_commitment(&commitment, &mut verifier_challenger);
1505 let sample: F = verifier_challenger.sample_algebra_element();
1506 let verifier_point = Point::expand_from_univariate(sample, NUM_VARIABLES);
1507 assert_eq!(verifier_point, point);
1508 pcs.verify_at(
1509 &commitment,
1510 &proof,
1511 &protocol,
1512 core::slice::from_ref(&verifier_point),
1513 &mut verifier_challenger,
1514 )
1515 .unwrap();
1516 }
1517
1518 #[test]
1526 fn verify_at_rejects_a_proof_when_the_caller_skips_absorbing_the_commitment() {
1527 let (pcs, commitment, proof, protocol, point) = open_at_fixture(0xFEED, 1);
1528
1529 let mut verifier_challenger = challenger();
1530 let err = pcs
1531 .verify_at(
1532 &commitment,
1533 &proof,
1534 &protocol,
1535 core::slice::from_ref(&point),
1536 &mut verifier_challenger,
1537 )
1538 .unwrap_err();
1539 assert!(
1540 matches!(err, BinaryPcsError::FinalCheck),
1541 "expected FinalCheck, got {err:?}"
1542 );
1543 }
1544
1545 #[test]
1556 fn commit_open_verify_round_trips_with_a_stacked_multi_table_layout() {
1557 let table_a_arity = 2;
1558 let table_b_arity = 3;
1559 let stacked_arity = 4;
1560
1561 let mut rng = SmallRng::seed_from_u64(0x57AC);
1562 let table_a = Table::rand(&mut rng, 2, table_a_arity);
1563 let table_b = Table::rand(&mut rng, 1, table_b_arity);
1564 let witness = SuffixProver::<F, F>::new_witness(vec![table_a, table_b], 0);
1565
1566 let protocol = OpeningProtocol::new(vec![
1567 TableSpec::new(
1568 TableShape::new(table_a_arity, 2),
1569 vec![OpeningBatch::new(vec![0, 1], Vec::new())],
1570 ),
1571 TableSpec::new(
1572 TableShape::new(table_b_arity, 1),
1573 vec![OpeningBatch::new(vec![0], vec![0])],
1577 ),
1578 ]);
1579
1580 let config = BinaryPcsConfig::try_new::<F, F>(stacked_arity, params()).unwrap();
1581 let pcs: BinaryPcs<F, F, MyMmcs, MyMmcs> = BinaryPcs::new(config, mmcs(), mmcs()).unwrap();
1582
1583 let mut prover_challenger = challenger();
1584 let (commitment, prover_data) = pcs.commit(witness, &mut prover_challenger).unwrap();
1585 let proof = pcs
1586 .open(prover_data, protocol.clone(), &mut prover_challenger)
1587 .unwrap();
1588
1589 let mut verifier_challenger = challenger();
1590 pcs.verify(&commitment, &proof, &mut verifier_challenger, protocol)
1591 .unwrap();
1592 }
1593}