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