use std::{path::PathBuf, time::Instant};
use clap::Parser;
use miden_assembly::diagnostics::{IntoDiagnostic, Report, WrapErr};
use miden_core_lib::CoreLibrary;
use miden_processor::{DefaultHost, ExecutionOptions, FastProcessor};
use miden_vm::{
HashFunction, ProvingOptions, TraceProvingInputs, internal::InputFile, prove_from_trace_sync,
};
use super::{
data::{Libraries, OutputFile, ProofFile},
utils::{get_masm_program, get_masp_program},
};
#[derive(Debug, Clone, Parser)]
#[command(about = "Prove a Miden program")]
pub struct ProveCmd {
#[arg(value_parser)]
program_file: PathBuf,
#[arg(short = 'e', long = "exp-cycles", default_value = "64")]
expected_cycles: u32,
#[arg(short = 'i', long = "input", value_parser)]
input_file: Option<PathBuf>,
#[arg(short = 'l', long = "libraries", value_parser)]
library_paths: Vec<PathBuf>,
#[arg(short = 'm', long = "max-cycles", default_value_t = ExecutionOptions::MAX_CYCLES)]
max_cycles: u32,
#[arg(short = 'n', long = "num-outputs", default_value = "16")]
num_outputs: usize,
#[arg(short = 'o', long = "output", value_parser)]
output_file: Option<PathBuf>,
#[arg(short = 'p', long = "proof", value_parser)]
proof_file: Option<PathBuf>,
#[arg(long = "hasher", default_value = "blake3-256")]
hasher: String,
#[arg(short = 's', long = "security", default_value = "96bits")]
security: String,
#[arg(long = "kernel", value_parser)]
kernel_file: Option<PathBuf>,
}
impl ProveCmd {
pub fn get_execution_options(&self) -> Result<ExecutionOptions, Report> {
ExecutionOptions::new(
Some(self.max_cycles),
self.expected_cycles,
ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
)
.map_err(|err| Report::msg(format!("{err}")))
}
pub fn get_proof_options(&self) -> Result<ProvingOptions, Report> {
let hash_fn = HashFunction::try_from(self.hasher.as_str())
.map_err(|err| Report::msg(format!("{err}")))?;
let proving_options = match self.security.as_str() {
"96bits" => ProvingOptions::with_96_bit_security(hash_fn),
other => {
return Err(Report::msg(format!(
"{other} is not a valid security setting. Currently only '96bits' is supported."
)));
},
};
Ok(proving_options)
}
pub fn execute(&self) -> Result<(), Report> {
println!("===============================================================================");
println!("Prove program: {}", self.program_file.display());
println!("-------------------------------------------------------------------------------");
let libraries = Libraries::new(&self.library_paths)?;
if let Some(ref kernel_path) = self.kernel_file
&& !kernel_path.is_file()
{
return Err(Report::msg(format!(
"Kernel file `{}` must be a file.",
kernel_path.display()
)));
}
let ext = self
.program_file
.extension()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_lowercase();
let input_data = InputFile::read(&self.input_file, &self.program_file)?;
let host = DefaultHost::default().with_library(&CoreLibrary::default())?;
let (program, package_debug_info, entrypoint_source_node, mut host) = match ext.as_str() {
"masp" => (get_masp_program(&self.program_file)?, None, None, host),
"masm" => {
let (program, package_debug_info, entrypoint_source_node, source_manager) =
get_masm_program(&self.program_file, &libraries, self.kernel_file.as_deref())?;
let mut host = host.with_source_manager(source_manager);
for library in libraries.libraries.iter().cloned() {
host.load_library(library)
.into_diagnostic()
.wrap_err("Failed to load library")?;
}
(program, package_debug_info, entrypoint_source_node, host)
},
_ => return Err(Report::msg("The provided file must have a .masm or .masp extension")),
};
let program_hash: [u8; 32] = program.hash().into();
println!("Proving program with hash {}...", hex::encode(program_hash));
let now = Instant::now();
let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?;
let advice_inputs = input_data.parse_advice_inputs().map_err(Report::msg)?;
let execution_options = self.get_execution_options()?;
let proving_options = self.get_proof_options()?;
let processor =
FastProcessor::new_with_options(stack_inputs, advice_inputs, execution_options)
.map_err(|err| Report::msg(format!("{err}")))?;
let trace_inputs = match (package_debug_info.as_ref(), entrypoint_source_node) {
(Some(debug_info), Some(entrypoint_source_node_id)) => processor
.execute_trace_inputs_with_package_debug_info_at_source_node_sync(
&program,
debug_info,
entrypoint_source_node_id,
&mut host,
)
.wrap_err("Failed to execute program")?,
(Some(debug_info), None) => processor
.execute_trace_inputs_with_package_debug_info_sync(&program, debug_info, &mut host)
.wrap_err("Failed to execute program")?,
(None, _) => processor
.execute_trace_inputs_sync(&program, &mut host)
.wrap_err("Failed to execute program")?,
};
let (stack_outputs, proof) =
prove_from_trace_sync(TraceProvingInputs::new(trace_inputs, proving_options))
.wrap_err("Failed to prove program")?;
println!("Program proved in {} ms", now.elapsed().as_millis());
ProofFile::write(proof, &self.proof_file, &self.program_file).map_err(Report::msg)?;
if let Some(output_path) = &self.output_file {
OutputFile::write(&stack_outputs, output_path).map_err(Report::msg)?;
} else {
let stack = stack_outputs.get_num_elements(self.num_outputs).to_vec();
let default_output_path = self.program_file.with_extension("outputs");
OutputFile::write(&stack_outputs, &default_output_path).map_err(Report::msg)?;
println!("Output: {stack:?}");
}
Ok(())
}
}