cargo_openvm/commands/
setup.rs

1use std::{
2    fs::{create_dir_all, write},
3    path::{Path, PathBuf},
4};
5
6use aws_config::{defaults, BehaviorVersion, Region};
7use aws_sdk_s3::Client;
8use clap::Parser;
9use eyre::{eyre, Context, Result};
10use openvm_sdk::{
11    config::AggregationSystemParams,
12    fs::{
13        write_object_to_file, EVM_HALO2_VERIFIER_BASE_NAME, EVM_HALO2_VERIFIER_INTERFACE_NAME,
14        EVM_HALO2_VERIFIER_PARENT_NAME, EVM_VERIFIER_ARTIFACT_FILENAME,
15    },
16    Sdk,
17};
18
19use crate::{
20    default::{
21        default_app_config, default_evm_halo2_verifier_path, default_internal_recursive_pk_path,
22        default_internal_recursive_vk_path, default_params_dir, default_root_pk_path,
23    },
24    util::evm_verifier_version_dir,
25};
26
27/// The maximum value of `k` to download Halo2 KZG trusted setup parameters for. This depends on the
28/// default verifier circuit and wrapper circuit sizes.
29const MAX_HALO2_VERIFIER_K_FOR_DOWNLOAD: usize = 24;
30
31#[derive(Parser)]
32#[command(name = "setup", about = "Set up OpenVM recursive proving artifacts.")]
33pub struct SetupCmd {
34    #[arg(
35        long,
36        default_value = "false",
37        help = "Force verifier regeneration even if the verifier artifacts already exist"
38    )]
39    pub force: bool,
40
41    #[arg(
42        long,
43        default_value = "false",
44        help = "Also cache the root proving key and download EVM verifier artifacts. ATTENTION: this requires large amounts of computation and memory."
45    )]
46    pub evm: bool,
47
48    #[arg(
49        long,
50        default_value = "false",
51        help = "Download pre-built EVM verifier artifacts from S3 instead of generating locally"
52    )]
53    pub download: bool,
54}
55
56impl SetupCmd {
57    pub async fn run(&self) -> Result<()> {
58        let sdk = Sdk::new(default_app_config(), AggregationSystemParams::default())?;
59
60        let internal_recursive_pk = sdk.agg_pk().internal_recursive;
61        let internal_recursive_vk = internal_recursive_pk.get_vk();
62
63        let internal_recursive_pk_path = PathBuf::from(default_internal_recursive_pk_path());
64        println!(
65            "Writing internal-recursive proving key to {}",
66            internal_recursive_pk_path.display()
67        );
68        write_object_to_file(&internal_recursive_pk_path, internal_recursive_pk)?;
69
70        let internal_recursive_vk_path = PathBuf::from(default_internal_recursive_vk_path());
71        println!(
72            "Writing internal-recursive verifying key to {}",
73            internal_recursive_vk_path.display()
74        );
75        write_object_to_file(&internal_recursive_vk_path, internal_recursive_vk)?;
76
77        if !self.evm {
78            return Ok(());
79        }
80
81        let root_pk_path = PathBuf::from(default_root_pk_path());
82        println!("Writing root proving key to {}", root_pk_path.display());
83        write_object_to_file(&root_pk_path, sdk.root_pk())?;
84
85        Self::download_params(10, MAX_HALO2_VERIFIER_K_FOR_DOWNLOAD as u32).await?;
86
87        let halo2_pk_path = PathBuf::from(crate::default::default_halo2_pk_path());
88        if !self.force && halo2_pk_path.exists() {
89            println!(
90                "Halo2 proving key already exists in {}",
91                halo2_pk_path.display()
92            );
93        } else if self.download {
94            Self::download_halo2_pk(&halo2_pk_path).await?;
95        } else {
96            Self::generate_halo2_pk(&sdk, &halo2_pk_path)?;
97        }
98
99        let verifier_dir = PathBuf::from(default_evm_halo2_verifier_path());
100        let versioned_verifier_dir = verifier_dir.join("src").join(evm_verifier_version_dir());
101        if !self.force && Self::verifier_artifacts_exist(&versioned_verifier_dir) {
102            println!(
103                "EVM verifier artifacts already exist in {}",
104                verifier_dir.display()
105            );
106        } else if self.download {
107            Self::download_verifier(&versioned_verifier_dir).await?;
108        } else {
109            Self::generate_verifier(&sdk, &verifier_dir)?;
110        }
111
112        Ok(())
113    }
114
115    fn verifier_artifacts_exist(versioned_verifier_dir: &Path) -> bool {
116        versioned_verifier_dir
117            .join(EVM_HALO2_VERIFIER_PARENT_NAME)
118            .exists()
119            && versioned_verifier_dir
120                .join(EVM_HALO2_VERIFIER_BASE_NAME)
121                .exists()
122            && versioned_verifier_dir
123                .join(EVM_VERIFIER_ARTIFACT_FILENAME)
124                .exists()
125            && versioned_verifier_dir
126                .join("interfaces")
127                .join(EVM_HALO2_VERIFIER_INTERFACE_NAME)
128                .exists()
129    }
130
131    fn check_solc_installed() -> bool {
132        std::process::Command::new("solc")
133            .arg("--version")
134            .output()
135            .is_ok()
136    }
137
138    fn generate_verifier(sdk: &Sdk, verifier_dir: &Path) -> Result<()> {
139        if !Self::check_solc_installed() {
140            return Err(eyre!(
141                "solc is not installed, please install solc or rerun with --download"
142            ));
143        }
144
145        #[cfg(feature = "evm-verify")]
146        {
147            use openvm_sdk::fs::write_evm_halo2_verifier_to_folder;
148
149            println!("Generating verifier contract locally. Tip: use `--download` to download pre-built artifacts from S3 instead.");
150            let version_dir = evm_verifier_version_dir();
151            let verifier = sdk.generate_halo2_verifier_solidity_with_version_name(&version_dir)?;
152            println!("Writing verifier contract to {}", verifier_dir.display());
153            write_evm_halo2_verifier_to_folder(verifier, verifier_dir, Some(&version_dir))?;
154            Ok(())
155        }
156
157        #[cfg(not(feature = "evm-verify"))]
158        {
159            let _ = sdk;
160            let _ = verifier_dir;
161            Err(eyre!(
162                "this cargo-openvm build does not include local EVM verifier generation support; rerun with --download"
163            ))
164        }
165    }
166
167    fn generate_halo2_pk(sdk: &Sdk, halo2_pk_path: &Path) -> Result<()> {
168        #[cfg(feature = "evm-prove")]
169        {
170            use openvm_sdk::fs::write_halo2_pk_to_file;
171
172            println!("Generating Halo2 proving key locally. Tip: use `--download` to download a pre-built key from S3 instead.");
173            println!("Writing Halo2 proving key to {}", halo2_pk_path.display());
174            write_halo2_pk_to_file(halo2_pk_path, &sdk.halo2_pk())?;
175            Ok(())
176        }
177
178        #[cfg(not(feature = "evm-prove"))]
179        {
180            let _ = sdk;
181            let _ = halo2_pk_path;
182            Err(eyre!(
183                "this cargo-openvm build does not include local Halo2 proving key generation support; rerun with --download"
184            ))
185        }
186    }
187
188    async fn download_verifier(versioned_verifier_dir: &PathBuf) -> Result<()> {
189        create_dir_all(versioned_verifier_dir)?;
190        let interface_dir = versioned_verifier_dir.join("interfaces");
191        create_dir_all(&interface_dir)?;
192
193        let config = defaults(BehaviorVersion::latest())
194            .region(Region::new("us-east-1"))
195            .no_credentials()
196            .load()
197            .await;
198        let client = Client::new(&config);
199
200        const ARTIFACTS_BUCKET: &str = "openvm-public-artifacts-us-east-1";
201        const FULL_VERSION: &str = env!("CARGO_PKG_VERSION");
202
203        let halo2_s3_prefix = format!("v{FULL_VERSION}/halo2/src/{}", evm_verifier_version_dir());
204        let files = [
205            (
206                EVM_HALO2_VERIFIER_PARENT_NAME,
207                versioned_verifier_dir.join(EVM_HALO2_VERIFIER_PARENT_NAME),
208            ),
209            (
210                EVM_HALO2_VERIFIER_BASE_NAME,
211                versioned_verifier_dir.join(EVM_HALO2_VERIFIER_BASE_NAME),
212            ),
213            (
214                EVM_VERIFIER_ARTIFACT_FILENAME,
215                versioned_verifier_dir.join(EVM_VERIFIER_ARTIFACT_FILENAME),
216            ),
217            (
218                EVM_HALO2_VERIFIER_INTERFACE_NAME,
219                interface_dir.join(EVM_HALO2_VERIFIER_INTERFACE_NAME),
220            ),
221        ];
222
223        for (name, local_path) in &files {
224            let key = if *name == EVM_HALO2_VERIFIER_INTERFACE_NAME {
225                format!("{halo2_s3_prefix}/interfaces/{name}")
226            } else {
227                format!("{halo2_s3_prefix}/{name}")
228            };
229            println!("Downloading {name}");
230            let resp = client
231                .get_object()
232                .bucket(ARTIFACTS_BUCKET)
233                .key(&key)
234                .send()
235                .await
236                .map_err(|e| eyre!("Failed to download s3://{ARTIFACTS_BUCKET}/{key}: {e}"))?;
237            let data = resp.body.collect().await?;
238            write(local_path, data.into_bytes())?;
239        }
240
241        Ok(())
242    }
243
244    async fn download_halo2_pk(halo2_pk_path: &Path) -> Result<()> {
245        if let Some(parent) = halo2_pk_path.parent() {
246            create_dir_all(parent)?;
247        }
248
249        let config = defaults(BehaviorVersion::latest())
250            .region(Region::new("us-east-1"))
251            .no_credentials()
252            .load()
253            .await;
254        let client = Client::new(&config);
255
256        const ARTIFACTS_BUCKET: &str = "openvm-public-artifacts-us-east-1";
257        const FULL_VERSION: &str = env!("CARGO_PKG_VERSION");
258
259        let key = format!("v{FULL_VERSION}/halo2.pk");
260        println!(
261            "Downloading Halo2 proving key to {}",
262            halo2_pk_path.display()
263        );
264        let resp = client
265            .get_object()
266            .bucket(ARTIFACTS_BUCKET)
267            .key(&key)
268            .send()
269            .await
270            .map_err(|e| eyre!("Failed to download s3://{ARTIFACTS_BUCKET}/{key}: {e}"))?;
271        let data = resp.body.collect().await?;
272        write(halo2_pk_path, data.into_bytes())?;
273
274        Ok(())
275    }
276
277    async fn download_params(min_k: u32, max_k: u32) -> Result<()> {
278        let default_params_dir = default_params_dir();
279        create_dir_all(&default_params_dir)
280            .with_context(|| format!("failed to create params directory {default_params_dir}"))?;
281
282        let config = defaults(BehaviorVersion::latest())
283            .region(Region::new("us-east-1"))
284            .no_credentials()
285            .load()
286            .await;
287        let client = Client::new(&config);
288
289        for k in min_k..=max_k {
290            let file_name = format!("kzg_bn254_{k}.srs");
291            let local_file_path = PathBuf::from(&default_params_dir).join(&file_name);
292            if !local_file_path.exists() {
293                println!("Downloading {file_name}");
294                let key = format!("challenge_0085/{file_name}");
295                let resp = client
296                    .get_object()
297                    .bucket("axiom-crypto")
298                    .key(&key)
299                    .send()
300                    .await
301                    .with_context(|| format!("failed to download {file_name} from S3"))?;
302                let data = resp
303                    .body
304                    .collect()
305                    .await
306                    .with_context(|| format!("failed to download {file_name} body from S3"))?;
307                write(&local_file_path, data.into_bytes())
308                    .with_context(|| format!("failed to write {}", local_file_path.display()))?;
309            }
310        }
311
312        Ok(())
313    }
314}