1use std::path::{Path, PathBuf};
2
3use clap::Parser;
4use eyre::{Context, Result};
5use openvm_sdk::{
6 fs::{decode_from_file, read_from_file_json, read_object_from_file},
7 prover::verify_app_proof,
8 types::VersionedVmStarkProof,
9 Sdk, OPENVM_VERSION,
10};
11
12use super::KeygenCargoArgs;
13#[cfg(feature = "evm-verify")]
14use crate::default::default_evm_halo2_verifier_path;
15use crate::{
16 default::default_agg_stark_vk_path,
17 util::{
18 get_app_commit_path, get_app_vk_path, get_files_with_ext, get_manifest_path_and_dir,
19 get_single_target_name_raw, get_target_dir, get_target_output_dir,
20 },
21};
22
23#[derive(Parser)]
24#[command(name = "verify", about = "Verify a proof")]
25pub struct VerifyCmd {
26 #[command(subcommand)]
27 command: VerifySubCommand,
28}
29
30#[derive(Parser)]
31enum VerifySubCommand {
32 App {
33 #[arg(
34 long,
35 action,
36 help = "Path to app verifying key, by default will search for it in ${target_dir}/openvm/app.vk",
37 help_heading = "OpenVM Options"
38 )]
39 app_vk: Option<PathBuf>,
40
41 #[arg(
42 long,
43 action,
44 help = "Path to app proof, by default will search the working directory for a file with extension .app.proof",
45 help_heading = "OpenVM Options"
46 )]
47 proof: Option<PathBuf>,
48
49 #[command(flatten)]
50 cargo_args: KeygenCargoArgs,
51 },
52 Stark {
53 #[arg(
56 long,
57 action,
58 help = "Path to app commit, by default will search for it using the binary target name",
59 help_heading = "OpenVM Options"
60 )]
61 app_commit: Option<PathBuf>,
62
63 #[arg(
64 long,
65 action,
66 help = "Path to STARK proof, by default will search the working directory for a file with extension .stark.proof",
67 help_heading = "OpenVM Options"
68 )]
69 proof: Option<PathBuf>,
70
71 #[command(flatten)]
72 cargo_args: SingleTargetCargoArgs,
73 },
74 #[cfg(feature = "evm-verify")]
75 Evm {
76 #[arg(
77 long,
78 action,
79 help = "Path to EVM proof, by default will search the working directory for a file with extension .evm.proof",
80 help_heading = "OpenVM Options"
81 )]
82 proof: Option<PathBuf>,
83 },
84}
85
86#[derive(Parser)]
87pub struct SingleTargetCargoArgs {
88 #[arg(
89 long,
90 short = 'p',
91 value_name = "PACKAGES",
92 help = "The package to run; by default is the package in the current workspace",
93 help_heading = "Package Selection"
94 )]
95 pub package: Option<String>,
96
97 #[arg(
98 long,
99 value_name = "BIN",
100 help = "Run the specified binary",
101 help_heading = "Target Selection"
102 )]
103 pub bin: Vec<String>,
104
105 #[arg(
106 long,
107 value_name = "EXAMPLE",
108 help = "Run the specified example",
109 help_heading = "Target Selection"
110 )]
111 pub example: Vec<String>,
112
113 #[arg(
114 long,
115 value_name = "NAME",
116 default_value = "release",
117 help = "Run with the given profile",
118 help_heading = "Compilation Options"
119 )]
120 pub profile: String,
121
122 #[arg(
123 long,
124 value_name = "DIR",
125 help = "Directory for all generated artifacts and intermediate files",
126 help_heading = "Output Options"
127 )]
128 pub target_dir: Option<PathBuf>,
129
130 #[arg(
131 long,
132 value_name = "PATH",
133 help = "Path to the Cargo.toml file, by default searches for the file in the current or any parent directory",
134 help_heading = "Manifest Options"
135 )]
136 pub manifest_path: Option<PathBuf>,
137}
138
139impl VerifyCmd {
140 pub fn run(&self) -> Result<()> {
141 match &self.command {
142 VerifySubCommand::App {
143 app_vk,
144 proof,
145 cargo_args,
146 } => {
147 let app_vk_path = if let Some(app_vk) = app_vk {
148 app_vk.to_path_buf()
149 } else {
150 let (manifest_path, _) = get_manifest_path_and_dir(&cargo_args.manifest_path)?;
151 let target_dir = get_target_dir(&cargo_args.target_dir, &manifest_path);
152 get_app_vk_path(&target_dir)
153 };
154 let app_vk = read_object_from_file(app_vk_path)?;
155
156 let proof_path = if let Some(proof) = proof {
157 proof.clone()
158 } else {
159 let files = get_files_with_ext(Path::new("."), "app.proof")?;
160 if files.len() > 1 {
161 return Err(eyre::eyre!("multiple .app.proof files found, please specify the path using option --proof"));
162 } else if files.is_empty() {
163 return Err(eyre::eyre!("no .app.proof file found, please specify the path using option --proof"));
164 }
165 files[0].clone()
166 };
167 println!("Verifying application proof at {}", proof_path.display());
168 let app_proof = decode_from_file(proof_path)?;
169 verify_app_proof(&app_vk, &app_proof)?;
170 }
171 VerifySubCommand::Stark {
172 app_commit,
173 proof,
174 cargo_args,
175 } => {
176 let agg_vk = read_object_from_file(default_agg_stark_vk_path())
177 .map_err(|e| {
178 eyre::eyre!(
179 "Failed to read aggregation STARK verifying key: {e}\nPlease run 'cargo openvm setup' first",
180 )
181 })?;
182 let app_commit_path = if let Some(app_commit) = app_commit {
183 app_commit.to_path_buf()
184 } else {
185 let (manifest_path, _) = get_manifest_path_and_dir(&cargo_args.manifest_path)?;
186 let target_dir = get_target_dir(&cargo_args.target_dir, &manifest_path);
187 let target_output_dir = get_target_output_dir(&target_dir, &cargo_args.profile);
188 let target_name = get_single_target_name_raw(
189 &cargo_args.bin,
190 &cargo_args.example,
191 &cargo_args.manifest_path,
192 &cargo_args.package,
193 )?;
194 get_app_commit_path(&target_output_dir, target_name)
195 };
196 let expected_app_commit = read_from_file_json(app_commit_path)?;
197
198 let proof_path = if let Some(proof) = proof {
199 proof.clone()
200 } else {
201 let files = get_files_with_ext(Path::new("."), "stark.proof")?;
202 if files.len() > 1 {
203 return Err(eyre::eyre!("multiple .stark.proof files found, please specify the path using option --proof"));
204 } else if files.is_empty() {
205 return Err(eyre::eyre!("no .stark.proof file found, please specify the path using option --proof"));
206 }
207 files[0].clone()
208 };
209 println!("Verifying STARK proof at {}", proof_path.display());
210 let stark_proof: VersionedVmStarkProof = read_from_file_json(proof_path)
211 .with_context(|| {
212 format!("Proof needs to be compatible with openvm v{OPENVM_VERSION}",)
213 })?;
214 if stark_proof.version != format!("v{OPENVM_VERSION}") {
215 eprintln!("Attempting to verify proof generated with openvm {}, but the verifier is on openvm v{OPENVM_VERSION}", stark_proof.version);
216 }
217 Sdk::verify_proof(&agg_vk, expected_app_commit, &stark_proof.try_into()?)?;
218 }
219 #[cfg(feature = "evm-verify")]
220 VerifySubCommand::Evm { proof } => {
221 use openvm_sdk::{fs::read_evm_halo2_verifier_from_folder, types::EvmProof};
222
223 let evm_verifier =
224 read_evm_halo2_verifier_from_folder(default_evm_halo2_verifier_path())
225 .map_err(|e| {
226 eyre::eyre!(
227 "Failed to read EVM verifier: {e}\nPlease run 'cargo openvm setup' first"
228 )
229 })?;
230
231 let proof_path = if let Some(proof) = proof {
232 proof.clone()
233 } else {
234 let files = get_files_with_ext(Path::new("."), "evm.proof")?;
235 if files.len() > 1 {
236 return Err(eyre::eyre!("multiple .evm.proof files found, please specify the path using option --proof"));
237 } else if files.is_empty() {
238 return Err(eyre::eyre!("no .evm.proof file found, please specify the path using option --proof"));
239 }
240 files[0].clone()
241 };
242 println!("Verifying EVM proof at {}", proof_path.display());
244 let evm_proof: EvmProof = read_from_file_json(proof_path).with_context(|| {
245 format!("Proof needs to be compatible with openvm v{OPENVM_VERSION}",)
246 })?;
247 if evm_proof.version != format!("v{OPENVM_VERSION}") {
248 eprintln!("Attempting to verify proof generated with openvm {}, but the verifier is on openvm v{OPENVM_VERSION}", evm_proof.version);
249 }
250 Sdk::verify_evm_halo2_proof(&evm_verifier, evm_proof)?;
251 }
252 }
253 println!("Proof verified successfully!");
254 Ok(())
255 }
256}