1use alloc::vec;
32use alloc::vec::Vec;
33
34use p3_binary_field::{PackedGf2, TowerLevel, Underlier};
35use p3_challenger::fs::TranscriptField;
36use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
37use p3_commit::Mmcs;
38use p3_field::Field;
39use p3_multilinear_util::point::Point;
40use p3_security::multilinear::{bit_ring_switch_claim_batching_term, bit_ring_switch_tensors_term};
41use p3_sumcheck::layout::{Layout, SuffixProver};
42use p3_sumcheck::ring_switch::bits::{
43 BitPacking, BitPackingView, BitRingSwitch, BitRingSwitchClaims,
44};
45use p3_sumcheck::{
46 OpeningBatch, OpeningProtocol, PrescribedOpeningSecurity, PrescribedPointPcs, TableShape,
47 TableSpec,
48};
49use p3_whir::{WhirDomain, WhirProver, WhirProverData};
50
51use crate::boolean::{BitOpening, BitReadings, BooleanBackend, BooleanMultilinearPcs};
52use crate::boolean_trace::BooleanTraceCommitment;
53use crate::fold::ChallengeField;
54use crate::packing::{Coordinates, PackedStack};
55use crate::whir::error::BooleanWhirError;
56use crate::whir::proof::BooleanWhirProof;
57use crate::whir::shape::ProofShape;
58
59type Binding<EF> = SuffixProver<EF, EF>;
63
64pub type BooleanWhirProver<EF, Dft, MT, Challenger> =
66 WhirProver<EF, EF, Dft, MT, Challenger, Binding<EF>>;
67
68pub type BooleanWhirData<EF, MT> = WhirProverData<EF, EF, MT, SuffixProver<EF, EF>>;
70
71pub type BooleanWhirTracePcs<EF, Dft, MT, Challenger> =
73 BooleanTraceCommitment<EF, BooleanWhirPcs<EF, Dft, MT, Challenger>>;
74
75pub struct BooleanWhirPcs<EF: Field, Dft, MT, Challenger> {
81 inner: BooleanWhirProver<EF, Dft, MT, Challenger>,
83 num_variables: usize,
85}
86
87impl<EF, Dft, MT, Challenger> BooleanWhirPcs<EF, Dft, MT, Challenger>
88where
89 EF: Field + TranscriptField + TowerLevel + Coordinates + Ord,
90 Dft: WhirDomain<EF, EF>,
91 MT: Mmcs<EF>,
92{
93 pub fn new(
101 inner: BooleanWhirProver<EF, Dft, MT, Challenger>,
102 num_variables: usize,
103 ) -> Result<Self, BooleanWhirError> {
104 let packed = Self::packed_variables(num_variables)?;
105 if inner.num_variables() != packed {
106 return Err(BooleanWhirError::ConfigArity {
107 expected: packed,
108 actual: inner.num_variables(),
109 });
110 }
111 Ok(Self {
112 inner,
113 num_variables,
114 })
115 }
116
117 #[must_use]
119 pub const fn num_variables(&self) -> usize {
120 self.num_variables
121 }
122
123 #[must_use]
125 pub const fn committed_bytes(&self) -> usize {
126 1 << (self.num_variables - 3)
127 }
128
129 fn packed_variables(num_variables: usize) -> Result<usize, BooleanWhirError> {
131 let absorbed = BitRingSwitch::<EF>::ABSORBED;
132 num_variables
133 .checked_sub(absorbed)
134 .ok_or(BooleanWhirError::WitnessTooNarrow {
135 needed: absorbed,
136 actual: num_variables,
137 })
138 }
139
140 fn protocol(&self, num_claims: usize) -> OpeningProtocol {
145 OpeningProtocol::new(vec![TableSpec::new(
146 TableShape::new(self.inner.num_variables(), 1),
147 (0..num_claims)
148 .map(|_| OpeningBatch::new(vec![0], Vec::new()))
149 .collect(),
150 )])
151 }
152
153 fn claims(openings: &[BitOpening<EF>]) -> Result<BitRingSwitchClaims<EF>, BooleanWhirError> {
155 let reductions = openings
156 .iter()
157 .map(Self::reduction)
158 .collect::<Result<Vec<_>, _>>()?;
159 BitRingSwitchClaims::new(reductions).map_err(BooleanWhirError::Reduction)
160 }
161
162 fn reduction(opening: &BitOpening<EF>) -> Result<BitRingSwitch<EF>, BooleanWhirError> {
164 if opening.next {
165 BitRingSwitch::with_successor(&opening.point, opening.row_variables)
166 } else {
167 BitRingSwitch::new(&opening.point)
168 }
169 .map_err(BooleanWhirError::Reduction)
170 }
171
172 fn current_openings(points: &[Point<EF>]) -> Vec<BitOpening<EF>> {
174 points
175 .iter()
176 .map(|point| BitOpening {
177 point: point.clone(),
178 row_variables: point.num_variables(),
179 current: true,
180 next: false,
181 })
182 .collect()
183 }
184
185 fn check_openings(&self, openings: &[BitOpening<EF>]) -> Result<(), BooleanWhirError> {
187 if openings.is_empty() {
188 return Err(BooleanWhirError::NoPoints);
189 }
190 for (index, opening) in openings.iter().enumerate() {
191 if opening.point.num_variables() != self.num_variables {
192 return Err(BooleanWhirError::PointArity {
193 expected: self.num_variables,
194 actual: opening.point.num_variables(),
195 });
196 }
197 if !opening.current && !opening.next {
198 return Err(BooleanWhirError::NoReading { index });
199 }
200 if opening.row_variables > self.num_variables {
201 return Err(BooleanWhirError::RowVariables {
202 index,
203 row_variables: opening.row_variables,
204 num_variables: self.num_variables,
205 });
206 }
207 }
208 Ok(())
209 }
210
211 fn packing(prover_data: &BooleanWhirData<EF, MT>) -> BitPackingView<'_, EF> {
219 BitPacking::from_packed(prover_data.table(0).poly(0))
220 .expect("a committed table is a hypercube over a byte-aligned level")
221 }
222}
223
224impl<EF, Dft, MT, Challenger> BooleanWhirPcs<EF, Dft, MT, Challenger>
225where
226 EF: Field + TranscriptField + TowerLevel + Coordinates + Ord + Send + Sync + ChallengeField<EF>,
227 Dft: WhirDomain<EF, EF>,
228 MT: Mmcs<EF>,
229 Challenger: FieldChallenger<EF>
230 + GrindingChallenger<Witness = EF>
231 + CanSampleUniformBits<EF>
232 + CanObserve<MT::Commitment>,
233{
234 #[must_use]
260 pub fn readings_security(
261 &self,
262 num_claims: usize,
263 successor_tensors: bool,
264 ) -> Option<PrescribedOpeningSecurity> {
265 let protocol = self.protocol(num_claims.min(1));
267 let mut security = self.inner.prescribed_security(&protocol)?;
268 let num_tensors = if successor_tensors { 3 } else { 1 };
270 security.charge_reduction(bit_ring_switch_tensors_term(
276 num_claims.min(1),
277 num_tensors,
278 BitRingSwitch::<EF>::ABSORBED,
279 self.inner.num_variables(),
280 EF::bits(),
281 ));
282 if num_claims > 1 {
284 security.charge_reduction(bit_ring_switch_claim_batching_term(num_claims, EF::bits()));
285 }
286 Some(security)
287 }
288
289 #[must_use]
293 pub fn proof_shape(&self, num_claims: usize, successor_tensors: bool) -> ProofShape {
294 ProofShape::of_bit_readings(&self.inner, num_claims, successor_tensors)
295 }
296
297 #[allow(clippy::type_complexity)]
311 pub fn open_readings(
312 &self,
313 prover_data: BooleanWhirData<EF, MT>,
314 openings: &[BitOpening<EF>],
315 challenger: &mut Challenger,
316 ) -> Result<(Vec<BitReadings<EF>>, BooleanWhirProof<EF, MT>), BooleanWhirError> {
317 self.check_openings(openings)?;
318 let claims = Self::claims(openings)?;
320 let packing = Self::packing(&prover_data);
321
322 let (reduction, surviving_point, _) =
324 tracing::info_span!("bit ring switch").in_scope(|| {
325 claims.prove::<<EF as ChallengeField<EF>>::SumcheckRepr, _, _>(&packing, challenger)
326 });
327
328 let readings = openings
330 .iter()
331 .zip(claims.reductions())
332 .zip(&reduction.claims)
333 .map(|((opening, switch), elements)| {
334 let current = opening
335 .current
336 .then(|| switch.incoming_claim(&elements.tensor));
337 let next = opening
338 .next
339 .then(|| switch.successor_claim(&elements.tensor, elements.successor.as_ref()))
340 .transpose()
341 .map_err(BooleanWhirError::Reduction)?;
342 Ok(BitReadings { current, next })
343 })
344 .collect::<Result<Vec<_>, BooleanWhirError>>()?;
345
346 let opening = self
349 .inner
350 .open_at(
351 prover_data,
352 &self.protocol(1),
353 &[surviving_point],
354 challenger,
355 )
356 .map_err(BooleanWhirError::Commit)?;
357
358 Ok((readings, BooleanWhirProof { reduction, opening }))
359 }
360
361 pub fn verify_readings(
371 &self,
372 commitment: &MT::Commitment,
373 openings: &[BitOpening<EF>],
374 readings: &[BitReadings<EF>],
375 proof: &BooleanWhirProof<EF, MT>,
376 challenger: &mut Challenger,
377 ) -> Result<(), BooleanWhirError> {
378 self.check_openings(openings)?;
379 if readings.len() != openings.len() || proof.reduction.claims.len() != openings.len() {
380 return Err(BooleanWhirError::ClaimCount {
381 expected: openings.len(),
382 values: readings.len(),
383 reductions: proof.reduction.claims.len(),
384 });
385 }
386 if let Some(index) = openings
388 .iter()
389 .zip(readings)
390 .position(|(opening, reading)| {
391 reading.current.is_some() != opening.current
392 || reading.next.is_some() != opening.next
393 })
394 {
395 return Err(BooleanWhirError::ReadingShape { index });
396 }
397 let claims = Self::claims(openings)?;
398
399 let readings = readings
401 .iter()
402 .map(|reading| (reading.current, reading.next))
403 .collect::<Vec<_>>();
404 let (surviving_point, surviving_value) = claims
405 .verify_readings(&proof.reduction, &readings, challenger)
406 .map_err(BooleanWhirError::ReductionProof)?;
407
408 let evals = self
410 .inner
411 .verify_at(
412 commitment,
413 &proof.opening,
414 &self.protocol(1),
415 &[surviving_point],
416 challenger,
417 )
418 .map_err(BooleanWhirError::Opening)?;
419
420 match evals.as_slice() {
422 [batch] if batch.current().first() == Some(&surviving_value) => Ok(()),
423 _ => Err(BooleanWhirError::SurvivingClaim),
424 }
425 }
426}
427
428impl<EF, Dft, MT, Challenger> BooleanMultilinearPcs<EF, Challenger>
429 for BooleanWhirPcs<EF, Dft, MT, Challenger>
430where
431 EF: Field + TranscriptField + TowerLevel + Coordinates + Ord + Send + Sync + ChallengeField<EF>,
432 Dft: WhirDomain<EF, EF>,
433 MT: Mmcs<EF>,
434 Challenger: FieldChallenger<EF>
435 + GrindingChallenger<Witness = EF>
436 + CanSampleUniformBits<EF>
437 + CanObserve<MT::Commitment>,
438{
439 fn observe_commitment(&self, commitment: &Self::Commitment, challenger: &mut Challenger) {
440 p3_commit::MultilinearPcs::<EF, Challenger>::observe_commitment(
441 &self.inner,
442 commitment,
443 challenger,
444 );
445 }
446
447 fn commit_bits<U: Underlier>(
448 &self,
449 bits: &[PackedGf2<U>],
450 challenger: &mut Challenger,
451 ) -> Result<(Self::Commitment, Self::ProverData), Self::Error> {
452 let stack = PackedStack::<PackedGf2<U>, EF>::from_columns(&[bits])
454 .map_err(BooleanWhirError::Packing)?;
455 if stack.column_num_variables() != self.inner.num_variables() {
456 return Err(BooleanWhirError::WitnessArity {
457 expected: self.inner.num_variables(),
458 actual: stack.column_num_variables(),
459 });
460 }
461
462 let folding = self.inner.round_folding_factor(0);
463 let witness = Binding::<EF>::new_witness(vec![stack.into_table()], folding);
464 p3_commit::MultilinearPcs::<EF, Challenger>::commit(&self.inner, witness, challenger)
465 .map_err(BooleanWhirError::Commit)
466 }
467
468 fn open_readings(
469 &self,
470 prover_data: Self::ProverData,
471 openings: &[BitOpening<EF>],
472 challenger: &mut Challenger,
473 ) -> Result<(Vec<BitReadings<EF>>, Self::Proof), Self::Error> {
474 Self::open_readings(self, prover_data, openings, challenger)
475 }
476
477 fn verify_readings(
478 &self,
479 commitment: &Self::Commitment,
480 openings: &[BitOpening<EF>],
481 readings: &[BitReadings<EF>],
482 proof: &Self::Proof,
483 challenger: &mut Challenger,
484 ) -> Result<(), Self::Error> {
485 Self::verify_readings(self, commitment, openings, readings, proof, challenger)
486 }
487
488 fn readings_security(
489 &self,
490 num_claims: usize,
491 successor_tensors: bool,
492 ) -> Option<PrescribedOpeningSecurity> {
493 Self::readings_security(self, num_claims, successor_tensors)
494 }
495
496 fn open_at_points(
497 &self,
498 prover_data: Self::ProverData,
499 points: &[Point<EF>],
500 challenger: &mut Challenger,
501 ) -> Result<(Vec<EF>, Self::Proof), Self::Error> {
502 let (readings, proof) =
503 self.open_readings(prover_data, &Self::current_openings(points), challenger)?;
504 let values = readings
505 .into_iter()
506 .map(|reading| {
507 reading
508 .current
509 .expect("an opening asking for the current reading carries it")
510 })
511 .collect();
512 Ok((values, proof))
513 }
514
515 fn verify_at_points(
516 &self,
517 commitment: &Self::Commitment,
518 points: &[Point<EF>],
519 values: &[EF],
520 proof: &Self::Proof,
521 challenger: &mut Challenger,
522 ) -> Result<(), Self::Error> {
523 let readings: Vec<BitReadings<EF>> = values
524 .iter()
525 .map(|&value| BitReadings {
526 current: Some(value),
527 next: None,
528 })
529 .collect();
530 self.verify_readings(
531 commitment,
532 &Self::current_openings(points),
533 &readings,
534 proof,
535 challenger,
536 )
537 }
538}
539
540impl<EF, Dft, MT, Challenger> BooleanBackend<EF> for BooleanWhirPcs<EF, Dft, MT, Challenger>
541where
542 EF: Field + TranscriptField + TowerLevel + Coordinates + Ord + Send + Sync,
543 Dft: WhirDomain<EF, EF>,
544 MT: Mmcs<EF>,
545{
546 type Commitment = MT::Commitment;
547 type ProverData = BooleanWhirData<EF, MT>;
548 type Proof = BooleanWhirProof<EF, MT>;
549 type Error = BooleanWhirError;
550
551 fn num_variables(&self) -> usize {
552 self.num_variables
553 }
554}