Skip to main content

miden_prover/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5#[cfg(feature = "std")]
6extern crate std;
7
8use alloc::{string::ToString, vec, vec::Vec};
9
10use ::serde::Serialize;
11use miden_air::{MidenMultiAir, ProverStatement, Statement};
12use miden_core::{Felt, field::QuadFelt, utils::RowMajorMatrix};
13use miden_crypto::stark::{
14    ProverInstance, StarkConfig,
15    lmcs::Lmcs,
16    proof::{StarkOutput, StarkProofData},
17};
18use miden_processor::{
19    FastProcessor, Program,
20    trace::{ExecutionTrace, build_trace},
21};
22use serde_wincode::{SerdeCompat, wincode};
23use tracing::instrument;
24
25mod proving_options;
26
27// EXPORTS
28// ================================================================================================
29pub use miden_air::{DeserializationError, MidenAir, PublicInputs, config};
30pub use miden_core::proof::{DeferredProof, ExecutionProof, HashFunction, StarkProof};
31pub use miden_processor::{
32    ExecutionError, ExecutionOptions, ExecutionOutput, FutureMaybeSend, Host, InputError,
33    ProgramInfo, StackInputs, StackOutputs, SyncHost, TraceBuildInputs, TraceGenerationContext,
34    Word, advice::AdviceInputs, crypto, field, serde, utils,
35};
36pub use proving_options::ProvingOptions;
37
38/// Inputs required to prove from pre-executed trace data.
39#[derive(Debug)]
40pub struct TraceProvingInputs {
41    trace_inputs: TraceBuildInputs,
42    options: ProvingOptions,
43}
44
45impl TraceProvingInputs {
46    /// Creates a new bundle of post-execution trace inputs and proof-generation options.
47    pub fn new(trace_inputs: TraceBuildInputs, options: ProvingOptions) -> Self {
48        Self { trace_inputs, options }
49    }
50
51    /// Consumes this bundle and returns its trace inputs and proof-generation options.
52    pub fn into_parts(self) -> (TraceBuildInputs, ProvingOptions) {
53        (self.trace_inputs, self.options)
54    }
55}
56
57// PROVER
58// ================================================================================================
59
60/// Executes and proves the specified `program` and returns the result together with a final
61/// STARK-based proof of the program's execution.
62///
63/// - `stack_inputs` specifies the initial state of the stack for the VM.
64/// - `advice_inputs` provides the initial nondeterministic inputs for the VM.
65/// - `host` specifies the host environment which contain non-deterministic (secret) inputs for the
66///   prover.
67/// - `execution_options` defines VM execution parameters such as cycle limits and fragmentation.
68/// - `proving_options` defines parameters for STARK proof generation.
69///
70/// # Errors
71/// Returns an error if program execution or STARK proof generation fails for any reason.
72#[instrument("prove_program", skip_all)]
73pub async fn prove(
74    program: &Program,
75    stack_inputs: StackInputs,
76    advice_inputs: AdviceInputs,
77    host: &mut impl Host,
78    execution_options: ExecutionOptions,
79    proving_options: ProvingOptions,
80) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
81    // execute the program to create an execution trace using FastProcessor
82    let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, execution_options)
83        .map_err(ExecutionError::advice_error_no_context)?;
84
85    let trace_inputs = {
86        let _span = tracing::info_span!("execute_miden_vm").entered();
87        processor.execute_trace_inputs(program, host).await?
88    };
89    prove_from_trace_sync(TraceProvingInputs::new(trace_inputs, proving_options))
90}
91
92/// Executes and proves the specified `program`, preserving wire-backed deferred proof material.
93///
94/// Use this when precompile claims should be proved later by a delegated or batching prover. The
95/// default [`prove`] API produces final deferred proof material instead.
96#[instrument("prove_program_partial", skip_all)]
97pub async fn prove_partial(
98    program: &Program,
99    stack_inputs: StackInputs,
100    advice_inputs: AdviceInputs,
101    host: &mut impl Host,
102    execution_options: ExecutionOptions,
103    proving_options: ProvingOptions,
104) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
105    let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, execution_options)
106        .map_err(ExecutionError::advice_error_no_context)?;
107
108    let trace_inputs = {
109        let _span = tracing::info_span!("execute_miden_vm").entered();
110        processor.execute_trace_inputs(program, host).await?
111    };
112    prove_partial_from_trace_sync(TraceProvingInputs::new(trace_inputs, proving_options))
113}
114
115/// Synchronous variant of [`prove()`].
116///
117/// Unlike `prove`, the sync path can overlap hasher-chiplet trace building with program
118/// execution (controlled by [`ExecutionOptions::with_overlapped_trace_build`], on by
119/// default); the produced trace and proof are identical either way.
120#[instrument("prove_program_sync", skip_all)]
121pub fn prove_sync(
122    program: &Program,
123    stack_inputs: StackInputs,
124    advice_inputs: AdviceInputs,
125    host: &mut impl SyncHost,
126    execution_options: ExecutionOptions,
127    proving_options: ProvingOptions,
128) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
129    // Snapshot the overlap flag before `execution_options` moves into the processor. The
130    // binding is std-gated only because the branch consuming it is: on no_std the streaming
131    // builder's thread does not exist and the flag is documented as ignored.
132    #[cfg(feature = "std")]
133    let overlapped_trace_build = execution_options.overlapped_trace_build();
134    let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, execution_options)
135        .map_err(ExecutionError::advice_error_no_context)?;
136
137    // Overlapped path: the hasher chiplet builds concurrently with execution,
138    // hiding the dominant serial part of trace building.
139    #[cfg(feature = "std")]
140    if overlapped_trace_build {
141        let trace = {
142            let _span = tracing::info_span!("execute_miden_vm").entered();
143            processor.execute_and_build_trace_sync(program, host)?
144        };
145        return prove_final_execution_trace(trace, proving_options);
146    }
147
148    let trace_inputs = {
149        let _span = tracing::info_span!("execute_miden_vm").entered();
150        processor.execute_trace_inputs_sync(program, host)?
151    };
152    prove_from_trace_sync(TraceProvingInputs::new(trace_inputs, proving_options))
153}
154
155/// Synchronous variant of [`prove_partial()`].
156///
157/// Like [`prove_sync`], the sync path can overlap hasher-chiplet trace building with program
158/// execution (controlled by [`ExecutionOptions::with_overlapped_trace_build`], on by
159/// default); the produced trace and proof are identical either way.
160#[instrument("prove_program_partial_sync", skip_all)]
161pub fn prove_partial_sync(
162    program: &Program,
163    stack_inputs: StackInputs,
164    advice_inputs: AdviceInputs,
165    host: &mut impl SyncHost,
166    execution_options: ExecutionOptions,
167    proving_options: ProvingOptions,
168) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
169    // Snapshot the overlap flag before `execution_options` moves into the processor. The
170    // binding is std-gated only because the branch consuming it is: on no_std the streaming
171    // builder's thread does not exist and the flag is documented as ignored.
172    #[cfg(feature = "std")]
173    let overlapped_trace_build = execution_options.overlapped_trace_build();
174    let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, execution_options)
175        .map_err(ExecutionError::advice_error_no_context)?;
176
177    // Overlapped path, mirroring `prove_sync`.
178    #[cfg(feature = "std")]
179    if overlapped_trace_build {
180        let trace = {
181            let _span = tracing::info_span!("execute_miden_vm").entered();
182            processor.execute_and_build_trace_sync(program, host)?
183        };
184        return prove_partial_execution_trace(trace, proving_options);
185    }
186
187    let trace_inputs = {
188        let _span = tracing::info_span!("execute_miden_vm").entered();
189        processor.execute_trace_inputs_sync(program, host)?
190    };
191    prove_partial_from_trace_sync(TraceProvingInputs::new(trace_inputs, proving_options))
192}
193
194/// Builds an execution trace from pre-executed trace inputs and proves it synchronously.
195///
196/// This is useful when program execution has already happened elsewhere and only trace building
197/// plus proof generation remain. The execution settings are already reflected in the supplied
198/// `TraceBuildInputs`, so only proof-generation options remain in this API.
199#[instrument("prove_trace_sync", skip_all)]
200pub fn prove_from_trace_sync(
201    inputs: TraceProvingInputs,
202) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
203    let (trace_inputs, options) = inputs.into_parts();
204    let trace = {
205        let _span = tracing::info_span!("build_miden_vm_trace").entered();
206        build_trace(trace_inputs)?
207    };
208    prove_final_execution_trace(trace, options)
209}
210
211/// Builds an execution trace from pre-executed trace inputs and proves it synchronously, preserving
212/// wire-backed deferred proof material.
213///
214/// This is the explicit partial-proof counterpart to [`prove_from_trace_sync`].
215#[instrument("prove_partial_trace_sync", skip_all)]
216pub fn prove_partial_from_trace_sync(
217    inputs: TraceProvingInputs,
218) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
219    let (trace_inputs, options) = inputs.into_parts();
220    let trace = {
221        let _span = tracing::info_span!("build_miden_vm_trace").entered();
222        build_trace(trace_inputs)?
223    };
224    prove_partial_execution_trace(trace, options)
225}
226
227fn prove_final_execution_trace(
228    trace: ExecutionTrace,
229    options: ProvingOptions,
230) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
231    let hash_fn = options.hash_fn();
232    let deferred_proof = {
233        let _span = tracing::info_span!("precompile_vm").entered();
234        miden_precompiles_prover::prove_deferred_state(trace.deferred_state(), hash_fn)
235            .map_err(|err| ExecutionError::ProvingError(err.to_string()))?
236    };
237
238    prove_miden_vm_execution_trace(trace, options, deferred_proof)
239}
240
241fn prove_partial_execution_trace(
242    trace: ExecutionTrace,
243    options: ProvingOptions,
244) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
245    let deferred_proof = {
246        let _precompile_vm_span = tracing::info_span!("precompile_vm").entered();
247        let _serialize_witness_span = tracing::info_span!("serialize_witness").entered();
248        let wire = trace
249            .deferred_state()
250            .to_wire()
251            .map_err(|err| ExecutionError::ProvingError(err.to_string()))?;
252        DeferredProof::Wire(wire)
253    };
254
255    prove_miden_vm_execution_trace(trace, options, deferred_proof)
256}
257
258#[instrument("miden_vm", skip_all)]
259fn prove_miden_vm_execution_trace(
260    trace: ExecutionTrace,
261    options: ProvingOptions,
262    deferred_proof: DeferredProof,
263) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
264    let trace_len_summary = trace.trace_len_summary();
265    tracing::event!(
266        tracing::Level::INFO,
267        "Generated execution traces: core={}, range={}, chiplets={}, poseidon2={}, padded={}",
268        trace_len_summary.core_trace_len(),
269        trace_len_summary.range_trace_len(),
270        trace_len_summary.chiplets_trace_len().trace_len(),
271        trace_len_summary.poseidon2_permutation_trace_len(),
272        trace_len_summary.padded_trace_len()
273    );
274
275    let stack_outputs = *trace.stack_outputs();
276    let hash_fn = options.hash_fn();
277
278    // Extract public inputs before consuming the trace for the per-AIR matrices.
279    let (public_values, aux_inputs) = trace.public_inputs().to_air_inputs();
280
281    let (core_matrix, chiplets_matrix, poseidon2_matrix) = trace.into_air_matrices();
282
283    let params = config::pcs_params();
284    let proof_bytes = match hash_fn {
285        HashFunction::Blake3_256 => {
286            let config = config::blake3_256_config(params, config::RELATION_DIGEST);
287            prove_stark(
288                &config,
289                core_matrix,
290                chiplets_matrix,
291                poseidon2_matrix,
292                &public_values,
293                &aux_inputs,
294            )
295        },
296        HashFunction::Keccak => {
297            let config = config::keccak_config(params, config::RELATION_DIGEST);
298            prove_stark(
299                &config,
300                core_matrix,
301                chiplets_matrix,
302                poseidon2_matrix,
303                &public_values,
304                &aux_inputs,
305            )
306        },
307        HashFunction::Rpo256 => {
308            let config = config::rpo_config(params, config::RELATION_DIGEST);
309            prove_stark(
310                &config,
311                core_matrix,
312                chiplets_matrix,
313                poseidon2_matrix,
314                &public_values,
315                &aux_inputs,
316            )
317        },
318        HashFunction::Poseidon2 => {
319            let config = config::poseidon2_config(params, config::RELATION_DIGEST);
320            prove_stark(
321                &config,
322                core_matrix,
323                chiplets_matrix,
324                poseidon2_matrix,
325                &public_values,
326                &aux_inputs,
327            )
328        },
329        HashFunction::Rpx256 => {
330            let config = config::rpx_config(params, config::RELATION_DIGEST);
331            prove_stark(
332                &config,
333                core_matrix,
334                chiplets_matrix,
335                poseidon2_matrix,
336                &public_values,
337                &aux_inputs,
338            )
339        },
340    }?;
341
342    let proof = ExecutionProof::from_parts(proof_bytes, hash_fn, deferred_proof);
343
344    Ok((stack_outputs, proof))
345}
346
347// STARK PROOF GENERATION
348// ================================================================================================
349
350/// Generates a multi-AIR STARK proof for the Miden trace set and public values.
351///
352/// Pre-seeds the challenger with the protocol parameters, the AIR public values, and the
353/// statement `aux_inputs` (program hash, final deferred root, and the concatenated kernel-procedure
354/// digests). Then delegates to the lifted multi-AIR prover.
355#[instrument("prove_stark", skip_all)]
356pub fn prove_stark<SC>(
357    config: &SC,
358    core_trace: RowMajorMatrix<Felt>,
359    chiplets_trace: RowMajorMatrix<Felt>,
360    poseidon2_trace: RowMajorMatrix<Felt>,
361    public_values: &[Felt],
362    aux_inputs: &[Felt],
363) -> Result<Vec<u8>, ExecutionError>
364where
365    SC: StarkConfig<Felt, QuadFelt>,
366    <SC::Lmcs as Lmcs>::Commitment: Serialize,
367{
368    let mut challenger = config.challenger();
369    config::observe_protocol_params(config.pcs(), &mut challenger);
370
371    // `air_inputs` are the public values read by the AIRs (stack i/o); `aux_inputs` are the
372    // statement inputs read during observation/boundary correction.
373    let statement =
374        Statement::new(MidenMultiAir::new(), public_values.to_vec(), aux_inputs.to_vec())
375            .map_err(|e| ExecutionError::ProvingError(e.to_string()))?;
376    let prover_statement =
377        ProverStatement::new(statement, vec![core_trace, chiplets_trace, poseidon2_trace])
378            .map_err(|e| ExecutionError::ProvingError(e.to_string()))?;
379
380    let output: StarkOutput<Felt, QuadFelt, SC> =
381        ProverInstance::new(config, &prover_statement, None)
382            .map_err(|e| ExecutionError::ProvingError(e.to_string()))?
383            .prove(challenger)
384            .map_err(|e| ExecutionError::ProvingError(e.to_string()))?;
385
386    let proof_encoding_config = wincode::config::Configuration::default();
387    let proof_bytes =
388        <SerdeCompat<StarkProofData<Felt, QuadFelt, SC>> as wincode::config::Serialize<_>>::serialize(
389            &output.proof,
390            proof_encoding_config,
391        )
392        .map_err(|e| ExecutionError::ProvingError(e.to_string()))?;
393    Ok(proof_bytes)
394}