#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
use alloc::{boxed::Box, vec::Vec};
use miden_air::{MidenMultiAir, PublicInputs, Statement, config};
use miden_core::{Felt, field::QuadFelt};
use miden_crypto::stark::{
StarkConfig, VerifierInstance, lmcs::Lmcs, proof::StarkProofData, verifier::VerifierError,
};
use serde::de::DeserializeOwned;
use serde_wincode::SerdeCompat;
const MAX_STARK_PROOF_BYTES: usize = 64 * 1024 * 1024;
mod exports {
pub use miden_core::{
Word,
precompile::{
PrecompileTranscriptState, PrecompileVerificationError, PrecompileVerifierRegistry,
},
program::{Kernel, ProgramInfo, StackInputs, StackOutputs},
proof::{ExecutionProof, HashFunction},
};
pub mod math {
pub use miden_core::Felt;
}
}
pub use exports::*;
pub fn verify(
program_info: ProgramInfo,
stack_inputs: StackInputs,
stack_outputs: StackOutputs,
proof: ExecutionProof,
) -> Result<u32, VerificationError> {
let (security_level, _commitment) = verify_with_precompiles(
program_info,
stack_inputs,
stack_outputs,
proof,
&PrecompileVerifierRegistry::new(),
)?;
Ok(security_level)
}
#[tracing::instrument("verify_program", skip_all)]
pub fn verify_with_precompiles(
program_info: ProgramInfo,
stack_inputs: StackInputs,
stack_outputs: StackOutputs,
proof: ExecutionProof,
precompile_verifiers: &PrecompileVerifierRegistry,
) -> Result<(u32, PrecompileTranscriptState), VerificationError> {
let security_level = proof.security_level();
let (hash_fn, proof_bytes, precompile_requests) = proof.into_parts();
let recomputed_transcript = precompile_verifiers
.requests_transcript(&precompile_requests)
.map_err(VerificationError::PrecompileVerificationError)?;
let pc_transcript_state = recomputed_transcript.state();
verify_stark(
program_info,
stack_inputs,
stack_outputs,
pc_transcript_state,
hash_fn,
proof_bytes,
)?;
Ok((security_level, pc_transcript_state))
}
fn verify_stark(
program_info: ProgramInfo,
stack_inputs: StackInputs,
stack_outputs: StackOutputs,
pc_transcript_state: PrecompileTranscriptState,
hash_fn: HashFunction,
proof_bytes: Vec<u8>,
) -> Result<(), VerificationError> {
let program_hash = *program_info.program_hash();
let pub_inputs =
PublicInputs::new(program_info, stack_inputs, stack_outputs, pc_transcript_state);
let (public_values, kernel_felts) = pub_inputs.to_air_inputs();
let params = config::pcs_params();
match hash_fn {
HashFunction::Blake3_256 => {
let config = config::blake3_256_config(params);
verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
},
HashFunction::Rpo256 => {
let config = config::rpo_config(params);
verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
},
HashFunction::Rpx256 => {
let config = config::rpx_config(params);
verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
},
HashFunction::Poseidon2 => {
let config = config::poseidon2_config(params);
verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
},
HashFunction::Keccak => {
let config = config::keccak_config(params);
verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
},
}
.map_err(|e| VerificationError::StarkVerificationError(program_hash, Box::new(e)))?;
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum VerificationError {
#[error("failed to verify STARK proof for program with hash {0}")]
StarkVerificationError(Word, #[source] Box<StarkVerificationError>),
#[error("failed to verify precompile calls")]
PrecompileVerificationError(#[source] PrecompileVerificationError),
}
#[derive(Debug, thiserror::Error)]
pub enum StarkVerificationError {
#[error("failed to deserialize proof: {0}")]
Deserialization(#[from] wincode::error::ReadError),
#[error("STARK proof is too large: {size} bytes exceeds the {max} byte limit")]
ProofTooLarge { size: usize, max: usize },
#[error(transparent)]
Verifier(#[from] VerifierError),
}
fn verify_stark_proof<SC>(
config: &SC,
public_values: &[Felt],
kernel_felts: &[Felt],
proof_bytes: &[u8],
) -> Result<(), StarkVerificationError>
where
SC: StarkConfig<Felt, QuadFelt>,
<SC::Lmcs as Lmcs>::Commitment: DeserializeOwned,
{
if proof_bytes.len() > MAX_STARK_PROOF_BYTES {
return Err(StarkVerificationError::ProofTooLarge {
size: proof_bytes.len(),
max: MAX_STARK_PROOF_BYTES,
});
}
let proof_encoding_config = wincode::config::Configuration::default()
.with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
let proof: StarkProofData<Felt, QuadFelt, SC> = <SerdeCompat<
StarkProofData<Felt, QuadFelt, SC>,
> as wincode::config::Deserialize<_>>::deserialize(
proof_bytes, proof_encoding_config
)?;
let mut challenger = config.challenger();
config::observe_protocol_params(&mut challenger);
let statement = Statement::<Felt, QuadFelt, _>::new(
MidenMultiAir::new(),
public_values.to_vec(),
kernel_felts.to_vec(),
)
.map_err(|e| StarkVerificationError::Verifier(VerifierError::from(e)))?;
VerifierInstance::new(config, &statement, None)
.expect("Miden AIRs declare no preprocessed columns")
.verify(&proof, challenger)?;
Ok(())
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use super::*;
#[test]
fn proof_encoding_config_rejects_oversized_native_vec_preallocation() {
let proof_encoding_config = wincode::config::Configuration::default()
.with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
let element_count = MAX_STARK_PROOF_BYTES + 1;
let mut length_prefix = Vec::new();
<usize as wincode::config::Serialize<_>>::serialize_into(
&mut length_prefix,
&element_count,
proof_encoding_config,
)
.unwrap();
let err = <Vec<u8> as wincode::config::Deserialize<_>>::deserialize(
&length_prefix,
proof_encoding_config,
)
.unwrap_err();
assert!(
matches!(
err,
wincode::error::ReadError::PreallocationSizeLimit { needed, limit }
if needed == element_count && limit == MAX_STARK_PROOF_BYTES
),
"expected proof encoding config to reject oversized allocation, got {err:?}"
);
}
#[test]
fn verify_stark_proof_rejects_oversized_proof_bytes() {
let params = config::pcs_params();
let config = config::poseidon2_config(params);
let proof_bytes = Vec::from_iter(core::iter::repeat_n(0, MAX_STARK_PROOF_BYTES + 1));
let err = verify_stark_proof(&config, &[], &[], &proof_bytes).unwrap_err();
assert!(
matches!(
err,
StarkVerificationError::ProofTooLarge {
size,
max: MAX_STARK_PROOF_BYTES,
} if size == proof_bytes.len()
),
"expected explicit proof byte limit to reject oversized proof, got {err:?}"
);
}
}