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, aux_inputs) = 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, &aux_inputs, &proof_bytes)
147 },
148 HashFunction::Rpo256 => {
149 let config = config::rpo_config(params);
150 verify_stark_proof(&config, &public_values, &aux_inputs, &proof_bytes)
151 },
152 HashFunction::Rpx256 => {
153 let config = config::rpx_config(params);
154 verify_stark_proof(&config, &public_values, &aux_inputs, &proof_bytes)
155 },
156 HashFunction::Poseidon2 => {
157 let config = config::poseidon2_config(params);
158 verify_stark_proof(&config, &public_values, &aux_inputs, &proof_bytes)
159 },
160 HashFunction::Keccak => {
161 let config = config::keccak_config(params);
162 verify_stark_proof(&config, &public_values, &aux_inputs, &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 aux_inputs: &[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(
234 MidenMultiAir::new(),
235 public_values.to_vec(),
236 aux_inputs.to_vec(),
237 )
238 .map_err(|e| StarkVerificationError::Verifier(VerifierError::from(e)))?;
239
240 VerifierInstance::new(config, &statement, None)
241 .expect("Miden AIRs declare no preprocessed columns")
242 .verify(&proof, challenger)?;
243 Ok(())
244}
245
246#[cfg(test)]
247mod tests {
248 use alloc::vec::Vec;
249
250 use super::*;
251
252 #[test]
253 fn proof_encoding_config_rejects_oversized_native_vec_preallocation() {
254 let proof_encoding_config = wincode::config::Configuration::default()
255 .with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
256 let element_count = MAX_STARK_PROOF_BYTES + 1;
257 let mut length_prefix = Vec::new();
258
259 <usize as wincode::config::Serialize<_>>::serialize_into(
260 &mut length_prefix,
261 &element_count,
262 proof_encoding_config,
263 )
264 .unwrap();
265 let err = <Vec<u8> as wincode::config::Deserialize<_>>::deserialize(
266 &length_prefix,
267 proof_encoding_config,
268 )
269 .unwrap_err();
270
271 assert!(
272 matches!(
273 err,
274 wincode::error::ReadError::PreallocationSizeLimit { needed, limit }
275 if needed == element_count && limit == MAX_STARK_PROOF_BYTES
276 ),
277 "expected proof encoding config to reject oversized allocation, got {err:?}"
278 );
279 }
280
281 #[test]
282 fn verify_stark_proof_rejects_oversized_proof_bytes() {
283 let params = config::pcs_params();
284 let config = config::poseidon2_config(params);
285 let proof_bytes = Vec::from_iter(core::iter::repeat_n(0, MAX_STARK_PROOF_BYTES + 1));
286
287 let err = verify_stark_proof(&config, &[], &[], &proof_bytes).unwrap_err();
288
289 assert!(
290 matches!(
291 err,
292 StarkVerificationError::ProofTooLarge {
293 size,
294 max: MAX_STARK_PROOF_BYTES,
295 } if size == proof_bytes.len()
296 ),
297 "expected explicit proof byte limit to reject oversized proof, got {err:?}"
298 );
299 }
300}