1use alloc::vec;
79use alloc::vec::Vec;
80
81use p3_binary_dft::EncodableLevel;
82use p3_binary_field::{PackedGf2, TowerLevel, Underlier};
83use p3_challenger::fs::TranscriptField;
84use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
85use p3_commit::{Mmcs, MultilinearPcs};
86use p3_field::Field;
87use p3_multilinear_util::point::Point;
88use p3_security::SecurityTerm;
89use p3_security::multilinear::{
90 bit_ring_switch_claim_batching_term, bit_ring_switch_tensors_term, bit_ring_switch_term,
91};
92use p3_sumcheck::layout::{Layout, SuffixProver};
93use p3_sumcheck::ring_switch::bits::{
94 BitPacking, BitPackingView, BitRingSwitch, BitRingSwitchClaims, BitRingSwitchClaimsProof,
95 BitRingSwitchProofError,
96};
97use p3_sumcheck::{
98 OpeningBatch, OpeningProtocol, PrescribedOpeningSecurity, PrescribedPointPcs, TableShape,
99 TableSpec,
100};
101use serde::de::DeserializeOwned;
102use serde::{Deserialize, Serialize};
103use thiserror::Error;
104
105use crate::error::BinaryPcsError;
106use crate::fold::{ChallengeField, FoldAlphabet};
107use crate::packing::{Coordinates, PackError, PackedStack};
108use crate::params::{BinaryPcsConfig, BinaryPcsConfigError};
109use crate::pcs::BinaryPcs;
110use crate::proof::BinaryPcsProof;
111use crate::prover::BinaryPcsProverData;
112
113pub trait BooleanBackend<EF> {
115 type Commitment: Clone + Serialize + DeserializeOwned;
117
118 type ProverData;
120
121 type Proof: Clone + Serialize + DeserializeOwned;
123
124 type Error: core::fmt::Debug;
126
127 fn num_variables(&self) -> usize;
129}
130
131pub trait BooleanMultilinearPcs<EF, Challenger>: BooleanBackend<EF> {
156 fn observe_commitment(&self, commitment: &Self::Commitment, challenger: &mut Challenger);
162
163 fn commit_bits<U: Underlier>(
169 &self,
170 bits: &[PackedGf2<U>],
171 challenger: &mut Challenger,
172 ) -> Result<(Self::Commitment, Self::ProverData), Self::Error>;
173
174 #[allow(clippy::type_complexity)]
182 fn open_readings(
183 &self,
184 prover_data: Self::ProverData,
185 openings: &[BitOpening<EF>],
186 challenger: &mut Challenger,
187 ) -> Result<(Vec<BitReadings<EF>>, Self::Proof), Self::Error>;
188
189 fn verify_readings(
195 &self,
196 commitment: &Self::Commitment,
197 openings: &[BitOpening<EF>],
198 readings: &[BitReadings<EF>],
199 proof: &Self::Proof,
200 challenger: &mut Challenger,
201 ) -> Result<(), Self::Error>;
202
203 fn readings_security(
209 &self,
210 num_claims: usize,
211 successor_tensors: bool,
212 ) -> Option<PrescribedOpeningSecurity>;
213
214 fn open_at_points(
226 &self,
227 prover_data: Self::ProverData,
228 points: &[Point<EF>],
229 challenger: &mut Challenger,
230 ) -> Result<(Vec<EF>, Self::Proof), Self::Error>;
231
232 fn verify_at_points(
240 &self,
241 commitment: &Self::Commitment,
242 points: &[Point<EF>],
243 values: &[EF],
244 proof: &Self::Proof,
245 challenger: &mut Challenger,
246 ) -> Result<(), Self::Error>;
247}
248
249#[derive(Clone, Debug, PartialEq, Eq)]
251pub struct BitOpening<EF> {
252 pub point: Point<EF>,
254 pub row_variables: usize,
256 pub current: bool,
258 pub next: bool,
260}
261
262#[derive(Clone, Copy, Debug, PartialEq, Eq)]
264pub struct BitReadings<EF> {
265 pub current: Option<EF>,
267 pub next: Option<EF>,
269}
270
271pub struct BooleanPcs<EF: EncodableLevel, MT, MX> {
276 inner: BinaryPcs<EF, EF, MT, MX>,
278 config: BinaryPcsConfig,
280 num_variables: usize,
282}
283
284impl<EF, MT, MX> BooleanPcs<EF, MT, MX>
285where
286 EF: ChallengeField<EF>
287 + EncodableLevel
288 + TranscriptField
289 + TowerLevel
290 + FoldAlphabet<EF>
291 + Coordinates,
292 MT: Mmcs<EF>,
293 MX: Mmcs<EF, Error = MT::Error>,
294{
295 pub fn new(
302 config: BinaryPcsConfig,
303 mmcs: MT,
304 round_mmcs: MX,
305 num_variables: usize,
306 ) -> Result<Self, BooleanPcsError<EF, MT::Error>> {
307 let packed = Self::packed_variables(num_variables)?;
308 if config.num_variables() != packed {
309 return Err(BooleanPcsError::ConfigArity {
310 expected: packed,
311 actual: config.num_variables(),
312 });
313 }
314 Ok(Self {
315 inner: BinaryPcs::new(config, mmcs, round_mmcs).map_err(BooleanPcsError::Config)?,
316 config,
317 num_variables,
318 })
319 }
320
321 fn packed_variables(num_variables: usize) -> Result<usize, BooleanPcsError<EF, MT::Error>> {
323 let absorbed = BitRingSwitch::<EF>::ABSORBED;
324 num_variables
325 .checked_sub(absorbed)
326 .ok_or(BooleanPcsError::WitnessTooNarrow {
327 needed: absorbed,
328 actual: num_variables,
329 })
330 }
331
332 #[must_use]
336 pub const fn num_variables(&self) -> usize {
337 self.num_variables
338 }
339
340 fn protocol(&self) -> OpeningProtocol {
344 OpeningProtocol::new(vec![TableSpec::new(
345 TableShape::new(self.inner.num_variables(), 1),
346 vec![OpeningBatch::new(vec![0], Vec::new())],
347 )])
348 }
349
350 #[must_use]
357 pub fn reduction_security(&self, num_claims: usize) -> [SecurityTerm; 2] {
358 [
359 bit_ring_switch_term(
360 num_claims.min(1),
361 BitRingSwitch::<EF>::ABSORBED,
362 self.inner.num_variables(),
363 EF::bits(),
364 ),
365 bit_ring_switch_claim_batching_term(num_claims, EF::bits()),
366 ]
367 }
368
369 #[must_use]
373 pub fn opening_security(&self, num_claims: usize) -> PrescribedOpeningSecurity {
374 self.readings_security(num_claims, false)
375 }
376
377 #[must_use]
394 pub fn readings_security(
395 &self,
396 num_claims: usize,
397 successor_tensors: bool,
398 ) -> PrescribedOpeningSecurity {
399 let num_tensors = if successor_tensors { 3 } else { 1 };
401 let mut terms = vec![
402 self.config
403 .security_regime()
404 .opening_term(num_claims.min(1)),
405 bit_ring_switch_tensors_term(
406 num_claims.min(1),
407 num_tensors,
408 BitRingSwitch::<EF>::ABSORBED,
409 self.inner.num_variables(),
410 EF::bits(),
411 ),
412 ];
413 if num_claims > 1 {
415 terms.push(bit_ring_switch_claim_batching_term(num_claims, EF::bits()));
416 }
417 PrescribedOpeningSecurity {
418 terms,
419 log2_max_candidates: 0.0,
421 }
422 }
423
424 fn packing(prover_data: &BinaryPcsProverData<EF, EF, MT>) -> BitPackingView<'_, EF> {
435 BitPacking::from_packed(prover_data.table(0).poly(0))
436 .expect("a committed table is a hypercube over a byte-aligned level")
437 }
438
439 fn check_openings(
441 &self,
442 openings: &[BitOpening<EF>],
443 ) -> Result<(), BooleanPcsError<EF, MT::Error>> {
444 if openings.is_empty() {
445 return Err(BooleanPcsError::NoPoints);
446 }
447 for (index, opening) in openings.iter().enumerate() {
448 if opening.point.num_variables() != self.num_variables {
449 return Err(BooleanPcsError::PointArity {
450 expected: self.num_variables,
451 actual: opening.point.num_variables(),
452 });
453 }
454 if !opening.current && !opening.next {
455 return Err(BooleanPcsError::NoReading { index });
456 }
457 if opening.row_variables > self.num_variables {
458 return Err(BooleanPcsError::RowVariables {
459 index,
460 row_variables: opening.row_variables,
461 num_variables: self.num_variables,
462 });
463 }
464 }
465 Ok(())
466 }
467
468 fn claims(
470 openings: &[BitOpening<EF>],
471 ) -> Result<BitRingSwitchClaims<EF>, BooleanPcsError<EF, MT::Error>> {
472 let reductions = openings
473 .iter()
474 .map(Self::reduction)
475 .collect::<Result<Vec<_>, _>>()?;
476 BitRingSwitchClaims::new(reductions).map_err(BooleanPcsError::Reduction)
477 }
478
479 fn reduction(
484 opening: &BitOpening<EF>,
485 ) -> Result<BitRingSwitch<EF>, BooleanPcsError<EF, MT::Error>> {
486 if opening.next {
487 BitRingSwitch::with_successor(&opening.point, opening.row_variables)
488 } else {
489 BitRingSwitch::new(&opening.point)
490 }
491 .map_err(BooleanPcsError::Reduction)
492 }
493
494 fn current_openings(points: &[Point<EF>]) -> Vec<BitOpening<EF>> {
496 points
497 .iter()
498 .map(|point| BitOpening {
499 point: point.clone(),
500 row_variables: point.num_variables(),
501 current: true,
502 next: false,
503 })
504 .collect()
505 }
506
507 #[allow(clippy::type_complexity)]
524 pub fn open_readings<Challenger>(
525 &self,
526 prover_data: BinaryPcsProverData<EF, EF, MT>,
527 openings: &[BitOpening<EF>],
528 challenger: &mut Challenger,
529 ) -> Result<(Vec<BitReadings<EF>>, BooleanProof<EF, MT, MX>), BooleanPcsError<EF, MT::Error>>
530 where
531 Challenger: FieldChallenger<EF>
532 + GrindingChallenger<Witness = EF>
533 + CanSampleUniformBits<EF>
534 + CanObserve<MT::Commitment>
535 + CanObserve<MX::Commitment>,
536 {
537 self.check_openings(openings)?;
538 let claims = Self::claims(openings)?;
540 let packing =
541 tracing::info_span!("read committed packing").in_scope(|| Self::packing(&prover_data));
542
543 let (reduction, surviving_point, surviving_value) = tracing::info_span!("bit ring switch")
545 .in_scope(|| {
546 claims.prove::<<EF as ChallengeField<EF>>::SumcheckRepr, _, _>(&packing, challenger)
547 });
548
549 let readings = openings
552 .iter()
553 .zip(claims.reductions())
554 .zip(&reduction.claims)
555 .map(|((opening, switch), elements)| {
556 let current = opening
557 .current
558 .then(|| switch.incoming_claim(&elements.tensor));
559 let next = opening
560 .next
561 .then(|| switch.successor_claim(&elements.tensor, elements.successor.as_ref()))
562 .transpose()
563 .map_err(BooleanPcsError::Reduction)?;
564 Ok(BitReadings { current, next })
565 })
566 .collect::<Result<Vec<_>, BooleanPcsError<EF, MT::Error>>>()?;
567
568 let opening = self
572 .inner
573 .try_open_at_known(
574 prover_data,
575 &self.protocol(),
576 &[surviving_point],
577 &[OpeningBatch::new(vec![surviving_value], Vec::new())],
578 challenger,
579 )
580 .map_err(BooleanPcsError::Commitment)?;
581
582 Ok((readings, BooleanProof { reduction, opening }))
583 }
584
585 pub fn verify_readings<Challenger>(
599 &self,
600 commitment: &MT::Commitment,
601 openings: &[BitOpening<EF>],
602 readings: &[BitReadings<EF>],
603 proof: &BooleanProof<EF, MT, MX>,
604 challenger: &mut Challenger,
605 ) -> Result<(), BooleanPcsError<EF, MT::Error>>
606 where
607 Challenger: FieldChallenger<EF>
608 + GrindingChallenger<Witness = EF>
609 + CanSampleUniformBits<EF>
610 + CanObserve<MT::Commitment>
611 + CanObserve<MX::Commitment>,
612 {
613 self.check_openings(openings)?;
614 if readings.len() != openings.len() || proof.reduction.claims.len() != openings.len() {
615 return Err(BooleanPcsError::ClaimCount {
616 expected: openings.len(),
617 values: readings.len(),
618 reductions: proof.reduction.claims.len(),
619 });
620 }
621 if let Some(index) = openings
623 .iter()
624 .zip(readings)
625 .position(|(opening, reading)| {
626 reading.current.is_some() != opening.current
627 || reading.next.is_some() != opening.next
628 })
629 {
630 return Err(BooleanPcsError::ReadingShape { index });
631 }
632 let claims = Self::claims(openings)?;
633
634 let readings = readings
636 .iter()
637 .map(|reading| (reading.current, reading.next))
638 .collect::<Vec<_>>();
639 let (surviving_point, surviving_value) = claims
640 .verify_readings(&proof.reduction, &readings, challenger)
641 .map_err(BooleanPcsError::ReductionProof)?;
642
643 let evals = self
645 .inner
646 .verify_at(
647 commitment,
648 &proof.opening,
649 &self.protocol(),
650 &[surviving_point],
651 challenger,
652 )
653 .map_err(BooleanPcsError::Commitment)?;
654
655 match evals.as_slice() {
660 [batch] if batch.current().first() == Some(&surviving_value) => Ok(()),
661 _ => Err(BooleanPcsError::SurvivingClaim),
662 }
663 }
664}
665
666impl<EF, MT, MX, Challenger> BooleanMultilinearPcs<EF, Challenger> for BooleanPcs<EF, MT, MX>
667where
668 EF: ChallengeField<EF>
669 + EncodableLevel
670 + TranscriptField
671 + TowerLevel
672 + FoldAlphabet<EF>
673 + Coordinates,
674 MT: Mmcs<EF>,
675 MX: Mmcs<EF, Error = MT::Error>,
676 Challenger: FieldChallenger<EF>
677 + GrindingChallenger<Witness = EF>
678 + CanSampleUniformBits<EF>
679 + CanObserve<MT::Commitment>
680 + CanObserve<MX::Commitment>,
681{
682 fn observe_commitment(&self, commitment: &Self::Commitment, challenger: &mut Challenger) {
683 self.inner.observe_commitment(commitment, challenger);
684 }
685
686 fn open_readings(
687 &self,
688 prover_data: Self::ProverData,
689 openings: &[BitOpening<EF>],
690 challenger: &mut Challenger,
691 ) -> Result<(Vec<BitReadings<EF>>, Self::Proof), Self::Error> {
692 Self::open_readings(self, prover_data, openings, challenger)
693 }
694
695 fn verify_readings(
696 &self,
697 commitment: &Self::Commitment,
698 openings: &[BitOpening<EF>],
699 readings: &[BitReadings<EF>],
700 proof: &Self::Proof,
701 challenger: &mut Challenger,
702 ) -> Result<(), Self::Error> {
703 Self::verify_readings(self, commitment, openings, readings, proof, challenger)
704 }
705
706 fn readings_security(
707 &self,
708 num_claims: usize,
709 successor_tensors: bool,
710 ) -> Option<PrescribedOpeningSecurity> {
711 Some(Self::readings_security(self, num_claims, successor_tensors))
712 }
713
714 fn commit_bits<U: Underlier>(
715 &self,
716 bits: &[PackedGf2<U>],
717 challenger: &mut Challenger,
718 ) -> Result<(Self::Commitment, Self::ProverData), Self::Error> {
719 let stack = PackedStack::<PackedGf2<U>, EF>::from_columns(&[bits])?;
721 if stack.column_num_variables() != self.inner.num_variables() {
722 return Err(BooleanPcsError::WitnessArity {
723 expected: self.inner.num_variables(),
724 actual: stack.column_num_variables(),
725 });
726 }
727
728 let witness = SuffixProver::<EF, EF>::new_witness(vec![stack.into_table()], 0);
729 self.inner
730 .commit(witness, challenger)
731 .map_err(BooleanPcsError::Commitment)
732 }
733
734 fn open_at_points(
735 &self,
736 prover_data: Self::ProverData,
737 points: &[Point<EF>],
738 challenger: &mut Challenger,
739 ) -> Result<(Vec<EF>, Self::Proof), Self::Error> {
740 let (readings, proof) =
741 self.open_readings(prover_data, &Self::current_openings(points), challenger)?;
742 let values = readings
743 .into_iter()
744 .map(|reading| {
745 reading
746 .current
747 .expect("an opening asking for the current reading carries it")
748 })
749 .collect();
750 Ok((values, proof))
751 }
752
753 fn verify_at_points(
754 &self,
755 commitment: &Self::Commitment,
756 points: &[Point<EF>],
757 values: &[EF],
758 proof: &Self::Proof,
759 challenger: &mut Challenger,
760 ) -> Result<(), Self::Error> {
761 let readings: Vec<BitReadings<EF>> = values
762 .iter()
763 .map(|&value| BitReadings {
764 current: Some(value),
765 next: None,
766 })
767 .collect();
768 self.verify_readings(
769 commitment,
770 &Self::current_openings(points),
771 &readings,
772 proof,
773 challenger,
774 )
775 }
776}
777
778#[derive(Clone, Serialize, Deserialize)]
780#[serde(bound(
781 serialize = "EF: TowerLevel, MT::Commitment: Serialize, MT::MultiProof: Serialize, MX::Commitment: Serialize, MX::MultiProof: Serialize",
782 deserialize = "EF: TowerLevel, MT::Commitment: Deserialize<'de>, MT::MultiProof: Deserialize<'de>, MX::Commitment: Deserialize<'de>, MX::MultiProof: Deserialize<'de>"
783))]
784pub struct BooleanProof<EF: Field, MT: Mmcs<EF>, MX: Mmcs<EF>> {
785 pub reduction: BitRingSwitchClaimsProof<EF>,
787 pub opening: BinaryPcsProof<EF, EF, MT, MX>,
789}
790
791#[derive(Debug, Error)]
793#[non_exhaustive]
794pub enum BooleanPcsError<EF, MmcsError> {
795 #[error("a bit witness of {actual} variables cannot absorb {needed} into one element")]
797 WitnessTooNarrow {
798 needed: usize,
800 actual: usize,
802 },
803
804 #[error(transparent)]
806 Config(BinaryPcsConfigError),
807
808 #[error("the schedule commits {actual} variables, the packing holds {expected}")]
810 ConfigArity {
811 expected: usize,
813 actual: usize,
815 },
816
817 #[error("the packed witness has {actual} variables, expected {expected}")]
819 WitnessArity {
820 expected: usize,
822 actual: usize,
824 },
825
826 #[error("an opening needs at least one point")]
828 NoPoints,
829
830 #[error("{expected} points against {values} values and {reductions} reductions")]
832 ClaimCount {
833 expected: usize,
835 values: usize,
837 reductions: usize,
839 },
840
841 #[error("the opening point names {actual} variables, expected {expected}")]
843 PointArity {
844 expected: usize,
846 actual: usize,
848 },
849
850 #[error("opening {index} asks for neither the current nor the successor reading")]
852 NoReading {
853 index: usize,
855 },
856
857 #[error("opening {index} names {row_variables} row variables, the witness {num_variables}")]
859 RowVariables {
860 index: usize,
862 row_variables: usize,
864 num_variables: usize,
866 },
867
868 #[error("the readings at opening {index} are not the ones it asks for")]
870 ReadingShape {
871 index: usize,
873 },
874
875 #[error(transparent)]
877 Packing(#[from] PackError),
878
879 #[error(transparent)]
881 Reduction(p3_sumcheck::ring_switch::bits::BitRingSwitchError),
882
883 #[error(transparent)]
885 ReductionProof(BitRingSwitchProofError),
886
887 #[error(transparent)]
889 Commitment(BinaryPcsError<EF, MmcsError>),
890
891 #[error("the opened value is not the claim the reduction left behind")]
895 SurvivingClaim,
896}
897
898impl<EF, MT, MX> BooleanBackend<EF> for BooleanPcs<EF, MT, MX>
899where
900 EF: EncodableLevel + TowerLevel,
901 MT: Mmcs<EF>,
902 MX: Mmcs<EF, Error = MT::Error>,
903{
904 type Commitment = MT::Commitment;
905 type ProverData = BinaryPcsProverData<EF, EF, MT>;
906 type Proof = BooleanProof<EF, MT, MX>;
907 type Error = BooleanPcsError<EF, MT::Error>;
908
909 fn num_variables(&self) -> usize {
910 self.num_variables
911 }
912}
913
914#[cfg(test)]
915mod tests {
916 use p3_binary_field::{BinaryField64, BinaryField128, Gf2, Ghash128, PackedGf2x64};
917 use p3_field::PrimeCharacteristicRing;
918 use p3_multilinear_util::poly::Poly;
919 use proptest::prelude::*;
920 use rand::rngs::SmallRng;
921 use rand::{RngExt, SeedableRng};
922
923 use super::*;
924 use crate::params::BinaryPcsParams;
925 use crate::test_util::{MyChallenger, MyMmcs, challenger, mmcs};
926
927 fn replayed(
929 pcs: &BooleanPcs<EF, MyMmcs, MyMmcs>,
930 commitment: &<MyMmcs as Mmcs<EF>>::Commitment,
931 ) -> MyChallenger {
932 let mut challenger = challenger();
934 pcs.observe_commitment(commitment, &mut challenger);
935 challenger
936 }
937
938 type EF = BinaryField128;
939
940 const fn blocks(log_bits: usize) -> usize {
942 1 << (log_bits - 6)
943 }
944
945 fn boolean_pcs(log_bits: usize) -> BooleanPcs<EF, MyMmcs, MyMmcs> {
947 let params = BinaryPcsParams {
948 log_inv_rate: 2,
949 pow_bits: 0,
950 security_level: 40,
951 };
952 let config = BinaryPcsConfig::try_new::<EF, EF>(log_bits - 7, params).unwrap();
954 BooleanPcs::new(config, mmcs(), mmcs(), log_bits).unwrap()
955 }
956
957 fn witness(seed: u64, log_bits: usize) -> Vec<PackedGf2x64> {
959 let mut rng = SmallRng::seed_from_u64(seed);
960 (0..blocks(log_bits))
961 .map(|_| PackedGf2x64::new(rng.random::<u64>()))
962 .collect()
963 }
964
965 fn embedded(bits: &[PackedGf2x64]) -> Poly<EF> {
969 Poly::new(
970 bits.iter()
971 .flat_map(|block| {
972 (0..PackedGf2x64::WIDTH).map(move |lane| {
973 if block.get(lane) == Gf2::ONE {
974 EF::ONE
975 } else {
976 EF::ZERO
977 }
978 })
979 })
980 .collect::<Vec<EF>>(),
981 )
982 }
983
984 #[test]
985 fn a_boolean_opening_round_trips_at_the_value_the_bits_define() {
986 const LOG_BITS: usize = 13;
996
997 let bits = witness(0xB001, LOG_BITS);
998 let pcs = boolean_pcs(LOG_BITS);
999 assert_eq!(pcs.num_variables(), LOG_BITS);
1000
1001 let mut prover_chal = challenger();
1002 let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
1003
1004 let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB002), LOG_BITS);
1005 let points = alloc::vec![point.clone()];
1006 let (values, proof) = pcs.open_at_points(data, &points, &mut prover_chal).unwrap();
1007
1008 assert_eq!(values, alloc::vec![embedded(&bits).eval_base(&point)]);
1009
1010 pcs.verify_at_points(
1011 &commitment,
1012 &points,
1013 &values,
1014 &proof,
1015 &mut replayed(&pcs, &commitment),
1016 )
1017 .unwrap();
1018 }
1019
1020 #[test]
1021 fn several_points_cost_one_commitment_opening() {
1022 const LOG_BITS: usize = 13;
1030 const NUM_POINTS: usize = 4;
1031
1032 let bits = witness(0xB00F, LOG_BITS);
1033 let pcs = boolean_pcs(LOG_BITS);
1034 let mut rng = SmallRng::seed_from_u64(0xB010);
1035 let points: Vec<Point<EF>> = (0..NUM_POINTS)
1036 .map(|_| Point::<EF>::rand(&mut rng, LOG_BITS))
1037 .collect();
1038
1039 let mut prover_chal = challenger();
1040 let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
1041 let (values, proof) = pcs.open_at_points(data, &points, &mut prover_chal).unwrap();
1042
1043 assert_eq!(proof.reduction.claims.len(), NUM_POINTS);
1045 assert_eq!(proof.opening.evals.len(), 1);
1046
1047 let reference = embedded(&bits);
1049 for (point, &value) in points.iter().zip(&values) {
1050 assert_eq!(value, reference.eval_base(point));
1051 }
1052
1053 pcs.verify_at_points(
1054 &commitment,
1055 &points,
1056 &values,
1057 &proof,
1058 &mut replayed(&pcs, &commitment),
1059 )
1060 .unwrap();
1061
1062 for index in 0..NUM_POINTS {
1064 let mut tampered = values.clone();
1065 tampered[index] += EF::ONE;
1066 let refused = pcs
1067 .verify_at_points(
1068 &commitment,
1069 &points,
1070 &tampered,
1071 &proof,
1072 &mut replayed(&pcs, &commitment),
1073 )
1074 .unwrap_err();
1075 assert!(
1077 matches!(
1078 refused,
1079 BooleanPcsError::ReductionProof(BitRingSwitchProofError::ClaimMismatch)
1080 ),
1081 "value {index}: {refused:?}"
1082 );
1083 }
1084
1085 let evidence = pcs.opening_security(NUM_POINTS);
1087 let labels: Vec<&str> = evidence.terms.iter().map(|term| term.label).collect();
1088 assert_eq!(
1089 labels,
1090 alloc::vec![
1091 p3_security::binary::BINARY_PCS_OPENING_LABEL,
1092 p3_security::BIT_RING_SWITCH_LABEL,
1093 p3_security::BIT_RING_SWITCH_CLAIM_BATCHING_LABEL,
1094 ]
1095 );
1096
1097 assert_eq!(
1099 evidence.terms[1].bits.bits(),
1100 p3_security::bit_ring_switch_error(1, 7, 6, 128).bits()
1101 );
1102 assert_eq!(
1104 evidence.terms[2].bits.bits(),
1105 p3_security::bit_ring_switch_claim_batching_error(NUM_POINTS, 128).bits()
1106 );
1107 assert_eq!(
1109 evidence.terms[0].bits.bits(),
1110 pcs.opening_security(1).terms[0].bits.bits()
1111 );
1112 }
1113
1114 #[test]
1115 fn a_false_value_is_rejected() {
1116 const LOG_BITS: usize = 13;
1118
1119 let bits = witness(0xFA15, LOG_BITS);
1120 let pcs = boolean_pcs(LOG_BITS);
1121 let mut prover_chal = challenger();
1122 let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
1123
1124 let points = alloc::vec![Point::<EF>::rand(
1125 &mut SmallRng::seed_from_u64(0xFA16),
1126 LOG_BITS
1127 )];
1128 let (values, proof) = pcs.open_at_points(data, &points, &mut prover_chal).unwrap();
1129
1130 let false_values = alloc::vec![values[0] + EF::ONE];
1131 let err = pcs
1132 .verify_at_points(
1133 &commitment,
1134 &points,
1135 &false_values,
1136 &proof,
1137 &mut replayed(&pcs, &commitment),
1138 )
1139 .unwrap_err();
1140 assert!(matches!(err, BooleanPcsError::ReductionProof(_)), "{err:?}");
1141 }
1142
1143 #[test]
1144 fn a_batch_over_another_witness_leaves_a_claim_the_commitment_does_not_open() {
1145 const LOG_BITS: usize = 13;
1156
1157 let pcs = boolean_pcs(LOG_BITS);
1158 let mut rng = SmallRng::seed_from_u64(0x0A11);
1159 let points = (0..3)
1160 .map(|_| Point::<EF>::rand(&mut rng, LOG_BITS))
1161 .collect::<Vec<_>>();
1162
1163 let (_, data_a) = pcs
1165 .commit_bits(&witness(0xAAAA, LOG_BITS), &mut challenger())
1166 .unwrap();
1167
1168 let mut chal = challenger();
1169 let (root_b, data_b) = pcs
1170 .commit_bits(&witness(0xBBBB, LOG_BITS), &mut chal)
1171 .unwrap();
1172
1173 let packing_a = BooleanPcs::<EF, MyMmcs, MyMmcs>::packing(&data_a);
1175 let claims = BitRingSwitchClaims::new(
1176 points
1177 .iter()
1178 .map(|point| BitRingSwitch::new(point).unwrap())
1179 .collect(),
1180 )
1181 .unwrap();
1182 let (reduction, surviving_point, surviving_value) =
1183 claims.prove::<Ghash128, _, _>(&packing_a, &mut chal);
1184 let values = claims
1185 .reductions()
1186 .iter()
1187 .zip(&reduction.claims)
1188 .map(|(switch, elements)| switch.incoming_claim(&elements.tensor))
1189 .collect::<Vec<_>>();
1190
1191 let opening = pcs
1193 .inner
1194 .try_open_at(data_b, &pcs.protocol(), &[surviving_point], &mut chal)
1195 .unwrap();
1196 assert_ne!(opening.evals[0].current()[0], surviving_value);
1197
1198 let proof = BooleanProof { reduction, opening };
1199 let err = pcs
1200 .verify_at_points(
1201 &root_b,
1202 &points,
1203 &values,
1204 &proof,
1205 &mut replayed(&pcs, &root_b),
1206 )
1207 .unwrap_err();
1208 assert!(matches!(err, BooleanPcsError::SurvivingClaim), "{err:?}");
1209
1210 let mut honest = challenger();
1212 let (root, data) = pcs
1213 .commit_bits(&witness(0xBBBB, LOG_BITS), &mut honest)
1214 .unwrap();
1215 let (honest_values, honest_proof) = pcs.open_at_points(data, &points, &mut honest).unwrap();
1216 pcs.verify_at_points(
1217 &root,
1218 &points,
1219 &honest_values,
1220 &honest_proof,
1221 &mut replayed(&pcs, &root),
1222 )
1223 .unwrap();
1224 }
1225
1226 #[test]
1227 fn a_shape_the_commitment_does_not_hold_is_refused() {
1228 let params = BinaryPcsParams {
1230 log_inv_rate: 2,
1231 pow_bits: 0,
1232 security_level: 40,
1233 };
1234
1235 let config = BinaryPcsConfig::try_new::<EF, EF>(6, params).unwrap();
1237 let narrow_witness = BooleanPcs::<EF, MyMmcs, MyMmcs>::new(config, mmcs(), mmcs(), 4).err();
1238 assert!(matches!(
1239 narrow_witness,
1240 Some(BooleanPcsError::WitnessTooNarrow {
1241 needed: 7,
1242 actual: 4
1243 })
1244 ));
1245
1246 let config = BinaryPcsConfig::try_new::<BinaryField64, EF>(6, params).unwrap();
1248 let wrong_field = BooleanPcs::<EF, MyMmcs, MyMmcs>::new(config, mmcs(), mmcs(), 13).err();
1249 assert!(matches!(
1250 wrong_field,
1251 Some(BooleanPcsError::Config(
1252 BinaryPcsConfigError::CommittedFieldMismatch {
1253 derived: 64,
1254 actual: 128,
1255 }
1256 ))
1257 ));
1258
1259 let config = BinaryPcsConfig::try_new::<EF, EF>(6, params).unwrap();
1261 let wrong_arity = BooleanPcs::<EF, MyMmcs, MyMmcs>::new(config, mmcs(), mmcs(), 14).err();
1262 assert!(matches!(
1263 wrong_arity,
1264 Some(BooleanPcsError::ConfigArity {
1265 expected: 7,
1266 actual: 6
1267 })
1268 ));
1269
1270 let pcs = boolean_pcs(13);
1272 let mut chal = challenger();
1273 let (commitment, data) = pcs.commit_bits(&witness(0x5417, 13), &mut chal).unwrap();
1274 let narrow = alloc::vec![Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x5418), 12)];
1275 assert!(matches!(
1276 pcs.open_at_points(data, &narrow, &mut chal).err(),
1277 Some(BooleanPcsError::PointArity {
1278 expected: 13,
1279 actual: 12
1280 })
1281 ));
1282 assert!(matches!(
1283 pcs.verify_at_points(&commitment, &narrow, &[EF::ZERO], &proof_stub(), &mut chal),
1284 Err(BooleanPcsError::PointArity { .. })
1285 ));
1286
1287 let (_, data) = pcs.commit_bits(&witness(0x5419, 13), &mut chal).unwrap();
1289 assert!(matches!(
1290 pcs.open_at_points(data, &[], &mut chal).err(),
1291 Some(BooleanPcsError::NoPoints)
1292 ));
1293 }
1294
1295 fn proof_stub() -> BooleanProof<EF, MyMmcs, MyMmcs> {
1297 let bits = witness(0x57AB, 13);
1298 let pcs = boolean_pcs(13);
1299 let mut chal = challenger();
1300 let (_, data) = pcs.commit_bits(&bits, &mut chal).unwrap();
1301 let points = alloc::vec![Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x57AC), 13)];
1302 pcs.open_at_points(data, &points, &mut chal).unwrap().1
1303 }
1304
1305 fn dense_successor_reading(cells: &Poly<EF>, point: &Point<EF>, row_variables: usize) -> EF {
1315 let (selector, rho) = point.split_at(point.num_variables() - row_variables);
1316 let eq_selector = Poly::<EF>::new_from_point(selector.as_slice(), EF::ONE);
1317 let eq_rho = Poly::<EF>::new_from_point(rho.as_slice(), EF::ONE);
1318 let rows = 1usize << row_variables;
1319 let mut reading = EF::ZERO;
1320 for (c, &gate) in eq_selector.as_slice().iter().enumerate() {
1321 for (z, &weight) in eq_rho.as_slice().iter().enumerate() {
1322 let x = (z + 1).min(rows - 1);
1323 reading += gate * weight * cells.as_slice()[c * rows + x];
1324 }
1325 }
1326 reading
1327 }
1328
1329 fn mixed_openings(seed: u64, log_bits: usize) -> Vec<BitOpening<EF>> {
1337 let mut rng = SmallRng::seed_from_u64(seed);
1338 [(log_bits, true, false), (10, false, true), (3, true, true)]
1339 .into_iter()
1340 .map(|(row_variables, current, next)| BitOpening {
1341 point: Point::<EF>::rand(&mut rng, log_bits),
1342 row_variables,
1343 current,
1344 next,
1345 })
1346 .collect()
1347 }
1348
1349 #[test]
1350 fn a_successor_reading_round_trips_at_the_value_the_bits_define() {
1351 const LOG_BITS: usize = 13;
1361
1362 let bits = witness(0x5ACC, LOG_BITS);
1363 let reference = embedded(&bits);
1364 let pcs = boolean_pcs(LOG_BITS);
1365 let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x5ACD), LOG_BITS);
1366
1367 let mut committed = challenger();
1368 let (commitment, data) = pcs.commit_bits(&bits, &mut committed).unwrap();
1369
1370 for row_variables in [0, 3, 7, 10, 13] {
1371 let openings = alloc::vec![BitOpening {
1372 point: point.clone(),
1373 row_variables,
1374 current: true,
1375 next: true,
1376 }];
1377 let mut prover_chal = committed.clone();
1378 let (readings, proof) = pcs
1379 .open_readings(data.clone(), &openings, &mut prover_chal)
1380 .unwrap();
1381
1382 assert_eq!(
1383 readings,
1384 alloc::vec![BitReadings {
1385 current: Some(reference.eval_base(&point)),
1386 next: Some(dense_successor_reading(&reference, &point, row_variables)),
1387 }],
1388 "rows {row_variables}"
1389 );
1390 assert_eq!(
1392 proof.reduction.claims[0].successor.is_some(),
1393 row_variables > BitRingSwitch::<EF>::ABSORBED,
1394 "rows {row_variables}"
1395 );
1396
1397 pcs.verify_readings(
1398 &commitment,
1399 &openings,
1400 &readings,
1401 &proof,
1402 &mut replayed(&pcs, &commitment),
1403 )
1404 .unwrap();
1405 }
1406 }
1407
1408 #[test]
1409 fn several_openings_with_mixed_views_cost_one_commitment_opening() {
1410 const LOG_BITS: usize = 13;
1416
1417 let bits = witness(0x3A1D, LOG_BITS);
1418 let reference = embedded(&bits);
1419 let pcs = boolean_pcs(LOG_BITS);
1420 let openings = mixed_openings(0x3A1E, LOG_BITS);
1421
1422 let mut prover_chal = challenger();
1423 let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
1424 let (readings, proof) = pcs
1425 .open_readings(data, &openings, &mut prover_chal)
1426 .unwrap();
1427
1428 assert_eq!(proof.reduction.claims.len(), 3);
1430 assert_eq!(proof.opening.evals.len(), 1);
1431 for batch in &proof.opening.evals {
1432 assert_eq!(batch.current().len(), 1);
1433 assert!(batch.next().is_empty());
1434 }
1435
1436 for (index, (opening, reading)) in openings.iter().zip(&readings).enumerate() {
1437 let current = opening.current.then(|| reference.eval_base(&opening.point));
1438 let next = opening.next.then(|| {
1439 dense_successor_reading(&reference, &opening.point, opening.row_variables)
1440 });
1441 assert_eq!(*reading, BitReadings { current, next }, "opening {index}");
1442 }
1443
1444 pcs.verify_readings(
1445 &commitment,
1446 &openings,
1447 &readings,
1448 &proof,
1449 &mut replayed(&pcs, &commitment),
1450 )
1451 .unwrap();
1452 }
1453
1454 #[test]
1455 fn a_false_successor_reading_is_rejected() {
1456 const LOG_BITS: usize = 13;
1461
1462 let bits = witness(0xFA17, LOG_BITS);
1463 let pcs = boolean_pcs(LOG_BITS);
1464 let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xFA18), LOG_BITS);
1465
1466 for row_variables in [3, 10] {
1467 let mut prover_chal = challenger();
1468 let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
1469 let openings = alloc::vec![BitOpening {
1470 point: point.clone(),
1471 row_variables,
1472 current: true,
1473 next: true,
1474 }];
1475 let (readings, proof) = pcs
1476 .open_readings(data, &openings, &mut prover_chal)
1477 .unwrap();
1478
1479 let false_readings = alloc::vec![BitReadings {
1480 next: readings[0].next.map(|next| next + EF::ONE),
1481 ..readings[0]
1482 }];
1483 let err = pcs
1484 .verify_readings(
1485 &commitment,
1486 &openings,
1487 &false_readings,
1488 &proof,
1489 &mut replayed(&pcs, &commitment),
1490 )
1491 .unwrap_err();
1492 assert!(
1493 matches!(
1494 err,
1495 BooleanPcsError::ReductionProof(
1496 BitRingSwitchProofError::SuccessorClaimMismatch
1497 )
1498 ),
1499 "rows {row_variables}: {err:?}"
1500 );
1501 }
1502 }
1503
1504 #[test]
1505 fn the_point_api_is_the_current_reading_api() {
1506 const LOG_BITS: usize = 13;
1514 const NUM_POINTS: usize = 3;
1515
1516 let bits = witness(0x9017, LOG_BITS);
1517 let pcs = boolean_pcs(LOG_BITS);
1518 let mut rng = SmallRng::seed_from_u64(0x9018);
1519 let points: Vec<Point<EF>> = (0..NUM_POINTS)
1520 .map(|_| Point::<EF>::rand(&mut rng, LOG_BITS))
1521 .collect();
1522 let openings: Vec<BitOpening<EF>> = points
1523 .iter()
1524 .map(|point| BitOpening {
1525 point: point.clone(),
1526 row_variables: point.num_variables(),
1527 current: true,
1528 next: false,
1529 })
1530 .collect();
1531
1532 let mut committed = challenger();
1533 let (commitment, data) = pcs.commit_bits(&bits, &mut committed).unwrap();
1534
1535 let mut point_chal = committed.clone();
1536 let (values, point_proof) = pcs
1537 .open_at_points(data.clone(), &points, &mut point_chal)
1538 .unwrap();
1539 let mut reading_chal = committed;
1540 let (readings, reading_proof) = pcs
1541 .open_readings(data, &openings, &mut reading_chal)
1542 .unwrap();
1543
1544 assert_eq!(
1545 postcard::to_allocvec(&point_proof).unwrap(),
1546 postcard::to_allocvec(&reading_proof).unwrap()
1547 );
1548 let expected: Vec<BitReadings<EF>> = values
1549 .iter()
1550 .map(|&value| BitReadings {
1551 current: Some(value),
1552 next: None,
1553 })
1554 .collect();
1555 assert_eq!(readings, expected);
1556 assert_eq!(
1557 point_chal.sample_algebra_element::<EF>(),
1558 reading_chal.sample_algebra_element::<EF>()
1559 );
1560
1561 let mut point_verifier = replayed(&pcs, &commitment);
1563 pcs.verify_at_points(
1564 &commitment,
1565 &points,
1566 &values,
1567 &point_proof,
1568 &mut point_verifier,
1569 )
1570 .unwrap();
1571 let mut reading_verifier = replayed(&pcs, &commitment);
1572 pcs.verify_readings(
1573 &commitment,
1574 &openings,
1575 &readings,
1576 &reading_proof,
1577 &mut reading_verifier,
1578 )
1579 .unwrap();
1580 assert_eq!(
1581 point_verifier.sample_algebra_element::<EF>(),
1582 reading_verifier.sample_algebra_element::<EF>()
1583 );
1584 }
1585
1586 #[test]
1587 fn successor_tensors_are_priced() {
1588 let pcs = boolean_pcs(13);
1595 let with = pcs.readings_security(1, true);
1596 let without = pcs.readings_security(1, false);
1597
1598 assert_eq!(with.terms[0], without.terms[0]);
1600 assert_eq!(with.terms[1].label, p3_security::BIT_RING_SWITCH_LABEL);
1601 assert!(with.terms[1].bits.bits() < without.terms[1].bits.bits());
1602
1603 assert_eq!(
1605 with.terms[1].bits.bits(),
1606 p3_security::bit_ring_switch_tensors_error(1, 3, 7, 6, 128).bits()
1607 );
1608
1609 let points = pcs.opening_security(1);
1610 assert_eq!(points.terms, without.terms);
1611 assert_eq!(points.log2_max_candidates, without.log2_max_candidates);
1612 }
1613
1614 #[test]
1615 fn a_reading_shape_the_opening_did_not_ask_for_is_refused() {
1616 const LOG_BITS: usize = 13;
1623
1624 let pcs = boolean_pcs(LOG_BITS);
1625 let openings = mixed_openings(0x54A9, LOG_BITS);
1626 let mut prover_chal = challenger();
1627 let (commitment, data) = pcs
1628 .commit_bits(&witness(0x5AA0, LOG_BITS), &mut prover_chal)
1629 .unwrap();
1630 let (readings, proof) = pcs
1631 .open_readings(data, &openings, &mut prover_chal)
1632 .unwrap();
1633
1634 let stray = EF::ONE;
1635 let mutations = [
1636 (
1637 0,
1638 BitReadings {
1639 next: Some(stray),
1640 ..readings[0]
1641 },
1642 ),
1643 (
1644 0,
1645 BitReadings {
1646 current: None,
1647 ..readings[0]
1648 },
1649 ),
1650 (
1651 1,
1652 BitReadings {
1653 current: Some(stray),
1654 ..readings[1]
1655 },
1656 ),
1657 (
1658 2,
1659 BitReadings {
1660 next: None,
1661 ..readings[2]
1662 },
1663 ),
1664 ];
1665 for (index, reading) in mutations {
1666 let mut tampered = readings.clone();
1667 tampered[index] = reading;
1668 let mut verifier = replayed(&pcs, &commitment);
1669 let err = pcs
1670 .verify_readings(&commitment, &openings, &tampered, &proof, &mut verifier)
1671 .unwrap_err();
1672 assert!(
1673 matches!(err, BooleanPcsError::ReadingShape { index: refused } if refused == index),
1674 "{err:?}"
1675 );
1676 assert_eq!(
1677 verifier.sample_algebra_element::<EF>(),
1678 replayed(&pcs, &commitment).sample_algebra_element::<EF>(),
1679 "opening {index}"
1680 );
1681 }
1682 }
1683
1684 #[test]
1685 fn an_opening_the_witness_cannot_answer_is_refused() {
1686 const LOG_BITS: usize = 13;
1693
1694 let pcs = boolean_pcs(LOG_BITS);
1695 let mut chal = challenger();
1696 let (commitment, data) = pcs
1697 .commit_bits(&witness(0xA5C0, LOG_BITS), &mut chal)
1698 .unwrap();
1699 let mut rng = SmallRng::seed_from_u64(0xA5C1);
1700 let answered = BitOpening {
1701 point: Point::<EF>::rand(&mut rng, LOG_BITS),
1702 row_variables: LOG_BITS,
1703 current: true,
1704 next: false,
1705 };
1706 let silent = BitOpening {
1707 point: Point::<EF>::rand(&mut rng, LOG_BITS),
1708 row_variables: LOG_BITS,
1709 current: false,
1710 next: false,
1711 };
1712 let past_the_witness = BitOpening {
1713 point: Point::<EF>::rand(&mut rng, LOG_BITS),
1714 row_variables: LOG_BITS + 1,
1715 current: false,
1716 next: true,
1717 };
1718 let readings = [
1719 BitReadings {
1720 current: Some(EF::ZERO),
1721 next: None,
1722 },
1723 BitReadings {
1724 current: None,
1725 next: Some(EF::ZERO),
1726 },
1727 ];
1728
1729 let silent_openings = [answered.clone(), silent];
1730 let wide_openings = [answered, past_the_witness];
1731 let mut before = chal.clone();
1732
1733 let err = pcs
1734 .open_readings(data.clone(), &silent_openings, &mut chal)
1735 .err();
1736 assert!(
1737 matches!(err, Some(BooleanPcsError::NoReading { index: 1 })),
1738 "{err:?}"
1739 );
1740 let err = pcs.open_readings(data, &wide_openings, &mut chal).err();
1741 assert!(
1742 matches!(
1743 err,
1744 Some(BooleanPcsError::RowVariables {
1745 index: 1,
1746 row_variables: 14,
1747 num_variables: 13,
1748 })
1749 ),
1750 "{err:?}"
1751 );
1752 assert_eq!(
1753 chal.sample_algebra_element::<EF>(),
1754 before.sample_algebra_element::<EF>()
1755 );
1756
1757 let proof = proof_stub();
1758 let mut verifier = replayed(&pcs, &commitment);
1759 let err = pcs
1760 .verify_readings(
1761 &commitment,
1762 &silent_openings,
1763 &readings,
1764 &proof,
1765 &mut verifier,
1766 )
1767 .unwrap_err();
1768 assert!(
1769 matches!(err, BooleanPcsError::NoReading { index: 1 }),
1770 "{err:?}"
1771 );
1772 let err = pcs
1773 .verify_readings(
1774 &commitment,
1775 &wide_openings,
1776 &readings,
1777 &proof,
1778 &mut verifier,
1779 )
1780 .unwrap_err();
1781 assert!(
1782 matches!(err, BooleanPcsError::RowVariables { index: 1, .. }),
1783 "{err:?}"
1784 );
1785 assert_eq!(
1786 verifier.sample_algebra_element::<EF>(),
1787 replayed(&pcs, &commitment).sample_algebra_element::<EF>()
1788 );
1789 }
1790
1791 proptest! {
1792 #![proptest_config(ProptestConfig { cases: 8, ..ProptestConfig::default() })]
1794
1795 #[test]
1797 fn a_boolean_opening_round_trips_over_random_inputs(
1798 log_bits in 13usize..=15,
1799 num_points in 1usize..=3,
1800 witness_seed: u64,
1801 point_seed: u64,
1802 ) {
1803 let bits = witness(witness_seed, log_bits);
1804 let pcs = boolean_pcs(log_bits);
1805
1806 let mut prover_chal = challenger();
1807 let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
1808 let mut rng = SmallRng::seed_from_u64(point_seed);
1809 let points: Vec<Point<EF>> = (0..num_points)
1810 .map(|_| Point::<EF>::rand(&mut rng, log_bits))
1811 .collect();
1812 let (values, proof) = pcs.open_at_points(data, &points, &mut prover_chal).unwrap();
1813
1814 let reference = embedded(&bits);
1815 for (point, &value) in points.iter().zip(&values) {
1816 prop_assert_eq!(value, reference.eval_base(point));
1817 }
1818 prop_assert!(
1819 pcs.verify_at_points(
1820 &commitment,
1821 &points,
1822 &values,
1823 &proof,
1824 &mut replayed(&pcs, &commitment)
1825 )
1826 .is_ok()
1827 );
1828 }
1829 }
1830}