use alloc::vec::Vec;
use core::marker::PhantomData;
use p3_challenger::fs::{
DomainSeparator, FieldToFieldCodec, FieldUnit, Hierarchy, Interaction, InteractionPattern,
Kind, Length, ProverState, SymmetricSteps, TranscriptField, VerifierState,
};
use p3_challenger::{CanObserve, CanSample, GrindingChallenger};
use p3_field::{ExtensionField, Field};
use crate::error::SumcheckError;
const VERSION: u8 = 1;
pub(crate) const NAME: &[u8] = b"p3-sumcheck-hvzk";
const CLAIM_BATCHING: &str = "claim_batching";
const JOINT_CLAIM: &str = "joint_claim";
const MASK_COMMITMENT: &str = "mask_commitment";
const MU_TILDE: &str = "mu_tilde";
const MASK_COMBINATION: &str = "mask_combination";
const ROUND_POLY: &str = "round_poly";
const ROUND_POW: &str = "round_pow";
const ROUND_CHALLENGE: &str = "round_challenge";
const PLAIN_COEFFICIENTS: usize = 3;
type Alphabet<F> = FieldUnit<F>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ZkPrelude {
RecordedClaims,
InheritedClaim,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ZkSumcheckShape {
pub prelude: ZkPrelude,
pub num_rounds: usize,
pub ell_zk: usize,
pub pow_bits: usize,
}
impl ZkSumcheckShape {
#[must_use]
pub const fn new_batching(num_rounds: usize, ell_zk: usize, pow_bits: usize) -> Self {
Self {
prelude: ZkPrelude::RecordedClaims,
num_rounds,
ell_zk,
pow_bits,
}
}
#[must_use]
pub const fn new_inherited(num_rounds: usize, ell_zk: usize, pow_bits: usize) -> Self {
Self {
prelude: ZkPrelude::InheritedClaim,
num_rounds,
ell_zk,
pow_bits,
}
}
#[must_use]
pub const fn round_poly_len(&self) -> usize {
if self.ell_zk > PLAIN_COEFFICIENTS {
self.ell_zk
} else {
PLAIN_COEFFICIENTS
}
}
#[must_use]
pub const fn wire_len(&self) -> usize {
self.round_poly_len() - 1
}
pub fn validate<F: Field>(&self) -> Result<(), SumcheckError> {
if F::TWO == F::ZERO {
return Err(SumcheckError::EvenCharacteristic);
}
if self.ell_zk < PLAIN_COEFFICIENTS {
return Err(SumcheckError::MaskTooShort {
ell_zk: self.ell_zk,
});
}
if self.num_rounds == 0 {
return Err(SumcheckError::NoRounds);
}
Ok(())
}
#[must_use]
pub fn pattern<F, EF>(&self) -> InteractionPattern
where
F: TranscriptField,
EF: ExtensionField<F>,
{
let mut steps = Vec::with_capacity(4 + 3 * self.num_rounds);
steps.push(match self.prelude {
ZkPrelude::RecordedClaims => Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Challenge,
CLAIM_BATCHING,
Length::Scalar,
),
ZkPrelude::InheritedClaim => Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Message,
JOINT_CLAIM,
Length::Scalar,
),
});
steps.push(Interaction::opaque(
Hierarchy::Atomic,
Kind::Message,
MASK_COMMITMENT,
Length::Scalar,
));
steps.push(Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Message,
MU_TILDE,
Length::Scalar,
));
steps.push(Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Challenge,
MASK_COMBINATION,
Length::Scalar,
));
let wire_len = self.wire_len();
for _ in 0..self.num_rounds {
steps.push(Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Message,
ROUND_POLY,
Length::Fixed(wire_len),
));
if self.pow_bits > 0 {
steps.push(Interaction::algebra::<F, F>(
Hierarchy::Atomic,
Kind::Pow,
ROUND_POW,
Length::Fixed(self.pow_bits),
));
}
steps.push(Interaction::algebra::<F, EF>(
Hierarchy::Atomic,
Kind::Challenge,
ROUND_CHALLENGE,
Length::Scalar,
));
}
InteractionPattern::new(steps).expect("a flat sequence of leaf steps is always well formed")
}
#[must_use]
pub fn domain_separator<F, EF>(&self) -> DomainSeparator<Alphabet<F>>
where
F: TranscriptField,
EF: ExtensionField<F>,
{
let mut separator = DomainSeparator::new(VERSION, NAME, self.pattern::<F, EF>());
separator.instance(&(self.ell_zk as u64).to_be_bytes());
separator
}
}
pub struct ZkProverTranscript<'a, C, F: TranscriptField, EF> {
state: ProverState<&'a mut C, Alphabet<F>>,
shape: ZkSumcheckShape,
_ef: PhantomData<EF>,
}
impl<'a, C, F, EF> ZkProverTranscript<'a, C, F, EF>
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F> + GrindingChallenger<Witness = F>,
{
pub fn new(challenger: &'a mut C, shape: ZkSumcheckShape) -> Self {
let separator = shape.domain_separator::<F, EF>();
Self {
state: ProverState::new(challenger, &separator),
shape,
_ef: PhantomData,
}
}
#[must_use]
pub const fn shape(&self) -> ZkSumcheckShape {
self.shape
}
pub fn batching_challenge(&mut self) -> EF {
play_batching_challenge::<F, EF, _>(&mut self.state, self.shape.prelude)
}
pub fn bind_claim(&mut self, claim: EF) {
play_bind_claim::<F, _, _>(&mut self.state, self.shape.prelude, claim);
}
pub fn masks<Com>(&mut self, commitment: Com, mu_tilde: EF) -> EF
where
Com: Clone,
C: CanObserve<Com>,
{
play_masks::<F, EF, _, _>(&mut self.state, commitment, mu_tilde)
}
pub fn round(&mut self, wire: &[EF]) -> (EF, Option<F>) {
self.state
.observe_extensions::<F, EF, FieldToFieldCodec<F>>(ROUND_POLY, wire);
let witness = (self.shape.pow_bits > 0)
.then(|| self.state.observe_pow(ROUND_POW, self.shape.pow_bits));
let challenge = self
.state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(ROUND_CHALLENGE)
.into_inner();
(challenge, witness)
}
pub fn finish(self) {
assert!(
self.state.finalize().is_empty(),
"the hiding sumcheck carries every value in its own proof",
);
}
}
pub struct ZkVerifierTranscript<'a, C, F: TranscriptField, EF> {
state: VerifierState<'static, &'a mut C, Alphabet<F>>,
shape: ZkSumcheckShape,
round: usize,
_ef: PhantomData<EF>,
}
impl<'a, C, F, EF> ZkVerifierTranscript<'a, C, F, EF>
where
F: TranscriptField,
EF: ExtensionField<F>,
C: CanObserve<F> + CanSample<F> + GrindingChallenger<Witness = F>,
{
pub fn new(challenger: &'a mut C, shape: ZkSumcheckShape) -> Self {
let separator = shape.domain_separator::<F, EF>();
Self {
state: VerifierState::new(challenger, &separator, &[]),
shape,
round: 0,
_ef: PhantomData,
}
}
#[must_use]
pub const fn shape(&self) -> ZkSumcheckShape {
self.shape
}
pub fn batching_challenge(&mut self) -> EF {
play_batching_challenge::<F, EF, _>(&mut self.state, self.shape.prelude)
}
pub fn bind_claim(&mut self, claim: EF) {
play_bind_claim::<F, _, _>(&mut self.state, self.shape.prelude, claim);
}
pub fn masks<Com>(&mut self, commitment: Com, mu_tilde: EF) -> EF
where
Com: Clone,
C: CanObserve<Com>,
{
play_masks::<F, EF, _, _>(&mut self.state, commitment, mu_tilde)
}
pub fn round(&mut self, wire: &[EF], witness: Option<F>) -> Result<EF, SumcheckError> {
let round = self.round;
self.round += 1;
self.state
.observe_extensions::<F, EF, FieldToFieldCodec<F>>(ROUND_POLY, wire)
.map_err(|_| SumcheckError::WireSizeMismatch {
round,
expected: self.shape.wire_len(),
actual: wire.len(),
})?;
if self.shape.pow_bits > 0 {
let Some(witness) = witness else {
self.state.abort();
return Err(SumcheckError::MissingPowWitness {
round,
difficulty: self.shape.pow_bits,
});
};
self.state
.observe_pow(ROUND_POW, self.shape.pow_bits, witness)
.map_err(|_| SumcheckError::InvalidPowWitness {
round,
difficulty: self.shape.pow_bits,
})?;
}
Ok(self
.state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(ROUND_CHALLENGE)
.into_inner())
}
pub fn abort(&mut self) {
self.state.abort();
}
pub fn finish(self) {
self.state
.finalize()
.expect("the hiding sumcheck reads an empty wire");
}
}
fn play_batching_challenge<F, EF, S>(state: &mut S, prelude: ZkPrelude) -> EF
where
F: TranscriptField,
EF: ExtensionField<F>,
S: SymmetricSteps,
S::Challenger: CanObserve<F> + CanSample<F>,
{
assert_eq!(
prelude,
ZkPrelude::RecordedClaims,
"a batch described as inheriting its claim draws no batching challenge",
);
state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(CLAIM_BATCHING)
.into_inner()
}
fn play_bind_claim<F, EF, S>(state: &mut S, prelude: ZkPrelude, claim: EF)
where
F: TranscriptField,
EF: ExtensionField<F>,
S: SymmetricSteps,
S::Challenger: CanObserve<F> + CanSample<F>,
{
assert_eq!(
prelude,
ZkPrelude::InheritedClaim,
"a batch described as batching recorded claims binds no inherited claim",
);
state.observe_extension::<F, EF, FieldToFieldCodec<F>>(JOINT_CLAIM, &claim);
}
fn play_masks<F, EF, S, Com>(state: &mut S, commitment: Com, mu_tilde: EF) -> EF
where
F: TranscriptField,
EF: ExtensionField<F>,
S: SymmetricSteps,
Com: Clone,
S::Challenger: CanObserve<F> + CanSample<F> + CanObserve<Com>,
{
state.observe_opaque(MASK_COMMITMENT, commitment);
state.observe_extension::<F, EF, FieldToFieldCodec<F>>(MU_TILDE, &mu_tilde);
state
.challenge_extension::<F, EF, FieldToFieldCodec<F>>(MASK_COMBINATION)
.into_inner()
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use p3_baby_bear::{BabyBear, Poseidon2BabyBear};
use p3_challenger::testing::pow_difficulties;
use p3_challenger::{CanSample, DuplexChallenger};
use p3_field::extension::BinomialExtensionField;
use p3_field::{BasedVectorSpace, PrimeCharacteristicRing, PrimeField64};
use p3_keccak::Keccak256Hash;
use p3_symmetric::CryptographicHasher;
use rand::SeedableRng;
use rand::rngs::StdRng;
use super::*;
type F = BabyBear;
type EF = BinomialExtensionField<F, 4>;
type Perm = Poseidon2BabyBear<16>;
type Ch = DuplexChallenger<F, Perm, 16, 8>;
type Com = [F; 4];
fn fresh_challenger() -> Ch {
let mut rng = StdRng::seed_from_u64(0xDEADBEEF);
Ch::new(Perm::new_from_rng_128(&mut rng))
}
fn stream_digest(values: &[u64]) -> [u8; 32] {
Keccak256Hash.hash_iter(values.iter().flat_map(|v| v.to_le_bytes()))
}
fn commitment(tag: u32) -> Com {
[F::from_u32(tag), F::ZERO, F::ONE, F::TWO]
}
const fn base_shape() -> ZkSumcheckShape {
ZkSumcheckShape::new_batching(3, 4, 0)
}
fn first_challenge(shape: ZkSumcheckShape) -> F {
let mut challenger = fresh_challenger();
shape.domain_separator::<F, EF>().seed(&mut challenger);
challenger.sample()
}
fn field_moves_the_seed(name: &str, perturb: impl FnOnce(&mut ZkSumcheckShape)) {
let mut tweaked = base_shape();
perturb(&mut tweaked);
assert_ne!(
first_challenge(base_shape()),
first_challenge(tweaked),
"changing {name} left the seed where it was",
);
}
#[test]
fn the_same_shape_seeds_the_same_stream_twice() {
assert_eq!(first_challenge(base_shape()), first_challenge(base_shape()));
}
#[test]
fn every_number_that_shapes_a_batch_reaches_the_seed() {
field_moves_the_seed("prelude", |s| s.prelude = ZkPrelude::InheritedClaim);
field_moves_the_seed("num_rounds", |s| s.num_rounds += 1);
field_moves_the_seed("ell_zk", |s| s.ell_zk += 1);
field_moves_the_seed("pow_bits", |s| s.pow_bits += 8);
assert_ne!(
first_challenge(ZkSumcheckShape::new_batching(3, 4, 8)),
first_challenge(ZkSumcheckShape::new_batching(3, 4, 9)),
);
assert_eq!(
ZkSumcheckShape::new_batching(3, 2, 0).wire_len(),
ZkSumcheckShape::new_batching(3, 3, 0).wire_len(),
);
assert_ne!(
first_challenge(ZkSumcheckShape::new_batching(3, 2, 0)),
first_challenge(ZkSumcheckShape::new_batching(3, 3, 0)),
);
}
#[test]
fn a_configuration_no_batch_can_run_under_is_rejected() {
assert_eq!(
ZkSumcheckShape::new_batching(3, 2, 0).validate::<F>(),
Err(SumcheckError::MaskTooShort { ell_zk: 2 }),
);
assert_eq!(
ZkSumcheckShape::new_batching(0, 4, 0).validate::<F>(),
Err(SumcheckError::NoRounds),
);
assert_eq!(base_shape().validate::<F>(), Ok(()));
}
fn drive_prover(
challenger: &mut Ch,
shape: ZkSumcheckShape,
commitment: Com,
mu_tilde: EF,
wires: &[Vec<EF>],
) -> (EF, EF, Vec<EF>, Vec<F>) {
let mut transcript = ZkProverTranscript::<Ch, F, EF>::new(challenger, shape);
let alpha = transcript.batching_challenge();
let eps = transcript.masks(commitment, mu_tilde);
let mut gammas = Vec::new();
let mut witnesses = Vec::new();
for wire in wires {
let (gamma, witness) = transcript.round(wire);
gammas.push(gamma);
witnesses.extend(witness);
}
transcript.finish();
(alpha, eps, gammas, witnesses)
}
fn drive_verifier(
challenger: &mut Ch,
shape: ZkSumcheckShape,
commitment: Com,
mu_tilde: EF,
wires: &[Vec<EF>],
witnesses: &[F],
) -> Result<(EF, EF, Vec<EF>), SumcheckError> {
let mut transcript = ZkVerifierTranscript::<Ch, F, EF>::new(challenger, shape);
let alpha = transcript.batching_challenge();
let eps = transcript.masks(commitment, mu_tilde);
let mut gammas = Vec::new();
for (round, wire) in wires.iter().enumerate() {
let witness = (shape.pow_bits > 0).then(|| witnesses[round]);
gammas.push(transcript.round(wire, witness)?);
}
transcript.finish();
Ok((alpha, eps, gammas))
}
fn honest_wires() -> Vec<Vec<EF>> {
vec![
vec![EF::ONE, EF::TWO, EF::from_u8(3)],
vec![EF::from_u8(4), EF::from_u8(5), EF::from_u8(6)],
]
}
#[test]
fn both_sides_of_a_batch_draw_the_same_challenges() {
let shape = ZkSumcheckShape::new_batching(2, 4, 0);
let wires = honest_wires();
let mut prover_challenger = fresh_challenger();
let (alpha, eps, gammas, witnesses) = drive_prover(
&mut prover_challenger,
shape,
commitment(7),
EF::from_u8(9),
&wires,
);
assert!(witnesses.is_empty(), "no grinding means no witness");
let mut verifier_challenger = fresh_challenger();
let (replayed_alpha, replayed_eps, replayed_gammas) = drive_verifier(
&mut verifier_challenger,
shape,
commitment(7),
EF::from_u8(9),
&wires,
&[],
)
.expect("the honest batch must replay");
assert_eq!(alpha, replayed_alpha);
assert_eq!(eps, replayed_eps);
assert_eq!(gammas, replayed_gammas);
assert_eq!(
CanSample::<F>::sample(&mut prover_challenger),
CanSample::<F>::sample(&mut verifier_challenger),
);
}
#[test]
fn a_recorded_grinding_witness_replays_to_the_same_challenges() {
let shape = ZkSumcheckShape::new_batching(2, 4, 4);
let wires = honest_wires();
let mut prover_challenger = fresh_challenger();
let (_, eps, gammas, witnesses) = drive_prover(
&mut prover_challenger,
shape,
commitment(7),
EF::from_u8(9),
&wires,
);
assert_eq!(witnesses.len(), 2, "one witness per guarded round");
let mut verifier_challenger = fresh_challenger();
let (_, replayed_eps, replayed_gammas) = drive_verifier(
&mut verifier_challenger,
shape,
commitment(7),
EF::from_u8(9),
&wires,
&witnesses,
)
.expect("the recorded witnesses must replay");
assert_eq!(eps, replayed_eps);
assert_eq!(gammas, replayed_gammas);
}
fn replay(commitment: Com, mu_tilde: EF, wires: &[Vec<EF>]) -> (EF, Vec<EF>) {
let mut challenger = fresh_challenger();
let shape = ZkSumcheckShape::new_batching(wires.len(), 4, 0);
let (_, eps, gammas) =
drive_verifier(&mut challenger, shape, commitment, mu_tilde, wires, &[])
.expect("a well-shaped batch always replays");
(eps, gammas)
}
#[test]
fn a_perturbed_mask_commitment_moves_every_later_challenge() {
let wires = honest_wires();
assert_ne!(
replay(commitment(7), EF::from_u8(9), &wires),
replay(commitment(8), EF::from_u8(9), &wires),
);
}
#[test]
fn a_perturbed_mu_tilde_moves_every_later_challenge() {
let wires = honest_wires();
assert_ne!(
replay(commitment(7), EF::from_u8(9), &wires),
replay(commitment(7), EF::from_u8(10), &wires),
);
}
#[test]
fn a_perturbed_wire_coefficient_moves_every_later_challenge() {
let honest = honest_wires();
for position in 0..honest[0].len() {
let mut tampered = honest.clone();
tampered[0][position] += EF::ONE;
assert_ne!(
replay(commitment(7), EF::from_u8(9), &honest),
replay(commitment(7), EF::from_u8(9), &tampered),
"tampering with wire coefficient {position} left the stream where it was",
);
}
}
#[test]
fn a_perturbed_inherited_claim_moves_every_later_challenge() {
let shape = ZkSumcheckShape::new_inherited(1, 4, 0);
let wire = [EF::ONE, EF::TWO, EF::from_u8(3)];
let draw = |claim: EF| {
let mut challenger = fresh_challenger();
let mut transcript = ZkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
transcript.bind_claim(claim);
let eps = transcript.masks(commitment(7), EF::from_u8(9));
let gamma = transcript.round(&wire, None).unwrap();
transcript.finish();
(eps, gamma)
};
assert_ne!(draw(EF::from_u8(11)), draw(EF::from_u8(12)));
}
#[test]
fn a_wire_of_the_wrong_width_is_rejected() {
let mut challenger = fresh_challenger();
let shape = ZkSumcheckShape::new_batching(1, 4, 0);
let mut transcript = ZkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
let _ = transcript.batching_challenge();
let _ = transcript.masks(commitment(7), EF::from_u8(9));
let err = transcript
.round(&[EF::ONE, EF::TWO], None)
.expect_err("a wire outside the described width must error");
assert_eq!(
err,
SumcheckError::WireSizeMismatch {
round: 0,
expected: 3,
actual: 2,
},
);
drop(transcript);
}
#[test]
fn a_round_missing_its_grinding_witness_is_rejected() {
let mut challenger = fresh_challenger();
let shape = ZkSumcheckShape::new_batching(1, 4, 4);
let mut transcript = ZkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
let _ = transcript.batching_challenge();
let _ = transcript.masks(commitment(7), EF::from_u8(9));
let err = transcript
.round(&[EF::ONE, EF::TWO, EF::from_u8(3)], None)
.expect_err("a described grinding step with no witness must error");
assert_eq!(
err,
SumcheckError::MissingPowWitness {
round: 0,
difficulty: 4,
},
);
}
#[test]
fn a_grinding_witness_below_the_difficulty_is_rejected() {
let mut challenger = fresh_challenger();
let shape = ZkSumcheckShape::new_batching(1, 4, 12);
let mut transcript = ZkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
let _ = transcript.batching_challenge();
let _ = transcript.masks(commitment(7), EF::from_u8(9));
let err = transcript
.round(&[EF::ONE, EF::TWO, EF::from_u8(3)], Some(F::ZERO))
.expect_err("a witness below the required difficulty must error");
assert_eq!(
err,
SumcheckError::InvalidPowWitness {
round: 0,
difficulty: 12,
},
);
}
#[test]
fn the_described_grinding_matches_the_configured_difficulty() {
for bits in [0, 4] {
let shape = ZkSumcheckShape::new_batching(4, 4, bits);
let described = pow_difficulties(&shape.pattern::<F, EF>());
let expected = if bits == 0 { 0 } else { shape.num_rounds };
assert_eq!(described.len(), expected);
assert!(
described
.iter()
.all(|&(label, got)| label == ROUND_POW && got == bits)
);
}
}
#[test]
fn the_challenge_stream_is_pinned() {
let extension_words = |value: &EF| -> Vec<u64> {
value
.as_basis_coefficients_slice()
.iter()
.map(F::as_canonical_u64)
.collect()
};
let wires = honest_wires();
let shape = ZkSumcheckShape::new_batching(2, 4, 0);
let mut prover_challenger = fresh_challenger();
let (alpha, eps, gammas, witnesses) = drive_prover(
&mut prover_challenger,
shape,
commitment(7),
EF::from_u8(9),
&wires,
);
let prover_next: F = prover_challenger.sample();
let mut stream = extension_words(&alpha);
stream.extend(extension_words(&eps));
stream.extend(gammas.iter().flat_map(extension_words));
stream.push(prover_next.as_canonical_u64());
assert_eq!(
stream_digest(&stream),
[
254, 63, 232, 210, 132, 116, 250, 106, 226, 104, 125, 26, 147, 235, 33, 70, 49,
221, 46, 219, 10, 107, 95, 110, 106, 17, 93, 160, 174, 29, 97, 156,
],
);
let mut verifier_challenger = fresh_challenger();
let (replayed_alpha, replayed_eps, replayed_gammas) = drive_verifier(
&mut verifier_challenger,
shape,
commitment(7),
EF::from_u8(9),
&wires,
&witnesses,
)
.expect("the honest batch must replay");
assert_eq!(alpha, replayed_alpha);
assert_eq!(eps, replayed_eps);
assert_eq!(gammas, replayed_gammas);
let verifier_next: F = verifier_challenger.sample();
assert_eq!(prover_next, verifier_next);
let inherited = ZkSumcheckShape::new_inherited(2, 4, 0);
let mut prover_challenger = fresh_challenger();
let mut prover_transcript =
ZkProverTranscript::<Ch, F, EF>::new(&mut prover_challenger, inherited);
prover_transcript.bind_claim(EF::from_u8(11));
let eps = prover_transcript.masks(commitment(7), EF::from_u8(9));
let mut gammas = Vec::new();
for wire in &wires {
let (gamma, _witness) = prover_transcript.round(wire);
gammas.push(gamma);
}
prover_transcript.finish();
let prover_next: F = prover_challenger.sample();
let mut stream = extension_words(&eps);
stream.extend(gammas.iter().flat_map(extension_words));
stream.push(prover_next.as_canonical_u64());
assert_eq!(
stream_digest(&stream),
[
170, 180, 70, 73, 217, 13, 161, 0, 67, 226, 192, 230, 52, 98, 66, 178, 120, 50, 53,
15, 16, 48, 140, 167, 165, 63, 61, 44, 121, 169, 24, 120,
],
);
let mut verifier_challenger = fresh_challenger();
let mut verifier_transcript =
ZkVerifierTranscript::<Ch, F, EF>::new(&mut verifier_challenger, inherited);
verifier_transcript.bind_claim(EF::from_u8(11));
let replayed_eps = verifier_transcript.masks(commitment(7), EF::from_u8(9));
let mut replayed_gammas = Vec::new();
for wire in &wires {
replayed_gammas.push(
verifier_transcript
.round(wire, None)
.expect("the honest batch must replay"),
);
}
verifier_transcript.finish();
assert_eq!(eps, replayed_eps);
assert_eq!(gammas, replayed_gammas);
let verifier_next: F = verifier_challenger.sample();
assert_eq!(prover_next, verifier_next);
}
#[test]
#[should_panic(
expected = "a batch described as inheriting its claim draws no batching challenge"
)]
fn a_prover_that_draws_a_batching_challenge_on_an_inherited_shape_fails_loudly() {
let mut challenger = fresh_challenger();
let mut transcript = ZkProverTranscript::<Ch, F, EF>::new(
&mut challenger,
ZkSumcheckShape::new_inherited(1, 4, 0),
);
let _ = transcript.batching_challenge();
}
#[test]
#[should_panic(
expected = "a batch described as batching recorded claims binds no inherited claim"
)]
fn a_prover_that_binds_an_inherited_claim_on_a_batching_shape_fails_loudly() {
let mut challenger = fresh_challenger();
let mut transcript = ZkProverTranscript::<Ch, F, EF>::new(
&mut challenger,
ZkSumcheckShape::new_batching(1, 4, 0),
);
transcript.bind_claim(EF::from_u8(11));
}
#[test]
#[should_panic(
expected = "a batch described as inheriting its claim draws no batching challenge"
)]
fn a_verifier_that_draws_a_batching_challenge_on_an_inherited_shape_fails_loudly() {
let mut challenger = fresh_challenger();
let mut transcript = ZkVerifierTranscript::<Ch, F, EF>::new(
&mut challenger,
ZkSumcheckShape::new_inherited(1, 4, 0),
);
let _ = transcript.batching_challenge();
}
#[test]
#[should_panic(
expected = "a batch described as batching recorded claims binds no inherited claim"
)]
fn a_verifier_that_binds_an_inherited_claim_on_a_batching_shape_fails_loudly() {
let mut challenger = fresh_challenger();
let mut transcript = ZkVerifierTranscript::<Ch, F, EF>::new(
&mut challenger,
ZkSumcheckShape::new_batching(1, 4, 0),
);
transcript.bind_claim(EF::from_u8(11));
}
}