ark_srs/
load.rs

1//! Utils for persisting serialized data to files and loading them into memroy.
2//! We deal with `ark-serialize::CanonicalSerialize` compatible objects.
3
4use alloc::{
5    borrow::ToOwned,
6    format,
7    string::{String, ToString},
8    vec::Vec,
9};
10use anyhow::{anyhow, Context, Result};
11use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, Write};
12use ark_std::rand::{distributions::Alphanumeric, Rng as _};
13use directories::ProjectDirs;
14use sha2::{Digest, Sha256};
15use std::{
16    fs::{self, create_dir_all, File},
17    io::BufReader,
18    path::{Path, PathBuf},
19};
20
21/// store any serializable data into `dest`.
22pub fn store_data<T: CanonicalSerialize>(data: T, dest: PathBuf) -> Result<()> {
23    let mut f = File::create(dest)?;
24    let mut bytes = Vec::new();
25    data.serialize_uncompressed(&mut bytes)?;
26    Ok(f.write_all(&bytes)?)
27}
28
29/// load any deserializable data into memory
30pub fn load_data<T: CanonicalDeserialize>(src: PathBuf) -> Result<T> {
31    let f = File::open(src)?;
32    // maximum 8 KB of buffer for memory exhaustion protection for malicious file
33    let mut reader = BufReader::with_capacity(8000, f);
34    let mut bytes = Vec::new();
35    reader.read_to_end(&mut bytes)?;
36
37    Ok(T::deserialize_uncompressed_unchecked(&bytes[..])?)
38}
39
40/// Download srs file and save to disk
41///
42/// - `basename`: the filename used in download URL
43/// - `dest`: the filename for local cache
44pub fn download_srs_file(basename: &str, dest: impl AsRef<Path>) -> Result<()> {
45    // Ensure download directory exists
46    create_dir_all(dest.as_ref().parent().context("no parent dir")?)
47        .context("Unable to create directory")?;
48
49    let version = "0.2.0"; // TODO infer or make configurable
50    let url = format!(
51        "https://github.com/EspressoSystems/ark-srs/releases/download/v{version}/{basename}",
52    );
53    tracing::info!("Downloading SRS from {url}");
54    let mut buf: Vec<u8> = Vec::new();
55    ureq::get(&url)
56        .call()?
57        .into_reader()
58        .read_to_end(&mut buf)?;
59
60    // Download to a temporary file and rename to dest on completion. This
61    // should prevent some errors if this function is called concurrently
62    // because the concurrent operations would happen on different files and the
63    // destination file should never be in an incomplete state.
64    let mut temp_path = dest.as_ref().as_os_str().to_owned();
65    let suffix: String = rand::thread_rng()
66        .sample_iter(&Alphanumeric)
67        .take(16)
68        .map(char::from)
69        .collect();
70    temp_path.push(format!(".temp.{suffix}"));
71    {
72        let mut f = File::create(&temp_path)?;
73        f.write_all(&buf)?;
74    }
75    std::fs::rename(temp_path, dest.as_ref())?;
76    tracing::info!("Saved SRS to {:?}", dest.as_ref());
77    Ok(())
78}
79
80/// The base data directory for the project
81fn get_project_root() -> Result<PathBuf> {
82    // (empty) qualifier, (empty) organization, and application name
83    // see more <https://docs.rs/directories/5.0.1/directories/struct.ProjectDirs.html#method.from>
84    Ok(ProjectDirs::from("", "", "ark-srs")
85        .context("Failed to get project root")?
86        .data_dir()
87        .to_path_buf())
88}
89
90/// loading KZG10 parameters from files
91pub mod kzg10 {
92    use super::*;
93    use ark_poly_commit::kzg10;
94
95    /// ceremonies for curve [Bn254][https://docs.rs/ark-bn254/latest/ark_bn254/]
96    pub mod bn254 {
97        use super::*;
98        use ark_bn254::Bn254;
99
100        /// Aztec2020 KZG setup
101        pub mod aztec {
102            use crate::constants::AZTEC20_CHECKSUMS;
103
104            use super::*;
105
106            /// Returns the default path for pre-serialized param files
107            pub fn default_path(project_root: Option<PathBuf>, degree: usize) -> Result<PathBuf> {
108                let mut path = if let Some(root) = project_root {
109                    root
110                } else {
111                    get_project_root()?
112                };
113                path.push("aztec20");
114                path.push(degree_to_basename(degree));
115                path.set_extension("bin");
116                Ok(path)
117            }
118
119            pub(crate) fn degree_to_basename(degree: usize) -> String {
120                format!("kzg10-aztec20-srs-{degree}.bin").to_string()
121            }
122
123            /// Load SRS from Aztec's ignition ceremony from files.
124            ///
125            /// # Note
126            /// we force specifying a `src` (instead of taking in `Option`) in
127            /// case the param files contains much more than `degree` needed.
128            /// And we want to avoid unnecessarily complicated logic for
129            /// iterating through all parameter files and find the smallest
130            /// param files that's bigger than the degree requested.
131            pub fn load_aztec_srs(
132                degree: usize,
133                src: PathBuf,
134            ) -> Result<kzg10::UniversalParams<Bn254>> {
135                let mut f = File::open(&src).map_err(|_| anyhow!("{} not found", src.display()))?;
136                // the max degree of the param file supported, parsed from file name
137                // getting the 1024 out of `data/aztec20/kzg10-aztec20-srs-1024.bin`
138                let f_degree = src
139                    .file_stem()
140                    .unwrap()
141                    .to_str()
142                    .unwrap()
143                    .rsplit_once('-')
144                    .expect("unconventional filename")
145                    .1
146                    .parse::<usize>()
147                    .expect("fail to parse to uint");
148
149                let mut bytes = Vec::new();
150                f.read_to_end(&mut bytes)?;
151
152                let checksum: [u8; 32] = Sha256::digest(&bytes).into();
153                if !AZTEC20_CHECKSUMS
154                    .iter()
155                    .any(|(d, cksum)| *d == f_degree && checksum == *cksum)
156                {
157                    tracing::error!("Checksum failed, removing {}", src.display());
158                    fs::remove_file(src)?;
159                    return Err(anyhow!("Checksum failed!"));
160                }
161
162                let mut srs = kzg10::UniversalParams::<Bn254>::deserialize_uncompressed_unchecked(
163                    &bytes[..],
164                )?;
165
166                // trim the srs to fit the actual requested degree
167                srs.powers_of_g.truncate(degree + 1);
168                Ok(srs)
169            }
170        }
171    }
172}