cargo_openvm/commands/
prove.rs

1use std::{path::PathBuf, sync::Arc};
2
3use clap::Parser;
4use eyre::Result;
5use openvm_circuit::arch::{
6    execution_mode::metered::segment_ctx::{
7        SegmentationLimits, DEFAULT_MAX_CELLS, DEFAULT_MAX_TRACE_HEIGHT_BITS,
8    },
9    instructions::exe::VmExe,
10};
11use openvm_sdk::{
12    config::{AggregationTreeConfig, AppConfig, SdkVmConfig},
13    fs::{encode_to_file, read_object_from_file, write_to_file_json},
14    keygen::AppProvingKey,
15    types::VersionedVmStarkProof,
16    Sdk, F,
17};
18
19use super::{RunArgs, RunCargoArgs};
20#[cfg(feature = "evm-prove")]
21use crate::util::read_default_agg_and_halo2_pk;
22use crate::{
23    commands::build,
24    default::default_agg_stark_pk_path,
25    input::read_to_stdin,
26    util::{get_app_pk_path, get_manifest_path_and_dir, get_single_target_name, get_target_dir},
27};
28
29#[derive(Parser)]
30#[command(name = "prove", about = "Generate a program proof")]
31pub struct ProveCmd {
32    #[command(subcommand)]
33    command: ProveSubCommand,
34}
35
36#[derive(Parser)]
37enum ProveSubCommand {
38    App {
39        #[arg(
40            long,
41            action,
42            help = "Path to app proof output, by default will be ./${bin_name}.app.proof",
43            help_heading = "Output"
44        )]
45        proof: Option<PathBuf>,
46
47        #[arg(
48            long,
49            action,
50            help = "Path to app proving key, by default will be ${target_dir}/openvm/app.pk",
51            help_heading = "OpenVM Options"
52        )]
53        app_pk: Option<PathBuf>,
54
55        #[command(flatten)]
56        run_args: RunArgs,
57
58        #[command(flatten)]
59        cargo_args: RunCargoArgs,
60
61        #[command(flatten)]
62        segmentation_args: SegmentationArgs,
63    },
64    Stark {
65        #[arg(
66            long,
67            action,
68            help = "Path to STARK proof output, by default will be ./${bin_name}.stark.proof",
69            help_heading = "Output"
70        )]
71        proof: Option<PathBuf>,
72
73        #[arg(
74            long,
75            action,
76            help = "Path to app proving key, by default will be ${target_dir}/openvm/app.pk",
77            help_heading = "OpenVM Options"
78        )]
79        app_pk: Option<PathBuf>,
80
81        #[command(flatten)]
82        run_args: RunArgs,
83
84        #[command(flatten)]
85        cargo_args: RunCargoArgs,
86
87        #[command(flatten)]
88        segmentation_args: SegmentationArgs,
89
90        #[command(flatten)]
91        agg_tree_config: AggregationTreeConfig,
92    },
93    #[cfg(feature = "evm-prove")]
94    Evm {
95        #[arg(
96            long,
97            action,
98            help = "Path to EVM proof output, by default will be ./${bin_name}.evm.proof",
99            help_heading = "Output"
100        )]
101        proof: Option<PathBuf>,
102
103        #[arg(
104            long,
105            action,
106            help = "Path to app proving key, by default will be ${target_dir}/openvm/app.pk",
107            help_heading = "OpenVM Options"
108        )]
109        app_pk: Option<PathBuf>,
110
111        #[command(flatten)]
112        run_args: RunArgs,
113
114        #[command(flatten)]
115        cargo_args: RunCargoArgs,
116
117        #[command(flatten)]
118        segmentation_args: SegmentationArgs,
119
120        #[command(flatten)]
121        agg_tree_config: AggregationTreeConfig,
122    },
123}
124
125#[derive(Clone, Copy, Parser)]
126pub struct SegmentationArgs {
127    /// Trace height threshold, in bits, across all chips for triggering segmentation for
128    /// continuations in the app proof. These thresholds are not exceeded except when they are too
129    /// small.
130    #[arg(
131        long,
132        default_value_t = DEFAULT_MAX_TRACE_HEIGHT_BITS,
133        help_heading = "OpenVM Options"
134    )]
135    pub segment_max_height_bits: u8,
136    /// Total cells used across all chips for triggering segmentation for continuations in the app
137    /// proof. These thresholds are not exceeded except when they are too small.
138    #[arg(
139        long,
140        default_value_t = DEFAULT_MAX_CELLS,
141        help_heading = "OpenVM Options"
142    )]
143    pub segment_max_cells: usize,
144}
145
146impl ProveCmd {
147    pub fn run(&self) -> Result<()> {
148        match &self.command {
149            ProveSubCommand::App {
150                app_pk,
151                proof,
152                run_args,
153                cargo_args,
154                segmentation_args,
155            } => {
156                let mut app_pk = load_app_pk(app_pk, cargo_args)?;
157                let app_config = get_app_config(&mut app_pk, segmentation_args);
158                let sdk = Sdk::new(app_config)?.with_app_pk(app_pk);
159                let (exe, target_name) = load_or_build_exe(run_args, cargo_args)?;
160
161                let app_proof = sdk
162                    .app_prover(exe)?
163                    .prove(read_to_stdin(&run_args.input)?)?;
164
165                let proof_path = if let Some(proof) = proof {
166                    proof
167                } else {
168                    &PathBuf::from(target_name).with_extension("app.proof")
169                };
170                println!(
171                    "App proof completed! Writing App proof to {}",
172                    proof_path.display()
173                );
174                encode_to_file(proof_path, app_proof)?;
175            }
176            ProveSubCommand::Stark {
177                app_pk,
178                proof,
179                run_args,
180                cargo_args,
181                segmentation_args,
182                agg_tree_config,
183            } => {
184                let mut app_pk = load_app_pk(app_pk, cargo_args)?;
185                let (exe, target_name) = load_or_build_exe(run_args, cargo_args)?;
186
187                let agg_pk = read_object_from_file(default_agg_stark_pk_path()).map_err(|e| {
188                    eyre::eyre!("Failed to read aggregation proving key: {}\nPlease run 'cargo openvm setup' first", e)
189                })?;
190                let app_config = get_app_config(&mut app_pk, segmentation_args);
191                let sdk = Sdk::new(app_config)?
192                    .with_agg_tree_config(*agg_tree_config)
193                    .with_app_pk(app_pk)
194                    .with_agg_pk(agg_pk);
195                let mut prover = sdk.prover(exe)?;
196                let app_commit = prover.app_commit();
197                println!("exe commit: {:?}", app_commit.app_exe_commit.to_bn254());
198                println!("vm commit: {:?}", app_commit.app_vm_commit.to_bn254());
199
200                let stark_proof = prover.prove(read_to_stdin(&run_args.input)?)?;
201                let stark_proof_bytes = VersionedVmStarkProof::new(stark_proof)?;
202
203                let proof_path = if let Some(proof) = proof {
204                    proof
205                } else {
206                    &PathBuf::from(target_name).with_extension("stark.proof")
207                };
208                println!(
209                    "STARK proof completed! Writing STARK proof to {}",
210                    proof_path.display()
211                );
212                write_to_file_json(proof_path, stark_proof_bytes)?;
213            }
214            #[cfg(feature = "evm-prove")]
215            ProveSubCommand::Evm {
216                app_pk,
217                proof,
218                run_args,
219                cargo_args,
220                segmentation_args,
221                agg_tree_config,
222            } => {
223                let mut app_pk = load_app_pk(app_pk, cargo_args)?;
224                let (exe, target_name) = load_or_build_exe(run_args, cargo_args)?;
225
226                println!("Generating EVM proof, this may take a lot of compute and memory...");
227                let (agg_pk, halo2_pk) = read_default_agg_and_halo2_pk().map_err(|e| {
228                    eyre::eyre!("Failed to read aggregation proving key: {}\nPlease run 'cargo openvm setup' first", e)
229                })?;
230                let app_config = get_app_config(&mut app_pk, segmentation_args);
231                let sdk = Sdk::new(app_config)?
232                    .with_agg_tree_config(*agg_tree_config)
233                    .with_app_pk(app_pk)
234                    .with_agg_pk(agg_pk)
235                    .with_halo2_pk(halo2_pk);
236                let mut prover = sdk.evm_prover(exe)?;
237                let app_commit = prover.stark_prover.app_commit();
238                println!("exe commit: {:?}", app_commit.app_exe_commit.to_bn254());
239                println!("vm commit: {:?}", app_commit.app_vm_commit.to_bn254());
240                let evm_proof = prover.prove_evm(read_to_stdin(&run_args.input)?)?;
241
242                let proof_path = if let Some(proof) = proof {
243                    proof
244                } else {
245                    &PathBuf::from(target_name).with_extension("evm.proof")
246                };
247                println!(
248                    "EVM proof completed! Writing EVM proof to {}",
249                    proof_path.display()
250                );
251                write_to_file_json(proof_path, evm_proof)?;
252            }
253        }
254        Ok(())
255    }
256}
257
258pub(crate) fn load_app_pk(
259    app_pk: &Option<PathBuf>,
260    cargo_args: &RunCargoArgs,
261) -> Result<AppProvingKey<SdkVmConfig>> {
262    let (manifest_path, _) = get_manifest_path_and_dir(&cargo_args.manifest_path)?;
263    let target_dir = get_target_dir(&cargo_args.target_dir, &manifest_path);
264
265    let app_pk_path = if let Some(app_pk) = app_pk {
266        app_pk.to_path_buf()
267    } else {
268        get_app_pk_path(&target_dir)
269    };
270
271    read_object_from_file(app_pk_path)
272}
273
274/// Returns `(exe, target_name.file_stem())` where target_name has no extension and only contains
275/// the file stem (in particular it does not include `examples/` if the target was an example)
276pub(crate) fn load_or_build_exe(
277    run_args: &RunArgs,
278    cargo_args: &RunCargoArgs,
279) -> Result<(VmExe<F>, String)> {
280    let exe_path = if let Some(exe) = &run_args.exe {
281        exe
282    } else {
283        // Build and get the executable name
284        let target_name = get_single_target_name(cargo_args)?;
285        let build_args = run_args.clone().into();
286        let cargo_args = cargo_args.clone().into();
287        let output_dir = build(&build_args, &cargo_args)?;
288        &output_dir.join(target_name.with_extension("vmexe"))
289    };
290
291    let app_exe = read_object_from_file(exe_path)?;
292    Ok((
293        app_exe,
294        exe_path.file_stem().unwrap().to_string_lossy().into_owned(),
295    ))
296}
297
298/// Should only be called when `app_pk` has only a single reference internally.
299/// Mutates the `SystemConfig` within `app_pk` and then returns the updated `AppConfig`.
300fn get_app_config(
301    app_pk: &mut AppProvingKey<SdkVmConfig>,
302    segmentation_args: &SegmentationArgs,
303) -> AppConfig<SdkVmConfig> {
304    Arc::get_mut(&mut app_pk.app_vm_pk)
305        .unwrap()
306        .vm_config
307        .system
308        .config
309        .set_segmentation_limits((*segmentation_args).into());
310    app_pk.app_config()
311}
312
313impl From<SegmentationArgs> for SegmentationLimits {
314    fn from(args: SegmentationArgs) -> Self {
315        SegmentationLimits {
316            max_trace_height: 1u32
317                .checked_shl(args.segment_max_height_bits as u32)
318                .expect("segment_max_height_bits too large"),
319            max_cells: args.segment_max_cells,
320            ..Default::default()
321        }
322    }
323}