Skip to main content

snarkvm_parameters/
macros.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#[macro_export]
17macro_rules! checksum {
18    ($bytes: expr) => {{
19        use sha2::Digest;
20        hex::encode(&sha2::Sha256::digest($bytes))
21    }};
22}
23
24#[macro_export]
25macro_rules! checksum_error {
26    ($expected: expr, $candidate: expr) => {
27        Err($crate::errors::ParameterError::ChecksumMismatch($expected, $candidate))
28    };
29}
30
31#[macro_export]
32macro_rules! remove_file {
33    ($filepath:expr) => {
34        // Safely remove the corrupt file, if it exists.
35        #[cfg(not(feature = "wasm"))]
36        if std::path::PathBuf::from(&$filepath).exists() {
37            match std::fs::remove_file(&$filepath) {
38                Ok(()) => println!("Removed {:?}. Please retry the command.", $filepath),
39                Err(err) => eprintln!("Failed to remove {:?}: {err}", $filepath),
40            }
41        }
42    };
43}
44
45macro_rules! impl_store_and_remote_fetch {
46    () => {
47        #[cfg(not(feature = "wasm"))]
48        fn store_bytes(buffer: &[u8], file_path: &std::path::Path) -> Result<(), $crate::errors::ParameterError> {
49            use snarkvm_utilities::Write;
50
51            #[cfg(not(feature = "no_std_out"))]
52            {
53                use colored::*;
54                let output = format!("{:>15} - Storing file in {:?}", "Installation", file_path);
55                println!("{}", output.dimmed());
56            }
57
58            // Ensure the folders up to the file path all exist.
59            let mut directory_path = file_path.to_path_buf();
60            directory_path.pop();
61            let _ = std::fs::create_dir_all(directory_path)?;
62
63            // Attempt to write the parameter buffer to a file.
64            match std::fs::File::create(file_path) {
65                Ok(mut file) => file.write_all(&buffer)?,
66                Err(error) => eprintln!("{}", error),
67            }
68            Ok(())
69        }
70
71        #[cfg(all(not(feature = "wasm"), not(target_env = "sgx")))]
72        fn remote_fetch(buffer: &mut Vec<u8>, url: &str) -> Result<(), $crate::errors::ParameterError> {
73            let mut easy = curl::easy::Easy::new();
74            easy.follow_location(true)?;
75            easy.url(url)?;
76
77            #[cfg(not(feature = "no_std_out"))]
78            {
79                use colored::*;
80
81                let output = format!("{:>15} - Downloading \"{}\"", "Installation", url);
82                println!("{}", output.dimmed());
83
84                easy.progress(true)?;
85                easy.progress_function(|total_download, current_download, _, _| {
86                    // avoid division by zero.
87                    if total_download == 0.0 {
88                        return true;
89                    }
90                    let percent = (current_download / total_download) * 100.0;
91                    let size_in_megabytes = total_download as u64 / 1_048_576;
92                    let output =
93                        format!("\r{:>15} - {:.2}% complete ({:#} MB total)", "Installation", percent, size_in_megabytes);
94                    print!("{}", output.dimmed());
95                    true
96                })?;
97            }
98
99            let mut transfer = easy.transfer();
100            transfer.write_function(|data| {
101                buffer.extend_from_slice(data);
102                Ok(data.len())
103            })?;
104            Ok(transfer.perform()?)
105        }
106
107        #[cfg(feature = "wasm")]
108        fn remote_fetch(url: &str) -> Result<Vec<u8>, $crate::errors::ParameterError> {
109            // Use the browser's XmlHttpRequest object to download the parameter file synchronously.
110            //
111            // This method blocks the event loop while the parameters are downloaded, and should be
112            // executed in a web worker to prevent the main browser window from freezing.
113            let xhr = web_sys::XmlHttpRequest::new().map_err(|_| {
114                $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest object not found".to_string())
115            })?;
116
117            // XmlHttpRequest if specified as synchronous cannot use the responseType property. It
118            // cannot thus download bytes directly and enforces a text encoding. To get back the
119            // original binary, a charset that does not corrupt the original bytes must be used.
120            xhr.override_mime_type("octet/binary; charset=ISO-8859-5").unwrap();
121
122            // Initialize and send the request.
123            xhr.open_with_async("GET", url, false).map_err(|_| {
124                $crate::errors::ParameterError::Wasm(
125                    "Download failed - This browser does not support synchronous requests".to_string(),
126                )
127            })?;
128            xhr.send()
129                .map_err(|_| $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))?;
130
131            // Wait for the response in a blocking fashion.
132            if xhr.response().is_ok() && xhr.status().unwrap() == 200 {
133                // Get the text from the response.
134                let rust_text = xhr
135                    .response_text()
136                    .map_err(|_| $crate::errors::ParameterError::Wasm("XMLHttpRequest failed".to_string()))?
137                    .ok_or($crate::errors::ParameterError::Wasm(
138                        "The request was successful but no parameters were received".to_string(),
139                    ))?;
140
141                // Re-encode the text back into bytes using the chosen encoding.
142                use encoding::Encoding;
143                encoding::all::ISO_8859_5
144                    .encode(&rust_text, encoding::EncoderTrap::Strict)
145                    .map_err(|_| $crate::errors::ParameterError::Wasm("Parameter decoding failed".to_string()))
146            } else {
147                Err($crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))
148            }
149        }
150    };
151}
152
153macro_rules! impl_load_bytes_logic_local {
154    ($filepath: expr, $buffer: expr, $expected_size: expr, $expected_checksum: expr) => {
155        // Ensure the size matches.
156        if $expected_size != $buffer.len() {
157            remove_file!($filepath);
158            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, $buffer.len()));
159        }
160
161        // Ensure the checksum matches.
162        let candidate_checksum = checksum!($buffer);
163        if $expected_checksum != candidate_checksum {
164            return checksum_error!($expected_checksum, candidate_checksum);
165        }
166
167        return Ok($buffer.to_vec());
168    };
169}
170
171macro_rules! impl_load_bytes_logic_remote {
172    ($remote_urls: expr, $local_dir: expr, $filename: expr, $metadata: expr, $expected_checksum: expr, $expected_size: expr) => {
173        cfg_if::cfg_if! {
174            if #[cfg(all(feature = "filesystem", not(feature="wasm")))] {
175                // Compose the correct file path for the parameter file.
176                let mut file_path = aleo_std::aleo_dir();
177                file_path.push($local_dir);
178                file_path.push($filename);
179
180                let buffer = if file_path.exists() {
181                    // Attempts to load the parameter file locally with an absolute path.
182                    std::fs::read(&file_path)?
183                } else {
184                    // Downloads the missing parameters and stores it in the local directory for use.
185                    #[cfg(not(feature = "no_std_out"))]
186                    {
187                        use colored::*;
188                        let path = format!("(in {:?})", file_path);
189                        eprintln!(
190                            "\n⚠️  \"{}\" does not exist. Downloading and storing it {}.\n",
191                            $filename, path.dimmed()
192                        );
193                    }
194
195                    // Load remote file
196                    cfg_if::cfg_if!{
197                        if #[cfg(all(not(feature = "wasm"), not(target_env = "sgx")))] {
198                            // Try each URL in order, falling back to the next if one fails.
199                            let remote_urls: &[&str] = &$remote_urls;
200                            let mut buffer = vec![];
201                            let mut last_error: Option<($crate::errors::ParameterError, &str)> = None;
202
203                            for base_url in remote_urls.iter() {
204                                // Remove the previous error (if any).
205                                cfg_if::cfg_if!{
206                                    if #[cfg(feature = "no_std_out")] {
207                                        last_error = None;
208                                    } else {
209                                        use colored::Colorize;
210                                        // If this is a retry, print the previous error as warning.
211                                        if let Some((err, url)) = last_error.take() {
212                                            eprintln!("{:>15} - {err}", "Warning".yellow());
213                                            eprintln!("{:>15} - Failed to fetch from \"{url}\". Trying next source...", "Warning".yellow());
214                                         }
215                                    }
216                                }
217
218                                let url = format!("{}/{}", base_url, $filename);
219                                buffer.clear();
220
221                                match Self::remote_fetch(&mut buffer, &url) {
222                                    Ok(()) => {
223                                        // Ensure the checksum matches.
224                                        let candidate_checksum = checksum!(&buffer);
225                                        if $expected_checksum == candidate_checksum {
226                                            // Success - break out of the loop
227                                            break;
228                                        } else {
229                                            last_error = Some(($crate::errors::ParameterError::ChecksumMismatch(
230                                                $expected_checksum.to_string(),
231                                                candidate_checksum,
232                                            ), base_url));
233                                        }
234                                    }
235                                    Err(err) => {
236                                        last_error = Some((err, base_url));
237                                    }
238                                }
239                            }
240
241                            // If all URLs failed, return the last error.
242                            if let Some((err, _)) = last_error {
243                                return Err(err);
244                            }
245
246                            match Self::store_bytes(&buffer, &file_path) {
247                                Ok(()) => buffer,
248                                Err(_) => {
249                                    eprintln!(
250                                        "\n❗ Error - Failed to store \"{}\" locally. Please download this file manually and ensure it is stored in {:?}.\n",
251                                        $filename, file_path
252                                    );
253                                    buffer
254                                }
255                            }
256                        } else {
257                            return Err($crate::errors::ParameterError::RemoteFetchDisabled);
258                        }
259                    }
260                };
261
262                // Ensure the size matches.
263                if $expected_size != buffer.len() {
264                    remove_file!(file_path);
265                    return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
266                }
267
268                // Ensure the checksum matches.
269                let candidate_checksum = checksum!(buffer.as_slice());
270                if $expected_checksum != candidate_checksum {
271                    return checksum_error!($expected_checksum, candidate_checksum)
272                }
273                return Ok(buffer);
274            } else {
275                cfg_if::cfg_if! {
276                    if #[cfg(feature = "wasm")] {
277                        // Try each URL in order, falling back to the next if one fails.
278                        let remote_urls: &[&str] = &$remote_urls;
279                        let mut buffer = vec![];
280                        let mut last_error: Option<$crate::errors::ParameterError> = None;
281
282                        for base_url in remote_urls.iter() {
283                            let url = format!("{}/{}", base_url, $filename);
284
285                            match Self::remote_fetch(&url) {
286                                Ok(fetched_buffer) => {
287                                    // Ensure the checksum matches.
288                                    let candidate_checksum = checksum!(&fetched_buffer);
289                                    if $expected_checksum == candidate_checksum {
290                                        buffer = fetched_buffer;
291                                        last_error = None;
292                                        break;
293                                    } else {
294                                        last_error = Some($crate::errors::ParameterError::ChecksumMismatch(
295                                            $expected_checksum.to_string(),
296                                            candidate_checksum,
297                                        ));
298                                    }
299                                }
300                                Err(e) => {
301                                    last_error = Some(e);
302                                }
303                            }
304                        }
305
306                        // If all URLs failed, return the last error.
307                        if let Some(e) = last_error {
308                            return Err(e);
309                        }
310
311                        // Ensure the size matches.
312                        if $expected_size != buffer.len() {
313                            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
314                        }
315
316                        return Ok(buffer)
317                    } else {
318                        return Err($crate::errors::ParameterError::FilesystemDisabled);
319                    }
320                }
321            }
322        }
323    }
324}
325
326#[macro_export]
327macro_rules! impl_local {
328    ($name: ident, $local_dir: expr, $fname: tt, "usrs") => {
329        #[derive(Clone, Debug, PartialEq, Eq)]
330        pub struct $name;
331
332        impl $name {
333            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
334
335            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
336                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
337                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
338                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
339
340                let _filepath = concat!($local_dir, $fname, ".", "usrs");
341                let buffer = include_bytes!(concat!($local_dir, $fname, ".", "usrs"));
342
343                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
344            }
345        }
346
347        paste::item! {
348            #[cfg(test)]
349            #[test]
350            fn [< test_ $fname _usrs >]() {
351                // Print error messages if loading fails. This can be simplified once assert_matches! is stable.
352                if let Err(err) = $name::load_bytes() {
353                    panic!("Failed to load bytes: {err}");
354                }
355            }
356        }
357    };
358    ($name: ident, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
359        #[derive(Clone, Debug, PartialEq, Eq)]
360        pub struct $name;
361
362        impl $name {
363            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
364
365            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
366                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
367                let expected_checksum: String =
368                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
369                let expected_size: usize =
370                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
371
372                let _filepath = concat!($local_dir, $credits_version, "/", $fname, ".", $ftype);
373                let buffer = include_bytes!(concat!($local_dir, $credits_version, "/", $fname, ".", $ftype));
374
375                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
376            }
377        }
378
379        paste::item! {
380            #[cfg(test)]
381            #[test]
382            fn [< test_ $credits_version _ $fname _ $ftype >]() {
383                if let Err(err) = $name::load_bytes() {
384                    panic!("Failed to load bytes: {err}");
385                }
386            }
387        }
388    };
389}
390
391#[macro_export]
392macro_rules! impl_remote {
393    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, "usrs") => {
394        pub struct $name;
395
396        impl $name {
397            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
398
399            impl_store_and_remote_fetch!();
400
401            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
402                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
403                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
404                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
405
406                // Construct the versioned filename.
407                let filename = match expected_checksum.get(0..7) {
408                    Some(sum) => format!("{}.{}.{}", $fname, "usrs", sum),
409                    _ => format!("{}.{}", $fname, "usrs"),
410                };
411
412                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
413            }
414        }
415        paste::item! {
416            #[cfg(test)]
417            #[test]
418            fn [< test_ $fname _usrs >]() {
419                assert!($name::load_bytes().is_ok());
420            }
421        }
422    };
423    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
424        pub struct $name;
425
426        impl $name {
427            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
428
429            impl_store_and_remote_fetch!();
430
431            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
432                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
433                let expected_checksum: String =
434                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
435                let expected_size: usize =
436                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
437
438                // Construct the versioned filename.
439                let filename = match expected_checksum.get(0..7) {
440                    Some(sum) => format!("{}.{}.{}", $fname, $ftype, sum),
441                    _ => format!("{}.{}", $fname, $ftype),
442                };
443
444                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
445            }
446
447            #[cfg(feature = "wasm")]
448            /// Verify external bytes.
449            pub fn verify_bytes(buffer: &[u8]) -> Result<(), $crate::errors::ParameterError> {
450                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
451                let expected_checksum: String =
452                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
453                let expected_size: usize =
454                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
455
456                // Ensure the size matches.
457                if buffer.len() != expected_size {
458                    return Err($crate::errors::ParameterError::SizeMismatch(expected_size, buffer.len()));
459                }
460
461                // Ensure the checksum matches.
462                let candidate_checksum = checksum!(buffer);
463                if expected_checksum != candidate_checksum {
464                    return checksum_error!(expected_checksum, candidate_checksum);
465                }
466                Ok(())
467            }
468        }
469
470        paste::item! {
471            #[cfg(test)]
472            #[test]
473            fn [< test_ $credits_version _ $fname _ $ftype >]() {
474                if let Err(err) = $name::load_bytes() {
475                    panic!("Failed to load bytes: {err}");
476                }
477            }
478        }
479    };
480}