use alloc::string::ToString;
use miden_core::proof::{ExecutionProof, HashFunction, PrecompileProof, VmProof};
use miden_processor::{
ExecutionError, ExecutionOptions, ExecutionWitness, FastProcessor, PrecompileWitness, Program,
StackInputs, StackOutputs, SyncHost, VmWitness,
advice::AdviceInputs,
trace::{self, VmTrace, build_trace_with_budget},
};
use crate::{config, prove_stark};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Prover {
hash_fn: HashFunction,
max_prover_memory_bytes: u64,
}
impl Prover {
pub const DEFAULT_MAX_PROVER_MEMORY_BYTES: u64 = trace::DEFAULT_MAX_PROVER_MEMORY_BYTES;
pub const fn new() -> Self {
Self {
hash_fn: HashFunction::Blake3_256,
max_prover_memory_bytes: Self::DEFAULT_MAX_PROVER_MEMORY_BYTES,
}
}
#[must_use]
pub const fn with_hash_fn(mut self, hash_fn: HashFunction) -> Self {
self.hash_fn = hash_fn;
self
}
#[must_use]
pub const fn with_max_prover_memory_bytes(mut self, max_prover_memory_bytes: u64) -> Self {
self.max_prover_memory_bytes = max_prover_memory_bytes;
self
}
pub const fn max_prover_memory_bytes(&self) -> u64 {
self.max_prover_memory_bytes
}
pub fn prove(&self, witness: ExecutionWitness) -> Result<ExecutionProof, ProverError> {
let (vm_witness, precompile_witness) = witness.into_parts();
let vm = self.prove_vm(vm_witness)?;
let Some(precompile_witness) = precompile_witness else {
return Ok(ExecutionProof::Complete { vm, precompile: None });
};
let precompile = precompile_witness
.state()
.to_wire()
.expect("execution witness state must have canonical deferred wire");
Ok(ExecutionProof::Deferred { vm, precompile })
}
pub fn prove_full(&self, witness: ExecutionWitness) -> Result<ExecutionProof, ProverError> {
let (vm_witness, precompile_witness) = witness.into_parts();
let vm = self.prove_vm(vm_witness)?;
let precompile = precompile_witness
.as_ref()
.map(|witness| self.prove_precompile(witness))
.transpose()?;
Ok(ExecutionProof::Complete { vm, precompile })
}
fn prove_vm(&self, witness: VmWitness) -> Result<VmProof, ProverError> {
let trace = {
let _span = tracing::info_span!("build_miden_vm_trace").entered();
build_trace_with_budget(witness, self.max_prover_memory_bytes)
.map_err(ProverError::TraceGeneration)?
};
self.prove_vm_trace(trace)
}
pub fn prove_precompile(
&self,
witness: &PrecompileWitness,
) -> Result<PrecompileProof, ProverError> {
let proof = miden_precompiles_prover::prove_deferred_state(witness.state(), self.hash_fn)
.map_err(ProverError::PrecompileProofGeneration)?;
Ok(PrecompileProof { proof, roots: witness.roots().to_vec() })
}
#[cfg(feature = "std")]
fn prove_full_trace(
&self,
trace: VmTrace,
precompile: Option<&PrecompileWitness>,
) -> Result<ExecutionProof, ProverError> {
let vm = self.prove_vm_trace(trace)?;
let precompile = precompile.map(|witness| self.prove_precompile(witness)).transpose()?;
Ok(ExecutionProof::Complete { vm, precompile })
}
#[tracing::instrument(name = "miden_vm", skip_all)]
fn prove_vm_trace(&self, trace: VmTrace) -> Result<VmProof, ProverError> {
let trace_len_summary = trace.trace_len_summary();
let params = config::pcs_params();
tracing::event!(
tracing::Level::INFO,
"Generated execution traces: core={}, range={}, chiplets={}, poseidon2={}, padded={}, \
estimated_prover_memory_bytes={:?}",
trace_len_summary.core_trace_len(),
trace_len_summary.range_trace_len(),
trace_len_summary.chiplets_trace_len().trace_len(),
trace_len_summary.poseidon2_permutation_trace_len(),
trace_len_summary.padded_trace_len(),
trace_len_summary.prover_memory_bytes(¶ms)
);
let precompile_root = trace.precompile_root();
let (public_values, aux_inputs) = trace.public_inputs().to_air_inputs();
let (core_matrix, chiplets_matrix, poseidon2_matrix) = trace.into_air_matrices();
let proof_bytes = match self.hash_fn {
HashFunction::Blake3_256 => {
let config = config::blake3_256_config(params, config::RELATION_DIGEST);
prove_stark(
&config,
core_matrix,
chiplets_matrix,
poseidon2_matrix,
&public_values,
&aux_inputs,
)
},
HashFunction::Keccak => {
let config = config::keccak_config(params, config::RELATION_DIGEST);
prove_stark(
&config,
core_matrix,
chiplets_matrix,
poseidon2_matrix,
&public_values,
&aux_inputs,
)
},
HashFunction::Rpo256 => {
let config = config::rpo_config(params, config::RELATION_DIGEST);
prove_stark(
&config,
core_matrix,
chiplets_matrix,
poseidon2_matrix,
&public_values,
&aux_inputs,
)
},
HashFunction::Poseidon2 => {
let config = config::poseidon2_config(params, config::RELATION_DIGEST);
prove_stark(
&config,
core_matrix,
chiplets_matrix,
poseidon2_matrix,
&public_values,
&aux_inputs,
)
},
HashFunction::Rpx256 => {
let config = config::rpx_config(params, config::RELATION_DIGEST);
prove_stark(
&config,
core_matrix,
chiplets_matrix,
poseidon2_matrix,
&public_values,
&aux_inputs,
)
},
}
.map_err(ProverError::VmProofGeneration)?;
let proof = miden_core::proof::StarkProof::new(proof_bytes, self.hash_fn);
Ok(VmProof { proof, precompile_root })
}
}
#[tracing::instrument(name = "prove_program_sync", skip_all)]
pub fn prove_sync(
prover: &Prover,
program: &Program,
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
host: &mut impl SyncHost,
execution_options: ExecutionOptions,
) -> Result<(StackOutputs, ExecutionProof), ExecutionError> {
#[cfg(feature = "std")]
let overlapped_trace_build = execution_options.overlapped_trace_build();
let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, execution_options)
.map_err(ExecutionError::advice_error_no_context)?;
#[cfg(feature = "std")]
if overlapped_trace_build {
let (trace, precompile) = {
let _span = tracing::info_span!("execute_miden_vm").entered();
processor.execute_and_build_trace_sync(
program,
host,
prover.max_prover_memory_bytes(),
)?
};
let stack_outputs = *trace.stack_outputs();
let proof = prover
.prove_full_trace(trace, precompile.as_ref())
.map_err(ProverError::into_execution_error)?;
return Ok((stack_outputs, proof));
}
let witness = {
let _span = tracing::info_span!("execute_miden_vm").entered();
processor.execute_for_proving_sync(program, host)?
};
let stack_outputs = *witness.claim().stack_outputs();
let proof = prover.prove_full(witness).map_err(ProverError::into_execution_error)?;
Ok((stack_outputs, proof))
}
impl Default for Prover {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ProverError {
#[error("failed to materialize VM execution trace: {0}")]
TraceGeneration(#[source] ExecutionError),
#[error("failed to prove VM execution trace: {0}")]
VmProofGeneration(#[source] ExecutionError),
#[error("failed to prove precompile witness: {0}")]
PrecompileProofGeneration(#[source] miden_precompiles_prover::ProveDeferredStateError),
}
impl ProverError {
fn into_execution_error(self) -> ExecutionError {
match self {
Self::TraceGeneration(error) | Self::VmProofGeneration(error) => error,
Self::PrecompileProofGeneration(error) => {
ExecutionError::ProvingError(error.to_string())
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prover_uses_canonical_default_and_allows_hash_override() {
let prover = Prover::new();
assert_eq!(prover.hash_fn, HashFunction::Blake3_256);
let prover = prover.with_hash_fn(HashFunction::Poseidon2);
assert_eq!(prover.hash_fn, HashFunction::Poseidon2);
}
#[test]
fn prover_uses_canonical_memory_budget_and_allows_override() {
let prover = Prover::new();
assert_eq!(prover.max_prover_memory_bytes(), Prover::DEFAULT_MAX_PROVER_MEMORY_BYTES);
let prover = prover.with_max_prover_memory_bytes(1 << 20);
assert_eq!(prover.max_prover_memory_bytes(), 1 << 20);
}
}