Skip to main content

snarkvm_parameters/
macros.rs

1// Copyright (c) 2019-2026 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            #[cfg(not(feature = "no_std_out"))]
74            {
75                use colored::*;
76                let output = format!("{:>15} - Downloading \"{}\"", "Installation", url);
77                println!("{}", output.dimmed());
78            }
79
80            let response = reqwest::blocking::get(url)?;
81            let bytes = response.bytes()?;
82            buffer.extend_from_slice(&bytes);
83
84            #[cfg(not(feature = "no_std_out"))]
85            {
86                use colored::*;
87                let size_in_megabytes = bytes.len() as u64 / 1_048_576;
88                let output = format!("{:>15} - Download complete ({} MB)", "Installation", size_in_megabytes);
89                println!("{}", output.dimmed());
90            }
91
92            Ok(())
93        }
94
95        #[cfg(feature = "wasm")]
96        fn remote_fetch(url: &str) -> Result<Vec<u8>, $crate::errors::ParameterError> {
97            // Use the browser's XmlHttpRequest object to download the parameter file synchronously.
98            //
99            // This method blocks the event loop while the parameters are downloaded, and should be
100            // executed in a web worker to prevent the main browser window from freezing.
101            let xhr = web_sys::XmlHttpRequest::new().map_err(|_| {
102                $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest object not found".to_string())
103            })?;
104
105            // XmlHttpRequest if specified as synchronous cannot use the responseType property. It
106            // cannot thus download bytes directly and enforces a text encoding. To get back the
107            // original binary, a charset that does not corrupt the original bytes must be used.
108            xhr.override_mime_type("octet/binary; charset=ISO-8859-5").unwrap();
109
110            // Initialize and send the request.
111            xhr.open_with_async("GET", url, false).map_err(|_| {
112                $crate::errors::ParameterError::Wasm(
113                    "Download failed - This browser does not support synchronous requests".to_string(),
114                )
115            })?;
116            xhr.send()
117                .map_err(|_| $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))?;
118
119            // Wait for the response in a blocking fashion.
120            if xhr.response().is_ok() && xhr.status().unwrap() == 200 {
121                // Get the text from the response.
122                let rust_text = xhr
123                    .response_text()
124                    .map_err(|_| $crate::errors::ParameterError::Wasm("XMLHttpRequest failed".to_string()))?
125                    .ok_or($crate::errors::ParameterError::Wasm(
126                        "The request was successful but no parameters were received".to_string(),
127                    ))?;
128
129                // Re-encode the text back into bytes using the chosen encoding.
130                use encoding::Encoding;
131                encoding::all::ISO_8859_5
132                    .encode(&rust_text, encoding::EncoderTrap::Strict)
133                    .map_err(|_| $crate::errors::ParameterError::Wasm("Parameter decoding failed".to_string()))
134            } else {
135                Err($crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))
136            }
137        }
138    };
139}
140
141macro_rules! impl_load_bytes_logic_local {
142    ($filepath: expr, $buffer: expr, $expected_size: expr, $expected_checksum: expr) => {
143        // Ensure the size matches.
144        if $expected_size != $buffer.len() {
145            remove_file!($filepath);
146            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, $buffer.len()));
147        }
148
149        // Ensure the checksum matches.
150        let candidate_checksum = checksum!($buffer);
151        if $expected_checksum != candidate_checksum {
152            return checksum_error!($expected_checksum, candidate_checksum);
153        }
154
155        return Ok($buffer.to_vec());
156    };
157}
158
159macro_rules! impl_load_bytes_logic_remote {
160    ($remote_urls: expr, $local_dir: expr, $filename: expr, $metadata: expr, $expected_checksum: expr, $expected_size: expr) => {
161        cfg_if::cfg_if! {
162            if #[cfg(all(feature = "filesystem", not(feature="wasm")))] {
163                // Compose the correct file path for the parameter file.
164                let mut file_path = aleo_std::aleo_dir();
165                file_path.push($local_dir);
166                file_path.push($filename);
167
168                let buffer = if file_path.exists() {
169                    // Attempts to load the parameter file locally with an absolute path.
170                    std::fs::read(&file_path)?
171                } else {
172                    // Downloads the missing parameters and stores it in the local directory for use.
173                    #[cfg(not(feature = "no_std_out"))]
174                    {
175                        use colored::*;
176                        let path = format!("(in {:?})", file_path);
177                        eprintln!(
178                            "\n⚠️  \"{}\" does not exist. Downloading and storing it {}.\n",
179                            $filename, path.dimmed()
180                        );
181                    }
182
183                    // Load remote file
184                    cfg_if::cfg_if!{
185                        if #[cfg(all(not(feature = "wasm"), not(target_env = "sgx")))] {
186                            // Try each URL in order, falling back to the next if one fails.
187                            let remote_urls: &[&str] = &$remote_urls;
188                            let mut buffer = vec![];
189                            let mut last_error: Option<($crate::errors::ParameterError, &str)> = None;
190
191                            for base_url in remote_urls.iter() {
192                                // Remove the previous error (if any).
193                                cfg_if::cfg_if!{
194                                    if #[cfg(feature = "no_std_out")] {
195                                        last_error = None;
196                                    } else {
197                                        use colored::Colorize;
198                                        // If this is a retry, print the previous error as warning.
199                                        if let Some((err, url)) = last_error.take() {
200                                            eprintln!("{:>15} - {err}", "Warning".yellow());
201                                            eprintln!("{:>15} - Failed to fetch from \"{url}\". Trying next source...", "Warning".yellow());
202                                         }
203                                    }
204                                }
205
206                                let url = format!("{}/{}", base_url, $filename);
207                                buffer.clear();
208
209                                match Self::remote_fetch(&mut buffer, &url) {
210                                    Ok(()) => {
211                                        // Ensure the checksum matches.
212                                        let candidate_checksum = checksum!(&buffer);
213                                        if $expected_checksum == candidate_checksum {
214                                            // Success - break out of the loop
215                                            break;
216                                        } else {
217                                            last_error = Some(($crate::errors::ParameterError::ChecksumMismatch(
218                                                $expected_checksum.to_string(),
219                                                candidate_checksum,
220                                            ), base_url));
221                                        }
222                                    }
223                                    Err(err) => {
224                                        last_error = Some((err, base_url));
225                                    }
226                                }
227                            }
228
229                            // If all URLs failed, return the last error.
230                            if let Some((err, _)) = last_error {
231                                return Err(err);
232                            }
233
234                            match Self::store_bytes(&buffer, &file_path) {
235                                Ok(()) => buffer,
236                                Err(_) => {
237                                    eprintln!(
238                                        "\n❗ Error - Failed to store \"{}\" locally. Please download this file manually and ensure it is stored in {:?}.\n",
239                                        $filename, file_path
240                                    );
241                                    buffer
242                                }
243                            }
244                        } else {
245                            return Err($crate::errors::ParameterError::RemoteFetchDisabled);
246                        }
247                    }
248                };
249
250                // Ensure the size matches.
251                if $expected_size != buffer.len() {
252                    remove_file!(file_path);
253                    return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
254                }
255
256                // Ensure the checksum matches.
257                let candidate_checksum = checksum!(buffer.as_slice());
258                if $expected_checksum != candidate_checksum {
259                    return checksum_error!($expected_checksum, candidate_checksum)
260                }
261                return Ok(buffer);
262            } else {
263                cfg_if::cfg_if! {
264                    if #[cfg(feature = "wasm")] {
265                        // Try each URL in order, falling back to the next if one fails.
266                        let remote_urls: &[&str] = &$remote_urls;
267                        let mut buffer = vec![];
268                        let mut last_error: Option<$crate::errors::ParameterError> = None;
269
270                        for base_url in remote_urls.iter() {
271                            let url = format!("{}/{}", base_url, $filename);
272
273                            match Self::remote_fetch(&url) {
274                                Ok(fetched_buffer) => {
275                                    // Ensure the checksum matches.
276                                    let candidate_checksum = checksum!(&fetched_buffer);
277                                    if $expected_checksum == candidate_checksum {
278                                        buffer = fetched_buffer;
279                                        last_error = None;
280                                        break;
281                                    } else {
282                                        last_error = Some($crate::errors::ParameterError::ChecksumMismatch(
283                                            $expected_checksum.to_string(),
284                                            candidate_checksum,
285                                        ));
286                                    }
287                                }
288                                Err(e) => {
289                                    last_error = Some(e);
290                                }
291                            }
292                        }
293
294                        // If all URLs failed, return the last error.
295                        if let Some(e) = last_error {
296                            return Err(e);
297                        }
298
299                        // Ensure the size matches.
300                        if $expected_size != buffer.len() {
301                            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
302                        }
303
304                        return Ok(buffer)
305                    } else {
306                        return Err($crate::errors::ParameterError::FilesystemDisabled);
307                    }
308                }
309            }
310        }
311    }
312}
313
314#[macro_export]
315macro_rules! impl_local {
316    ($name: ident, $local_dir: expr, $fname: tt, "usrs") => {
317        #[derive(Clone, Debug, PartialEq, Eq)]
318        pub struct $name;
319
320        impl $name {
321            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
322
323            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
324                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
325                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
326                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
327
328                let _filepath = concat!($local_dir, $fname, ".", "usrs");
329                let buffer = include_bytes!(concat!($local_dir, $fname, ".", "usrs"));
330
331                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
332            }
333        }
334
335        paste::item! {
336            #[cfg(test)]
337            #[test]
338            fn [< test_ $fname _usrs >]() {
339                // Print error messages if loading fails. This can be simplified once assert_matches! is stable.
340                if let Err(err) = $name::load_bytes() {
341                    panic!("Failed to load bytes: {err}");
342                }
343            }
344        }
345    };
346    ($name: ident, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
347        #[derive(Clone, Debug, PartialEq, Eq)]
348        pub struct $name;
349
350        impl $name {
351            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
352
353            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
354                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
355                let expected_checksum: String =
356                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
357                let expected_size: usize =
358                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
359
360                let _filepath = concat!($local_dir, $credits_version, "/", $fname, ".", $ftype);
361                let buffer = include_bytes!(concat!($local_dir, $credits_version, "/", $fname, ".", $ftype));
362
363                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
364            }
365        }
366
367        paste::item! {
368            #[cfg(test)]
369            #[test]
370            fn [< test_ $credits_version _ $fname _ $ftype >]() {
371                if let Err(err) = $name::load_bytes() {
372                    panic!("Failed to load bytes: {err}");
373                }
374            }
375        }
376    };
377}
378
379#[macro_export]
380macro_rules! impl_remote {
381    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, "usrs") => {
382        pub struct $name;
383
384        impl $name {
385            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
386
387            impl_store_and_remote_fetch!();
388
389            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
390                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
391                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
392                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
393
394                // Construct the versioned filename.
395                let filename = match expected_checksum.get(0..7) {
396                    Some(sum) => format!("{}.{}.{}", $fname, "usrs", sum),
397                    _ => format!("{}.{}", $fname, "usrs"),
398                };
399
400                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
401            }
402        }
403        paste::item! {
404            #[cfg(test)]
405            #[test]
406            fn [< test_ $fname _usrs >]() {
407                assert!($name::load_bytes().is_ok());
408            }
409        }
410    };
411    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
412        pub struct $name;
413
414        impl $name {
415            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
416
417            impl_store_and_remote_fetch!();
418
419            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
420                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
421                let expected_checksum: String =
422                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
423                let expected_size: usize =
424                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
425
426                // Construct the versioned filename.
427                let filename = match expected_checksum.get(0..7) {
428                    Some(sum) => format!("{}.{}.{}", $fname, $ftype, sum),
429                    _ => format!("{}.{}", $fname, $ftype),
430                };
431
432                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
433            }
434
435            #[cfg(feature = "wasm")]
436            /// Verify external bytes.
437            pub fn verify_bytes(buffer: &[u8]) -> Result<(), $crate::errors::ParameterError> {
438                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
439                let expected_checksum: String =
440                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
441                let expected_size: usize =
442                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
443
444                // Ensure the size matches.
445                if buffer.len() != expected_size {
446                    return Err($crate::errors::ParameterError::SizeMismatch(expected_size, buffer.len()));
447                }
448
449                // Ensure the checksum matches.
450                let candidate_checksum = checksum!(buffer);
451                if expected_checksum != candidate_checksum {
452                    return checksum_error!(expected_checksum, candidate_checksum);
453                }
454                Ok(())
455            }
456        }
457
458        paste::item! {
459            #[cfg(test)]
460            #[test]
461            fn [< test_ $credits_version _ $fname _ $ftype >]() {
462                if let Err(err) = $name::load_bytes() {
463                    panic!("Failed to load bytes: {err}");
464                }
465            }
466        }
467    };
468}