use alloc::vec;
use alloc::vec::Vec;
use p3_binary_field::{BitCoordinates, PackedGf2, TowerLevel, Underlier};
use p3_challenger::fs::TranscriptField;
use p3_challenger::{CanObserve, CanSampleUniformBits, FieldChallenger, GrindingChallenger};
use p3_commit::Mmcs;
use p3_field::{ExtensionField, Field};
use p3_matrix::dense::RowMajorMatrix;
use p3_multilinear_util::point::Point;
use p3_security::multilinear::{bit_ring_switch_claim_batching_term, bit_ring_switch_tensors_term};
use p3_sumcheck::layout::{Layout, SuffixProver, Table};
use p3_sumcheck::ring_switch::bits::{
BitPacking, BitPackingView, BitRingSwitch, BitRingSwitchClaims,
};
use p3_sumcheck::{
OpeningBatch, OpeningProtocol, PrescribedOpeningSecurity, PrescribedPointPcs, TableShape,
TableSpec,
};
use p3_whir::{WhirDomain, WhirProver, WhirProverData};
use crate::boolean::{BitOpening, BitReadings, BooleanBackend, BooleanMultilinearPcs};
use crate::boolean_trace::BooleanTraceCommitment;
use crate::fold::BitChallengeField;
use crate::packing::{Coordinates, PackedStack, PackedWords, hypercube_variables};
use crate::whir::error::BooleanWhirError;
use crate::whir::proof::BooleanWhirProof;
use crate::whir::shape::ProofShape;
type Binding<F, EF> = SuffixProver<F, EF>;
pub type BooleanWhirProver<F, EF, Dft, MT, Challenger> =
WhirProver<EF, F, Dft, MT, Challenger, Binding<F, EF>>;
pub type BooleanWhirData<F, EF, MT> = WhirProverData<F, EF, MT, Binding<F, EF>>;
pub type BooleanWhirTracePcs<F, EF, Dft, MT, Challenger> =
BooleanTraceCommitment<EF, BooleanWhirPcs<F, EF, Dft, MT, Challenger>>;
pub struct BooleanWhirPcs<F: Field, EF: ExtensionField<F>, Dft, MT, Challenger> {
inner: BooleanWhirProver<F, EF, Dft, MT, Challenger>,
num_variables: usize,
}
impl<F, EF, Dft, MT, Challenger> BooleanWhirPcs<F, EF, Dft, MT, Challenger>
where
F: Field + TranscriptField + TowerLevel + Coordinates + Ord,
EF: BitCoordinates + ExtensionField<F>,
Dft: WhirDomain<F, EF>,
MT: Mmcs<F>,
{
pub fn new(
inner: BooleanWhirProver<F, EF, Dft, MT, Challenger>,
num_variables: usize,
) -> Result<Self, BooleanWhirError> {
let packed = Self::packed_variables(num_variables)?;
if inner.num_variables() != packed {
return Err(BooleanWhirError::ConfigArity {
expected: packed,
actual: inner.num_variables(),
});
}
Ok(Self {
inner,
num_variables,
})
}
#[must_use]
pub const fn num_variables(&self) -> usize {
self.num_variables
}
#[must_use]
pub const fn committed_bytes(&self) -> usize {
1 << (self.num_variables - 3)
}
fn packed_variables(num_variables: usize) -> Result<usize, BooleanWhirError> {
let absorbed = BitRingSwitch::<F, EF>::ABSORBED;
num_variables
.checked_sub(absorbed)
.ok_or(BooleanWhirError::WitnessTooNarrow {
needed: absorbed,
actual: num_variables,
})
}
fn protocol(&self, num_claims: usize) -> OpeningProtocol {
OpeningProtocol::new(vec![TableSpec::new(
TableShape::new(self.inner.num_variables(), 1),
(0..num_claims)
.map(|_| OpeningBatch::new(vec![0], Vec::new()))
.collect(),
)])
}
fn claims(openings: &[BitOpening<EF>]) -> Result<BitRingSwitchClaims<F, EF>, BooleanWhirError> {
let reductions = openings
.iter()
.map(Self::reduction)
.collect::<Result<Vec<_>, _>>()?;
BitRingSwitchClaims::new(reductions).map_err(BooleanWhirError::Reduction)
}
fn reduction(opening: &BitOpening<EF>) -> Result<BitRingSwitch<F, EF>, BooleanWhirError> {
if opening.next {
BitRingSwitch::with_successor(&opening.point, opening.row_variables)
} else {
BitRingSwitch::new(&opening.point)
}
.map_err(BooleanWhirError::Reduction)
}
fn current_openings(points: &[Point<EF>]) -> Vec<BitOpening<EF>> {
points
.iter()
.map(|point| BitOpening {
point: point.clone(),
row_variables: point.num_variables(),
current: true,
next: false,
})
.collect()
}
fn check_openings(&self, openings: &[BitOpening<EF>]) -> Result<(), BooleanWhirError> {
if openings.is_empty() {
return Err(BooleanWhirError::NoPoints);
}
for (index, opening) in openings.iter().enumerate() {
if opening.point.num_variables() != self.num_variables {
return Err(BooleanWhirError::PointArity {
expected: self.num_variables,
actual: opening.point.num_variables(),
});
}
if !opening.current && !opening.next {
return Err(BooleanWhirError::NoReading { index });
}
if opening.row_variables > self.num_variables {
return Err(BooleanWhirError::RowVariables {
index,
row_variables: opening.row_variables,
num_variables: self.num_variables,
});
}
}
Ok(())
}
fn packing(prover_data: &BooleanWhirData<F, EF, MT>) -> BitPackingView<'_, F> {
BitPacking::from_packed(prover_data.table(0).poly(0))
.expect("a committed table is a hypercube over a byte-aligned level")
}
}
impl<F, EF, Dft, MT, Challenger> BooleanWhirPcs<F, EF, Dft, MT, Challenger>
where
F: Field + TranscriptField + TowerLevel + Coordinates + Ord + Send + Sync,
EF: BitChallengeField<F>,
Dft: WhirDomain<F, EF>,
MT: Mmcs<F>,
Challenger: FieldChallenger<F>
+ GrindingChallenger<Witness = F>
+ CanSampleUniformBits<F>
+ CanObserve<MT::Commitment>,
{
#[must_use]
pub fn readings_security(
&self,
num_claims: usize,
successor_tensors: bool,
) -> Option<PrescribedOpeningSecurity> {
let protocol = self.protocol(num_claims.min(1));
let mut security = self.inner.prescribed_security(&protocol)?;
let num_tensors = if successor_tensors { 3 } else { 1 };
security.charge_reduction(bit_ring_switch_tensors_term(
num_claims.min(1),
num_tensors,
BitRingSwitch::<F, EF>::BATCHED,
self.inner.num_variables(),
EF::bits(),
));
if num_claims > 1 {
security.charge_reduction(bit_ring_switch_claim_batching_term(num_claims, EF::bits()));
}
Some(security)
}
#[must_use]
pub fn proof_shape(&self, num_claims: usize, successor_tensors: bool) -> ProofShape {
ProofShape::of_bit_readings(&self.inner, num_claims, successor_tensors)
}
#[allow(clippy::type_complexity)]
fn commit_column(
&self,
elements: Vec<F>,
challenger: &mut Challenger,
) -> Result<(MT::Commitment, BooleanWhirData<F, EF, MT>), BooleanWhirError> {
let actual = hypercube_variables(elements.len()).map_err(BooleanWhirError::Packing)?;
if actual != self.inner.num_variables() {
return Err(BooleanWhirError::WitnessArity {
expected: self.inner.num_variables(),
actual,
});
}
let len = elements.len();
let table = Table::new(RowMajorMatrix::new(elements, len));
let folding = self.inner.round_folding_factor(0);
let witness = Binding::<F, EF>::new_witness(vec![table], folding);
p3_commit::MultilinearPcs::<EF, Challenger>::commit(&self.inner, witness, challenger)
.map_err(BooleanWhirError::Commit)
}
#[allow(clippy::type_complexity)]
pub fn open_readings(
&self,
prover_data: BooleanWhirData<F, EF, MT>,
openings: &[BitOpening<EF>],
challenger: &mut Challenger,
) -> Result<(Vec<BitReadings<EF>>, BooleanWhirProof<F, EF, MT>), BooleanWhirError> {
self.check_openings(openings)?;
let claims = Self::claims(openings)?;
let packing = Self::packing(&prover_data);
let (reduction, surviving_point, _) =
tracing::info_span!("bit ring switch").in_scope(|| {
claims
.prove::<<EF as BitChallengeField<F>>::SumcheckRepr, _, _>(&packing, challenger)
});
let readings = openings
.iter()
.zip(claims.reductions())
.zip(&reduction.claims)
.map(|((opening, switch), elements)| {
let current = opening
.current
.then(|| switch.incoming_claim(&elements.tensor));
let next = opening
.next
.then(|| switch.successor_claim(&elements.tensor, elements.successor.as_ref()))
.transpose()
.map_err(BooleanWhirError::Reduction)?;
Ok(BitReadings { current, next })
})
.collect::<Result<Vec<_>, BooleanWhirError>>()?;
let opening = self
.inner
.open_at(
prover_data,
&self.protocol(1),
&[surviving_point],
challenger,
)
.map_err(BooleanWhirError::Commit)?;
Ok((readings, BooleanWhirProof { reduction, opening }))
}
pub fn verify_readings(
&self,
commitment: &MT::Commitment,
openings: &[BitOpening<EF>],
readings: &[BitReadings<EF>],
proof: &BooleanWhirProof<F, EF, MT>,
challenger: &mut Challenger,
) -> Result<(), BooleanWhirError> {
self.check_openings(openings)?;
if readings.len() != openings.len() || proof.reduction.claims.len() != openings.len() {
return Err(BooleanWhirError::ClaimCount {
expected: openings.len(),
values: readings.len(),
reductions: proof.reduction.claims.len(),
});
}
if let Some(index) = openings
.iter()
.zip(readings)
.position(|(opening, reading)| {
reading.current.is_some() != opening.current
|| reading.next.is_some() != opening.next
})
{
return Err(BooleanWhirError::ReadingShape { index });
}
let claims = Self::claims(openings)?;
let readings = readings
.iter()
.map(|reading| (reading.current, reading.next))
.collect::<Vec<_>>();
let (surviving_point, surviving_value) = claims
.verify_readings(&proof.reduction, &readings, challenger)
.map_err(BooleanWhirError::ReductionProof)?;
let evals = self
.inner
.verify_at(
commitment,
&proof.opening,
&self.protocol(1),
&[surviving_point],
challenger,
)
.map_err(BooleanWhirError::Opening)?;
match evals.as_slice() {
[batch] if batch.current().first() == Some(&surviving_value) => Ok(()),
_ => Err(BooleanWhirError::SurvivingClaim),
}
}
}
impl<F, EF, Dft, MT, Challenger> BooleanMultilinearPcs<EF, Challenger>
for BooleanWhirPcs<F, EF, Dft, MT, Challenger>
where
F: Field + TranscriptField + TowerLevel + Coordinates + Ord + Send + Sync,
EF: BitChallengeField<F>,
Dft: WhirDomain<F, EF>,
MT: Mmcs<F>,
Challenger: FieldChallenger<F>
+ GrindingChallenger<Witness = F>
+ CanSampleUniformBits<F>
+ CanObserve<MT::Commitment>,
{
fn observe_commitment(&self, commitment: &Self::Commitment, challenger: &mut Challenger) {
p3_commit::MultilinearPcs::<EF, Challenger>::observe_commitment(
&self.inner,
commitment,
challenger,
);
}
fn commit_bits<U: Underlier>(
&self,
bits: &[PackedGf2<U>],
challenger: &mut Challenger,
) -> Result<(Self::Commitment, Self::ProverData), Self::Error> {
let stack = PackedStack::<PackedGf2<U>, F>::from_columns(&[bits])
.map_err(BooleanWhirError::Packing)?;
self.commit_column(stack.into_poly().into_evals(), challenger)
}
fn commit_packed(
&self,
packed: PackedWords<F>,
challenger: &mut Challenger,
) -> Result<(Self::Commitment, Self::ProverData), Self::Error> {
self.commit_column(packed.into_elements(), challenger)
}
fn packed_words(words: usize) -> Option<PackedWords<F>> {
PackedWords::zeroed(words)
}
fn open_readings(
&self,
prover_data: Self::ProverData,
openings: &[BitOpening<EF>],
challenger: &mut Challenger,
) -> Result<(Vec<BitReadings<EF>>, Self::Proof), Self::Error> {
Self::open_readings(self, prover_data, openings, challenger)
}
fn verify_readings(
&self,
commitment: &Self::Commitment,
openings: &[BitOpening<EF>],
readings: &[BitReadings<EF>],
proof: &Self::Proof,
challenger: &mut Challenger,
) -> Result<(), Self::Error> {
Self::verify_readings(self, commitment, openings, readings, proof, challenger)
}
fn readings_security(
&self,
num_claims: usize,
successor_tensors: bool,
) -> Option<PrescribedOpeningSecurity> {
Self::readings_security(self, num_claims, successor_tensors)
}
fn open_at_points(
&self,
prover_data: Self::ProverData,
points: &[Point<EF>],
challenger: &mut Challenger,
) -> Result<(Vec<EF>, Self::Proof), Self::Error> {
let (readings, proof) =
self.open_readings(prover_data, &Self::current_openings(points), challenger)?;
let values = readings
.into_iter()
.map(|reading| {
reading
.current
.expect("an opening asking for the current reading carries it")
})
.collect();
Ok((values, proof))
}
fn verify_at_points(
&self,
commitment: &Self::Commitment,
points: &[Point<EF>],
values: &[EF],
proof: &Self::Proof,
challenger: &mut Challenger,
) -> Result<(), Self::Error> {
let readings: Vec<BitReadings<EF>> = values
.iter()
.map(|&value| BitReadings {
current: Some(value),
next: None,
})
.collect();
self.verify_readings(
commitment,
&Self::current_openings(points),
&readings,
proof,
challenger,
)
}
}
impl<F, EF, Dft, MT, Challenger> BooleanBackend<EF> for BooleanWhirPcs<F, EF, Dft, MT, Challenger>
where
F: Field + TranscriptField + TowerLevel + Coordinates + Ord + Send + Sync,
EF: BitCoordinates + ExtensionField<F>,
Dft: WhirDomain<F, EF>,
MT: Mmcs<F>,
{
type Val = F;
type Commitment = MT::Commitment;
type ProverData = BooleanWhirData<F, EF, MT>;
type Proof = BooleanWhirProof<F, EF, MT>;
type Error = BooleanWhirError;
fn num_variables(&self) -> usize {
self.num_variables
}
}