mod evaluate;
mod gather;
mod plan;
use alloc::vec::Vec;
use core::marker::PhantomData;
use p3_binary_dft::EncodableLevel;
use p3_binary_field::TowerLevel;
use p3_challenger::fs::TranscriptField;
use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
use p3_commit::{Mmcs, MultilinearPcs};
use p3_field::Field;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use p3_multilinear_util::split_eq::SplitEq;
use p3_sumcheck::layout::{Table, plan_stacked_layout};
use p3_sumcheck::ring_switch::bits::BitRingSwitch;
use p3_sumcheck::{
OpeningEvals, OpeningProtocol, PrescribedOpeningSecurity, PrescribedPointPcs, TableShape,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use self::plan::{OpeningRoute, opening_evals, sample_points, value_count};
use crate::boolean::{
BitOpening, BitReadings, BooleanBackend, BooleanMultilinearPcs, BooleanPcs, BooleanPcsError,
BooleanProof,
};
use crate::boolean_trace_transcript::{
ColumnBatchProverTranscript, ColumnBatchShape, ColumnBatchVerifierTranscript,
};
use crate::fold::{ChallengeField, FoldAlphabet};
use crate::packing::Coordinates;
use crate::params::BinaryPcsConfig;
use crate::prover::BinaryPcsProverData;
const WORD_BITS: usize = 64;
pub struct BooleanTraceCommitment<EF, B> {
inner: B,
_marker: PhantomData<EF>,
}
pub type BooleanTracePcs<EF, MT, MX> = BooleanTraceCommitment<EF, BooleanPcs<EF, MT, MX>>;
impl<EF, MT, MX> BooleanTraceCommitment<EF, BooleanPcs<EF, MT, MX>>
where
EF: ChallengeField<EF>
+ EncodableLevel
+ TranscriptField
+ TowerLevel
+ FoldAlphabet<EF>
+ Coordinates,
MT: Mmcs<EF>,
MX: Mmcs<EF, Error = MT::Error>,
{
pub fn new(
config: BinaryPcsConfig,
mmcs: MT,
round_mmcs: MX,
num_variables: usize,
) -> Result<Self, BooleanTraceError<EF, MT::Error>> {
BooleanPcs::new(config, mmcs, round_mmcs, num_variables)
.map(Self::from_commitment)
.map_err(BooleanTraceCommitmentError::Boolean)
}
}
impl<EF, B> BooleanTraceCommitment<EF, B>
where
EF: ChallengeField<EF>
+ EncodableLevel
+ TranscriptField
+ TowerLevel
+ FoldAlphabet<EF>
+ Coordinates,
B: BooleanBackend<EF>,
{
pub const fn from_commitment(inner: B) -> Self {
Self {
inner,
_marker: PhantomData,
}
}
#[must_use]
pub fn num_variables(&self) -> usize {
self.inner.num_variables()
}
fn combine_columns(values: &[EF], column_point: &Point<EF>) -> EF {
let padded_len = 1usize << column_point.num_variables();
let mut padded = Vec::with_capacity(padded_len);
padded.extend_from_slice(values);
padded.resize(padded_len, EF::ZERO);
SplitEq::<EF, EF>::new_packed(column_point, EF::ONE).eval_ext(Poly::new(padded).as_view())
}
}
pub struct BooleanTraceCommitmentData<EF: Field, D> {
inner: D,
tables: Vec<Table<EF>>,
}
pub type BooleanTraceData<EF, MT> = BooleanTraceCommitmentData<EF, BinaryPcsProverData<EF, EF, MT>>;
impl<EF: Field, D> Clone for BooleanTraceCommitmentData<EF, D>
where
D: Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
tables: self.tables.clone(),
}
}
}
impl<EF: Field, D> BooleanTraceCommitmentData<EF, D> {
#[must_use]
pub fn table(&self, index: usize) -> &Table<EF> {
&self.tables[index]
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound(
serialize = "EF: TowerLevel, P: Serialize",
deserialize = "EF: TowerLevel, P: Deserialize<'de>"
))]
pub struct BooleanTraceCommitmentProof<EF: Field, P> {
pub values: Vec<EF>,
pub opening: P,
}
pub type BooleanTraceProof<EF, MT, MX> = BooleanTraceCommitmentProof<EF, BooleanProof<EF, MT, MX>>;
pub type BooleanTraceError<EF, MmcsError> =
BooleanTraceCommitmentError<BooleanPcsError<EF, MmcsError>>;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BooleanTraceCommitmentError<E> {
#[error(transparent)]
Boolean(E),
#[error("the shapes stack to {actual} variables, the commitment holds {expected}")]
StackedArity {
expected: usize,
actual: usize,
},
#[error("table {table} has shape {actual:?}, protocol requires {expected:?}")]
TableShapeMismatch {
table: usize,
expected: TableShape,
actual: TableShape,
},
#[error("the prover retains {actual} tables, while the opening protocol describes {expected}")]
TableCountMismatch {
expected: usize,
actual: usize,
},
#[error("column {column} of table {table} holds a cell outside the two Boolean values")]
NonBooleanCell {
table: usize,
column: usize,
},
#[error("{actual} points against {expected} opening batches")]
PointCount {
expected: usize,
actual: usize,
},
#[error("the point for table {table} names {actual} variables, expected {expected}")]
PointArity {
table: usize,
expected: usize,
actual: usize,
},
#[error("the proof carries {actual} values against {expected} opened readings")]
ValueCount {
expected: usize,
actual: usize,
},
#[error(transparent)]
ColumnBatchTranscript(#[from] p3_challenger::fs::TranscriptError),
#[error("column batch {batch} returned an aggregate value different from its claimed columns")]
ColumnBatchValueMismatch {
batch: usize,
},
}
impl<EF, B, Challenger> MultilinearPcs<EF, Challenger> for BooleanTraceCommitment<EF, B>
where
EF: ChallengeField<EF>
+ EncodableLevel
+ TranscriptField
+ TowerLevel
+ FoldAlphabet<EF>
+ Coordinates,
B: BooleanMultilinearPcs<EF, Challenger>,
Challenger: FieldChallenger<EF>
+ GrindingChallenger<Witness = EF>
+ CanSampleUniformBits<EF>
+ CanObserve<B::Commitment>,
{
type Val = EF;
type Commitment = B::Commitment;
type ProverData = BooleanTraceCommitmentData<EF, B::ProverData>;
type Proof = BooleanTraceCommitmentProof<EF, B::Proof>;
type Error = BooleanTraceCommitmentError<B::Error>;
type ProverError = BooleanTraceCommitmentError<B::Error>;
type Witness = Vec<Table<EF>>;
type OpeningProtocol = OpeningProtocol;
fn num_vars(&self) -> usize {
self.num_variables()
}
fn commit(
&self,
witness: Self::Witness,
challenger: &mut Challenger,
) -> Result<(Self::Commitment, Self::ProverData), Self::ProverError> {
let bits =
tracing::info_span!("gather boolean bits").in_scope(|| self.gather_bits(&witness))?;
let (commitment, inner) = self
.inner
.commit_bits(&bits, challenger)
.map_err(BooleanTraceCommitmentError::Boolean)?;
Ok((
commitment,
BooleanTraceCommitmentData {
inner,
tables: witness,
},
))
}
fn observe_commitment(&self, commitment: &Self::Commitment, challenger: &mut Challenger) {
self.inner.observe_commitment(commitment, challenger);
}
fn open(
&self,
prover_data: Self::ProverData,
protocol: Self::OpeningProtocol,
challenger: &mut Challenger,
) -> Result<Self::Proof, Self::ProverError> {
Self::validate_source_shapes(&prover_data.tables, &protocol)?;
let points = sample_points(&protocol, challenger);
self.open_at(prover_data, &protocol, &points, challenger)
}
fn verify(
&self,
commitment: &Self::Commitment,
proof: &Self::Proof,
challenger: &mut Challenger,
protocol: Self::OpeningProtocol,
) -> Result<(), Self::Error> {
self.observe_commitment(commitment, challenger);
let points = sample_points(&protocol, challenger);
self.verify_at(commitment, proof, &protocol, &points, challenger)
.map(|_| ())
}
}
impl<EF, B, Challenger> PrescribedPointPcs<EF, Challenger> for BooleanTraceCommitment<EF, B>
where
EF: ChallengeField<EF>
+ EncodableLevel
+ TranscriptField
+ TowerLevel
+ FoldAlphabet<EF>
+ Coordinates,
B: BooleanMultilinearPcs<EF, Challenger>,
Challenger: FieldChallenger<EF>
+ GrindingChallenger<Witness = EF>
+ CanSampleUniformBits<EF>
+ CanObserve<B::Commitment>,
{
fn prescribed_security(&self, protocol: &OpeningProtocol) -> Option<PrescribedOpeningSecurity> {
let shapes = protocol.table_shapes();
if plan_stacked_layout(&shapes).0 != self.num_variables() {
return None;
}
let absorbed = BitRingSwitch::<EF>::ABSORBED;
let route = OpeningRoute::new(protocol);
let mut security = self.inner.readings_security(
route.num_reductions(),
route.successor_tensors(&shapes, absorbed),
)?;
if let OpeningRoute::Batched(shape) = route {
security.charge_reduction(p3_security::multilinear::column_batch_term(
shape.num_batches * shape.num_views(),
shape.column_variables(),
EF::bits(),
));
}
Some(security)
}
fn open_at(
&self,
prover_data: Self::ProverData,
protocol: &OpeningProtocol,
points: &[Point<EF>],
challenger: &mut Challenger,
) -> Result<Self::Proof, Self::ProverError> {
Self::validate_source_shapes(&prover_data.tables, protocol)?;
let placements = self.validate_opening(protocol, points)?;
let shape = match OpeningRoute::new(protocol) {
OpeningRoute::PerColumn(plan) => {
let openings = Self::bit_openings(protocol, plan.claims(), points, &placements);
let (readings, opening) = self
.inner
.open_readings(prover_data.inner, &openings, challenger)
.map_err(BooleanTraceCommitmentError::Boolean)?;
let values = plan.values(&readings);
return Ok(BooleanTraceCommitmentProof { values, opening });
}
OpeningRoute::Batched(shape) => shape,
};
let BooleanTraceCommitmentData { inner, tables } = prover_data;
let ColumnBatchShape { width, next, .. } = shape;
let run = shape.values_per_batch();
let mut values = Vec::with_capacity(run * shape.num_batches);
tracing::info_span!("evaluate boolean columns", width, next).in_scope(|| {
for point in points {
let (current, successor) = Self::evaluate_views(&tables[0], point, next);
values.extend(current);
values.extend(successor);
}
});
let mut transcript = ColumnBatchProverTranscript::new(challenger, shape);
let mut openings = Vec::with_capacity(shape.num_batches);
let mut expected = Vec::with_capacity(shape.num_batches);
for (point, batch_values) in points.iter().zip(values.chunks_exact(run)) {
let (current, successor) = batch_values.split_at(width);
let column_point = transcript.batch(point, current, successor);
expected.push(BitReadings {
current: Some(Self::combine_columns(current, &column_point)),
next: next.then(|| Self::combine_columns(successor, &column_point)),
});
let mut lifted_point = column_point;
lifted_point.extend(point);
openings.push(BitOpening {
point: lifted_point,
row_variables: shape.table_variables,
current: true,
next,
});
}
transcript.finish();
let (readings, opening) = self
.inner
.open_readings(inner, &openings, challenger)
.map_err(BooleanTraceCommitmentError::Boolean)?;
if readings.len() != expected.len() {
return Err(BooleanTraceCommitmentError::ColumnBatchValueMismatch {
batch: expected.len(),
});
}
for (batch, (actual, expected)) in readings.iter().zip(&expected).enumerate() {
if actual != expected {
return Err(BooleanTraceCommitmentError::ColumnBatchValueMismatch { batch });
}
}
Ok(BooleanTraceCommitmentProof { values, opening })
}
fn verify_at(
&self,
commitment: &Self::Commitment,
proof: &Self::Proof,
protocol: &OpeningProtocol,
points: &[Point<EF>],
challenger: &mut Challenger,
) -> Result<Vec<OpeningEvals<EF>>, Self::Error> {
let placements = self.validate_opening(protocol, points)?;
let expected_values = value_count(protocol);
if proof.values.len() != expected_values {
return Err(BooleanTraceCommitmentError::ValueCount {
expected: expected_values,
actual: proof.values.len(),
});
}
let shape = match OpeningRoute::new(protocol) {
OpeningRoute::PerColumn(plan) => {
let openings = Self::bit_openings(protocol, plan.claims(), points, &placements);
self.inner
.verify_readings(
commitment,
&openings,
&plan.readings(&proof.values),
&proof.opening,
challenger,
)
.map_err(BooleanTraceCommitmentError::Boolean)?;
return Ok(opening_evals(protocol, &proof.values));
}
OpeningRoute::Batched(shape) => shape,
};
let ColumnBatchShape { width, next, .. } = shape;
let run = shape.values_per_batch();
let mut transcript = ColumnBatchVerifierTranscript::new(challenger, shape);
let mut openings = Vec::with_capacity(shape.num_batches);
let mut readings = Vec::with_capacity(shape.num_batches);
for (point, batch_values) in points.iter().zip(proof.values.chunks_exact(run)) {
let (current, successor) = batch_values.split_at(width);
let column_point = transcript.batch(point, current, successor)?;
readings.push(BitReadings {
current: Some(Self::combine_columns(current, &column_point)),
next: next.then(|| Self::combine_columns(successor, &column_point)),
});
let mut lifted_point = column_point;
lifted_point.extend(point);
openings.push(BitOpening {
point: lifted_point,
row_variables: shape.table_variables,
current: true,
next,
});
}
transcript.finish();
self.inner
.verify_readings(commitment, &openings, &readings, &proof.opening, challenger)
.map_err(BooleanTraceCommitmentError::Boolean)?;
Ok(opening_evals(protocol, &proof.values))
}
}
#[cfg(test)]
mod tests;