use alloc::vec::Vec;
use core::marker::PhantomData;
use p3_challenger::fs::TranscriptField;
use p3_challenger::{CanObserve, FieldChallenger, GrindingChallenger};
use p3_commit::Mmcs;
use p3_field::{ExtensionField, Field, TwoAdicField};
use p3_matrix::Matrix;
use p3_multilinear_util::point::Point;
use p3_zk_codes::{ZkEncoding, ZkEncodingWithRandomness};
use rand::CryptoRng;
use super::common::{mask_endpoints, sample_masks};
use super::layout::ZkLayout;
use super::round::{MaskedRounds, PlainPiece};
use crate::extrapolate_01inf;
use crate::layout::{PrefixProver, SuffixProver};
use crate::strategy::SumcheckProver;
use crate::table::{OpeningEvals, OpeningRequest};
use crate::zk::data::{ZkSumcheckData, ZkSumcheckHandoff};
use crate::zk::transcript::{ZkProverTranscript, ZkSumcheckShape};
pub struct ZkProver<F, EF, Enc, M, L>
where
F: Field,
EF: ExtensionField<F>,
Enc: ZkEncoding<EF>,
M: Mmcs<EF>,
{
inner: L,
encoding: Enc,
mmcs: M,
_marker: PhantomData<(F, EF)>,
}
pub type ZkPrefixProver<F, EF, Enc, M> = ZkProver<F, EF, Enc, M, PrefixProver<F, EF>>;
pub type ZkSuffixProver<F, EF, Enc, M> = ZkProver<F, EF, Enc, M, SuffixProver<F, EF>>;
impl<F, EF, Enc, M, L> ZkProver<F, EF, Enc, M, L>
where
F: TwoAdicField,
EF: ExtensionField<F>,
Enc: ZkEncodingWithRandomness<EF>,
M: Mmcs<EF>,
L: ZkLayout<F, EF>,
{
pub const fn new(inner: L, encoding: Enc, mmcs: M) -> Self {
Self {
inner,
encoding,
mmcs,
_marker: PhantomData,
}
}
pub fn folding(&self) -> usize {
self.inner.folding()
}
pub fn num_variables(&self) -> usize {
self.inner.num_variables()
}
pub fn eval<Ch>(
&mut self,
table_idx: usize,
batch: &OpeningRequest,
challenger: &mut Ch,
) -> OpeningEvals<EF>
where
F: TranscriptField,
Ch: FieldChallenger<F> + GrindingChallenger<Witness = F>,
{
self.inner.eval(table_idx, batch, challenger)
}
pub fn add_virtual_eval<Ch>(&mut self, challenger: &mut Ch) -> EF
where
F: TranscriptField,
Ch: FieldChallenger<F> + GrindingChallenger<Witness = F>,
{
self.inner.add_virtual_eval(challenger)
}
#[tracing::instrument(skip_all)]
pub fn into_sumcheck<R, Ch>(
self,
zk_data: &mut ZkSumcheckData<F, EF>,
pow_bits: usize,
challenger: &mut Ch,
rng: &mut R,
) -> ZkSumcheckHandoff<F, EF, M>
where
F: TranscriptField,
EF: TwoAdicField,
Enc::Codeword: Matrix<EF>,
R: CryptoRng,
Ch: FieldChallenger<F> + GrindingChallenger<Witness = F> + CanObserve<M::Commitment>,
{
let k = self.inner.folding();
let ell_zk = self.encoding.message_len();
let n_vars = self.inner.num_variables();
let shape = ZkSumcheckShape::new_batching(k, ell_zk, pow_bits);
shape
.validate::<F>()
.expect("a prover's own configuration must describe a masked batch");
assert!(
k <= n_vars,
"folding_factor must be <= poly.num_variables()",
);
let mut transcript = ZkProverTranscript::<Ch, F, EF>::new(challenger, shape);
let alpha: EF = transcript.batching_challenge();
let accumulators = self.inner.claims().batched_accumulators(alpha);
let mut plain_sum = self.inner.batched_sum(alpha);
let (masks, mask_randomness, mask_oracle) =
sample_masks::<EF, _, _, _>(k, &self.encoding, &self.mmcs, rng);
let (mu_tilde, sum_endpoints_init) = mask_endpoints::<EF>(&masks, k);
zk_data.mu_tilde = mu_tilde;
let eps: EF = transcript.masks(mask_oracle.0.clone(), mu_tilde);
let mut rs: Vec<EF> = Vec::with_capacity(k);
let mut rounds = MaskedRounds::new(&masks, ell_zk, sum_endpoints_init, eps);
for _ in 0..k {
let (plain_c0, plain_c_inf) = accumulators.round_coefficients(&rs);
let gamma_j = rounds.play(
PlainPiece {
c0: plain_c0,
c_inf: plain_c_inf,
},
&mut transcript,
zk_data,
);
plain_sum = extrapolate_01inf(plain_c0, plain_sum - plain_c0, plain_c_inf, gamma_j);
rs.push(gamma_j);
}
transcript.finish();
let rs = Point::new(rs);
let prod_poly = self.inner.zk_residual_handoff(&rs, alpha, eps);
let residual_sum = eps * plain_sum;
debug_assert_eq!(
prod_poly.dot_product(),
residual_sum,
"residual product polynomial dot product must equal eps * plain_residual_sum",
);
ZkSumcheckHandoff {
residual_prover: SumcheckProver::new(prod_poly, residual_sum),
randomness: rs,
eps,
mask_messages: masks,
mask_randomness,
mask_oracle,
}
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use proptest::prelude::*;
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
use crate::layout::{PrefixProver, SuffixProver};
use crate::strategy::VariableOrder;
use crate::table::OpeningBatch;
use crate::tests::transcript_fingerprint;
use crate::zk::test_helpers::{
EF, F, MyChallenger, MyMmcs, build_prover_verifier, make_setup, run_roundtrip,
};
use crate::zk::{ZkLayout, ZkSumcheckData};
#[test]
fn prover_verifier_roundtrip_prefix() {
run_roundtrip(VariableOrder::Prefix, 8, 3, 4, 1, 1, 0)
.expect("honest roundtrip should accept");
}
#[test]
fn prover_verifier_roundtrip_prefix_with_no_residual_variables() {
run_roundtrip(VariableOrder::Prefix, 3, 3, 4, 1, 1, 0)
.expect("fully folded prefix roundtrip should accept");
}
#[test]
fn prover_verifier_roundtrip_suffix() {
run_roundtrip(VariableOrder::Suffix, 8, 3, 4, 1, 1, 0)
.expect("honest roundtrip should accept");
}
#[test]
fn long_mask_horner_path_prefix() {
run_roundtrip(VariableOrder::Prefix, 8, 3, 32, 1, 1, 0)
.expect("honest roundtrip should accept");
}
#[test]
fn long_mask_horner_path_suffix() {
run_roundtrip(VariableOrder::Suffix, 8, 3, 32, 1, 1, 0)
.expect("honest roundtrip should accept");
}
#[test]
fn prover_verifier_roundtrip_mixed_current_next_batch() {
let n_vars = 8;
let folding_factor = 3;
let ell_zk = 4;
let seed = 7u64;
let pow_bits = 0;
let (perm, mmcs, encoding) = make_setup(seed, ell_zk);
let mut data_rng = StdRng::seed_from_u64(seed.wrapping_add(1));
let evals: Vec<F> = (0..(1usize << n_vars)).map(|_| data_rng.random()).collect();
let (mut prover, mut verifier, _) =
build_prover_verifier::<PrefixProver<F, EF>>(evals, folding_factor, encoding, mmcs);
let mut prover_challenger = MyChallenger::new(perm.clone());
let mut verifier_challenger = MyChallenger::new(perm);
let batch = OpeningBatch::new(vec![0], vec![0]);
let evals = prover.eval(0, &batch, &mut prover_challenger);
assert_eq!(evals.len(), batch.len());
verifier
.add_claim(0, &batch, &evals, &mut verifier_challenger)
.unwrap();
let virtual_eval = prover.add_virtual_eval(&mut prover_challenger);
verifier.add_virtual_eval(virtual_eval, &mut verifier_challenger);
let mut zk_data = ZkSumcheckData::<F, EF>::default();
let mut prover_rng = StdRng::seed_from_u64(seed.wrapping_add(2));
let prover_handoff = prover.into_sumcheck(
&mut zk_data,
pow_bits,
&mut prover_challenger,
&mut prover_rng,
);
let mask_commitment = prover_handoff.mask_oracle.0.clone();
let verifier_handoff = verifier
.into_sumcheck::<MyMmcs, _>(
&zk_data,
&mask_commitment,
ell_zk,
folding_factor,
pow_bits,
&mut verifier_challenger,
)
.expect("verifier should accept mixed current/Next batch");
assert_eq!(
prover_handoff
.randomness
.iter()
.copied()
.collect::<Vec<_>>(),
verifier_handoff
.randomness
.iter()
.copied()
.collect::<Vec<_>>(),
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(16))]
#[test]
fn prop_completeness_prefix(
n_vars in 3usize..=8,
ell_zk in 3usize..=5,
num_concrete in 0usize..=2,
num_virtual in 0usize..=2,
seed in 0u64..1024,
) {
prop_assume!(num_concrete + num_virtual >= 1);
let folding_factor = 1 + (seed as usize % n_vars);
prop_assert!(
run_roundtrip(VariableOrder::Prefix, n_vars, folding_factor, ell_zk, num_concrete, num_virtual, seed)
.is_ok()
);
}
#[test]
fn prop_completeness_suffix(
n_vars in 3usize..=8,
ell_zk in 3usize..=5,
num_concrete in 0usize..=2,
num_virtual in 0usize..=2,
seed in 0u64..1024,
) {
prop_assume!(num_concrete + num_virtual >= 1);
let folding_factor = 1 + (seed as usize % (n_vars - 1));
prop_assert!(
run_roundtrip(VariableOrder::Suffix, n_vars, folding_factor, ell_zk, num_concrete, num_virtual, seed)
.is_ok()
);
}
}
#[test]
fn hiding_batch_transcripts_are_pinned() {
fn run<L: ZkLayout<F, EF>>() -> [u32; 4] {
let (perm, mmcs, encoding) = make_setup(11, 4);
let mut data_rng = StdRng::seed_from_u64(12);
let evals: Vec<F> = (0..256).map(|_| data_rng.random()).collect();
let (mut prover, _verifier, _n_vars) =
build_prover_verifier::<L>(evals, 3, encoding, mmcs);
let mut ch = MyChallenger::new(perm);
prover.eval(0, &OpeningBatch::new(vec![0], vec![0]), &mut ch);
prover.eval(0, &OpeningBatch::new(vec![0], Vec::new()), &mut ch);
let _ = prover.add_virtual_eval(&mut ch);
let _ = prover.add_virtual_eval(&mut ch);
let mut zk_data = ZkSumcheckData::<F, EF>::default();
let mut prover_rng = StdRng::seed_from_u64(13);
let mut handoff = prover.into_sumcheck(&mut zk_data, 0, &mut ch, &mut prover_rng);
transcript_fingerprint(&mut handoff.residual_prover, &mut ch)
}
assert_eq!(
run::<PrefixProver<F, EF>>(),
[45553658, 1785166884, 1370940203, 990586681]
);
assert_eq!(
run::<SuffixProver<F, EF>>(),
[1049900532, 349798432, 102124702, 1192330144]
);
}
}