1#![no_std]
2
3extern crate alloc;
4
5#[cfg(feature = "std")]
6extern crate std;
7
8use alloc::{boxed::Box, vec::Vec};
9
10use miden_air::{MidenMultiAir, PublicInputs, Statement, config};
11use miden_core::{Felt, field::QuadFelt};
12use miden_crypto::stark::{
13 StarkConfig, VerifierInstance, lmcs::Lmcs, proof::StarkProofData, verifier::VerifierError,
14};
15use serde::de::DeserializeOwned;
16use serde_wincode::SerdeCompat;
17
18const MAX_STARK_PROOF_BYTES: usize = 64 * 1024 * 1024;
19
20mod exports {
23 pub use miden_core::{
24 Word,
25 precompile::{
26 PrecompileTranscriptState, PrecompileVerificationError, PrecompileVerifierRegistry,
27 },
28 program::{Kernel, ProgramInfo, StackInputs, StackOutputs},
29 proof::{ExecutionProof, HashFunction},
30 };
31 pub mod math {
32 pub use miden_core::Felt;
33 }
34}
35pub use exports::*;
36
37pub fn verify(
63 program_info: ProgramInfo,
64 stack_inputs: StackInputs,
65 stack_outputs: StackOutputs,
66 proof: ExecutionProof,
67) -> Result<u32, VerificationError> {
68 let (security_level, _commitment) = verify_with_precompiles(
69 program_info,
70 stack_inputs,
71 stack_outputs,
72 proof,
73 &PrecompileVerifierRegistry::new(),
74 )?;
75 Ok(security_level)
76}
77
78#[tracing::instrument("verify_program", skip_all)]
93pub fn verify_with_precompiles(
94 program_info: ProgramInfo,
95 stack_inputs: StackInputs,
96 stack_outputs: StackOutputs,
97 proof: ExecutionProof,
98 precompile_verifiers: &PrecompileVerifierRegistry,
99) -> Result<(u32, PrecompileTranscriptState), VerificationError> {
100 let security_level = proof.security_level();
101
102 let (hash_fn, proof_bytes, precompile_requests) = proof.into_parts();
103
104 let recomputed_transcript = precompile_verifiers
109 .requests_transcript(&precompile_requests)
110 .map_err(VerificationError::PrecompileVerificationError)?;
111 let pc_transcript_state = recomputed_transcript.state();
112
113 verify_stark(
114 program_info,
115 stack_inputs,
116 stack_outputs,
117 pc_transcript_state,
118 hash_fn,
119 proof_bytes,
120 )?;
121
122 Ok((security_level, pc_transcript_state))
123}
124
125fn verify_stark(
129 program_info: ProgramInfo,
130 stack_inputs: StackInputs,
131 stack_outputs: StackOutputs,
132 pc_transcript_state: PrecompileTranscriptState,
133 hash_fn: HashFunction,
134 proof_bytes: Vec<u8>,
135) -> Result<(), VerificationError> {
136 let program_hash = *program_info.program_hash();
137
138 let pub_inputs =
139 PublicInputs::new(program_info, stack_inputs, stack_outputs, pc_transcript_state);
140 let (public_values, kernel_felts) = pub_inputs.to_air_inputs();
141
142 let params = config::pcs_params();
143 match hash_fn {
144 HashFunction::Blake3_256 => {
145 let config = config::blake3_256_config(params);
146 verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
147 },
148 HashFunction::Rpo256 => {
149 let config = config::rpo_config(params);
150 verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
151 },
152 HashFunction::Rpx256 => {
153 let config = config::rpx_config(params);
154 verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
155 },
156 HashFunction::Poseidon2 => {
157 let config = config::poseidon2_config(params);
158 verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
159 },
160 HashFunction::Keccak => {
161 let config = config::keccak_config(params);
162 verify_stark_proof(&config, &public_values, &kernel_felts, &proof_bytes)
163 },
164 }
165 .map_err(|e| VerificationError::StarkVerificationError(program_hash, Box::new(e)))?;
166
167 Ok(())
168}
169
170#[derive(Debug, thiserror::Error)]
175pub enum VerificationError {
176 #[error("failed to verify STARK proof for program with hash {0}")]
177 StarkVerificationError(Word, #[source] Box<StarkVerificationError>),
178 #[error("failed to verify precompile calls")]
179 PrecompileVerificationError(#[source] PrecompileVerificationError),
180}
181
182#[derive(Debug, thiserror::Error)]
187pub enum StarkVerificationError {
188 #[error("failed to deserialize proof: {0}")]
189 Deserialization(#[from] wincode::error::ReadError),
190 #[error("STARK proof is too large: {size} bytes exceeds the {max} byte limit")]
191 ProofTooLarge { size: usize, max: usize },
192 #[error(transparent)]
193 Verifier(#[from] VerifierError),
194}
195
196fn verify_stark_proof<SC>(
202 config: &SC,
203 public_values: &[Felt],
204 kernel_felts: &[Felt],
205 proof_bytes: &[u8],
206) -> Result<(), StarkVerificationError>
207where
208 SC: StarkConfig<Felt, QuadFelt>,
209 <SC::Lmcs as Lmcs>::Commitment: DeserializeOwned,
210{
211 if proof_bytes.len() > MAX_STARK_PROOF_BYTES {
212 return Err(StarkVerificationError::ProofTooLarge {
213 size: proof_bytes.len(),
214 max: MAX_STARK_PROOF_BYTES,
215 });
216 }
217
218 let proof_encoding_config = wincode::config::Configuration::default()
219 .with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
220 let proof: StarkProofData<Felt, QuadFelt, SC> = <SerdeCompat<
221 StarkProofData<Felt, QuadFelt, SC>,
222 > as wincode::config::Deserialize<_>>::deserialize(
223 proof_bytes, proof_encoding_config
224 )?;
225
226 let mut challenger = config.challenger();
227 config::observe_protocol_params(&mut challenger);
228
229 let statement = Statement::<Felt, QuadFelt, _>::new(
233 MidenMultiAir::new(),
234 public_values.to_vec(),
235 kernel_felts.to_vec(),
236 )
237 .map_err(|e| StarkVerificationError::Verifier(VerifierError::from(e)))?;
238
239 VerifierInstance::new(config, &statement, None)
240 .expect("Miden AIRs declare no preprocessed columns")
241 .verify(&proof, challenger)?;
242 Ok(())
243}
244
245#[cfg(test)]
246mod tests {
247 use alloc::vec::Vec;
248
249 use super::*;
250
251 #[test]
252 fn proof_encoding_config_rejects_oversized_native_vec_preallocation() {
253 let proof_encoding_config = wincode::config::Configuration::default()
254 .with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
255 let element_count = MAX_STARK_PROOF_BYTES + 1;
256 let mut length_prefix = Vec::new();
257
258 <usize as wincode::config::Serialize<_>>::serialize_into(
259 &mut length_prefix,
260 &element_count,
261 proof_encoding_config,
262 )
263 .unwrap();
264 let err = <Vec<u8> as wincode::config::Deserialize<_>>::deserialize(
265 &length_prefix,
266 proof_encoding_config,
267 )
268 .unwrap_err();
269
270 assert!(
271 matches!(
272 err,
273 wincode::error::ReadError::PreallocationSizeLimit { needed, limit }
274 if needed == element_count && limit == MAX_STARK_PROOF_BYTES
275 ),
276 "expected proof encoding config to reject oversized allocation, got {err:?}"
277 );
278 }
279
280 #[test]
281 fn verify_stark_proof_rejects_oversized_proof_bytes() {
282 let params = config::pcs_params();
283 let config = config::poseidon2_config(params);
284 let proof_bytes = Vec::from_iter(core::iter::repeat_n(0, MAX_STARK_PROOF_BYTES + 1));
285
286 let err = verify_stark_proof(&config, &[], &[], &proof_bytes).unwrap_err();
287
288 assert!(
289 matches!(
290 err,
291 StarkVerificationError::ProofTooLarge {
292 size,
293 max: MAX_STARK_PROOF_BYTES,
294 } if size == proof_bytes.len()
295 ),
296 "expected explicit proof byte limit to reject oversized proof, got {err:?}"
297 );
298 }
299}