1#![no_std]
2
3extern crate alloc;
4
5#[cfg(feature = "std")]
6extern crate std;
7
8use alloc::{boxed::Box, sync::Arc};
9
10use miden_air::{MidenMultiAir, PublicInputs, Statement, config};
11use miden_core::{
12 Felt,
13 deferred::{DEFAULT_MAX_DEFERRED_ELEMENTS, TRUE_DIGEST},
14 field::QuadFelt,
15};
16use miden_crypto::stark::{
17 StarkConfig, VerifierInstance, lmcs::Lmcs, proof::StarkProofData, verifier::VerifierError,
18};
19use miden_serde_utils::deserialize_schema_exact;
20use serde::de::DeserializeOwned;
21use serde_wincode::{SerdeCompat, wincode};
22
23const MAX_STARK_PROOF_BYTES: usize = 64 * 1024 * 1024;
25
26mod exports {
29 pub use miden_core::{
30 Word,
31 deferred::{DeferredState, IntegrityError},
32 program::{ExecutionClaim, KernelDescriptor, ProgramInfo, StackInputs, StackOutputs},
33 proof::{DeferredProof, ExecutionProof, HashFunction, StarkProof},
34 };
35 pub mod math {
36 pub use miden_core::Felt;
37 }
38}
39pub use exports::*;
40
41pub mod recursive;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Verifier {
54 max_deferred_elements: usize,
55}
56
57impl Default for Verifier {
58 fn default() -> Self {
59 Self {
60 max_deferred_elements: DEFAULT_MAX_DEFERRED_ELEMENTS,
61 }
62 }
63}
64
65impl Verifier {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub const fn with_max_deferred_elements(mut self, max_deferred_elements: usize) -> Self {
73 self.max_deferred_elements = max_deferred_elements;
74 self
75 }
76
77 pub fn verify(
92 &self,
93 proof: ExecutionProof,
94 claim: ExecutionClaim,
95 ) -> Result<u32, VerificationError> {
96 let miden_security_level = proof.security_level();
97 let (final_deferred_root, precompile_security_level) =
98 resolve_final_deferred_root(proof.deferred_proof())?;
99
100 verify_stark(claim, final_deferred_root, proof.miden_proof())?;
101
102 Ok(precompile_security_level
103 .map(|level| miden_security_level.min(level))
104 .unwrap_or(miden_security_level))
105 }
106
107 pub fn verify_partial(
124 &self,
125 proof: ExecutionProof,
126 claim: ExecutionClaim,
127 ) -> Result<(u32, Unsettled), VerificationError> {
128 let security_level = proof.security_level();
129 let deferred_state =
130 hydrate_deferred_state(proof.deferred_proof(), self.max_deferred_elements)?;
131
132 verify_stark(claim, deferred_state.root(), proof.miden_proof())?;
133
134 Ok((security_level, Unsettled(deferred_state)))
135 }
136}
137
138#[must_use = "the deferred obligation must be settled or re-exposed, not dropped"]
144#[derive(Debug)]
145pub struct Unsettled(DeferredState);
146
147impl Unsettled {
148 pub fn root(&self) -> Word {
150 self.0.root()
151 }
152
153 pub fn into_state(self) -> DeferredState {
155 self.0
156 }
157}
158
159pub fn verify(proof: ExecutionProof, claim: ExecutionClaim) -> Result<u32, VerificationError> {
171 Verifier::default().verify(proof, claim)
172}
173
174fn resolve_final_deferred_root(
178 deferred_proof: &DeferredProof,
179) -> Result<(Word, Option<u32>), VerificationError> {
180 match deferred_proof {
181 DeferredProof::Empty => Ok((TRUE_DIGEST, None)),
182 DeferredProof::Wire(_) => Err(VerificationError::UnsupportedDeferredProof),
183 DeferredProof::Stark { proof, .. } => {
184 let root = miden_precompiles_prover::verify_deferred(deferred_proof)?;
185 Ok((root, Some(stark_security_level(proof))))
186 },
187 }
188}
189
190fn hydrate_deferred_state(
191 deferred_proof: &DeferredProof,
192 max_deferred_elements: usize,
193) -> Result<DeferredState, VerificationError> {
194 match deferred_proof {
195 DeferredProof::Wire(wire) => Ok(DeferredState::from_wire(
196 Arc::new(miden_precompiles::registry()),
197 wire,
198 max_deferred_elements,
199 )?),
200 DeferredProof::Empty | DeferredProof::Stark { .. } => {
201 Err(VerificationError::UnsupportedDeferredProof)
202 },
203 }
204}
205
206fn stark_security_level(_proof: &StarkProof) -> u32 {
207 96
212}
213
214fn verify_stark(
215 claim: ExecutionClaim,
216 final_deferred_root: Word,
217 stark_proof: &StarkProof,
218) -> Result<(), VerificationError> {
219 let program_hash = claim.program_root();
220
221 let pub_inputs = PublicInputs::new(
222 claim.to_program_info(),
223 *claim.stack_inputs(),
224 *claim.stack_outputs(),
225 final_deferred_root,
226 );
227 let (public_values, aux_inputs) = pub_inputs.to_air_inputs();
228
229 let hash_fn = stark_proof.hash_fn();
230 let proof_bytes = stark_proof.bytes();
231 let params = config::pcs_params();
232 match hash_fn {
233 HashFunction::Blake3_256 => {
234 let config = config::blake3_256_config(params, config::RELATION_DIGEST);
235 verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
236 },
237 HashFunction::Rpo256 => {
238 let config = config::rpo_config(params, config::RELATION_DIGEST);
239 verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
240 },
241 HashFunction::Rpx256 => {
242 let config = config::rpx_config(params, config::RELATION_DIGEST);
243 verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
244 },
245 HashFunction::Poseidon2 => {
246 let config = config::poseidon2_config(params, config::RELATION_DIGEST);
247 verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
248 },
249 HashFunction::Keccak => {
250 let config = config::keccak_config(params, config::RELATION_DIGEST);
251 verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
252 },
253 }
254 .map_err(|e| VerificationError::StarkVerificationError(program_hash, Box::new(e)))?;
255
256 Ok(())
257}
258
259#[derive(Debug, thiserror::Error)]
264pub enum VerificationError {
265 #[error("failed to verify STARK proof for program with hash {0}")]
266 StarkVerificationError(Word, #[source] Box<StarkVerificationError>),
267 #[error("deferred-DAG integrity check failed: {0}")]
268 DeferredIntegrity(#[from] IntegrityError),
269 #[error("failed to verify STARK-backed deferred proof: {0}")]
270 DeferredStarkVerification(#[from] miden_precompiles_prover::VerifyError),
271 #[error("deferred proof form is not supported by this verification mode")]
272 UnsupportedDeferredProof,
273}
274
275#[derive(Debug, thiserror::Error)]
280pub enum StarkVerificationError {
281 #[error("failed to deserialize proof: {0}")]
282 Deserialization(#[from] wincode::error::ReadError),
283 #[error("STARK proof is too large: {size} bytes exceeds the {max} byte limit")]
284 ProofTooLarge { size: usize, max: usize },
285 #[error(transparent)]
286 Verifier(#[from] VerifierError),
287}
288
289fn verify_stark_proof<SC>(
295 config: &SC,
296 public_values: &[Felt],
297 aux_inputs: &[Felt],
298 proof_bytes: &[u8],
299) -> Result<(), StarkVerificationError>
300where
301 SC: StarkConfig<Felt, QuadFelt>,
302 <SC::Lmcs as Lmcs>::Commitment: DeserializeOwned,
303{
304 if proof_bytes.len() > MAX_STARK_PROOF_BYTES {
305 return Err(StarkVerificationError::ProofTooLarge {
306 size: proof_bytes.len(),
307 max: MAX_STARK_PROOF_BYTES,
308 });
309 }
310
311 let proof_encoding_config = wincode::config::Configuration::default()
312 .with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
313 let proof = deserialize_schema_exact::<SerdeCompat<StarkProofData<Felt, QuadFelt, SC>>, _>(
314 proof_bytes,
315 proof_encoding_config,
316 )?;
317
318 let mut challenger = config.challenger();
319 config::observe_protocol_params(config.pcs(), &mut challenger);
320
321 let statement = Statement::<Felt, QuadFelt, _>::new(
326 MidenMultiAir::new(),
327 public_values.to_vec(),
328 aux_inputs.to_vec(),
329 )
330 .map_err(|e| StarkVerificationError::Verifier(VerifierError::from(e)))?;
331
332 VerifierInstance::new(config, &statement, None)
333 .expect("Miden AIRs declare no preprocessed columns")
334 .verify(&proof, challenger)?;
335 Ok(())
336}
337
338#[cfg(test)]
339mod tests {
340 use alloc::vec::Vec;
341
342 use miden_core::deferred::DeferredStateWire;
343
344 use super::*;
345
346 #[test]
347 fn final_deferred_root_resolution_accepts_empty_rejects_wire_and_verifies_stark() {
348 let (root, security_level) = resolve_final_deferred_root(&DeferredProof::Empty).unwrap();
349 assert_eq!(root, TRUE_DIGEST);
350 assert_eq!(security_level, None);
351
352 let wire = DeferredProof::wire(DeferredStateWire::default());
353 let err = resolve_final_deferred_root(&wire).unwrap_err();
354 assert!(
355 matches!(err, VerificationError::UnsupportedDeferredProof),
356 "expected wire-backed partial proof to be rejected, got {err:?}"
357 );
358
359 let stark = DeferredProof::stark(
360 StarkProof::new(Vec::from([0_u8]), HashFunction::Poseidon2),
361 TRUE_DIGEST,
362 );
363 let err = resolve_final_deferred_root(&stark).unwrap_err();
364 assert!(
365 matches!(err, VerificationError::DeferredStarkVerification(_)),
366 "expected invalid STARK-backed precompile VM proof to be verified and rejected, got {err:?}"
367 );
368 }
369
370 #[test]
371 fn partial_deferred_hydration_accepts_wire_and_rejects_final_forms() {
372 let wire = DeferredStateWire::default();
373 let deferred_proof = DeferredProof::wire(wire.clone());
374 let deferred_state = hydrate_deferred_state(&deferred_proof, DEFAULT_MAX_DEFERRED_ELEMENTS)
375 .expect("empty wire should hydrate under the standard precompile registry");
376
377 assert_eq!(deferred_state.root(), TRUE_DIGEST);
378 assert_eq!(deferred_state.to_wire().unwrap(), wire);
379
380 for final_proof in [
381 DeferredProof::Empty,
382 DeferredProof::stark(
383 StarkProof::new(Vec::from([0_u8]), HashFunction::Poseidon2),
384 TRUE_DIGEST,
385 ),
386 ] {
387 let err =
388 hydrate_deferred_state(&final_proof, DEFAULT_MAX_DEFERRED_ELEMENTS).unwrap_err();
389 assert!(
390 matches!(err, VerificationError::UnsupportedDeferredProof),
391 "expected final proof material to be rejected by partial hydration, got {err:?}"
392 );
393 }
394 }
395
396 #[test]
397 fn proof_encoding_config_rejects_oversized_native_vec_preallocation() {
398 let proof_encoding_config = wincode::config::Configuration::default()
399 .with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
400 let element_count = MAX_STARK_PROOF_BYTES + 1;
401 let mut length_prefix = Vec::new();
402
403 <usize as wincode::config::Serialize<_>>::serialize_into(
404 &mut length_prefix,
405 &element_count,
406 proof_encoding_config,
407 )
408 .unwrap();
409 let err = <Vec<u8> as wincode::config::Deserialize<_>>::deserialize(
410 &length_prefix,
411 proof_encoding_config,
412 )
413 .unwrap_err();
414
415 assert!(
416 matches!(
417 err,
418 wincode::error::ReadError::PreallocationSizeLimit { needed, limit }
419 if needed == element_count && limit == MAX_STARK_PROOF_BYTES
420 ),
421 "expected proof encoding config to reject oversized allocation, got {err:?}"
422 );
423 }
424
425 #[test]
426 fn verify_stark_proof_rejects_oversized_proof_bytes() {
427 let params = config::pcs_params();
428 let config = config::poseidon2_config(params, config::RELATION_DIGEST);
429 let proof_bytes = Vec::from_iter(core::iter::repeat_n(0, MAX_STARK_PROOF_BYTES + 1));
430
431 let err = verify_stark_proof(&config, &[], &[], &proof_bytes).unwrap_err();
432
433 assert!(
434 matches!(
435 err,
436 StarkVerificationError::ProofTooLarge {
437 size,
438 max: MAX_STARK_PROOF_BYTES,
439 } if size == proof_bytes.len()
440 ),
441 "expected explicit proof byte limit to reject oversized proof, got {err:?}"
442 );
443 }
444}