1use alloc::{string::ToString, vec, vec::Vec};
2
3use miden_core::proof::{ExecutionProof, HashFunction, PrecompileProof, PrecompileStatus, VmProof};
4use miden_processor::{
5 ExecutionError, ExecutionOptions, ExecutionWitness, FastProcessor, PrecompileWitness, Program,
6 StackInputs, StackOutputs, SyncHost, VmWitness,
7 advice::AdviceInputs,
8 trace::{self, VmTrace, build_trace_with_budget},
9};
10
11use crate::{config, prove_stark};
12
13#[derive(Debug, Clone, Eq, PartialEq)]
19pub struct Prover {
20 hash_fn: HashFunction,
21 max_prover_memory_bytes: u64,
22 max_precompile_prover_memory_bytes: u64,
23}
24
25impl Prover {
26 pub const DEFAULT_MAX_PROVER_MEMORY_BYTES: u64 = trace::DEFAULT_MAX_PROVER_MEMORY_BYTES;
33
34 pub const DEFAULT_MAX_PRECOMPILE_PROVER_MEMORY_BYTES: u64 =
37 miden_precompiles_prover::DEFAULT_MAX_PRECOMPILE_PROVER_MEMORY_BYTES;
38
39 pub const fn new() -> Self {
41 Self {
42 hash_fn: HashFunction::Blake3_256,
43 max_prover_memory_bytes: Self::DEFAULT_MAX_PROVER_MEMORY_BYTES,
44 max_precompile_prover_memory_bytes: Self::DEFAULT_MAX_PRECOMPILE_PROVER_MEMORY_BYTES,
45 }
46 }
47
48 #[must_use]
50 pub const fn with_hash_fn(mut self, hash_fn: HashFunction) -> Self {
51 self.hash_fn = hash_fn;
52 self
53 }
54
55 #[must_use]
58 pub const fn with_max_prover_memory_bytes(mut self, max_prover_memory_bytes: u64) -> Self {
59 self.max_prover_memory_bytes = max_prover_memory_bytes;
60 self
61 }
62
63 pub const fn max_prover_memory_bytes(&self) -> u64 {
66 self.max_prover_memory_bytes
67 }
68
69 #[must_use]
72 pub const fn with_max_precompile_prover_memory_bytes(
73 mut self,
74 max_precompile_prover_memory_bytes: u64,
75 ) -> Self {
76 self.max_precompile_prover_memory_bytes = max_precompile_prover_memory_bytes;
77 self
78 }
79
80 pub const fn max_precompile_prover_memory_bytes(&self) -> u64 {
83 self.max_precompile_prover_memory_bytes
84 }
85
86 pub fn prove(&self, witness: ExecutionWitness) -> Result<ExecutionProof, ProverError> {
91 let (vm_witness, precompile_witness) = witness.into_parts();
92 let vm = self.prove_vm(vm_witness)?;
93 let Some(precompile_witness) = precompile_witness else {
94 return Ok(ExecutionProof::new(vm, PrecompileStatus::Empty));
95 };
96 Ok(ExecutionProof::new(vm, PrecompileStatus::Deferred(precompile_witness)))
97 }
98
99 pub fn prove_full(&self, witness: ExecutionWitness) -> Result<ExecutionProof, ProverError> {
103 let (vm_witness, precompile_witness) = witness.into_parts();
104 let vm = self.prove_vm(vm_witness)?;
105 let precompile = precompile_witness
106 .map(|witness| self.prove_precompiles(vec![witness]))
107 .transpose()?;
108 let precompile = match precompile {
109 Some(precompile) => PrecompileStatus::Proven(precompile),
110 None => PrecompileStatus::Empty,
111 };
112 Ok(ExecutionProof::new(vm, precompile))
113 }
114
115 pub fn prove_vm_witness(&self, witness: VmWitness) -> Result<ExecutionProof, ProverError> {
120 if witness.has_precompiles() {
121 return Err(ProverError::VmWitnessHasPrecompiles);
122 }
123
124 let vm = self.prove_vm(witness)?;
125 Ok(ExecutionProof::new(vm, PrecompileStatus::Empty))
126 }
127
128 fn prove_vm(&self, witness: VmWitness) -> Result<VmProof, ProverError> {
130 let trace = {
131 let _span = tracing::info_span!("build_miden_vm_trace").entered();
132 build_trace_with_budget(witness, self.max_prover_memory_bytes)
133 .map_err(ProverError::TraceGeneration)?
134 };
135
136 self.prove_vm_trace(trace)
137 }
138
139 pub fn prove_precompiles(
148 &self,
149 witnesses: Vec<PrecompileWitness>,
150 ) -> Result<PrecompileProof, ProverError> {
151 miden_precompiles_prover::prove_precompiles_with_budget(
152 witnesses,
153 self.hash_fn,
154 self.max_precompile_prover_memory_bytes,
155 )
156 .map_err(ProverError::PrecompileProofGeneration)
157 }
158
159 #[cfg(feature = "std")]
160 fn prove_full_trace(
161 &self,
162 trace: VmTrace,
163 precompile: Option<PrecompileWitness>,
164 ) -> Result<ExecutionProof, ProverError> {
165 let vm = self.prove_vm_trace(trace)?;
166 let precompile =
167 precompile.map(|witness| self.prove_precompiles(vec![witness])).transpose()?;
168 let precompile = match precompile {
169 Some(precompile) => PrecompileStatus::Proven(precompile),
170 None => PrecompileStatus::Empty,
171 };
172 Ok(ExecutionProof::new(vm, precompile))
173 }
174
175 #[tracing::instrument(name = "miden_vm", skip_all)]
180 fn prove_vm_trace(&self, trace: VmTrace) -> Result<VmProof, ProverError> {
181 let trace_len_summary = trace.trace_len_summary();
182 let params = config::pcs_params();
183 tracing::event!(
184 tracing::Level::INFO,
185 "Generated execution traces: core={}, range={}, chiplets={}, poseidon2={}, padded={}, \
186 estimated_prover_memory_bytes={:?}",
187 trace_len_summary.core_trace_len(),
188 trace_len_summary.range_trace_len(),
189 trace_len_summary.chiplets_trace_len().trace_len(),
190 trace_len_summary.poseidon2_permutation_trace_len(),
191 trace_len_summary.padded_trace_len(),
192 trace_len_summary.prover_memory_bytes(¶ms)
193 );
194
195 let precompile_root = trace.precompile_root();
196 let (public_values, aux_inputs) = trace.public_inputs().to_air_inputs();
197 let (core_matrix, chiplets_matrix, poseidon2_matrix) = trace.into_air_matrices();
198
199 let proof_bytes = match self.hash_fn {
200 HashFunction::Blake3_256 => {
201 let config = config::blake3_256_config(params, config::RELATION_DIGEST);
202 prove_stark(
203 &config,
204 core_matrix,
205 chiplets_matrix,
206 poseidon2_matrix,
207 &public_values,
208 &aux_inputs,
209 )
210 },
211 HashFunction::Keccak => {
212 let config = config::keccak_config(params, config::RELATION_DIGEST);
213 prove_stark(
214 &config,
215 core_matrix,
216 chiplets_matrix,
217 poseidon2_matrix,
218 &public_values,
219 &aux_inputs,
220 )
221 },
222 HashFunction::Rpo256 => {
223 let config = config::rpo_config(params, config::RELATION_DIGEST);
224 prove_stark(
225 &config,
226 core_matrix,
227 chiplets_matrix,
228 poseidon2_matrix,
229 &public_values,
230 &aux_inputs,
231 )
232 },
233 HashFunction::Poseidon2 => {
234 let config = config::poseidon2_config(params, config::RELATION_DIGEST);
235 prove_stark(
236 &config,
237 core_matrix,
238 chiplets_matrix,
239 poseidon2_matrix,
240 &public_values,
241 &aux_inputs,
242 )
243 },
244 HashFunction::Rpx256 => {
245 let config = config::rpx_config(params, config::RELATION_DIGEST);
246 prove_stark(
247 &config,
248 core_matrix,
249 chiplets_matrix,
250 poseidon2_matrix,
251 &public_values,
252 &aux_inputs,
253 )
254 },
255 }
256 .map_err(ProverError::VmProofGeneration)?;
257
258 let proof = miden_core::proof::StarkProof::new(proof_bytes, self.hash_fn);
259 Ok(VmProof { proof, precompile_root })
260 }
261}
262
263#[tracing::instrument(name = "prove_program_sync", skip_all)]
272pub fn prove_sync(
273 prover: &Prover,
274 program: &Program,
275 stack_inputs: StackInputs,
276 advice_inputs: AdviceInputs,
277 host: &mut impl SyncHost,
278 execution_options: ExecutionOptions,
279) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
280 #[cfg(feature = "std")]
281 let overlapped_trace_build = execution_options.overlapped_trace_build();
282 let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, execution_options)
283 .map_err(ExecutionError::advice_error_no_context)?;
284
285 #[cfg(feature = "std")]
286 if overlapped_trace_build {
287 let (trace, precompile) = {
288 let _span = tracing::info_span!("execute_miden_vm").entered();
289 processor.execute_and_build_trace_sync(
290 program,
291 host,
292 prover.max_prover_memory_bytes(),
293 )?
294 };
295 let stack_outputs = *trace.stack_outputs();
296 let proof = prover
297 .prove_full_trace(trace, precompile)
298 .map_err(ProverError::into_execution_error)?;
299 return Ok((stack_outputs, proof));
300 }
301
302 let witness = {
303 let _span = tracing::info_span!("execute_miden_vm").entered();
304 processor.execute_for_proving_sync(program, host)?
305 };
306 let stack_outputs = *witness.claim().stack_outputs();
307 let proof = prover.prove_full(witness).map_err(ProverError::into_execution_error)?;
308 Ok((stack_outputs, proof))
309}
310
311impl Default for Prover {
312 fn default() -> Self {
313 Self::new()
314 }
315}
316
317#[derive(Debug, thiserror::Error)]
319#[non_exhaustive]
320pub enum ProverError {
321 #[error("VM witness contains deferred precompile work")]
323 VmWitnessHasPrecompiles,
324 #[error("failed to materialize VM execution trace: {0}")]
326 TraceGeneration(#[source] ExecutionError),
327 #[error("failed to prove VM execution trace: {0}")]
329 VmProofGeneration(#[source] ExecutionError),
330 #[error("failed to prove precompile witness: {0}")]
332 PrecompileProofGeneration(#[source] miden_precompiles_prover::PrecompileProvingError),
333}
334
335impl ProverError {
336 fn into_execution_error(self) -> ExecutionError {
337 match self {
338 Self::VmWitnessHasPrecompiles => ExecutionError::ProvingError(self.to_string()),
339 Self::TraceGeneration(error) | Self::VmProofGeneration(error) => error,
340 Self::PrecompileProofGeneration(error) => {
341 ExecutionError::ProvingError(error.to_string())
342 },
343 }
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn prover_uses_canonical_default_and_allows_hash_override() {
353 let prover = Prover::new();
354 assert_eq!(prover.hash_fn, HashFunction::Blake3_256);
355
356 let prover = prover.with_hash_fn(HashFunction::Poseidon2);
357 assert_eq!(prover.hash_fn, HashFunction::Poseidon2);
358 }
359
360 #[test]
361 fn prover_uses_canonical_memory_budget_and_allows_override() {
362 let prover = Prover::new();
363 assert_eq!(prover.max_prover_memory_bytes(), Prover::DEFAULT_MAX_PROVER_MEMORY_BYTES);
364
365 let prover = prover.with_max_prover_memory_bytes(1 << 20);
366 assert_eq!(prover.max_prover_memory_bytes(), 1 << 20);
367 }
368
369 #[test]
370 fn prover_uses_canonical_precompile_memory_budget_and_allows_override() {
371 let prover = Prover::new();
372 assert_eq!(
373 prover.max_precompile_prover_memory_bytes(),
374 Prover::DEFAULT_MAX_PRECOMPILE_PROVER_MEMORY_BYTES
375 );
376
377 let prover = prover.with_max_precompile_prover_memory_bytes(1 << 20);
378 assert_eq!(prover.max_precompile_prover_memory_bytes(), 1 << 20);
379 }
380
381 fn trivial_precompile_witness() -> PrecompileWitness {
386 use alloc::sync::Arc;
387
388 use miden_core::deferred::{DeferredState, Node, PrecompileRegistry, TRUE_DIGEST};
389
390 let registry = Arc::new(PrecompileRegistry::new());
391 let mut state =
392 DeferredState::new(registry).expect("empty registry state should initialize");
393 let statement = state
394 .register(Node::and(TRUE_DIGEST, TRUE_DIGEST))
395 .expect("trivial AND node should register");
396 state
397 .log_statement(statement)
398 .expect("trivial statement should log into the deferred root");
399 state
400 .into_witness()
401 .expect("trivial deferred state should export")
402 .expect("non-TRUE root should export a witness")
403 }
404
405 #[test]
406 fn prove_precompiles_applies_the_configured_memory_budget() {
407 let err = Prover::new()
411 .with_max_precompile_prover_memory_bytes(1)
412 .prove_precompiles(vec![trivial_precompile_witness()])
413 .expect_err("a 1-byte budget must reject even the minimal chiplet trace shapes");
414 assert!(
415 matches!(
416 err,
417 ProverError::PrecompileProofGeneration(
418 miden_precompiles_prover::PrecompileProvingError::MemoryBudgetExceeded {
419 budget_bytes: 1,
420 ..
421 }
422 )
423 ),
424 "expected MemoryBudgetExceeded {{ budget_bytes: 1, .. }}, got: {err:?}"
425 );
426 }
427}