use alloc::vec;
use alloc::vec::Vec;
use core::marker::PhantomData;
use itertools::Itertools;
use p3_challenger::{CanObserve, FieldChallenger};
use p3_commit::{Pcs, PolynomialSpace};
use p3_field::{BasedVectorSpace, Field, PrimeCharacteristicRing, TwoAdicField};
use p3_matrix::dense::RowMajorMatrixView;
use p3_matrix::stack::VerticalPair;
use p3_miden_air::{BusType, MidenAir};
use p3_util::zip_eq::zip_eq;
use tracing::{debug_span, instrument};
use crate::periodic_tables::evaluate_periodic_at_point;
use crate::symbolic_builder::get_log_quotient_degree;
use crate::util::verifier_row_to_ext;
use crate::{
AirWithBoundaryConstraints, Domain, PcsError, Proof, StarkGenericConfig, Val,
VerifierConstraintFolder,
};
pub fn recompose_quotient_from_chunks<SC>(
quotient_chunks_domains: &[Domain<SC>],
quotient_chunks: &[Vec<SC::Challenge>],
zeta: SC::Challenge,
) -> SC::Challenge
where
SC: StarkGenericConfig,
{
let zps = quotient_chunks_domains
.iter()
.enumerate()
.map(|(i, domain)| {
quotient_chunks_domains
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, other_domain)| {
other_domain.vanishing_poly_at_point(zeta)
* other_domain
.vanishing_poly_at_point(domain.first_point())
.inverse()
})
.product::<SC::Challenge>()
})
.collect_vec();
quotient_chunks
.iter()
.enumerate()
.map(|(ch_i, ch)| {
zps[ch_i]
* ch.iter()
.enumerate()
.map(|(e_i, &c)| SC::Challenge::ith_basis_element(e_i).unwrap() * c)
.sum::<SC::Challenge>()
})
.sum::<SC::Challenge>()
}
#[allow(clippy::too_many_arguments)]
pub fn verify_constraints<SC, A, PcsErr>(
air: &A,
trace_local: &[SC::Challenge],
trace_next: &[SC::Challenge],
preprocessed_local: Option<&[SC::Challenge]>,
preprocessed_next: Option<&[SC::Challenge]>,
aux_local: Option<&[SC::Challenge]>,
aux_next: Option<&[SC::Challenge]>,
randomness: &[SC::Challenge],
aux_bus_boundary_values: &[SC::Challenge],
public_values: &[Val<SC>],
trace_domain: Domain<SC>,
zeta: SC::Challenge,
alpha: SC::Challenge,
quotient: SC::Challenge,
) -> Result<(), VerificationError<PcsErr>>
where
SC: StarkGenericConfig + Sync,
A: MidenAir<Val<SC>, SC::Challenge>,
Val<SC>: TwoAdicField,
{
let sels = trace_domain.selectors_at_point(zeta);
let periodic_values: Vec<SC::Challenge> = evaluate_periodic_at_point::<Val<SC>, SC::Challenge>(
air.periodic_table(),
trace_domain,
zeta,
);
let main = VerticalPair::new(
RowMajorMatrixView::new_row(trace_local),
RowMajorMatrixView::new_row(trace_next),
);
let preprocessed = match (preprocessed_local, preprocessed_next) {
(Some(local), Some(next)) => Some(VerticalPair::new(
RowMajorMatrixView::new_row(local),
RowMajorMatrixView::new_row(next),
)),
_ => None,
};
let aux_local_ext;
let aux_next_ext;
let aux = match (aux_local, aux_next) {
(Some(local), Some(next)) => {
aux_local_ext = verifier_row_to_ext::<Val<SC>, SC::Challenge>(local)
.ok_or(VerificationError::InvalidProofShape)?;
aux_next_ext = verifier_row_to_ext::<Val<SC>, SC::Challenge>(next)
.ok_or(VerificationError::InvalidProofShape)?;
VerticalPair::new(
RowMajorMatrixView::new_row(&aux_local_ext),
RowMajorMatrixView::new_row(&aux_next_ext),
)
}
_ => {
let empty: &[SC::Challenge] = &[];
VerticalPair::new(
RowMajorMatrixView::new_row(empty),
RowMajorMatrixView::new_row(empty),
)
}
};
let mut folder: VerifierConstraintFolder<'_, SC> = VerifierConstraintFolder {
main,
aux,
randomness,
aux_bus_boundary_values,
preprocessed,
public_values,
periodic_values: &periodic_values,
is_first_row: sels.is_first_row,
is_last_row: sels.is_last_row,
is_transition: sels.is_transition,
alpha,
accumulator: SC::Challenge::ZERO,
};
air.eval(&mut folder);
let folded_constraints = folder.accumulator;
if folded_constraints * sels.inv_vanishing != quotient {
return Err(VerificationError::OodEvaluationMismatch { index: None });
}
Ok(())
}
#[allow(clippy::type_complexity)]
fn process_preprocessed_trace<SC, A>(
air: &A,
opened_values: &crate::proof::OpenedValues<SC::Challenge>,
pcs: &SC::Pcs,
trace_domain: <SC::Pcs as Pcs<SC::Challenge, SC::Challenger>>::Domain,
is_zk: usize,
) -> Result<
(
usize,
Option<<SC::Pcs as Pcs<SC::Challenge, SC::Challenger>>::Commitment>,
),
VerificationError<PcsError<SC>>,
>
where
SC: StarkGenericConfig,
A: MidenAir<Val<SC>, SC::Challenge>,
Val<SC>: TwoAdicField,
{
let preprocessed = air.preprocessed_trace();
let preprocessed_width = preprocessed.as_ref().map(|m| m.width).unwrap_or(0);
let preprocessed_local_len = opened_values
.preprocessed_local
.as_ref()
.map_or(0, |v| v.len());
let preprocessed_next_len = opened_values
.preprocessed_next
.as_ref()
.map_or(0, |v| v.len());
if preprocessed_width != preprocessed_local_len || preprocessed_width != preprocessed_next_len {
return Err(VerificationError::InvalidProofShape);
}
if preprocessed_width > 0 {
assert_eq!(is_zk, 0); let height = preprocessed.as_ref().unwrap().values.len() / preprocessed_width;
assert_eq!(
height,
trace_domain.size(),
"Verifier's preprocessed trace height must be equal to trace domain size"
);
let (preprocessed_commit, _) = debug_span!("process preprocessed trace")
.in_scope(|| pcs.commit([(trace_domain, preprocessed.unwrap())]));
Ok((preprocessed_width, Some(preprocessed_commit)))
} else {
Ok((preprocessed_width, None))
}
}
#[instrument(skip_all)]
pub fn verify<SC, A>(
config: &SC,
air: &A,
proof: &Proof<SC>,
public_values: &[Val<SC>],
var_length_public_inputs: &[&[&[Val<SC>]]],
) -> Result<(), VerificationError<PcsError<SC>>>
where
SC: StarkGenericConfig + Sync,
A: MidenAir<Val<SC>, SC::Challenge>,
Val<SC>: TwoAdicField,
{
let air = &AirWithBoundaryConstraints {
inner: air,
phantom: PhantomData::<SC>,
};
let Proof {
commitments,
opened_values,
opening_proof,
aux_finals,
degree_bits,
} = proof;
let pcs = config.pcs();
let degree = 1 << degree_bits;
let aux_width = air.aux_width();
let num_randomness = air.num_randomness();
let trace_domain = pcs.natural_domain_for_degree(degree);
let (preprocessed_width, preprocessed_commit) =
process_preprocessed_trace::<SC, _>(air, opened_values, pcs, trace_domain, config.is_zk())?;
let log_quotient_degree = get_log_quotient_degree::<Val<SC>, SC::Challenge, _>(
air,
preprocessed_width,
public_values.len(),
config.is_zk(),
aux_width,
num_randomness,
);
let quotient_degree = 1 << (log_quotient_degree + config.is_zk());
let mut challenger = config.initialise_challenger();
let init_trace_domain = pcs.natural_domain_for_degree(degree >> (config.is_zk()));
let quotient_domain =
trace_domain.create_disjoint_domain(1 << (degree_bits + log_quotient_degree));
let quotient_chunks_domains = quotient_domain.split_domains(quotient_degree);
let randomized_quotient_chunks_domains = quotient_chunks_domains
.iter()
.map(|domain| pcs.natural_domain_for_degree(domain.size() << (config.is_zk())))
.collect_vec();
if (opened_values.random.is_some() != SC::Pcs::ZK)
|| (commitments.random.is_some() != SC::Pcs::ZK)
{
return Err(VerificationError::RandomizationError);
}
challenger.observe(Val::<SC>::from_usize(proof.degree_bits));
challenger.observe(Val::<SC>::from_usize(proof.degree_bits - config.is_zk()));
challenger.observe(Val::<SC>::from_usize(preprocessed_width));
challenger.observe(commitments.trace.clone());
if preprocessed_width > 0 {
challenger.observe(preprocessed_commit.as_ref().unwrap().clone());
}
challenger.observe_slice(public_values);
let num_randomness = air.num_randomness();
let air_width = air.width();
let bus_types = air.bus_types();
let valid_shape = opened_values.trace_local.len() == air_width
&& opened_values.trace_next.len() == air_width
&& opened_values.quotient_chunks.len() == quotient_degree
&& opened_values
.quotient_chunks
.iter()
.all(|qc| qc.len() == SC::Challenge::DIMENSION)
&& opened_values.random.as_ref().is_none_or(|r_comm| r_comm.len() == SC::Challenge::DIMENSION)
&& if num_randomness > 0 {
let aux_width_base = aux_width * SC::Challenge::DIMENSION;
match (&opened_values.aux_trace_local, &opened_values.aux_trace_next) {
(Some(l), Some(n)) => l.len() == aux_width_base
&& n.len() == aux_width_base
&& aux_finals.len() == aux_width,
_ => false,
}
} else {
opened_values.aux_trace_local.is_none() && opened_values.aux_trace_next.is_none() && aux_finals.is_empty()
};
if !valid_shape {
return Err(VerificationError::InvalidProofShape);
}
let randomness = if num_randomness != 0 {
let randomness: Vec<SC::Challenge> = (0..num_randomness)
.map(|_| challenger.sample_algebra_element())
.collect();
if let Some(aux_commit) = &commitments.aux {
challenger.observe(aux_commit.clone());
} else {
return Err(VerificationError::InvalidProofShape);
}
for aux_final in aux_finals {
challenger.observe_algebra_element(*aux_final);
}
randomness
} else {
if commitments.aux.is_some() {
return Err(VerificationError::InvalidProofShape);
}
vec![]
};
let alpha = challenger.sample_algebra_element();
challenger.observe(commitments.quotient_chunks.clone());
if let Some(r_commit) = commitments.random.clone() {
challenger.observe(r_commit);
}
let zeta = challenger.sample_algebra_element();
let zeta_next = init_trace_domain
.next_point(zeta)
.ok_or(VerificationError::NextPointUnavailable)?;
let mut coms_to_verify = if let Some(random_commit) = &commitments.random {
let random_values = opened_values
.random
.as_ref()
.ok_or(VerificationError::RandomizationError)?;
vec![(
random_commit.clone(),
vec![(trace_domain, vec![(zeta, random_values.clone())])],
)]
} else {
vec![]
};
coms_to_verify.extend(vec![
(
commitments.trace.clone(),
vec![(
trace_domain,
vec![
(zeta, opened_values.trace_local.clone()),
(zeta_next, opened_values.trace_next.clone()),
],
)],
),
(
commitments.quotient_chunks.clone(),
zip_eq(
randomized_quotient_chunks_domains.iter(),
&opened_values.quotient_chunks,
VerificationError::InvalidProofShape,
)?
.map(|(domain, values)| (*domain, vec![(zeta, values.clone())]))
.collect_vec(),
),
]);
if let Some(aux_commit) = &commitments.aux {
let aux_local = opened_values
.aux_trace_local
.as_ref()
.ok_or(VerificationError::InvalidProofShape)?;
let aux_next = opened_values
.aux_trace_next
.as_ref()
.ok_or(VerificationError::InvalidProofShape)?;
coms_to_verify.push((
aux_commit.clone(),
vec![(
trace_domain,
vec![(zeta, aux_local.clone()), (zeta_next, aux_next.clone())],
)],
));
}
if preprocessed_width > 0 {
let preprocessed_local = opened_values
.preprocessed_local
.as_ref()
.ok_or(VerificationError::InvalidProofShape)?;
let preprocessed_next = opened_values
.preprocessed_next
.as_ref()
.ok_or(VerificationError::InvalidProofShape)?;
coms_to_verify.push((
preprocessed_commit.unwrap(),
vec![(
trace_domain,
vec![
(zeta, preprocessed_local.clone()),
(zeta_next, preprocessed_next.clone()),
],
)],
));
}
pcs.verify(coms_to_verify, opening_proof, &mut challenger)
.map_err(VerificationError::InvalidOpeningArgument)?;
let quotient = recompose_quotient_from_chunks::<SC>(
"ient_chunks_domains,
&opened_values.quotient_chunks,
zeta,
);
for (idx, (bus_type, aux_final)) in bus_types.iter().zip(aux_finals).enumerate() {
let public_inputs_for_bus = *var_length_public_inputs
.get(idx)
.ok_or(VerificationError::InvalidProofShape)?;
let expected_final = match bus_type {
BusType::Multiset => bus_multiset_boundary_varlen::<_, SC>(
&randomness,
public_inputs_for_bus.iter().copied(),
),
BusType::Logup => bus_logup_boundary_varlen::<_, SC>(
&randomness,
public_inputs_for_bus.iter().copied(),
),
};
if *aux_final != expected_final {
return Err(VerificationError::InvalidBusBoundaryValues);
}
}
verify_constraints::<SC, _, PcsError<SC>>(
air,
&opened_values.trace_local,
&opened_values.trace_next,
opened_values.preprocessed_local.as_deref(),
opened_values.preprocessed_next.as_deref(),
opened_values.aux_trace_local.as_deref(),
opened_values.aux_trace_next.as_deref(),
&randomness,
aux_finals,
public_values,
init_trace_domain,
zeta,
alpha,
quotient,
)?;
Ok(())
}
pub fn bus_multiset_boundary_varlen<
'a,
I: IntoIterator<Item = &'a [Val<SC>]>,
SC: StarkGenericConfig,
>(
randomness: &[SC::Challenge],
public_inputs: I,
) -> SC::Challenge {
let mut bus_p_last = SC::Challenge::ONE;
let rand = randomness;
for row in public_inputs {
let mut p_last = rand[0];
for (c, p_i) in row.iter().enumerate() {
p_last += SC::Challenge::from(*p_i) * rand[c + 1];
}
bus_p_last *= p_last;
}
bus_p_last
}
pub fn bus_logup_boundary_varlen<
'a,
I: IntoIterator<Item = &'a [Val<SC>]>,
SC: StarkGenericConfig,
>(
randomness: &[SC::Challenge],
public_inputs: I,
) -> SC::Challenge {
let mut bus_q_last = SC::Challenge::ZERO;
let rand = randomness;
for row in public_inputs {
let mut q_last = rand[0];
for (c, p_i) in row.iter().enumerate() {
let p_i = *p_i;
q_last += SC::Challenge::from(p_i) * rand[c + 1];
}
bus_q_last += q_last.inverse();
}
bus_q_last
}
#[derive(Debug)]
pub enum VerificationError<PcsErr> {
InvalidProofShape,
InvalidOpeningArgument(PcsErr),
OodEvaluationMismatch {
index: Option<usize>,
},
RandomizationError,
NextPointUnavailable,
InvalidBusBoundaryValues,
}