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                    let percent = (current_download / total_download) * 100.0;
87                    let size_in_megabytes = total_download as u64 / 1_048_576;
88                    let output =
89                        format!("\r{:>15} - {:.2}% complete ({:#} MB total)", "Installation", percent, size_in_megabytes);
90                    print!("{}", output.dimmed());
91                    true
92                })?;
93            }
94
95            let mut transfer = easy.transfer();
96            transfer.write_function(|data| {
97                buffer.extend_from_slice(data);
98                Ok(data.len())
99            })?;
100            Ok(transfer.perform()?)
101        }
102
103        #[cfg(feature = "wasm")]
104        fn remote_fetch(url: &str) -> Result<Vec<u8>, $crate::errors::ParameterError> {
105            // Use the browser's XmlHttpRequest object to download the parameter file synchronously.
106            //
107            // This method blocks the event loop while the parameters are downloaded, and should be
108            // executed in a web worker to prevent the main browser window from freezing.
109            let xhr = web_sys::XmlHttpRequest::new().map_err(|_| {
110                $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest object not found".to_string())
111            })?;
112
113            // XmlHttpRequest if specified as synchronous cannot use the responseType property. It
114            // cannot thus download bytes directly and enforces a text encoding. To get back the
115            // original binary, a charset that does not corrupt the original bytes must be used.
116            xhr.override_mime_type("octet/binary; charset=ISO-8859-5").unwrap();
117
118            // Initialize and send the request.
119            xhr.open_with_async("GET", url, false).map_err(|_| {
120                $crate::errors::ParameterError::Wasm(
121                    "Download failed - This browser does not support synchronous requests".to_string(),
122                )
123            })?;
124            xhr.send()
125                .map_err(|_| $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))?;
126
127            // Wait for the response in a blocking fashion.
128            if xhr.response().is_ok() && xhr.status().unwrap() == 200 {
129                // Get the text from the response.
130                let rust_text = xhr
131                    .response_text()
132                    .map_err(|_| $crate::errors::ParameterError::Wasm("XMLHttpRequest failed".to_string()))?
133                    .ok_or($crate::errors::ParameterError::Wasm(
134                        "The request was successful but no parameters were received".to_string(),
135                    ))?;
136
137                // Re-encode the text back into bytes using the chosen encoding.
138                use encoding::Encoding;
139                encoding::all::ISO_8859_5
140                    .encode(&rust_text, encoding::EncoderTrap::Strict)
141                    .map_err(|_| $crate::errors::ParameterError::Wasm("Parameter decoding failed".to_string()))
142            } else {
143                Err($crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))
144            }
145        }
146    };
147}
148
149macro_rules! impl_load_bytes_logic_local {
150    ($filepath: expr, $buffer: expr, $expected_size: expr, $expected_checksum: expr) => {
151        // Ensure the size matches.
152        if $expected_size != $buffer.len() {
153            remove_file!($filepath);
154            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, $buffer.len()));
155        }
156
157        // Ensure the checksum matches.
158        let candidate_checksum = checksum!($buffer);
159        if $expected_checksum != candidate_checksum {
160            return checksum_error!($expected_checksum, candidate_checksum);
161        }
162
163        return Ok($buffer.to_vec());
164    };
165}
166
167macro_rules! impl_load_bytes_logic_remote {
168    ($remote_url: expr, $local_dir: expr, $filename: expr, $metadata: expr, $expected_checksum: expr, $expected_size: expr) => {
169        cfg_if::cfg_if! {
170            if #[cfg(all(feature = "filesystem", not(feature="wasm")))] {
171                // Compose the correct file path for the parameter file.
172                let mut file_path = aleo_std::aleo_dir();
173                file_path.push($local_dir);
174                file_path.push($filename);
175
176                let buffer = if file_path.exists() {
177                    // Attempts to load the parameter file locally with an absolute path.
178                    std::fs::read(&file_path)?
179                } else {
180                    // Downloads the missing parameters and stores it in the local directory for use.
181                    #[cfg(not(feature = "no_std_out"))]
182                    {
183                        use colored::*;
184                        let path = format!("(in {:?})", file_path);
185                        eprintln!(
186                            "\n⚠️  \"{}\" does not exist. Downloading and storing it {}.\n",
187                            $filename, path.dimmed()
188                        );
189                    }
190
191                    // Load remote file
192                    cfg_if::cfg_if!{
193                        if #[cfg(all(not(feature = "wasm"), not(target_env = "sgx")))] {
194                            let url = format!("{}/{}", $remote_url, $filename);
195                            let mut buffer = vec![];
196                            Self::remote_fetch(&mut buffer, &url)?;
197
198                            // Ensure the checksum matches.
199                            let candidate_checksum = checksum!(&buffer);
200                            if $expected_checksum != candidate_checksum {
201                                return checksum_error!($expected_checksum, candidate_checksum)
202                            }
203
204                            match Self::store_bytes(&buffer, &file_path) {
205                                Ok(()) => buffer,
206                                Err(_) => {
207                                    eprintln!(
208                                        "\n❗ Error - Failed to store \"{}\" locally. Please download this file manually and ensure it is stored in {:?}.\n",
209                                        $filename, file_path
210                                    );
211                                    buffer
212                                }
213                            }
214                        } else {
215                            return Err($crate::errors::ParameterError::RemoteFetchDisabled);
216                        }
217                    }
218                };
219
220                // Ensure the size matches.
221                if $expected_size != buffer.len() {
222                    remove_file!(file_path);
223                    return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
224                }
225
226                // Ensure the checksum matches.
227                let candidate_checksum = checksum!(buffer.as_slice());
228                if $expected_checksum != candidate_checksum {
229                    return checksum_error!($expected_checksum, candidate_checksum)
230                }
231                return Ok(buffer);
232            } else {
233                cfg_if::cfg_if! {
234                    if #[cfg(feature = "wasm")] {
235                        let url = format!("{}/{}", $remote_url, $filename);
236                        let buffer = Self::remote_fetch(&url)?;
237
238                        // Ensure the size matches.
239                        if $expected_size != buffer.len() {
240                            remove_file!(file_path);
241                            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
242                        }
243
244                        // Ensure the checksum matches.
245                        let candidate_checksum = checksum!(&buffer);
246                        if $expected_checksum != candidate_checksum {
247                            return checksum_error!($expected_checksum, candidate_checksum)
248                        }
249
250                        return Ok(buffer)
251                    } else {
252                        return Err($crate::errors::ParameterError::FilesystemDisabled);
253                    }
254                }
255            }
256        }
257    }
258}
259
260#[macro_export]
261macro_rules! impl_local {
262    ($name: ident, $local_dir: expr, $fname: tt, "usrs") => {
263        #[derive(Clone, Debug, PartialEq, Eq)]
264        pub struct $name;
265
266        impl $name {
267            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
268
269            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
270                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
271                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
272                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
273
274                let _filepath = concat!($local_dir, $fname, ".", "usrs");
275                let buffer = include_bytes!(concat!($local_dir, $fname, ".", "usrs"));
276
277                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
278            }
279        }
280
281        paste::item! {
282            #[cfg(test)]
283            #[test]
284            fn [< test_ $fname _usrs >]() {
285                assert!($name::load_bytes().is_ok());
286            }
287        }
288    };
289    ($name: ident, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
290        #[derive(Clone, Debug, PartialEq, Eq)]
291        pub struct $name;
292
293        impl $name {
294            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
295
296            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
297                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
298                let expected_checksum: String =
299                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
300                let expected_size: usize =
301                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
302
303                let _filepath = concat!($local_dir, $credits_version, "/", $fname, ".", $ftype);
304                let buffer = include_bytes!(concat!($local_dir, $credits_version, "/", $fname, ".", $ftype));
305
306                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
307            }
308        }
309
310        paste::item! {
311            #[cfg(test)]
312            #[test]
313            fn [< test_ $credits_version _ $fname _ $ftype >]() {
314                assert!($name::load_bytes().is_ok());
315            }
316        }
317    };
318}
319
320#[macro_export]
321macro_rules! impl_remote {
322    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, "usrs") => {
323        pub struct $name;
324
325        impl $name {
326            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
327
328            impl_store_and_remote_fetch!();
329
330            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
331                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
332                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
333                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
334
335                // Construct the versioned filename.
336                let filename = match expected_checksum.get(0..7) {
337                    Some(sum) => format!("{}.{}.{}", $fname, "usrs", sum),
338                    _ => format!("{}.{}", $fname, "usrs"),
339                };
340
341                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
342            }
343        }
344        paste::item! {
345            #[cfg(test)]
346            #[test]
347            fn [< test_ $fname _usrs >]() {
348                assert!($name::load_bytes().is_ok());
349            }
350        }
351    };
352    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
353        pub struct $name;
354
355        impl $name {
356            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
357
358            impl_store_and_remote_fetch!();
359
360            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
361                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
362                let expected_checksum: String =
363                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
364                let expected_size: usize =
365                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
366
367                // Construct the versioned filename.
368                let filename = match expected_checksum.get(0..7) {
369                    Some(sum) => format!("{}.{}.{}", $fname, $ftype, sum),
370                    _ => format!("{}.{}", $fname, $ftype),
371                };
372
373                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
374            }
375        }
376
377        paste::item! {
378            #[cfg(test)]
379            #[test]
380            fn [< test_ $credits_version _ $fname _ $ftype >]() {
381                assert!($name::load_bytes().is_ok());
382            }
383        }
384    };
385}