use alloc::vec;
use alloc::vec::Vec;
use core::marker::PhantomData;
use p3_binary_field::TowerLevel;
use p3_challenger::fs::{
DomainSeparator, FieldToFieldCodec, FieldUnit, Hierarchy, Interaction, InteractionPattern,
Kind, Length, ProverState, TranscriptBound, TranscriptField, VerifierState,
};
use p3_challenger::{CanObserve, CanSample};
use p3_multilinear_util::point::Point;
use super::basis::Coefficients;
use super::reduction::BitRingSwitch;
const VERSION: u8 = 1;
pub(crate) const NAME: &[u8] = b"p3-sumcheck-bit-ring-switch";
const EVALUATION_POINT: &str = "evaluation_point";
const TENSOR_ROWS: &str = "tensor_rows";
const CARRY_ROWS: &str = "carry_rows";
const LAST_ROWS: &str = "last_rows";
const BATCHING_POINT: &str = "batching_point";
const TENSOR_BATCHING: &str = "tensor_batching";
const SUCCESSOR_ELEMENTS: usize = 2;
const BATCHED_SUMCHECK: &str = "batched_sumcheck";
const SURVIVING_CLAIM: &str = "surviving_claim";
type Alphabet<EF> = FieldUnit<EF>;
struct BatchedSumcheck;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BitRingSwitchShape {
pub num_variables: usize,
pub successor_rows: Option<usize>,
}
impl BitRingSwitchShape {
#[must_use]
pub const fn new(num_variables: usize) -> Self {
Self {
num_variables,
successor_rows: None,
}
}
#[must_use]
pub const fn with_successor_rows(num_variables: usize, row_variables: usize) -> Self {
Self {
num_variables,
successor_rows: Some(row_variables),
}
}
#[must_use]
pub fn sumcheck_rounds<EF: TowerLevel>(&self) -> usize {
let absorbed = BitRingSwitch::<EF>::ABSORBED;
assert!(
self.num_variables >= absorbed,
"the evaluation point must name at least the {absorbed} absorbed coordinates"
);
self.num_variables - absorbed
}
#[must_use]
pub fn pattern<EF: TranscriptField + TowerLevel>(&self) -> InteractionPattern {
let rows = |label| {
Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Message,
label,
Length::Fixed(Coefficients::<EF>::DIMENSION),
)
};
let successor = self.successor_rows.is_some();
let mut steps = vec![
Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Message,
EVALUATION_POINT,
Length::Fixed(self.num_variables),
),
rows(TENSOR_ROWS),
];
if successor {
steps.extend([rows(CARRY_ROWS), rows(LAST_ROWS)]);
}
steps.push(Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Challenge,
BATCHING_POINT,
Length::Fixed(BitRingSwitch::<EF>::ABSORBED),
));
if successor {
steps.push(Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Challenge,
TENSOR_BATCHING,
Length::Scalar,
));
}
steps.extend([
Interaction::marker::<BatchedSumcheck>(
Hierarchy::Begin,
Kind::Protocol,
BATCHED_SUMCHECK,
),
Interaction::marker::<BatchedSumcheck>(
Hierarchy::End,
Kind::Protocol,
BATCHED_SUMCHECK,
),
Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Message,
SURVIVING_CLAIM,
Length::Scalar,
),
]);
InteractionPattern::new(steps).expect("one matched bracket is always well formed")
}
#[must_use]
pub fn domain_separator<EF: TranscriptField + TowerLevel>(
&self,
) -> DomainSeparator<Alphabet<EF>> {
let mut separator = DomainSeparator::new(VERSION, NAME, self.pattern::<EF>());
if let Some(rows) = self.successor_rows {
separator.instance(&(rows as u64).to_be_bytes());
}
separator
}
}
pub struct BitRingSwitchProverTranscript<'a, C, EF: TranscriptField> {
state: ProverState<&'a mut C, Alphabet<EF>>,
shape: BitRingSwitchShape,
}
impl<'a, C, EF> BitRingSwitchProverTranscript<'a, C, EF>
where
EF: TranscriptField + TowerLevel,
C: CanObserve<EF> + CanSample<EF>,
{
pub fn new(challenger: &'a mut C, shape: BitRingSwitchShape) -> Self {
Self {
state: ProverState::new(challenger, &shape.domain_separator::<EF>()),
shape,
}
}
pub fn statement(
&mut self,
point: &Point<EF>,
rows: &[EF],
successor: Option<(&[EF], &[EF])>,
) -> (Point<EF>, Option<EF>) {
assert_eq!(
successor.is_some(),
self.shape.successor_rows.is_some(),
"the successor elements are sent exactly when the shape describes them",
);
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(
EVALUATION_POINT,
point.as_slice(),
);
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_ROWS, rows);
if let Some((carry, last)) = successor {
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(CARRY_ROWS, carry);
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(LAST_ROWS, last);
}
let batching_point = Point::new(
self.state
.challenge_extensions::<EF, EF, FieldToFieldCodec<EF>>(
BATCHING_POINT,
BitRingSwitch::<EF>::ABSORBED,
)
.into_iter()
.map(TranscriptBound::into_inner)
.collect(),
);
let alpha = successor.map(|_| {
self.state
.challenge_extension::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_BATCHING)
.into_inner()
});
(batching_point, alpha)
}
pub fn batched_sumcheck<R>(&mut self, run: impl FnOnce(&mut C) -> R) -> R {
self.state
.begin_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
let output = run(self.state.challenger_mut());
self.state.end_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
output
}
pub fn surviving_claim(&mut self, value: EF) {
self.state
.observe_extension::<EF, EF, FieldToFieldCodec<EF>>(SURVIVING_CLAIM, &value);
}
pub fn finish(self) {
assert!(
self.state.finalize().is_empty(),
"a bit-alphabet reduction carries every value in its own proof",
);
}
}
pub struct BitRingSwitchVerifierTranscript<'a, C, EF: TranscriptField> {
state: VerifierState<'static, &'a mut C, Alphabet<EF>>,
shape: BitRingSwitchShape,
_level: PhantomData<EF>,
}
impl<'a, C, EF> BitRingSwitchVerifierTranscript<'a, C, EF>
where
EF: TranscriptField + TowerLevel,
C: CanObserve<EF> + CanSample<EF>,
{
pub fn new(challenger: &'a mut C, shape: BitRingSwitchShape) -> Self {
Self {
state: VerifierState::new(challenger, &shape.domain_separator::<EF>(), &[]),
shape,
_level: PhantomData,
}
}
pub fn statement(
&mut self,
point: &Point<EF>,
rows: &[EF],
successor: Option<(&[EF], &[EF])>,
) -> Result<(Point<EF>, Option<EF>), TranscriptWidth> {
if let Err(error) = self.check_widths(point, rows, successor) {
self.state.abort();
return Err(error);
}
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(
EVALUATION_POINT,
point.as_slice(),
);
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_ROWS, rows);
if let Some((carry, last)) = successor {
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(CARRY_ROWS, carry);
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(LAST_ROWS, last);
}
let batching_point = Point::new(
self.state
.challenge_extensions::<EF, EF, FieldToFieldCodec<EF>>(
BATCHING_POINT,
BitRingSwitch::<EF>::ABSORBED,
)
.into_iter()
.map(TranscriptBound::into_inner)
.collect(),
);
let alpha = successor.map(|_| {
self.state
.challenge_extension::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_BATCHING)
.into_inner()
});
Ok((batching_point, alpha))
}
fn check_widths(
&self,
point: &Point<EF>,
rows: &[EF],
successor: Option<(&[EF], &[EF])>,
) -> Result<(), TranscriptWidth> {
check_statement_widths(&self.shape, point, rows, successor)
}
pub fn batched_sumcheck<R>(&mut self, run: impl FnOnce(&mut C) -> R) -> R {
self.state
.begin_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
let output = run(self.state.challenger_mut());
self.state.end_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
output
}
pub fn surviving_claim(&mut self, value: EF) {
self.state
.observe_extension::<EF, EF, FieldToFieldCodec<EF>>(SURVIVING_CLAIM, &value);
}
pub fn abort(&mut self) {
self.state.abort();
}
pub fn finish(self) {
self.state
.finalize()
.expect("a bit-alphabet reduction reads an empty wire");
}
}
fn check_statement_widths<EF: TowerLevel>(
shape: &BitRingSwitchShape,
point: &Point<EF>,
rows: &[EF],
successor: Option<(&[EF], &[EF])>,
) -> Result<(), TranscriptWidth> {
if point.num_variables() != shape.num_variables {
return Err(TranscriptWidth::Point {
expected: shape.num_variables,
actual: point.num_variables(),
});
}
let successor_rows = successor
.into_iter()
.flat_map(|(carry, last)| [carry, last]);
if let Some(malformed) = core::iter::once(rows)
.chain(successor_rows)
.find(|rows| rows.len() != Coefficients::<EF>::DIMENSION)
{
return Err(TranscriptWidth::TensorRows {
expected: Coefficients::<EF>::DIMENSION,
actual: malformed.len(),
});
}
let expected = shape.successor_rows.is_some();
if successor.is_some() == expected {
Ok(())
} else {
Err(TranscriptWidth::successor_elements(
expected,
successor.is_some(),
))
}
}
pub(crate) const CLAIMS_NAME: &[u8] = b"p3-sumcheck-bit-ring-switch-claims";
const CLAIM_BATCHING: &str = "claim_batching";
#[derive(Clone, Copy, Debug)]
pub struct ClaimStatement<'a, EF> {
pub point: &'a Point<EF>,
pub rows: &'a [EF],
pub successor: Option<(&'a [EF], &'a [EF])>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ClaimsDraws<EF> {
pub batching_point: Point<EF>,
pub alpha: Option<EF>,
pub lambda: EF,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BitRingSwitchClaimsShape {
pub claims: Vec<BitRingSwitchShape>,
}
impl BitRingSwitchClaimsShape {
#[must_use]
pub fn sends_successor(&self) -> bool {
self.claims
.iter()
.any(|claim| claim.successor_rows.is_some())
}
#[must_use]
pub fn pattern<EF: TranscriptField + TowerLevel>(&self) -> InteractionPattern {
let rows = |label| {
Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Message,
label,
Length::Fixed(Coefficients::<EF>::DIMENSION),
)
};
let scalar_challenge = |label| {
Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Challenge,
label,
Length::Scalar,
)
};
let mut steps = Vec::new();
for claim in &self.claims {
steps.push(Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Message,
EVALUATION_POINT,
Length::Fixed(claim.num_variables),
));
steps.push(rows(TENSOR_ROWS));
if claim.successor_rows.is_some() {
steps.extend([rows(CARRY_ROWS), rows(LAST_ROWS)]);
}
}
steps.push(Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Challenge,
BATCHING_POINT,
Length::Fixed(BitRingSwitch::<EF>::ABSORBED),
));
if self.sends_successor() {
steps.push(scalar_challenge(TENSOR_BATCHING));
}
steps.push(scalar_challenge(CLAIM_BATCHING));
steps.extend([
Interaction::marker::<BatchedSumcheck>(
Hierarchy::Begin,
Kind::Protocol,
BATCHED_SUMCHECK,
),
Interaction::marker::<BatchedSumcheck>(
Hierarchy::End,
Kind::Protocol,
BATCHED_SUMCHECK,
),
Interaction::algebra::<EF, EF>(
Hierarchy::Atomic,
Kind::Message,
SURVIVING_CLAIM,
Length::Scalar,
),
]);
InteractionPattern::new(steps).expect("one matched bracket is always well formed")
}
#[must_use]
pub fn domain_separator<EF: TranscriptField + TowerLevel>(
&self,
) -> DomainSeparator<Alphabet<EF>> {
let mut separator = DomainSeparator::new(VERSION, CLAIMS_NAME, self.pattern::<EF>());
separator.instance(&(self.claims.len() as u64).to_be_bytes());
for claim in &self.claims {
match claim.successor_rows {
Some(rows) => separator.instance(&(rows as u64).to_be_bytes()),
None => separator.instance(&[]),
};
}
separator
}
}
pub struct BitRingSwitchClaimsProverTranscript<'a, C, EF: TranscriptField> {
state: ProverState<&'a mut C, Alphabet<EF>>,
shape: BitRingSwitchClaimsShape,
}
impl<'a, C, EF> BitRingSwitchClaimsProverTranscript<'a, C, EF>
where
EF: TranscriptField + TowerLevel,
C: CanObserve<EF> + CanSample<EF>,
{
pub fn new(challenger: &'a mut C, shape: BitRingSwitchClaimsShape) -> Self {
Self {
state: ProverState::new(challenger, &shape.domain_separator::<EF>()),
shape,
}
}
pub fn statement(&mut self, claims: &[ClaimStatement<'_, EF>]) -> ClaimsDraws<EF> {
assert_eq!(
claims.len(),
self.shape.claims.len(),
"one statement per claim the shape describes"
);
for (claim, shape) in claims.iter().zip(&self.shape.claims) {
assert_eq!(
claim.successor.is_some(),
shape.successor_rows.is_some(),
"the successor elements are sent exactly when the shape describes them",
);
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(
EVALUATION_POINT,
claim.point.as_slice(),
);
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_ROWS, claim.rows);
if let Some((carry, last)) = claim.successor {
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(CARRY_ROWS, carry);
self.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(LAST_ROWS, last);
}
}
let batching_point = Point::new(
self.state
.challenge_extensions::<EF, EF, FieldToFieldCodec<EF>>(
BATCHING_POINT,
BitRingSwitch::<EF>::ABSORBED,
)
.into_iter()
.map(TranscriptBound::into_inner)
.collect(),
);
let alpha = self.shape.sends_successor().then(|| {
self.state
.challenge_extension::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_BATCHING)
.into_inner()
});
let lambda = self
.state
.challenge_extension::<EF, EF, FieldToFieldCodec<EF>>(CLAIM_BATCHING)
.into_inner();
ClaimsDraws {
batching_point,
alpha,
lambda,
}
}
pub fn batched_sumcheck<R>(&mut self, run: impl FnOnce(&mut C) -> R) -> R {
self.state
.begin_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
let output = run(self.state.challenger_mut());
self.state.end_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
output
}
pub fn surviving_claim(&mut self, value: EF) {
self.state
.observe_extension::<EF, EF, FieldToFieldCodec<EF>>(SURVIVING_CLAIM, &value);
}
pub fn finish(self) {
assert!(
self.state.finalize().is_empty(),
"a batch of bit-alphabet claims carries every value in its own proof",
);
}
}
pub struct BitRingSwitchClaimsVerifierTranscript<'a, C, EF: TranscriptField> {
state: VerifierState<'static, &'a mut C, Alphabet<EF>>,
shape: BitRingSwitchClaimsShape,
}
impl<'a, C, EF> BitRingSwitchClaimsVerifierTranscript<'a, C, EF>
where
EF: TranscriptField + TowerLevel,
C: CanObserve<EF> + CanSample<EF>,
{
pub fn new(challenger: &'a mut C, shape: BitRingSwitchClaimsShape) -> Self {
Self {
state: VerifierState::new(challenger, &shape.domain_separator::<EF>(), &[]),
shape,
}
}
pub fn statement(
&mut self,
claims: &[ClaimStatement<'_, EF>],
) -> Result<ClaimsDraws<EF>, TranscriptWidth> {
let checked = if claims.len() == self.shape.claims.len() {
claims
.iter()
.zip(&self.shape.claims)
.try_for_each(|(claim, shape)| {
check_statement_widths(shape, claim.point, claim.rows, claim.successor)
})
} else {
Err(TranscriptWidth::Claims {
expected: self.shape.claims.len(),
actual: claims.len(),
})
};
if let Err(error) = checked {
self.state.abort();
return Err(error);
}
for claim in claims {
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(
EVALUATION_POINT,
claim.point.as_slice(),
);
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_ROWS, claim.rows);
if let Some((carry, last)) = claim.successor {
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(CARRY_ROWS, carry);
let _ = self
.state
.observe_extensions::<EF, EF, FieldToFieldCodec<EF>>(LAST_ROWS, last);
}
}
let batching_point = Point::new(
self.state
.challenge_extensions::<EF, EF, FieldToFieldCodec<EF>>(
BATCHING_POINT,
BitRingSwitch::<EF>::ABSORBED,
)
.into_iter()
.map(TranscriptBound::into_inner)
.collect(),
);
let alpha = self.shape.sends_successor().then(|| {
self.state
.challenge_extension::<EF, EF, FieldToFieldCodec<EF>>(TENSOR_BATCHING)
.into_inner()
});
let lambda = self
.state
.challenge_extension::<EF, EF, FieldToFieldCodec<EF>>(CLAIM_BATCHING)
.into_inner();
Ok(ClaimsDraws {
batching_point,
alpha,
lambda,
})
}
pub fn batched_sumcheck<R>(&mut self, run: impl FnOnce(&mut C) -> R) -> R {
self.state
.begin_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
let output = run(self.state.challenger_mut());
self.state.end_protocol::<BatchedSumcheck>(BATCHED_SUMCHECK);
output
}
pub fn surviving_claim(&mut self, value: EF) {
self.state
.observe_extension::<EF, EF, FieldToFieldCodec<EF>>(SURVIVING_CLAIM, &value);
}
pub fn abort(&mut self) {
self.state.abort();
}
pub fn finish(self) {
self.state
.finalize()
.expect("a batch of bit-alphabet claims reads an empty wire");
}
}
#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TranscriptWidth {
#[error("the evaluation point names {actual} coordinates, expected {expected}")]
Point {
expected: usize,
actual: usize,
},
#[error("the tensor element carries {actual} rows, expected {expected}")]
TensorRows {
expected: usize,
actual: usize,
},
#[error("the batch binds {actual} claims, expected {expected}")]
Claims {
expected: usize,
actual: usize,
},
#[error("the statement carries {actual} successor elements, expected {expected}")]
SuccessorElements {
expected: usize,
actual: usize,
},
}
impl TranscriptWidth {
#[must_use]
pub(crate) const fn successor_elements(expected: bool, actual: bool) -> Self {
const fn count(present: bool) -> usize {
if present { SUCCESSOR_ELEMENTS } else { 0 }
}
Self::SuccessorElements {
expected: count(expected),
actual: count(actual),
}
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use p3_binary_field::{BinaryChallenger, BinaryField16};
use p3_challenger::HashChallenger;
use p3_challenger::testing::{
SeedDigest, assert_seeds_pairwise_distinct, pow_difficulties, seed_digest,
};
use p3_field::PrimeCharacteristicRing;
use p3_keccak::Keccak256Hash;
use super::*;
type EF = BinaryField16;
type Chal = BinaryChallenger<EF, HashChallenger<u8, Keccak256Hash, 32>>;
const NUM_VARIABLES: usize = 8;
const NUM_ROWS: usize = 16;
fn fresh_challenger() -> Chal {
Chal::from_hasher(Vec::new(), Keccak256Hash)
}
const SUCCESSOR_ROWS: usize = 6;
const fn base_shape() -> BitRingSwitchShape {
BitRingSwitchShape::new(NUM_VARIABLES)
}
const fn successor_shape() -> BitRingSwitchShape {
BitRingSwitchShape::with_successor_rows(NUM_VARIABLES, SUCCESSOR_ROWS)
}
fn seed_of(shape: BitRingSwitchShape) -> SeedDigest {
seed_digest(&shape.domain_separator::<EF>())
}
fn one_step_from_base() -> Vec<(&'static str, BitRingSwitchShape)> {
let BitRingSwitchShape {
num_variables: _,
successor_rows: _,
} = base_shape();
let mut wider = base_shape();
wider.num_variables += 1;
alloc::vec![
("num_variables", wider),
("successor_rows", successor_shape()),
(
"successor_rows_count",
BitRingSwitchShape::with_successor_rows(NUM_VARIABLES, SUCCESSOR_ROWS + 1),
),
]
}
fn point_of(num_variables: usize, seed: u32) -> Point<EF> {
Point::new(
(0..num_variables)
.map(|i| EF::from_u32(seed * 31 + i as u32 + 1))
.collect(),
)
}
fn rows_of(seed: u32) -> Vec<EF> {
(0..NUM_ROWS)
.map(|i| EF::from_u32(seed * 17 + i as u32 + 1))
.collect()
}
type Draws = (Point<EF>, Option<EF>, EF);
type SuccessorRows<'a> = Option<(&'a [EF], &'a [EF])>;
fn drive_prover(
challenger: &mut Chal,
shape: BitRingSwitchShape,
point: &Point<EF>,
rows: &[EF],
successor: SuccessorRows<'_>,
claim: EF,
) -> Draws {
let mut transcript = BitRingSwitchProverTranscript::<Chal, EF>::new(challenger, shape);
let (batching_point, alpha) = transcript.statement(point, rows, successor);
let delegated = transcript.batched_sumcheck(<Chal as CanSample<EF>>::sample);
transcript.surviving_claim(claim);
transcript.finish();
(batching_point, alpha, delegated)
}
fn drive_verifier(
challenger: &mut Chal,
shape: BitRingSwitchShape,
point: &Point<EF>,
rows: &[EF],
successor: SuccessorRows<'_>,
claim: EF,
) -> Result<Draws, TranscriptWidth> {
let mut transcript = BitRingSwitchVerifierTranscript::<Chal, EF>::new(challenger, shape);
let (batching_point, alpha) = transcript.statement(point, rows, successor)?;
let delegated = transcript.batched_sumcheck(<Chal as CanSample<EF>>::sample);
transcript.surviving_claim(claim);
transcript.finish();
Ok((batching_point, alpha, delegated))
}
fn replay(point: &Point<EF>, rows: &[EF], claim: EF) -> (Draws, EF) {
replay_in(
BitRingSwitchShape::new(point.num_variables()),
point,
rows,
None,
claim,
)
}
fn replay_in(
shape: BitRingSwitchShape,
point: &Point<EF>,
rows: &[EF],
successor: SuccessorRows<'_>,
claim: EF,
) -> (Draws, EF) {
let mut challenger = fresh_challenger();
let draws = drive_verifier(&mut challenger, shape, point, rows, successor, claim)
.expect("a well-shaped run always replays");
(draws, CanSample::<EF>::sample(&mut challenger))
}
#[test]
fn the_same_shape_seeds_the_same_stream_twice() {
assert_eq!(seed_of(base_shape()), seed_of(base_shape()));
}
#[test]
fn no_two_configurations_of_the_shape_share_a_seed() {
let mut seeds = alloc::vec![("base", seed_of(base_shape()))];
seeds.extend(
one_step_from_base()
.into_iter()
.map(|(field, shape)| (field, seed_of(shape))),
);
assert_seeds_pairwise_distinct(&seeds);
}
#[test]
fn the_description_asks_for_no_grinding() {
assert!(pow_difficulties(&base_shape().pattern::<EF>()).is_empty());
}
#[test]
fn the_two_derived_widths_come_from_the_level() {
assert_eq!(BitRingSwitch::<EF>::ABSORBED, 4);
assert_eq!(Coefficients::<EF>::DIMENSION, NUM_ROWS);
assert_eq!(base_shape().sumcheck_rounds::<EF>(), 4);
}
#[test]
fn both_sides_of_a_run_draw_the_same_stream() {
let point = point_of(NUM_VARIABLES, 3);
let rows = rows_of(5);
let mut prover_challenger = fresh_challenger();
let proved = drive_prover(
&mut prover_challenger,
base_shape(),
&point,
&rows,
None,
EF::ONE,
);
let mut verifier_challenger = fresh_challenger();
let replayed = drive_verifier(
&mut verifier_challenger,
base_shape(),
&point,
&rows,
None,
EF::ONE,
)
.expect("the honest run must replay");
assert_eq!(proved, replayed);
assert_eq!(
CanSample::<EF>::sample(&mut prover_challenger),
CanSample::<EF>::sample(&mut verifier_challenger),
);
}
#[test]
fn a_perturbed_point_coordinate_moves_every_later_draw() {
let honest = point_of(NUM_VARIABLES, 3);
let rows = rows_of(5);
let baseline = replay(&honest, &rows, EF::ONE);
for position in 0..NUM_VARIABLES {
let mut moved = honest.as_slice().to_vec();
moved[position] += EF::ONE;
assert_ne!(
baseline,
replay(&Point::new(moved), &rows, EF::ONE),
"tampering with coordinate {position} left the stream where it was",
);
}
}
#[test]
fn a_perturbed_tensor_row_moves_every_later_draw() {
let point = point_of(NUM_VARIABLES, 3);
let honest = rows_of(5);
let baseline = replay(&point, &honest, EF::ONE);
for position in [0, NUM_ROWS / 2, NUM_ROWS - 1] {
let mut tampered = honest.clone();
tampered[position] += EF::ONE;
assert_ne!(
baseline,
replay(&point, &tampered, EF::ONE),
"tampering with row {position} left the stream where it was",
);
}
}
#[test]
fn the_batching_draw_is_the_first_challenge_and_answers_to_the_rows() {
let pattern = base_shape().pattern::<EF>();
let labels: Vec<&str> = pattern
.interactions()
.iter()
.map(Interaction::label)
.collect();
let point_at = labels
.iter()
.position(|&label| label == EVALUATION_POINT)
.expect("the description binds the evaluation point");
let rows_at = labels
.iter()
.position(|&label| label == TENSOR_ROWS)
.expect("the description binds the tensor rows");
let first_challenge = pattern
.interactions()
.iter()
.position(|interaction| interaction.kind() == Kind::Challenge)
.expect("the description draws a challenge");
assert_eq!(labels[first_challenge], BATCHING_POINT);
assert!(
point_at < first_challenge,
"the evaluation point is bound at step {point_at}, the first challenge is step \
{first_challenge}",
);
assert!(
rows_at < first_challenge,
"the rows are bound at step {rows_at}, the first challenge is step \
{first_challenge}",
);
let point = point_of(NUM_VARIABLES, 3);
let honest = rows_of(5);
let (baseline, _) = replay(&point, &honest, EF::ONE);
for position in 0..NUM_ROWS {
let mut tampered = honest.clone();
tampered[position] += EF::ONE;
let (moved, _) = replay(&point, &tampered, EF::ONE);
assert_ne!(
baseline.0, moved.0,
"row {position} left the batching point where it was",
);
}
}
#[test]
fn a_perturbed_surviving_claim_moves_the_state_the_sponge_is_handed_back_in() {
let point = point_of(NUM_VARIABLES, 3);
let rows = rows_of(5);
assert_ne!(
replay(&point, &rows, EF::ONE),
replay(&point, &rows, EF::TWO),
);
}
#[test]
fn a_tensor_element_of_the_wrong_width_is_rejected() {
let mut challenger = fresh_challenger();
let mut transcript =
BitRingSwitchVerifierTranscript::<Chal, EF>::new(&mut challenger, base_shape());
let err = transcript
.statement(&point_of(NUM_VARIABLES, 3), &[EF::ONE], None)
.expect_err("a tensor element outside the described width must error");
assert_eq!(
err,
TranscriptWidth::TensorRows {
expected: NUM_ROWS,
actual: 1,
},
);
drop(transcript);
}
#[test]
fn an_evaluation_point_of_the_wrong_width_is_rejected() {
let mut challenger = fresh_challenger();
let mut transcript =
BitRingSwitchVerifierTranscript::<Chal, EF>::new(&mut challenger, base_shape());
let err = transcript
.statement(&point_of(3, 3), &rows_of(5), None)
.expect_err("a point outside the described width must error");
assert_eq!(
err,
TranscriptWidth::Point {
expected: NUM_VARIABLES,
actual: 3,
},
);
drop(transcript);
}
#[test]
fn a_successor_element_list_outside_the_description_is_rejected() {
let point = point_of(NUM_VARIABLES, 3);
let rows = rows_of(5);
let (carry, last) = (rows_of(7), rows_of(11));
let cases: [(BitRingSwitchShape, SuccessorRows<'_>, TranscriptWidth); 3] = [
(
base_shape(),
Some((&carry, &last)),
TranscriptWidth::SuccessorElements {
expected: 0,
actual: 2,
},
),
(
successor_shape(),
None,
TranscriptWidth::SuccessorElements {
expected: 2,
actual: 0,
},
),
(
successor_shape(),
Some((&[EF::ONE], &last)),
TranscriptWidth::TensorRows {
expected: NUM_ROWS,
actual: 1,
},
),
];
for (shape, successor, expected) in cases {
let mut challenger = fresh_challenger();
let mut transcript =
BitRingSwitchVerifierTranscript::<Chal, EF>::new(&mut challenger, shape);
let err = transcript
.statement(&point, &rows, successor)
.expect_err("a successor list outside the description must error");
assert_eq!(err, expected);
drop(transcript);
}
}
#[test]
fn both_sides_of_a_successor_run_draw_the_same_stream() {
let point = point_of(NUM_VARIABLES, 3);
let rows = rows_of(5);
let (carry, last) = (rows_of(7), rows_of(11));
let successor = Some((carry.as_slice(), last.as_slice()));
let mut prover_challenger = fresh_challenger();
let proved = drive_prover(
&mut prover_challenger,
successor_shape(),
&point,
&rows,
successor,
EF::ONE,
);
let mut verifier_challenger = fresh_challenger();
let replayed = drive_verifier(
&mut verifier_challenger,
successor_shape(),
&point,
&rows,
successor,
EF::ONE,
)
.expect("the honest run must replay");
assert_eq!(proved, replayed);
assert!(
proved.1.is_some(),
"a successor shape draws the element batching"
);
assert_eq!(
CanSample::<EF>::sample(&mut prover_challenger),
CanSample::<EF>::sample(&mut verifier_challenger),
);
}
#[test]
fn the_batching_draws_answer_to_the_successor_rows() {
let pattern = successor_shape().pattern::<EF>();
let labels: Vec<&str> = pattern
.interactions()
.iter()
.map(Interaction::label)
.collect();
let position = |wanted: &str| {
labels
.iter()
.position(|&label| label == wanted)
.expect("the successor description holds every step")
};
let first_challenge = pattern
.interactions()
.iter()
.position(|interaction| interaction.kind() == Kind::Challenge)
.expect("the description draws a challenge");
assert_eq!(labels[first_challenge], BATCHING_POINT);
assert!(position(TENSOR_ROWS) < position(CARRY_ROWS));
assert!(position(CARRY_ROWS) < position(LAST_ROWS));
assert!(
position(LAST_ROWS) < first_challenge,
"the last rows are bound at step {}, the first challenge is step {first_challenge}",
position(LAST_ROWS),
);
assert_eq!(position(TENSOR_BATCHING), first_challenge + 1);
let point = point_of(NUM_VARIABLES, 3);
let rows = rows_of(5);
let (carry, last) = (rows_of(7), rows_of(11));
let replay_with = |carry: &[EF], last: &[EF]| {
replay_in(
successor_shape(),
&point,
&rows,
Some((carry, last)),
EF::ONE,
)
.0
};
let baseline = replay_with(&carry, &last);
let mut bumped_carry = carry.clone();
bumped_carry[NUM_ROWS / 2] += EF::ONE;
let mut bumped_last = last.clone();
bumped_last[NUM_ROWS - 1] += EF::ONE;
for (element, moved) in [
("carry", replay_with(&bumped_carry, &last)),
("last", replay_with(&carry, &bumped_last)),
] {
assert_ne!(
baseline.0, moved.0,
"a {element} row left the batching point where it was",
);
assert_ne!(
baseline.1, moved.1,
"a {element} row left the element batching where it was",
);
}
}
#[test]
#[should_panic(expected = "the evaluation point must name at least the 4 absorbed")]
fn a_point_shorter_than_the_absorbed_tail_has_no_round_count() {
let _ = BitRingSwitchShape::new(3).sumcheck_rounds::<EF>();
}
#[test]
fn every_batch_of_claims_seeds_its_own_stream() {
let plain = base_shape();
let successor = successor_shape();
let wider = BitRingSwitchShape::with_successor_rows(NUM_VARIABLES, SUCCESSOR_ROWS + 1);
let batch = |claims: &[BitRingSwitchShape]| {
seed_digest(
&BitRingSwitchClaimsShape {
claims: claims.to_vec(),
}
.domain_separator::<EF>(),
)
};
let seeds = [
("two plain", batch(&[plain, plain])),
("three plain", batch(&[plain, plain, plain])),
("plain, successor", batch(&[plain, successor])),
("successor, plain", batch(&[successor, plain])),
("plain, wider successor", batch(&[plain, wider])),
("one-claim run", seed_of(plain)),
];
assert_seeds_pairwise_distinct(&seeds);
assert!(
pow_difficulties(
&BitRingSwitchClaimsShape {
claims: vec![plain, successor]
}
.pattern::<EF>()
)
.is_empty()
);
}
#[test]
fn a_batch_statement_of_the_wrong_count_is_refused_before_absorbing() {
let shape = BitRingSwitchClaimsShape {
claims: vec![base_shape(), base_shape()],
};
let point = point_of(NUM_VARIABLES, 1);
let rows = rows_of(1);
let claim = ClaimStatement {
point: &point,
rows: &rows,
successor: None,
};
let mut challenger = fresh_challenger();
let mut transcript =
BitRingSwitchClaimsVerifierTranscript::<Chal, EF>::new(&mut challenger, shape);
assert_eq!(
transcript.statement(&[claim]).unwrap_err(),
TranscriptWidth::Claims {
expected: 2,
actual: 1
}
);
}
}