1mod evaluate;
73mod gather;
74mod plan;
75
76use alloc::vec::Vec;
77use core::marker::PhantomData;
78
79use p3_binary_dft::EncodableLevel;
80use p3_binary_field::TowerLevel;
81use p3_challenger::fs::TranscriptField;
82use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
83use p3_commit::{Mmcs, MultilinearPcs};
84use p3_field::Field;
85use p3_multilinear_util::point::Point;
86use p3_multilinear_util::poly::Poly;
87use p3_multilinear_util::split_eq::SplitEq;
88use p3_sumcheck::layout::{Table, plan_stacked_layout};
89use p3_sumcheck::ring_switch::bits::BitRingSwitch;
90use p3_sumcheck::{
91 OpeningEvals, OpeningProtocol, PrescribedOpeningSecurity, PrescribedPointPcs, TableShape,
92};
93use serde::{Deserialize, Serialize};
94use thiserror::Error;
95
96use self::plan::{OpeningRoute, opening_evals, sample_points, value_count};
97use crate::boolean::{
98 BitOpening, BitReadings, BooleanBackend, BooleanMultilinearPcs, BooleanPcs, BooleanPcsError,
99 BooleanProof,
100};
101use crate::boolean_trace_transcript::{
102 ColumnBatchProverTranscript, ColumnBatchShape, ColumnBatchVerifierTranscript,
103};
104use crate::fold::{ChallengeField, FoldAlphabet};
105use crate::packing::Coordinates;
106use crate::params::BinaryPcsConfig;
107use crate::prover::BinaryPcsProverData;
108
109const WORD_BITS: usize = 64;
111
112pub struct BooleanTraceCommitment<EF, B> {
118 inner: B,
120 _marker: PhantomData<EF>,
122}
123
124pub type BooleanTracePcs<EF, MT, MX> = BooleanTraceCommitment<EF, BooleanPcs<EF, MT, MX>>;
126
127impl<EF, MT, MX> BooleanTraceCommitment<EF, BooleanPcs<EF, MT, MX>>
128where
129 EF: ChallengeField<EF>
130 + EncodableLevel
131 + TranscriptField
132 + TowerLevel
133 + FoldAlphabet<EF>
134 + Coordinates,
135 MT: Mmcs<EF>,
136 MX: Mmcs<EF, Error = MT::Error>,
137{
138 pub fn new(
148 config: BinaryPcsConfig,
149 mmcs: MT,
150 round_mmcs: MX,
151 num_variables: usize,
152 ) -> Result<Self, BooleanTraceError<EF, MT::Error>> {
153 BooleanPcs::new(config, mmcs, round_mmcs, num_variables)
154 .map(Self::from_commitment)
155 .map_err(BooleanTraceCommitmentError::Boolean)
156 }
157}
158
159impl<EF, B> BooleanTraceCommitment<EF, B>
160where
161 EF: ChallengeField<EF>
162 + EncodableLevel
163 + TranscriptField
164 + TowerLevel
165 + FoldAlphabet<EF>
166 + Coordinates,
167 B: BooleanBackend<EF>,
168{
169 pub const fn from_commitment(inner: B) -> Self {
171 Self {
172 inner,
173 _marker: PhantomData,
174 }
175 }
176
177 #[must_use]
179 pub fn num_variables(&self) -> usize {
180 self.inner.num_variables()
181 }
182
183 fn combine_columns(values: &[EF], column_point: &Point<EF>) -> EF {
185 let padded_len = 1usize << column_point.num_variables();
186 let mut padded = Vec::with_capacity(padded_len);
187 padded.extend_from_slice(values);
188 padded.resize(padded_len, EF::ZERO);
189 SplitEq::<EF, EF>::new_packed(column_point, EF::ONE).eval_ext(Poly::new(padded).as_view())
190 }
191}
192
193pub struct BooleanTraceCommitmentData<EF: Field, D> {
195 inner: D,
197 tables: Vec<Table<EF>>,
199}
200
201pub type BooleanTraceData<EF, MT> = BooleanTraceCommitmentData<EF, BinaryPcsProverData<EF, EF, MT>>;
203
204impl<EF: Field, D> Clone for BooleanTraceCommitmentData<EF, D>
205where
206 D: Clone,
207{
208 fn clone(&self) -> Self {
209 Self {
210 inner: self.inner.clone(),
211 tables: self.tables.clone(),
212 }
213 }
214}
215
216impl<EF: Field, D> BooleanTraceCommitmentData<EF, D> {
217 #[must_use]
223 pub fn table(&self, index: usize) -> &Table<EF> {
224 &self.tables[index]
225 }
226}
227
228#[derive(Clone, Serialize, Deserialize)]
230#[serde(bound(
231 serialize = "EF: TowerLevel, P: Serialize",
232 deserialize = "EF: TowerLevel, P: Deserialize<'de>"
233))]
234pub struct BooleanTraceCommitmentProof<EF: Field, P> {
235 pub values: Vec<EF>,
237 pub opening: P,
239}
240
241pub type BooleanTraceProof<EF, MT, MX> = BooleanTraceCommitmentProof<EF, BooleanProof<EF, MT, MX>>;
243
244pub type BooleanTraceError<EF, MmcsError> =
246 BooleanTraceCommitmentError<BooleanPcsError<EF, MmcsError>>;
247
248#[derive(Debug, Error)]
250#[non_exhaustive]
251pub enum BooleanTraceCommitmentError<E> {
252 #[error(transparent)]
254 Boolean(E),
255
256 #[error("the shapes stack to {actual} variables, the commitment holds {expected}")]
258 StackedArity {
259 expected: usize,
261 actual: usize,
263 },
264
265 #[error("table {table} has shape {actual:?}, protocol requires {expected:?}")]
267 TableShapeMismatch {
268 table: usize,
270 expected: TableShape,
272 actual: TableShape,
274 },
275
276 #[error("the prover retains {actual} tables, while the opening protocol describes {expected}")]
278 TableCountMismatch {
279 expected: usize,
281 actual: usize,
283 },
284
285 #[error("column {column} of table {table} holds a cell outside the two Boolean values")]
287 NonBooleanCell {
288 table: usize,
290 column: usize,
292 },
293
294 #[error("{actual} points against {expected} opening batches")]
296 PointCount {
297 expected: usize,
299 actual: usize,
301 },
302
303 #[error("the point for table {table} names {actual} variables, expected {expected}")]
305 PointArity {
306 table: usize,
308 expected: usize,
310 actual: usize,
312 },
313
314 #[error("the proof carries {actual} values against {expected} opened readings")]
319 ValueCount {
320 expected: usize,
322 actual: usize,
324 },
325
326 #[error(transparent)]
328 ColumnBatchTranscript(#[from] p3_challenger::fs::TranscriptError),
329
330 #[error("column batch {batch} returned an aggregate value different from its claimed columns")]
332 ColumnBatchValueMismatch {
333 batch: usize,
335 },
336}
337
338impl<EF, B, Challenger> MultilinearPcs<EF, Challenger> for BooleanTraceCommitment<EF, B>
339where
340 EF: ChallengeField<EF>
341 + EncodableLevel
342 + TranscriptField
343 + TowerLevel
344 + FoldAlphabet<EF>
345 + Coordinates,
346 B: BooleanMultilinearPcs<EF, Challenger>,
347 Challenger: FieldChallenger<EF>
348 + GrindingChallenger<Witness = EF>
349 + CanSampleUniformBits<EF>
350 + CanObserve<B::Commitment>,
351{
352 type Val = EF;
353 type Commitment = B::Commitment;
354 type ProverData = BooleanTraceCommitmentData<EF, B::ProverData>;
355 type Proof = BooleanTraceCommitmentProof<EF, B::Proof>;
356 type Error = BooleanTraceCommitmentError<B::Error>;
357 type ProverError = BooleanTraceCommitmentError<B::Error>;
358 type Witness = Vec<Table<EF>>;
359 type OpeningProtocol = OpeningProtocol;
360
361 fn num_vars(&self) -> usize {
362 self.num_variables()
363 }
364
365 fn commit(
366 &self,
367 witness: Self::Witness,
368 challenger: &mut Challenger,
369 ) -> Result<(Self::Commitment, Self::ProverData), Self::ProverError> {
370 let bits =
372 tracing::info_span!("gather boolean bits").in_scope(|| self.gather_bits(&witness))?;
373 let (commitment, inner) = self
374 .inner
375 .commit_bits(&bits, challenger)
376 .map_err(BooleanTraceCommitmentError::Boolean)?;
377 Ok((
378 commitment,
379 BooleanTraceCommitmentData {
380 inner,
381 tables: witness,
382 },
383 ))
384 }
385
386 fn observe_commitment(&self, commitment: &Self::Commitment, challenger: &mut Challenger) {
387 self.inner.observe_commitment(commitment, challenger);
388 }
389
390 fn open(
391 &self,
392 prover_data: Self::ProverData,
393 protocol: Self::OpeningProtocol,
394 challenger: &mut Challenger,
395 ) -> Result<Self::Proof, Self::ProverError> {
396 Self::validate_source_shapes(&prover_data.tables, &protocol)?;
397 let points = sample_points(&protocol, challenger);
399 self.open_at(prover_data, &protocol, &points, challenger)
400 }
401
402 fn verify(
403 &self,
404 commitment: &Self::Commitment,
405 proof: &Self::Proof,
406 challenger: &mut Challenger,
407 protocol: Self::OpeningProtocol,
408 ) -> Result<(), Self::Error> {
409 self.observe_commitment(commitment, challenger);
415
416 let points = sample_points(&protocol, challenger);
418 self.verify_at(commitment, proof, &protocol, &points, challenger)
419 .map(|_| ())
420 }
421}
422
423impl<EF, B, Challenger> PrescribedPointPcs<EF, Challenger> for BooleanTraceCommitment<EF, B>
424where
425 EF: ChallengeField<EF>
426 + EncodableLevel
427 + TranscriptField
428 + TowerLevel
429 + FoldAlphabet<EF>
430 + Coordinates,
431 B: BooleanMultilinearPcs<EF, Challenger>,
432 Challenger: FieldChallenger<EF>
433 + GrindingChallenger<Witness = EF>
434 + CanSampleUniformBits<EF>
435 + CanObserve<B::Commitment>,
436{
437 fn prescribed_security(&self, protocol: &OpeningProtocol) -> Option<PrescribedOpeningSecurity> {
438 let shapes = protocol.table_shapes();
440 if plan_stacked_layout(&shapes).0 != self.num_variables() {
441 return None;
442 }
443 let absorbed = BitRingSwitch::<EF>::ABSORBED;
445 let route = OpeningRoute::new(protocol);
446 let mut security = self.inner.readings_security(
447 route.num_reductions(),
448 route.successor_tensors(&shapes, absorbed),
449 )?;
450 if let OpeningRoute::Batched(shape) = route {
451 security.charge_reduction(p3_security::multilinear::column_batch_term(
459 shape.num_batches * shape.num_views(),
460 shape.column_variables(),
461 EF::bits(),
462 ));
463 }
464 Some(security)
465 }
466
467 fn open_at(
468 &self,
469 prover_data: Self::ProverData,
470 protocol: &OpeningProtocol,
471 points: &[Point<EF>],
472 challenger: &mut Challenger,
473 ) -> Result<Self::Proof, Self::ProverError> {
474 Self::validate_source_shapes(&prover_data.tables, protocol)?;
476 let placements = self.validate_opening(protocol, points)?;
477 let shape = match OpeningRoute::new(protocol) {
478 OpeningRoute::PerColumn(plan) => {
479 let openings = Self::bit_openings(protocol, plan.claims(), points, &placements);
480 let (readings, opening) = self
481 .inner
482 .open_readings(prover_data.inner, &openings, challenger)
483 .map_err(BooleanTraceCommitmentError::Boolean)?;
484 let values = plan.values(&readings);
485 return Ok(BooleanTraceCommitmentProof { values, opening });
486 }
487 OpeningRoute::Batched(shape) => shape,
488 };
489
490 let BooleanTraceCommitmentData { inner, tables } = prover_data;
491 let ColumnBatchShape { width, next, .. } = shape;
492 let run = shape.values_per_batch();
493 let mut values = Vec::with_capacity(run * shape.num_batches);
494 tracing::info_span!("evaluate boolean columns", width, next).in_scope(|| {
495 for point in points {
496 let (current, successor) = Self::evaluate_views(&tables[0], point, next);
497 values.extend(current);
498 values.extend(successor);
499 }
500 });
501
502 let mut transcript = ColumnBatchProverTranscript::new(challenger, shape);
503 let mut openings = Vec::with_capacity(shape.num_batches);
504 let mut expected = Vec::with_capacity(shape.num_batches);
505 for (point, batch_values) in points.iter().zip(values.chunks_exact(run)) {
506 let (current, successor) = batch_values.split_at(width);
507 let column_point = transcript.batch(point, current, successor);
509 expected.push(BitReadings {
510 current: Some(Self::combine_columns(current, &column_point)),
511 next: next.then(|| Self::combine_columns(successor, &column_point)),
512 });
513 let mut lifted_point = column_point;
514 lifted_point.extend(point);
515 openings.push(BitOpening {
516 point: lifted_point,
517 row_variables: shape.table_variables,
518 current: true,
519 next,
520 });
521 }
522 transcript.finish();
523
524 let (readings, opening) = self
525 .inner
526 .open_readings(inner, &openings, challenger)
527 .map_err(BooleanTraceCommitmentError::Boolean)?;
528 if readings.len() != expected.len() {
529 return Err(BooleanTraceCommitmentError::ColumnBatchValueMismatch {
530 batch: expected.len(),
531 });
532 }
533 for (batch, (actual, expected)) in readings.iter().zip(&expected).enumerate() {
534 if actual != expected {
535 return Err(BooleanTraceCommitmentError::ColumnBatchValueMismatch { batch });
536 }
537 }
538 Ok(BooleanTraceCommitmentProof { values, opening })
539 }
540
541 fn verify_at(
542 &self,
543 commitment: &Self::Commitment,
544 proof: &Self::Proof,
545 protocol: &OpeningProtocol,
546 points: &[Point<EF>],
547 challenger: &mut Challenger,
548 ) -> Result<Vec<OpeningEvals<EF>>, Self::Error> {
549 let placements = self.validate_opening(protocol, points)?;
551 let expected_values = value_count(protocol);
552 if proof.values.len() != expected_values {
553 return Err(BooleanTraceCommitmentError::ValueCount {
554 expected: expected_values,
555 actual: proof.values.len(),
556 });
557 }
558
559 let shape = match OpeningRoute::new(protocol) {
560 OpeningRoute::PerColumn(plan) => {
561 let openings = Self::bit_openings(protocol, plan.claims(), points, &placements);
562
563 self.inner
565 .verify_readings(
566 commitment,
567 &openings,
568 &plan.readings(&proof.values),
569 &proof.opening,
570 challenger,
571 )
572 .map_err(BooleanTraceCommitmentError::Boolean)?;
573 return Ok(opening_evals(protocol, &proof.values));
574 }
575 OpeningRoute::Batched(shape) => shape,
576 };
577
578 let ColumnBatchShape { width, next, .. } = shape;
579 let run = shape.values_per_batch();
580 let mut transcript = ColumnBatchVerifierTranscript::new(challenger, shape);
581 let mut openings = Vec::with_capacity(shape.num_batches);
582 let mut readings = Vec::with_capacity(shape.num_batches);
583 for (point, batch_values) in points.iter().zip(proof.values.chunks_exact(run)) {
584 let (current, successor) = batch_values.split_at(width);
585 let column_point = transcript.batch(point, current, successor)?;
587 readings.push(BitReadings {
588 current: Some(Self::combine_columns(current, &column_point)),
589 next: next.then(|| Self::combine_columns(successor, &column_point)),
590 });
591 let mut lifted_point = column_point;
592 lifted_point.extend(point);
593 openings.push(BitOpening {
594 point: lifted_point,
595 row_variables: shape.table_variables,
596 current: true,
597 next,
598 });
599 }
600 transcript.finish();
601
602 self.inner
604 .verify_readings(commitment, &openings, &readings, &proof.opening, challenger)
605 .map_err(BooleanTraceCommitmentError::Boolean)?;
606
607 Ok(opening_evals(protocol, &proof.values))
608 }
609}
610
611#[cfg(test)]
612mod tests;