mod evaluate;
mod gather;
mod plan;
use alloc::vec;
use alloc::vec::Vec;
use core::marker::PhantomData;
use p3_binary_dft::EncodableLevel;
use p3_binary_field::{BitCoordinates, PackedGf2, PackedGf2x64, TowerLevel};
use p3_challenger::fs::TranscriptField;
use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
use p3_commit::{Mmcs, MultilinearPcs};
use p3_field::{ExtensionField, 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, TableRun, 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: BitCoordinates + ExtensionField<B::Val>,
B: BooleanBackend<EF>,
B::Val: TranscriptField + TowerLevel,
{
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 block_claims<'a>(
run: &'a TableRun,
point: &'a Point<EF>,
column_point: &'a Point<EF>,
current: &'a [EF],
successor: &'a [EF],
) -> impl Iterator<Item = (BitOpening<EF>, BitReadings<EF>)> + 'a {
let next = run.shape.next;
run.blocks.iter().map(move |block| {
let columns = block.first..(block.first + (1 << block.variables)).min(current.len());
let block_point = Point::new(column_point.as_slice()[..block.variables].to_vec());
let readings = BitReadings {
current: Some(Self::combine_columns(
¤t[columns.clone()],
&block_point,
)),
next: next.then(|| Self::combine_columns(&successor[columns], &block_point)),
};
let mut local = block_point;
local.extend(point);
let opening = BitOpening {
point: block.prefix.lift_prefix(&local),
row_variables: run.shape.table_variables,
current: true,
next,
};
(opening, readings)
})
}
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 = "P: Serialize", deserialize = "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("table {table} declares {bits} bit columns out of {width}")]
BitRegionWidth {
table: usize,
bits: usize,
width: 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("{actual} supplied value runs against {expected} opening batches")]
KnownValueCount {
expected: usize,
actual: usize,
},
#[error("the value run supplied for batch {batch} does not match the columns it reads")]
KnownValueShape {
batch: usize,
},
#[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: BitCoordinates + ExtensionField<B::Val>,
B: BooleanMultilinearPcs<EF, Challenger>,
B::Val: TranscriptField + TowerLevel,
Challenger: FieldChallenger<B::Val>
+ GrindingChallenger<Witness = B::Val>
+ CanSampleUniformBits<B::Val>
+ CanObserve<B::Commitment>,
{
type Val = B::Val;
type Commitment = B::Commitment;
type ProverData = BooleanTraceCommitmentData<B::Val, B::ProverData>;
type Proof = BooleanTraceCommitmentProof<EF, B::Proof>;
type Error = BooleanTraceCommitmentError<B::Error>;
type ProverError = BooleanTraceCommitmentError<B::Error>;
type Witness = Vec<Table<B::Val>>;
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 num_words = 1 << (self.num_variables() - 6);
let gather = |words: &mut [u64]| {
tracing::info_span!("gather boolean bits")
.in_scope(|| self.gather_bits(&witness, words))
};
let committed = match B::packed_words(num_words) {
Some(mut packed) => {
gather(packed.words_mut())?;
self.inner.commit_packed(packed, challenger)
}
None => {
let mut words = vec![0u64; num_words];
gather(&mut words)?;
let bits = words
.into_iter()
.map(PackedGf2::new)
.collect::<Vec<PackedGf2x64>>();
self.inner.commit_bits(&bits, challenger)
}
};
let (commitment, inner) = committed.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: BitCoordinates + ExtensionField<B::Val>,
B: BooleanMultilinearPcs<EF, Challenger>,
B::Val: TranscriptField + TowerLevel,
Challenger: FieldChallenger<B::Val>
+ GrindingChallenger<Witness = B::Val>
+ CanSampleUniformBits<B::Val>
+ CanObserve<B::Commitment>,
{
fn bit_region(&self, _table: usize, width: usize) -> usize {
width
}
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::<B::Val, 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(runs) = route {
let claims = runs
.iter()
.map(|run| run.shape.num_batches * run.shape.num_views() * run.blocks.len())
.sum();
let column_variables = runs
.iter()
.map(|run| run.shape.column_variables())
.max()
.unwrap_or(0);
security.charge_reduction(p3_security::multilinear::column_batch_term(
claims,
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> {
let known = alloc::vec![None; protocol.num_openings()];
self.open_at_known(prover_data, protocol, points, &known, challenger)
}
fn open_at_known(
&self,
prover_data: Self::ProverData,
protocol: &OpeningProtocol,
points: &[Point<EF>],
known: &[Option<OpeningEvals<EF>>],
challenger: &mut Challenger,
) -> Result<Self::Proof, Self::ProverError> {
Self::validate_source_shapes(&prover_data.tables, protocol)?;
let placements = self.validate_opening(protocol, points)?;
Self::validate_known(protocol, known)?;
let runs = 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(runs) => runs,
};
let BooleanTraceCommitmentData { inner, tables } = prover_data;
let mut values = Vec::with_capacity(value_count(protocol));
let mut openings = Vec::new();
let mut expected = Vec::new();
let mut batch_points = points.iter();
let mut batch_known = known.iter();
for run in &runs {
let ColumnBatchShape {
width,
next,
num_batches,
..
} = run.shape;
let run_points: Vec<_> = batch_points.by_ref().take(num_batches).collect();
let run_known = batch_known.by_ref().take(num_batches);
let offset = values.len();
tracing::info_span!("evaluate boolean columns", width, next).in_scope(|| {
for (point, known) in run_points.iter().zip(run_known) {
if let Some(known) = known {
values.extend_from_slice(known.current());
values.extend_from_slice(known.next());
continue;
}
let (current, successor) =
Self::evaluate_views(&tables[run.table], point, next);
values.extend(current);
values.extend(successor);
}
});
let mut transcript = ColumnBatchProverTranscript::new(challenger, run.shape);
let chunks = values[offset..].chunks_exact(run.shape.values_per_batch());
for (point, batch_values) in run_points.into_iter().zip(chunks) {
let (current, successor) = batch_values.split_at(width);
let column_point = transcript.batch(point, current, successor);
for (opening, readings) in
Self::block_claims(run, point, &column_point, current, successor)
{
openings.push(opening);
expected.push(readings);
}
}
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 runs = 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(runs) => runs,
};
let mut openings = Vec::new();
let mut readings = Vec::new();
let mut batch_points = points.iter();
let mut batch_values = proof.values.as_slice();
for run in &runs {
let run_len = run.shape.values_per_batch() * run.shape.num_batches;
let (run_values, rest) = batch_values.split_at(run_len);
batch_values = rest;
let mut transcript = ColumnBatchVerifierTranscript::new(challenger, run.shape);
for (point, values) in batch_points
.by_ref()
.take(run.shape.num_batches)
.zip(run_values.chunks_exact(run.shape.values_per_batch()))
{
let (current, successor) = values.split_at(run.shape.width);
let column_point = transcript.batch(point, current, successor)?;
for (opening, reading) in
Self::block_claims(run, point, &column_point, current, successor)
{
openings.push(opening);
readings.push(reading);
}
}
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;