#![no_std]
#[macro_use]
extern crate alloc;
use alloc::vec::Vec;
use core::cmp;
use air::proof::merge_ood_evaluations;
pub use air::{
proof::Proof, Air, AirContext, Assertion, BoundaryConstraint, BoundaryConstraintGroup,
ConstraintCompositionCoefficients, ConstraintDivisor, DeepCompositionCoefficients,
EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree,
};
pub use crypto;
use crypto::{ElementHasher, Hasher, RandomCoin, VectorCommitment};
use fri::FriVerifier;
pub use math;
use math::{
fields::{CubeExtension, QuadExtension},
FieldElement, ToElements,
};
pub use utils::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader,
};
mod channel;
use channel::VerifierChannel;
mod evaluator;
use evaluator::evaluate_constraints;
mod composer;
use composer::DeepComposer;
mod errors;
pub use errors::VerifierError;
pub fn verify<AIR, HashFn, RandCoin, VC>(
proof: Proof,
pub_inputs: AIR::PublicInputs,
acceptable_options: &AcceptableOptions,
) -> Result<(), VerifierError>
where
AIR: Air,
HashFn: ElementHasher<BaseField = AIR::BaseField>,
RandCoin: RandomCoin<BaseField = AIR::BaseField, Hasher = HashFn>,
VC: VectorCommitment<HashFn>,
{
acceptable_options.validate::<HashFn>(&proof)?;
let mut public_coin_seed = proof.context.to_elements();
public_coin_seed.append(&mut pub_inputs.to_elements());
let air = AIR::new(proof.trace_info().clone(), pub_inputs, proof.options().clone());
match air.options().field_extension() {
FieldExtension::None => {
let public_coin = RandCoin::new(&public_coin_seed);
let channel = VerifierChannel::new(&air, proof)?;
perform_verification::<AIR, AIR::BaseField, HashFn, RandCoin, VC>(
air,
channel,
public_coin,
)
},
FieldExtension::Quadratic => {
if !<QuadExtension<AIR::BaseField>>::is_supported() {
return Err(VerifierError::UnsupportedFieldExtension(2));
}
let public_coin = RandCoin::new(&public_coin_seed);
let channel = VerifierChannel::new(&air, proof)?;
perform_verification::<AIR, QuadExtension<AIR::BaseField>, HashFn, RandCoin, VC>(
air,
channel,
public_coin,
)
},
FieldExtension::Cubic => {
if !<CubeExtension<AIR::BaseField>>::is_supported() {
return Err(VerifierError::UnsupportedFieldExtension(3));
}
let public_coin = RandCoin::new(&public_coin_seed);
let channel = VerifierChannel::new(&air, proof)?;
perform_verification::<AIR, CubeExtension<AIR::BaseField>, HashFn, RandCoin, VC>(
air,
channel,
public_coin,
)
},
}
}
fn perform_verification<A, E, H, R, V>(
air: A,
mut channel: VerifierChannel<E, H, V>,
mut public_coin: R,
) -> Result<(), VerifierError>
where
E: FieldElement<BaseField = A::BaseField>,
A: Air,
H: ElementHasher<BaseField = A::BaseField>,
R: RandomCoin<BaseField = A::BaseField, Hasher = H>,
V: VectorCommitment<H>,
{
const MAIN_TRACE_IDX: usize = 0;
const AUX_TRACE_IDX: usize = 1;
let trace_commitments = channel.read_trace_commitments();
public_coin.reseed(trace_commitments[MAIN_TRACE_IDX]);
let aux_trace_rand_elements = if air.trace_info().is_multi_segment() {
let aux_rand_elements = air
.get_aux_rand_elements(&mut public_coin)
.expect("failed to generate the random elements needed to build the auxiliary trace");
public_coin.reseed(trace_commitments[AUX_TRACE_IDX]);
Some(aux_rand_elements)
} else {
None
};
let constraint_coeffs = air
.get_constraint_composition_coefficients(&mut public_coin)
.map_err(|_| VerifierError::RandomCoinError)?;
let constraint_commitment = channel.read_constraint_commitment();
public_coin.reseed(constraint_commitment);
let z = public_coin.draw::<E>().map_err(|_| VerifierError::RandomCoinError)?;
let ood_trace_frame = channel.read_ood_trace_frame();
let ood_main_trace_frame = ood_trace_frame.main_frame();
let ood_aux_trace_frame = ood_trace_frame.aux_frame();
let ood_constraint_evaluation_1 = evaluate_constraints(
&air,
constraint_coeffs,
&ood_main_trace_frame,
&ood_aux_trace_frame,
aux_trace_rand_elements.as_ref(),
z,
);
let ood_constraint_evaluations = channel.read_ood_constraint_frame();
let ood_constraint_evaluation_2 = ood_constraint_evaluations
.current_row()
.iter()
.enumerate()
.fold(E::ZERO, |result, (i, &value)| {
result + z.exp_vartime(((i * (air.trace_length())) as u32).into()) * value
});
if ood_constraint_evaluation_1 != ood_constraint_evaluation_2 {
return Err(VerifierError::InconsistentOodConstraintEvaluations);
}
let ood_evals = merge_ood_evaluations(&ood_trace_frame, &ood_constraint_evaluations);
let digest = H::hash_elements(&ood_evals);
public_coin.reseed(digest);
let deep_coefficients = air
.get_deep_composition_coefficients::<E, R>(&mut public_coin)
.map_err(|_| VerifierError::RandomCoinError)?;
let fri_verifier = FriVerifier::new(
&mut channel,
&mut public_coin,
air.options().to_fri_options(),
air.trace_poly_degree(),
)
.map_err(VerifierError::FriVerificationFailed)?;
let pow_nonce = channel.read_pow_nonce();
if public_coin.check_leading_zeros(pow_nonce) < air.options().grinding_factor() {
return Err(VerifierError::QuerySeedProofOfWorkVerificationFailed);
}
let mut query_positions = public_coin
.draw_integers(air.options().num_queries(), air.lde_domain_size(), pow_nonce)
.map_err(|_| VerifierError::RandomCoinError)?;
query_positions.sort_unstable();
query_positions.dedup();
let (queried_main_trace_states, queried_aux_trace_states) =
channel.read_queried_trace_states(&query_positions)?;
let queried_constraint_evaluations = channel.read_constraint_evaluations(&query_positions)?;
let composer = DeepComposer::new(&air, &query_positions, z, deep_coefficients);
let deep_evaluations = composer.compose_columns(
queried_main_trace_states,
queried_aux_trace_states,
queried_constraint_evaluations,
ood_main_trace_frame,
ood_aux_trace_frame,
ood_constraint_evaluations,
);
fri_verifier
.verify(&mut channel, &deep_evaluations, &query_positions)
.map_err(VerifierError::FriVerificationFailed)
}
pub enum AcceptableOptions {
MinConjecturedSecurity(u32),
MinProvenSecurity(u32),
OptionSet(Vec<ProofOptions>),
}
impl AcceptableOptions {
pub fn validate<H: Hasher>(&self, proof: &Proof) -> Result<(), VerifierError> {
match self {
AcceptableOptions::MinConjecturedSecurity(minimal_security) => {
let conjectured_security = proof.conjectured_security::<H>();
if !conjectured_security.is_at_least(*minimal_security) {
return Err(VerifierError::InsufficientConjecturedSecurity(
*minimal_security,
conjectured_security.bits(),
));
}
},
AcceptableOptions::MinProvenSecurity(minimal_security) => {
let proven_security = proof.proven_security::<H>();
if !proven_security.is_at_least(*minimal_security) {
return Err(VerifierError::InsufficientProvenSecurity(
*minimal_security,
cmp::max(proven_security.ldr_bits(), proven_security.udr_bits()),
));
}
},
AcceptableOptions::OptionSet(options) => {
if !options.iter().any(|opt| opt == proof.options()) {
return Err(VerifierError::UnacceptableProofOptions);
}
},
}
Ok(())
}
}