use alloc::vec;
use alloc::vec::Vec;
use core::marker::PhantomData;
use p3_challenger::fs::{
DomainSeparator, FieldToFieldCodec, FieldUnit, Hierarchy, Interaction, InteractionPattern,
Kind, Length, ProverState, TranscriptField, VerifierState,
};
use p3_challenger::{CanObserve, CanSample};
use p3_field::ExtensionField;
use p3_multilinear_util::point::Point;
use crate::error::SumcheckError;
use crate::layout::LayoutStrategy;
use crate::strategy::VariableOrder;
use crate::table::{OpeningEvals, TableShape};
const VERSION: u8 = 1;
pub(crate) const COMMITMENT_NAME: &[u8] = b"p3-sumcheck-layout-commitment";
pub(crate) const OPENING_NAME: &[u8] = b"p3-sumcheck-layout-opening";
pub(crate) const VIRTUAL_NAME: &[u8] = b"p3-sumcheck-layout-ood";
pub(crate) const BATCHING_NAME: &[u8] = b"p3-sumcheck-layout-batching";
const COMMITMENT: &str = "commitment";
const OPENING_POINT: &str = "opening_point";
const CURRENT_EVALS: &str = "current_evals";
const NEXT_EVALS: &str = "next_evals";
const VIRTUAL_POINT: &str = "virtual_point";
const VIRTUAL_EVAL: &str = "virtual_eval";
const BATCHING: &str = "batching";
type Alphabet<F> = FieldUnit<F>;
#[must_use]
pub fn commitment_domain_separator<F: TranscriptField>() -> DomainSeparator<Alphabet<F>> {
let pattern = InteractionPattern::new(vec![Interaction::opaque(
Hierarchy::Atomic,
Kind::Message,
COMMITMENT,
Length::Scalar,
)])
.expect("a single leaf step is always well formed");
DomainSeparator::new(VERSION, COMMITMENT_NAME, pattern)
}
pub fn observe_commitment<F, C, Com>(challenger: &mut C, root: Com)
where
F: TranscriptField,
C: CanObserve<F> + CanObserve<Com>,
Com: Clone,
{
let separator = commitment_domain_separator::<F>();
let mut state = ProverState::new(challenger, &separator);
state.observe_opaque(COMMITMENT, root);
assert!(
state.finalize().is_empty(),
"a commitment absorption carries no wire bytes",
);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct LayoutBinding {
pub(crate) num_variables: usize,
pub(crate) strategy: LayoutStrategy,
pub(crate) tables: Vec<TableShape>,
}
impl LayoutBinding {
pub(crate) const fn new(
num_variables: usize,
strategy: LayoutStrategy,
tables: Vec<TableShape>,
) -> Self {
Self {
num_variables,
strategy,
tables,
}
}
fn bind<F>(&self, separator: &mut DomainSeparator<Alphabet<F>>)
where
F: TranscriptField,
{
let Self {
num_variables: _,
strategy:
LayoutStrategy {
reverse_selectors: _,
variable_order: _,
},
tables: _,
} = self;
separator.instance(&(self.num_variables as u64).to_be_bytes());
separator.instance(&[u8::from(self.strategy.reverse_selectors)]);
separator.instance(&[match self.strategy.variable_order {
VariableOrder::Prefix => 0,
VariableOrder::Suffix => 1,
}]);
separator.instance(&(self.tables.len() as u64).to_be_bytes());
for shape in &self.tables {
separator
.instance(&(shape.num_variables() as u64).to_be_bytes())
.instance(&(shape.width() as u64).to_be_bytes());
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PointSource {
Drawn,
Given,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct OpeningShape {
pub(crate) binding: LayoutBinding,
pub(crate) table_index: usize,
pub(crate) table_variables: usize,
pub(crate) current: Vec<usize>,
pub(crate) next: Vec<usize>,
pub(crate) point: PointSource,
}
impl OpeningShape {
pub(crate) fn new(
binding: LayoutBinding,
table_index: usize,
table_variables: usize,
current: &[usize],
next: &[usize],
point: PointSource,
) -> Self {
Self {
binding,
table_index,
table_variables,
current: current.to_vec(),
next: next.to_vec(),
point,
}
}
pub(crate) const fn num_current(&self) -> usize {
self.current.len()
}
pub(crate) const fn num_next(&self) -> usize {
self.next.len()
}
pub(crate) fn pattern<F, EF>(&self) -> InteractionPattern
where
F: TranscriptField,
EF: ExtensionField<F>,
{
let mut steps = Vec::with_capacity(3);
if self.point == PointSource::Drawn {
steps.push(Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Challenge,
OPENING_POINT,
Length::Scalar,
));
}
if self.num_current() > 0 {
steps.push(Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Message,
CURRENT_EVALS,
Length::Fixed(self.num_current()),
));
}
if self.num_next() > 0 {
steps.push(Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Message,
NEXT_EVALS,
Length::Fixed(self.num_next()),
));
}
InteractionPattern::new(steps).expect("a flat sequence of leaf steps is always well formed")
}
pub(crate) fn domain_separator<F, EF>(&self) -> DomainSeparator<Alphabet<F>>
where
F: TranscriptField,
EF: ExtensionField<F>,
{
let Self {
binding: _,
table_index: _,
table_variables: _,
current: _,
next: _,
point: _,
} = self;
let mut separator = DomainSeparator::new(VERSION, OPENING_NAME, self.pattern::<F, EF>());
self.binding.bind::<F>(&mut separator);
separator.instance(&(self.table_index as u64).to_be_bytes());
separator.instance(&(self.table_variables as u64).to_be_bytes());
separator.instance(&(self.current.len() as u64).to_be_bytes());
for &column in &self.current {
separator.instance(&(column as u64).to_be_bytes());
}
separator.instance(&(self.next.len() as u64).to_be_bytes());
for &column in &self.next {
separator.instance(&(column as u64).to_be_bytes());
}
separator
}
}
pub(crate) struct OpeningProverTranscript<'a, C, F: TranscriptField, EF> {
state: ProverState<&'a mut C, Alphabet<F>>,
shape: OpeningShape,
_ef: PhantomData<EF>,
}
impl<'a, C, F, EF> OpeningProverTranscript<'a, C, F, EF>
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F>,
{
pub(crate) fn new(challenger: &'a mut C, shape: OpeningShape) -> Self {
let separator = shape.domain_separator::<F, EF>();
Self {
state: ProverState::new(challenger, &separator),
shape,
_ef: PhantomData,
}
}
pub(crate) fn point(&mut self) -> Point<EF> {
assert_eq!(
self.shape.point,
PointSource::Drawn,
"a batch whose point the caller fixes draws no point of its own",
);
let challenge = self
.state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(OPENING_POINT)
.into_inner();
Point::expand_from_univariate(challenge, self.shape.table_variables)
}
pub(crate) fn evaluations(&mut self, evals: &OpeningEvals<EF>) {
if self.shape.num_current() > 0 {
self.state
.observe_extensions::<F, EF, FieldToFieldCodec<F>>(CURRENT_EVALS, evals.current());
}
if self.shape.num_next() > 0 {
self.state
.observe_extensions::<F, EF, FieldToFieldCodec<F>>(NEXT_EVALS, evals.next());
}
}
pub(crate) fn finish(self) {
assert!(
self.state.finalize().is_empty(),
"the layout carries every claimed evaluation in its own proof",
);
}
}
pub(crate) struct OpeningVerifierTranscript<'a, C, F: TranscriptField, EF> {
state: VerifierState<'static, &'a mut C, Alphabet<F>>,
shape: OpeningShape,
_ef: PhantomData<EF>,
}
impl<'a, C, F, EF> OpeningVerifierTranscript<'a, C, F, EF>
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F>,
{
pub(crate) fn new(challenger: &'a mut C, shape: OpeningShape) -> Self {
let separator = shape.domain_separator::<F, EF>();
Self {
state: VerifierState::new(challenger, &separator, &[]),
shape,
_ef: PhantomData,
}
}
pub(crate) fn point(&mut self) -> Point<EF> {
assert_eq!(
self.shape.point,
PointSource::Drawn,
"a batch whose point the caller fixes draws no point of its own",
);
let challenge = self
.state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(OPENING_POINT)
.into_inner();
Point::expand_from_univariate(challenge, self.shape.table_variables)
}
pub(crate) fn evaluations(&mut self, evals: &OpeningEvals<EF>) -> Result<(), SumcheckError> {
if evals.current().len() != self.shape.num_current()
|| evals.next().len() != self.shape.num_next()
{
self.state.abort();
return Err(SumcheckError::OpeningShapeMismatch {
table_idx: self.shape.table_index,
expected_current: self.shape.num_current(),
expected_next: self.shape.num_next(),
actual_current: evals.current().len(),
actual_next: evals.next().len(),
});
}
if self.shape.num_current() > 0 {
self.state
.observe_extensions::<F, EF, FieldToFieldCodec<F>>(CURRENT_EVALS, evals.current())
.expect("the direct group was counted against its step first");
}
if self.shape.num_next() > 0 {
self.state
.observe_extensions::<F, EF, FieldToFieldCodec<F>>(NEXT_EVALS, evals.next())
.expect("the successor group was counted against its step first");
}
Ok(())
}
pub(crate) fn finish(self) {
self.state
.finalize()
.expect("the layout reads an empty wire");
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct VirtualShape {
pub(crate) binding: LayoutBinding,
}
impl VirtualShape {
pub(crate) const fn new(binding: LayoutBinding) -> Self {
Self { binding }
}
pub(crate) fn pattern<F, EF>(&self) -> InteractionPattern
where
F: TranscriptField,
EF: ExtensionField<F>,
{
InteractionPattern::new(vec![
Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Challenge,
VIRTUAL_POINT,
Length::Scalar,
),
Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Message,
VIRTUAL_EVAL,
Length::Scalar,
),
])
.expect("a flat sequence of leaf steps is always well formed")
}
pub(crate) fn domain_separator<F, EF>(&self) -> DomainSeparator<Alphabet<F>>
where
F: TranscriptField,
EF: ExtensionField<F>,
{
let Self { binding: _ } = self;
let mut separator = DomainSeparator::new(VERSION, VIRTUAL_NAME, self.pattern::<F, EF>());
self.binding.bind::<F>(&mut separator);
separator
}
}
pub(crate) struct VirtualProverTranscript<'a, C, F: TranscriptField, EF> {
state: ProverState<&'a mut C, Alphabet<F>>,
shape: VirtualShape,
_ef: PhantomData<EF>,
}
impl<'a, C, F, EF> VirtualProverTranscript<'a, C, F, EF>
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F>,
{
pub(crate) fn new(challenger: &'a mut C, shape: VirtualShape) -> Self {
let separator = shape.domain_separator::<F, EF>();
Self {
state: ProverState::new(challenger, &separator),
shape,
_ef: PhantomData,
}
}
pub(crate) fn point(&mut self) -> Point<EF> {
let challenge = self
.state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(VIRTUAL_POINT)
.into_inner();
Point::expand_from_univariate(challenge, self.shape.binding.num_variables)
}
pub(crate) fn evaluation(&mut self, eval: EF) {
self.state
.observe_extension::<F, EF, FieldToFieldCodec<F>>(VIRTUAL_EVAL, &eval);
}
pub(crate) fn finish(self) {
assert!(
self.state.finalize().is_empty(),
"the layout carries every claimed evaluation in its own proof",
);
}
}
pub(crate) struct VirtualVerifierTranscript<'a, C, F: TranscriptField, EF> {
state: VerifierState<'static, &'a mut C, Alphabet<F>>,
shape: VirtualShape,
_ef: PhantomData<EF>,
}
impl<'a, C, F, EF> VirtualVerifierTranscript<'a, C, F, EF>
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F>,
{
pub(crate) fn new(challenger: &'a mut C, shape: VirtualShape) -> Self {
let separator = shape.domain_separator::<F, EF>();
Self {
state: VerifierState::new(challenger, &separator, &[]),
shape,
_ef: PhantomData,
}
}
pub(crate) fn point(&mut self) -> Point<EF> {
let challenge = self
.state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(VIRTUAL_POINT)
.into_inner();
Point::expand_from_univariate(challenge, self.shape.binding.num_variables)
}
pub(crate) fn evaluation(&mut self, eval: EF) {
self.state
.observe_extension::<F, EF, FieldToFieldCodec<F>>(VIRTUAL_EVAL, &eval);
}
pub(crate) fn finish(self) {
self.state
.finalize()
.expect("the layout reads an empty wire");
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct BatchingShape {
pub(crate) binding: LayoutBinding,
pub(crate) num_claims: usize,
pub(crate) num_virtual_claims: usize,
}
impl BatchingShape {
pub(crate) const fn new(
binding: LayoutBinding,
num_claims: usize,
num_virtual_claims: usize,
) -> Self {
Self {
binding,
num_claims,
num_virtual_claims,
}
}
pub(crate) fn pattern<F, EF>(&self) -> InteractionPattern
where
F: TranscriptField,
EF: ExtensionField<F>,
{
InteractionPattern::new(vec![Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Challenge,
BATCHING,
Length::Scalar,
)])
.expect("a single leaf step is always well formed")
}
pub(crate) fn domain_separator<F, EF>(&self) -> DomainSeparator<Alphabet<F>>
where
F: TranscriptField,
EF: ExtensionField<F>,
{
let Self {
binding: _,
num_claims: _,
num_virtual_claims: _,
} = self;
let mut separator = DomainSeparator::new(VERSION, BATCHING_NAME, self.pattern::<F, EF>());
self.binding.bind::<F>(&mut separator);
separator.instance(&(self.num_claims as u64).to_be_bytes());
separator.instance(&(self.num_virtual_claims as u64).to_be_bytes());
separator
}
}
pub(crate) fn prover_batching_challenge<C, F, EF>(challenger: &mut C, shape: &BatchingShape) -> EF
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F>,
{
let separator = shape.domain_separator::<F, EF>();
let mut state = ProverState::new(challenger, &separator);
let alpha = state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(BATCHING)
.into_inner();
assert!(
state.finalize().is_empty(),
"drawing a challenge writes nothing to the proof",
);
alpha
}
pub(crate) fn verifier_batching_challenge<C, F, EF>(challenger: &mut C, shape: &BatchingShape) -> EF
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F>,
{
let separator = shape.domain_separator::<F, EF>();
let mut state = VerifierState::new(challenger, &separator, &[]);
let alpha = state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(BATCHING)
.into_inner();
state
.finalize()
.expect("drawing a challenge reads an empty wire");
alpha
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use alloc::vec::Vec;
use p3_baby_bear::{BabyBear, Poseidon2BabyBear};
use p3_challenger::DuplexChallenger;
use p3_challenger::testing::{SeedDigest, assert_seeds_pairwise_distinct, seed_digest};
use p3_field::PrimeCharacteristicRing;
use p3_field::extension::BinomialExtensionField;
use rand::SeedableRng;
use rand::rngs::SmallRng;
use super::*;
use crate::table::OpeningBatch;
type F = BabyBear;
type EF = BinomialExtensionField<F, 4>;
type Perm = Poseidon2BabyBear<16>;
type Ch = DuplexChallenger<F, Perm, 16, 8>;
fn fresh_challenger() -> Ch {
let mut rng = SmallRng::seed_from_u64(0xDEADBEEF);
Ch::new(Perm::new_from_rng_128(&mut rng))
}
fn base_tables() -> Vec<TableShape> {
vec![TableShape::new(9, 2), TableShape::new(9, 2)]
}
fn base_binding() -> LayoutBinding {
LayoutBinding::new(
11,
LayoutStrategy::new(true, VariableOrder::Prefix),
base_tables(),
)
}
fn base_opening() -> OpeningShape {
OpeningShape::new(base_binding(), 1, 9, &[0, 1, 2], &[3], PointSource::Drawn)
}
fn one_step_from_base_binding() -> Vec<(&'static str, LayoutBinding)> {
let LayoutBinding {
num_variables: _,
strategy:
LayoutStrategy {
reverse_selectors: _,
variable_order: _,
},
tables: _,
} = base_binding();
let mut wider = base_binding();
wider.num_variables = 12;
let mut selectors_first = base_binding();
selectors_first.strategy.reverse_selectors = false;
let mut suffix_first = base_binding();
suffix_first.strategy.variable_order = VariableOrder::Suffix;
let mut extra_table = base_binding();
extra_table.tables.push(TableShape::new(8, 1));
let mut regrouped = base_binding();
regrouped.tables = vec![TableShape::new(9, 3), TableShape::new(9, 1)];
vec![
("stacked arity", wider),
("selector placement", selectors_first),
("variable order", suffix_first),
("table count", extra_table),
("table widths", regrouped),
]
}
fn base_evals() -> OpeningEvals<EF> {
OpeningBatch::new(vec![EF::ONE, EF::TWO, EF::from_u8(3)], vec![EF::from_u8(4)])
}
fn labelled_opening(name: &str, shape: &OpeningShape) -> (String, SeedDigest) {
(
String::from(name),
seed_digest(&shape.domain_separator::<F, EF>()),
)
}
#[test]
fn every_knob_of_a_recorded_batch_reaches_the_seed() {
let OpeningShape {
binding: _,
table_index: _,
table_variables: _,
current: _,
next: _,
point: _,
} = base_opening();
let mut seeds = vec![labelled_opening("baseline", &base_opening())];
for (name, binding) in one_step_from_base_binding() {
let mut moved = base_opening();
moved.binding = binding;
seeds.push(labelled_opening(name, &moved));
}
let mut other_table = base_opening();
other_table.table_index = 0;
seeds.push(labelled_opening("table index", &other_table));
let mut narrower = base_opening();
narrower.table_variables = 8;
seeds.push(labelled_opening("table arity", &narrower));
let mut fewer_direct = base_opening();
fewer_direct.current = vec![0, 1];
seeds.push(labelled_opening("direct count", &fewer_direct));
let mut other_direct = base_opening();
other_direct.current = vec![0, 1, 3];
seeds.push(labelled_opening("direct columns", &other_direct));
let mut reordered_direct = base_opening();
reordered_direct.current = vec![2, 1, 0];
seeds.push(labelled_opening("direct order", &reordered_direct));
let mut more_next = base_opening();
more_next.next = vec![3, 4];
seeds.push(labelled_opening("successor count", &more_next));
let mut other_next = base_opening();
other_next.next = vec![4];
seeds.push(labelled_opening("successor columns", &other_next));
let mut given = base_opening();
given.point = PointSource::Given;
seeds.push(labelled_opening("point source", &given));
assert_seeds_pairwise_distinct(&seeds);
}
#[test]
fn every_knob_of_an_out_of_domain_claim_reaches_the_seed() {
let digest = |binding: LayoutBinding| {
seed_digest(&VirtualShape::new(binding).domain_separator::<F, EF>())
};
let mut seeds = vec![(String::from("baseline"), digest(base_binding()))];
for (name, binding) in one_step_from_base_binding() {
seeds.push((String::from(name), digest(binding)));
}
assert_seeds_pairwise_distinct(&seeds);
}
#[test]
fn every_knob_of_the_batching_challenge_reaches_the_seed() {
let BatchingShape {
binding: _,
num_claims: _,
num_virtual_claims: _,
} = BatchingShape::new(base_binding(), 4, 2);
let digest = |binding: LayoutBinding, claims: usize, virtuals: usize| {
seed_digest(&BatchingShape::new(binding, claims, virtuals).domain_separator::<F, EF>())
};
let mut seeds = vec![(String::from("baseline"), digest(base_binding(), 4, 2))];
for (name, binding) in one_step_from_base_binding() {
seeds.push((String::from(name), digest(binding, 4, 2)));
}
seeds.push((String::from("concrete count"), digest(base_binding(), 5, 2)));
seeds.push((
String::from("out-of-domain count"),
digest(base_binding(), 4, 3),
));
assert_seeds_pairwise_distinct(&seeds);
}
#[test]
fn the_three_components_of_one_layout_stay_on_distinct_seeds() {
let binding = base_binding();
let single_column =
OpeningShape::new(binding.clone(), 0, 11, &[0], &[], PointSource::Drawn);
let seeds = [
(
"one-column batch",
seed_digest(&single_column.domain_separator::<F, EF>()),
),
(
"out-of-domain claim",
seed_digest(&VirtualShape::new(binding.clone()).domain_separator::<F, EF>()),
),
(
"batching challenge",
seed_digest(&BatchingShape::new(binding, 1, 1).domain_separator::<F, EF>()),
),
];
assert_seeds_pairwise_distinct(&seeds);
}
fn replay_opening(shape: OpeningShape, evals: &OpeningEvals<EF>) -> (Point<EF>, F) {
let mut challenger = fresh_challenger();
let mut transcript = OpeningVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
let point = transcript.point();
transcript.evaluations(evals).unwrap();
transcript.finish();
(point, CanSample::<F>::sample(&mut challenger))
}
#[test]
fn both_sides_of_a_recorded_batch_land_on_one_point_and_one_state() {
let shape = base_opening();
let evals = base_evals();
let mut prover_challenger = fresh_challenger();
let mut prover =
OpeningProverTranscript::<Ch, F, EF>::new(&mut prover_challenger, shape.clone());
let prover_point = prover.point();
prover.evaluations(&evals);
prover.finish();
let prover_state = CanSample::<F>::sample(&mut prover_challenger);
let (verifier_point, verifier_state) = replay_opening(shape, &evals);
assert_eq!(prover_point, verifier_point);
assert_eq!(prover_state, verifier_state);
}
#[test]
fn a_perturbed_direct_evaluation_moves_the_state_the_caller_continues_from() {
let shape = base_opening();
let honest = base_evals();
let mut tampered_current = honest.current().to_vec();
tampered_current[1] = EF::from_u8(5);
let tampered = OpeningBatch::new(tampered_current, honest.next().to_vec());
assert_ne!(
replay_opening(shape.clone(), &honest).1,
replay_opening(shape, &tampered).1
);
}
#[test]
fn a_perturbed_successor_evaluation_moves_the_state_the_caller_continues_from() {
let shape = base_opening();
let honest = base_evals();
let tampered = OpeningBatch::new(honest.current().to_vec(), vec![EF::from_u8(5)]);
assert_ne!(
replay_opening(shape.clone(), &honest).1,
replay_opening(shape, &tampered).1
);
}
#[test]
fn a_recorded_batch_of_the_wrong_width_is_rejected() {
let mut challenger = fresh_challenger();
let mut transcript =
OpeningVerifierTranscript::<Ch, F, EF>::new(&mut challenger, base_opening());
let _point = transcript.point();
let short = OpeningBatch::new(vec![EF::ONE, EF::TWO], vec![EF::from_u8(4)]);
let err = transcript
.evaluations(&short)
.expect_err("a batch outside the described widths must error");
assert_eq!(
err,
SumcheckError::OpeningShapeMismatch {
table_idx: 1,
expected_current: 3,
expected_next: 1,
actual_current: 2,
actual_next: 1,
}
);
drop(transcript);
}
#[test]
fn a_batch_with_no_successor_group_describes_one_step_fewer() {
let binding = base_binding();
let direct_only = OpeningShape::new(binding, 1, 9, &[0, 1, 2], &[], PointSource::Drawn);
let seeds = [
(
"direct only",
seed_digest(&direct_only.domain_separator::<F, EF>()),
),
(
"direct and successor",
seed_digest(&base_opening().domain_separator::<F, EF>()),
),
];
assert_seeds_pairwise_distinct(&seeds);
let evals = OpeningBatch::new(vec![EF::ONE, EF::TWO, EF::from_u8(3)], Vec::new());
let mut challenger = fresh_challenger();
let mut transcript =
OpeningVerifierTranscript::<Ch, F, EF>::new(&mut challenger, direct_only);
let _point = transcript.point();
transcript.evaluations(&evals).unwrap();
transcript.finish();
}
#[test]
fn a_caller_fixed_point_draws_nothing_of_its_own() {
let shape = OpeningShape::new(base_binding(), 1, 9, &[0, 1, 2], &[3], PointSource::Given);
let evals = base_evals();
let mut prover_challenger = fresh_challenger();
let mut prover =
OpeningProverTranscript::<Ch, F, EF>::new(&mut prover_challenger, shape.clone());
prover.evaluations(&evals);
prover.finish();
let mut verifier_challenger = fresh_challenger();
let mut verifier =
OpeningVerifierTranscript::<Ch, F, EF>::new(&mut verifier_challenger, shape);
verifier.evaluations(&evals).unwrap();
verifier.finish();
assert_eq!(
CanSample::<F>::sample(&mut prover_challenger),
CanSample::<F>::sample(&mut verifier_challenger),
);
}
#[test]
#[should_panic(expected = "draws no point of its own")]
fn drawing_a_point_a_batch_does_not_describe_is_a_caller_bug() {
let shape = OpeningShape::new(base_binding(), 1, 9, &[0, 1, 2], &[3], PointSource::Given);
let mut challenger = fresh_challenger();
let mut transcript = OpeningProverTranscript::<Ch, F, EF>::new(&mut challenger, shape);
let _ = transcript.point();
}
#[test]
fn both_sides_of_an_out_of_domain_claim_land_on_one_point_and_one_state() {
let shape = VirtualShape::new(base_binding());
let mut prover_challenger = fresh_challenger();
let mut prover =
VirtualProverTranscript::<Ch, F, EF>::new(&mut prover_challenger, shape.clone());
let prover_point = prover.point();
prover.evaluation(EF::from_u8(7));
prover.finish();
let mut verifier_challenger = fresh_challenger();
let mut verifier =
VirtualVerifierTranscript::<Ch, F, EF>::new(&mut verifier_challenger, shape);
let verifier_point = verifier.point();
verifier.evaluation(EF::from_u8(7));
verifier.finish();
assert_eq!(prover_point.num_variables(), 11);
assert_eq!(prover_point, verifier_point);
assert_eq!(
CanSample::<F>::sample(&mut prover_challenger),
CanSample::<F>::sample(&mut verifier_challenger),
);
}
#[test]
fn a_perturbed_out_of_domain_evaluation_moves_the_state_the_caller_continues_from() {
let shape = VirtualShape::new(base_binding());
let replay = |eval: EF| {
let mut challenger = fresh_challenger();
let mut transcript =
VirtualVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape.clone());
let _point = transcript.point();
transcript.evaluation(eval);
transcript.finish();
CanSample::<F>::sample(&mut challenger)
};
assert_ne!(replay(EF::from_u8(7)), replay(EF::from_u8(8)));
}
#[test]
fn both_sides_of_the_batching_challenge_draw_the_same_value() {
let shape = BatchingShape::new(base_binding(), 4, 2);
let mut prover_challenger = fresh_challenger();
let prover: EF = prover_batching_challenge::<Ch, F, EF>(&mut prover_challenger, &shape);
let mut verifier_challenger = fresh_challenger();
let verifier: EF =
verifier_batching_challenge::<Ch, F, EF>(&mut verifier_challenger, &shape);
assert_eq!(prover, verifier);
assert_eq!(
CanSample::<F>::sample(&mut prover_challenger),
CanSample::<F>::sample(&mut verifier_challenger),
);
}
#[test]
fn a_perturbed_commitment_moves_the_state_the_caller_continues_from() {
let bind = |root: [F; 8]| {
let mut challenger = fresh_challenger();
observe_commitment::<F, _, _>(&mut challenger, root);
CanSample::<F>::sample(&mut challenger)
};
let honest = [F::ONE; 8];
let mut tampered = honest;
tampered[0] = F::TWO;
assert_ne!(bind(honest), bind(tampered));
}
#[test]
fn the_commitment_phase_never_shares_a_seed_with_the_phases_that_follow_it() {
let seeds = [
(
"commitment",
seed_digest(&commitment_domain_separator::<F>()),
),
(
"one-column batch",
seed_digest(
&OpeningShape::new(base_binding(), 0, 11, &[0], &[], PointSource::Drawn)
.domain_separator::<F, EF>(),
),
),
(
"out-of-domain claim",
seed_digest(&VirtualShape::new(base_binding()).domain_separator::<F, EF>()),
),
(
"batching challenge",
seed_digest(&BatchingShape::new(base_binding(), 1, 1).domain_separator::<F, EF>()),
),
];
assert_seeds_pairwise_distinct(&seeds);
}
}