use alloc::vec::Vec;
use p3_binary_dft::EncodableLevel;
use p3_binary_field::TowerLevel;
use p3_challenger::FieldChallenger;
use p3_challenger::fs::TranscriptField;
use p3_field::Field;
use p3_multilinear_util::point::Point;
use p3_sumcheck::layout::{Table, TablePlacement, plan_stacked_layout};
use p3_sumcheck::{OpeningEvals, OpeningPointMismatch, OpeningProtocol, TableShape};
use super::{BooleanTraceCommitment, BooleanTraceCommitmentError};
use crate::boolean::{BitOpening, BitReadings, BooleanBackend};
use crate::boolean_trace_transcript::ColumnBatchShape;
use crate::fold::{ChallengeField, FoldAlphabet};
use crate::packing::Coordinates;
impl<EF, B> BooleanTraceCommitment<EF, B>
where
EF: ChallengeField<EF>
+ EncodableLevel
+ TranscriptField
+ TowerLevel
+ FoldAlphabet<EF>
+ Coordinates,
B: BooleanBackend<EF>,
{
pub(super) fn placements(
&self,
shapes: &[TableShape],
) -> Result<Vec<TablePlacement>, BooleanTraceCommitmentError<B::Error>> {
let (arity, placements) = plan_stacked_layout(shapes);
if arity != self.num_variables() {
return Err(BooleanTraceCommitmentError::StackedArity {
expected: self.num_variables(),
actual: arity,
});
}
Ok(placements)
}
pub(super) fn bit_openings(
protocol: &OpeningProtocol,
claims: &[ColumnClaim],
points: &[Point<EF>],
placements: &[TablePlacement],
) -> Vec<BitOpening<EF>> {
let shapes = protocol.table_shapes();
let mut by_table = alloc::vec![None; shapes.len()];
for placement in placements {
by_table[placement.idx()] = Some(placement);
}
claims
.iter()
.map(|claim| {
let placement =
by_table[claim.table].expect("the planner places every supplied shape");
BitOpening {
point: placement.selectors()[claim.column].lift_prefix(&points[claim.opening]),
row_variables: shapes[claim.table].num_variables(),
current: claim.current_at.is_some(),
next: claim.next_at.is_some(),
}
})
.collect()
}
pub(super) fn validate_opening(
&self,
protocol: &OpeningProtocol,
points: &[Point<EF>],
) -> Result<Vec<TablePlacement>, BooleanTraceCommitmentError<B::Error>> {
let placements = self.placements(&protocol.table_shapes())?;
protocol
.check_points(points)
.map_err(|mismatch| match mismatch {
OpeningPointMismatch::Count { expected, actual } => {
BooleanTraceCommitmentError::PointCount { expected, actual }
}
OpeningPointMismatch::Arity {
table,
expected,
actual,
} => BooleanTraceCommitmentError::PointArity {
table,
expected,
actual,
},
})?;
Ok(placements)
}
pub(super) fn validate_source_shapes(
tables: &[Table<EF>],
protocol: &OpeningProtocol,
) -> Result<(), BooleanTraceCommitmentError<B::Error>> {
let expected = protocol.table_shapes();
if tables.len() != expected.len() {
return Err(BooleanTraceCommitmentError::TableCountMismatch {
expected: expected.len(),
actual: tables.len(),
});
}
for (table, expected) in expected.iter().copied().enumerate() {
let actual = tables[table].shape();
if actual != expected {
return Err(BooleanTraceCommitmentError::TableShapeMismatch {
table,
expected,
actual,
});
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub(super) enum OpeningRoute {
Batched(ColumnBatchShape),
PerColumn(ClaimPlan),
}
impl OpeningRoute {
pub(super) fn new(protocol: &OpeningProtocol) -> Self {
batched_shape(protocol)
.map_or_else(|| Self::PerColumn(ClaimPlan::of(protocol)), Self::Batched)
}
pub(super) const fn num_reductions(&self) -> usize {
match self {
Self::Batched(shape) => shape.num_batches,
Self::PerColumn(plan) => plan.claims().len(),
}
}
pub(super) fn successor_tensors(&self, shapes: &[TableShape], absorbed: usize) -> bool {
match self {
Self::Batched(shape) => shape.next && shape.table_variables > absorbed,
Self::PerColumn(plan) => plan.claims().iter().any(|claim| {
claim.next_at.is_some() && shapes[claim.table].num_variables() > absorbed
}),
}
}
}
fn batched_shape(protocol: &OpeningProtocol) -> Option<ColumnBatchShape> {
let shapes = protocol.table_shapes();
if shapes.len() != 1 || protocol.num_openings() == 0 {
return None;
}
let width = shapes[0].width();
let columns = (0..width).collect::<Vec<_>>();
let next = protocol
.iter_openings()
.next()
.is_some_and(|(_, batch)| !batch.next().is_empty());
let complete = |read: &[usize], asked: bool| {
if asked {
read == columns.as_slice()
} else {
read.is_empty()
}
};
protocol
.iter_openings()
.all(|(table, batch)| {
table == 0 && complete(batch.current(), true) && complete(batch.next(), next)
})
.then(|| ColumnBatchShape {
table_variables: shapes[0].num_variables(),
width,
num_batches: protocol.num_openings(),
next,
})
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ColumnClaim {
pub(super) table: usize,
pub(super) column: usize,
pub(super) opening: usize,
pub(super) current_at: Option<usize>,
pub(super) next_at: Option<usize>,
}
pub(super) fn column_claims(protocol: &OpeningProtocol) -> Vec<ColumnClaim> {
let mut claims = Vec::new();
let mut cursor = 0;
for (opening, (table, batch)) in protocol.iter_openings().enumerate() {
let next_cursor = cursor + batch.current().len();
let mut answered = alloc::vec![false; batch.next().len()];
for (offset, &column) in batch.current().iter().enumerate() {
let at = batch
.next()
.iter()
.zip(&answered)
.position(|(&other, &taken)| other == column && !taken);
if let Some(at) = at {
answered[at] = true;
}
claims.push(ColumnClaim {
table,
column,
opening,
current_at: Some(cursor + offset),
next_at: at.map(|at| next_cursor + at),
});
}
for (at, &column) in batch.next().iter().enumerate() {
if !answered[at] {
claims.push(ColumnClaim {
table,
column,
opening,
current_at: None,
next_at: Some(next_cursor + at),
});
}
}
cursor = next_cursor + batch.next().len();
}
claims
}
pub(super) fn value_count(protocol: &OpeningProtocol) -> usize {
protocol.iter_openings().map(|(_, batch)| batch.len()).sum()
}
#[derive(Clone, Debug)]
pub(super) struct ClaimPlan {
claims: Vec<ColumnClaim>,
len: usize,
}
impl ClaimPlan {
pub(super) fn of(protocol: &OpeningProtocol) -> Self {
Self::new(column_claims(protocol), value_count(protocol))
.expect("the per-column claims write every value position exactly once")
}
pub(super) fn new(claims: Vec<ColumnClaim>, len: usize) -> Option<Self> {
let mut written = alloc::vec![false; len];
for claim in &claims {
for at in [claim.current_at, claim.next_at].into_iter().flatten() {
if at >= len || core::mem::replace(&mut written[at], true) {
return None;
}
}
}
written
.into_iter()
.all(|written| written)
.then_some(Self { claims, len })
}
pub(super) const fn claims(&self) -> &[ColumnClaim] {
self.claims.as_slice()
}
pub(super) fn values<EF: Field>(&self, readings: &[BitReadings<EF>]) -> Vec<EF> {
let mut values = alloc::vec![EF::ZERO; self.len];
for (claim, reading) in self.claims.iter().zip(readings) {
if let Some(at) = claim.current_at {
values[at] = reading
.current
.expect("a claim asking for the reading at the point carries it");
}
if let Some(at) = claim.next_at {
values[at] = reading
.next
.expect("a claim asking for the reading one row ahead carries it");
}
}
values
}
pub(super) fn readings<EF: Field>(&self, values: &[EF]) -> Vec<BitReadings<EF>> {
self.claims
.iter()
.map(|claim| BitReadings {
current: claim.current_at.map(|at| values[at]),
next: claim.next_at.map(|at| values[at]),
})
.collect()
}
}
pub(super) fn opening_evals<EF: Field>(
protocol: &OpeningProtocol,
values: &[EF],
) -> Vec<OpeningEvals<EF>> {
let mut evals = Vec::with_capacity(protocol.num_openings());
let mut cursor = 0;
for (_, batch) in protocol.iter_openings() {
let next_cursor = cursor + batch.current().len();
let end = next_cursor + batch.next().len();
evals.push(OpeningEvals::new(
values[cursor..next_cursor].to_vec(),
values[next_cursor..end].to_vec(),
));
cursor = end;
}
evals
}
pub(super) fn sample_points<EF, Challenger>(
protocol: &OpeningProtocol,
challenger: &mut Challenger,
) -> Vec<Point<EF>>
where
EF: Field,
Challenger: FieldChallenger<EF>,
{
let shapes = protocol.table_shapes();
protocol
.iter_openings()
.map(|(table, _)| {
let num_variables = shapes[table].num_variables();
Point::new((0..num_variables).map(|_| challenger.sample()).collect())
})
.collect()
}