zcash_proofs/
lib.rs

1//! *Zcash circuits and proofs.*
2//!
3//! `zcash_proofs` contains the zk-SNARK circuits used by Zcash, and the APIs for creating
4//! and verifying proofs.
5//!
6//! ## Feature flags
7#![doc = document_features::document_features!()]
8//!
9
10#![cfg_attr(docsrs, feature(doc_cfg))]
11#![cfg_attr(docsrs, feature(doc_auto_cfg))]
12// Catch documentation errors caused by code changes.
13#![deny(rustdoc::broken_intra_doc_links)]
14// Temporary until we have addressed all Result<T, ()> cases.
15#![allow(clippy::result_unit_err)]
16
17use bellman::groth16::{prepare_verifying_key, PreparedVerifyingKey, VerifyingKey};
18use bls12_381::Bls12;
19use sapling::circuit::{
20    OutputParameters, PreparedOutputVerifyingKey, PreparedSpendVerifyingKey, SpendParameters,
21};
22
23use std::fs::File;
24use std::io::{self, BufReader};
25use std::path::Path;
26
27#[cfg(feature = "directories")]
28use std::path::PathBuf;
29
30pub mod circuit;
31mod hashreader;
32pub mod sprout;
33
34#[cfg(any(feature = "local-prover", feature = "bundled-prover"))]
35pub mod prover;
36
37#[cfg(feature = "download-params")]
38mod downloadreader;
39
40// Circuit names
41
42/// The sapling spend parameters file name.
43pub const SAPLING_SPEND_NAME: &str = "sapling-spend.params";
44
45/// The sapling output parameters file name.
46pub const SAPLING_OUTPUT_NAME: &str = "sapling-output.params";
47
48/// The sprout parameters file name.
49pub const SPROUT_NAME: &str = "sprout-groth16.params";
50
51// Circuit hashes
52const SAPLING_SPEND_HASH: &str = "8270785a1a0d0bc77196f000ee6d221c9c9894f55307bd9357c3f0105d31ca63991ab91324160d8f53e2bbd3c2633a6eb8bdf5205d822e7f3f73edac51b2b70c";
53const SAPLING_OUTPUT_HASH: &str = "657e3d38dbb5cb5e7dd2970e8b03d69b4787dd907285b5a7f0790dcc8072f60bf593b32cc2d1c030e00ff5ae64bf84c5c3beb84ddc841d48264b4a171744d028";
54const SPROUT_HASH: &str = "e9b238411bd6c0ec4791e9d04245ec350c9c5744f5610dfcce4365d5ca49dfefd5054e371842b3f88fa1b9d7e8e075249b3ebabd167fa8b0f3161292d36c180a";
55
56// Circuit parameter file sizes
57const SAPLING_SPEND_BYTES: u64 = 47958396;
58const SAPLING_OUTPUT_BYTES: u64 = 3592860;
59const SPROUT_BYTES: u64 = 725523612;
60
61#[cfg(feature = "download-params")]
62const DOWNLOAD_URL: &str = "https://download.z.cash/downloads";
63
64/// The paths to the Sapling parameter files.
65#[cfg(feature = "download-params")]
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct SaplingParameterPaths {
68    /// The path to the Sapling spend parameter file.
69    pub spend: PathBuf,
70
71    /// The path to the Sapling output parameter file.
72    pub output: PathBuf,
73}
74
75/// Returns the default folder that the Zcash proving parameters are located in.
76#[cfg(feature = "directories")]
77pub fn default_params_folder() -> Option<PathBuf> {
78    #[cfg(windows)]
79    {
80        use known_folders::{get_known_folder_path, KnownFolder};
81        get_known_folder_path(KnownFolder::RoamingAppData).map(|base| base.join("ZcashParams"))
82    }
83
84    #[cfg(target_os = "macos")]
85    {
86        xdg::BaseDirectories::new()
87            .ok()
88            .map(|base_dirs| base_dirs.get_data_home().join("ZcashParams"))
89    }
90
91    #[cfg(not(any(windows, target_os = "macos")))]
92    {
93        home::home_dir().map(|base| base.join(".zcash-params"))
94    }
95}
96
97/// Download the Zcash Sapling parameters if needed, and store them in the default location.
98/// Always checks the sizes and hashes of the files, even if they didn't need to be downloaded.
99///
100/// A download timeout can be set using the `MINREQ_TIMEOUT` environmental variable.
101///
102/// This mirrors the behaviour of the `fetch-params.sh` script from `zcashd`.
103#[cfg(feature = "download-params")]
104#[deprecated(
105    since = "0.6.0",
106    note = "please replace with `download_sapling_parameters`, and use `download_sprout_parameters` if needed"
107)]
108pub fn download_parameters() -> Result<(), minreq::Error> {
109    download_sapling_parameters(None).map(|_sapling_paths| ())
110}
111
112/// Download the Zcash Sapling parameters if needed, and store them in the default location.
113/// Always checks the sizes and hashes of the files, even if they didn't need to be downloaded.
114///
115/// This mirrors the behaviour of the `fetch-params.sh` script from `zcashd`.
116///
117/// Use `timeout` to set a timeout in seconds for each file download.
118/// If `timeout` is `None`, a timeout can be set using the `MINREQ_TIMEOUT` environmental variable.
119///
120/// Returns the paths to the downloaded files.
121#[cfg(feature = "download-params")]
122pub fn download_sapling_parameters(
123    timeout: Option<u64>,
124) -> Result<SaplingParameterPaths, minreq::Error> {
125    let spend = fetch_params(
126        SAPLING_SPEND_NAME,
127        SAPLING_SPEND_HASH,
128        SAPLING_SPEND_BYTES,
129        timeout,
130    )?;
131    let output = fetch_params(
132        SAPLING_OUTPUT_NAME,
133        SAPLING_OUTPUT_HASH,
134        SAPLING_OUTPUT_BYTES,
135        timeout,
136    )?;
137
138    Ok(SaplingParameterPaths { spend, output })
139}
140
141/// Download the Zcash Sprout parameters if needed, and store them in the default location.
142/// Always checks the size and hash of the file, even if it didn't need to be downloaded.
143///
144/// This mirrors the behaviour of the `fetch-params.sh` script from `zcashd`.
145///
146/// Use `timeout` to set a timeout in seconds for the file download.
147/// If `timeout` is `None`, a timeout can be set using the `MINREQ_TIMEOUT` environmental variable.
148///
149/// Returns the path to the downloaded file.
150#[cfg(feature = "download-params")]
151pub fn download_sprout_parameters(timeout: Option<u64>) -> Result<PathBuf, minreq::Error> {
152    fetch_params(SPROUT_NAME, SPROUT_HASH, SPROUT_BYTES, timeout)
153}
154
155/// Download the specified parameters if needed, and store them in the default location.
156/// Always checks the size and hash of the file, even if it didn't need to be downloaded.
157///
158/// See [`download_sapling_parameters`] for details.
159#[cfg(feature = "download-params")]
160fn fetch_params(
161    name: &str,
162    expected_hash: &str,
163    expected_bytes: u64,
164    timeout: Option<u64>,
165) -> Result<PathBuf, minreq::Error> {
166    // Ensure that the default Zcash parameters location exists.
167    let params_dir = default_params_folder().ok_or_else(|| {
168        io::Error::new(io::ErrorKind::Other, "Could not load default params folder")
169    })?;
170    std::fs::create_dir_all(&params_dir)?;
171
172    let params_path = params_dir.join(name);
173
174    // Download parameters if needed.
175    // TODO: use try_exists when it stabilises, to exit early on permissions errors (#83186)
176    if !params_path.exists() {
177        let result = stream_params_downloads_to_disk(
178            &params_path,
179            name,
180            expected_hash,
181            expected_bytes,
182            timeout,
183        );
184
185        // Remove the file on error, and return the download or hash error.
186        if result.is_err() {
187            let _ = std::fs::remove_file(&params_path);
188            result?;
189        }
190    } else {
191        // TODO: avoid reading the files twice
192        // Either:
193        // - return Ok if the paths exist, or
194        // - always load and return the parameters, for newly downloaded and existing files.
195
196        let file_path_string = params_path.to_string_lossy();
197
198        // Check the file size is correct before hashing large amounts of data.
199        verify_file_size(&params_path, expected_bytes, name, &file_path_string).expect(
200            "parameter file size is not correct, \
201             please clean your Zcash parameters directory and re-run `fetch-params`.",
202        );
203
204        // Read the file to verify the hash,
205        // discarding bytes after they're hashed.
206        let params_file = File::open(&params_path)?;
207        let params_file = BufReader::with_capacity(1024 * 1024, params_file);
208        let params_file = hashreader::HashReader::new(params_file);
209
210        verify_hash(
211            params_file,
212            io::sink(),
213            expected_hash,
214            expected_bytes,
215            name,
216            &file_path_string,
217        )?;
218    }
219
220    Ok(params_path)
221}
222
223/// Download the specified parameter file, stream it to `params_path`, and check its hash.
224///
225/// See [`download_sapling_parameters`] for details.
226#[cfg(feature = "download-params")]
227fn stream_params_downloads_to_disk(
228    params_path: &Path,
229    name: &str,
230    expected_hash: &str,
231    expected_bytes: u64,
232    timeout: Option<u64>,
233) -> Result<(), minreq::Error> {
234    use downloadreader::ResponseLazyReader;
235    use std::io::{BufWriter, Read};
236
237    // Fail early if the directory isn't writeable.
238    let new_params_file = File::create(params_path)?;
239    let new_params_file = BufWriter::with_capacity(1024 * 1024, new_params_file);
240
241    // Set up the download requests.
242    //
243    // It's necessary for us to host these files in two parts,
244    // because of CloudFlare's maximum cached file size limit of 512 MB.
245    // The files must fit in the cache to prevent "denial of wallet" attacks.
246    let params_url_1 = format!("{}/{}.part.1", DOWNLOAD_URL, name);
247    // TODO: skip empty part.2 files when downloading sapling spend and sapling output
248    let params_url_2 = format!("{}/{}.part.2", DOWNLOAD_URL, name);
249
250    let mut params_download_1 = minreq::get(&params_url_1);
251    let mut params_download_2 = minreq::get(&params_url_2);
252    if let Some(timeout) = timeout {
253        params_download_1 = params_download_1.with_timeout(timeout);
254        params_download_2 = params_download_2.with_timeout(timeout);
255    }
256
257    // Download the responses and write them to a new file,
258    // verifying the hash as bytes are read.
259    let params_download_1 = ResponseLazyReader::from(params_download_1);
260    let params_download_2 = ResponseLazyReader::from(params_download_2);
261
262    // Limit the download size to avoid DoS.
263    // This also avoids launching the second request, if the first request provides enough bytes.
264    let params_download = params_download_1
265        .chain(params_download_2)
266        .take(expected_bytes);
267    let params_download = BufReader::with_capacity(1024 * 1024, params_download);
268    let params_download = hashreader::HashReader::new(params_download);
269
270    verify_hash(
271        params_download,
272        new_params_file,
273        expected_hash,
274        expected_bytes,
275        name,
276        &format!("{} + {}", params_url_1, params_url_2),
277    )?;
278
279    Ok(())
280}
281
282/// Zcash Sprout and Sapling groth16 circuit parameters.
283pub struct ZcashParameters {
284    pub spend_params: SpendParameters,
285    pub spend_vk: PreparedSpendVerifyingKey,
286    pub output_params: OutputParameters,
287    pub output_vk: PreparedOutputVerifyingKey,
288    pub sprout_vk: Option<PreparedVerifyingKey<Bls12>>,
289}
290
291/// Load the specified parameters, checking the sizes and hashes of the files.
292///
293/// Returns the loaded parameters.
294pub fn load_parameters(
295    spend_path: &Path,
296    output_path: &Path,
297    sprout_path: Option<&Path>,
298) -> ZcashParameters {
299    // Check the file sizes are correct before hashing large amounts of data.
300    verify_file_size(
301        spend_path,
302        SAPLING_SPEND_BYTES,
303        "sapling spend",
304        &spend_path.to_string_lossy(),
305    )
306    .expect(
307        "parameter file size is not correct, \
308         please clean your Zcash parameters directory and re-run `fetch-params`.",
309    );
310
311    verify_file_size(
312        output_path,
313        SAPLING_OUTPUT_BYTES,
314        "sapling output",
315        &output_path.to_string_lossy(),
316    )
317    .expect(
318        "parameter file size is not correct, \
319         please clean your Zcash parameters directory and re-run `fetch-params`.",
320    );
321
322    if let Some(sprout_path) = sprout_path {
323        verify_file_size(
324            sprout_path,
325            SPROUT_BYTES,
326            "sprout groth16",
327            &sprout_path.to_string_lossy(),
328        )
329        .expect(
330            "parameter file size is not correct, \
331             please clean your Zcash parameters directory and re-run `fetch-params`.",
332        );
333    }
334
335    // Load from each of the paths
336    let spend_fs = File::open(spend_path).expect("couldn't load Sapling spend parameters file");
337    let output_fs = File::open(output_path).expect("couldn't load Sapling output parameters file");
338    let sprout_fs =
339        sprout_path.map(|p| File::open(p).expect("couldn't load Sprout groth16 parameters file"));
340
341    parse_parameters(
342        BufReader::with_capacity(1024 * 1024, spend_fs),
343        BufReader::with_capacity(1024 * 1024, output_fs),
344        sprout_fs.map(|fs| BufReader::with_capacity(1024 * 1024, fs)),
345    )
346}
347
348/// Parse Bls12 keys from bytes as serialized by [`groth16::Parameters::write`].
349///
350/// This function will panic if it encounters unparsable data.
351///
352/// [`groth16::Parameters::write`]: bellman::groth16::Parameters::write
353pub fn parse_parameters<R: io::Read>(
354    spend_fs: R,
355    output_fs: R,
356    sprout_fs: Option<R>,
357) -> ZcashParameters {
358    let mut spend_fs = hashreader::HashReader::new(spend_fs);
359    let mut output_fs = hashreader::HashReader::new(output_fs);
360    let mut sprout_fs = sprout_fs.map(hashreader::HashReader::new);
361
362    // Deserialize params
363    let spend_params = SpendParameters::read(&mut spend_fs, false)
364        .expect("couldn't deserialize Sapling spend parameters");
365    let output_params = OutputParameters::read(&mut output_fs, false)
366        .expect("couldn't deserialize Sapling spend parameters");
367
368    // We only deserialize the verifying key for the Sprout parameters, which
369    // appears at the beginning of the parameter file. The rest is loaded
370    // during proving time.
371    let sprout_vk = sprout_fs.as_mut().map(|fs| {
372        VerifyingKey::<Bls12>::read(fs).expect("couldn't deserialize Sprout Groth16 verifying key")
373    });
374
375    // There is extra stuff (the transcript) at the end of the parameter file which is
376    // used to verify the parameter validity, but we're not interested in that. We do
377    // want to read it, though, so that the BLAKE2b computed afterward is consistent
378    // with `b2sum` on the files.
379    let mut sink = io::sink();
380
381    // TODO: use the correct paths for Windows and macOS
382    //       use the actual file paths supplied by the caller
383    verify_hash(
384        spend_fs,
385        &mut sink,
386        SAPLING_SPEND_HASH,
387        SAPLING_SPEND_BYTES,
388        SAPLING_SPEND_NAME,
389        "a file",
390    )
391    .expect(
392        "Sapling spend parameter file is not correct, \
393         please clean your `~/.zcash-params/` and re-run `fetch-params`.",
394    );
395
396    verify_hash(
397        output_fs,
398        &mut sink,
399        SAPLING_OUTPUT_HASH,
400        SAPLING_OUTPUT_BYTES,
401        SAPLING_OUTPUT_NAME,
402        "a file",
403    )
404    .expect(
405        "Sapling output parameter file is not correct, \
406         please clean your `~/.zcash-params/` and re-run `fetch-params`.",
407    );
408
409    if let Some(sprout_fs) = sprout_fs {
410        verify_hash(
411            sprout_fs,
412            &mut sink,
413            SPROUT_HASH,
414            SPROUT_BYTES,
415            SPROUT_NAME,
416            "a file",
417        )
418        .expect(
419            "Sprout groth16 parameter file is not correct, \
420             please clean your `~/.zcash-params/` and re-run `fetch-params`.",
421        );
422    }
423
424    // Prepare verifying keys
425    let spend_vk = spend_params.prepared_verifying_key();
426    let output_vk = output_params.prepared_verifying_key();
427    let sprout_vk = sprout_vk.map(|vk| prepare_verifying_key(&vk));
428
429    ZcashParameters {
430        spend_params,
431        spend_vk,
432        output_params,
433        output_vk,
434        sprout_vk,
435    }
436}
437
438/// Check if the size of the file at `params_path` matches `expected_bytes`,
439/// using filesystem metadata.
440///
441/// Returns an error containing `name` and `params_source` on failure.
442fn verify_file_size(
443    params_path: &Path,
444    expected_bytes: u64,
445    name: &str,
446    params_source: &str,
447) -> Result<(), io::Error> {
448    let file_size = std::fs::metadata(params_path)?.len();
449
450    if file_size != expected_bytes {
451        return Err(io::Error::new(
452            io::ErrorKind::InvalidData,
453            format!(
454                "{} failed validation:\n\
455                 expected: {} bytes,\n\
456                 actual:   {} bytes from {:?}",
457                name, expected_bytes, file_size, params_source,
458            ),
459        ));
460    }
461
462    Ok(())
463}
464
465/// Check if the Blake2b hash from `hash_reader` matches `expected_hash`,
466/// while streaming from `hash_reader` into `sink`.
467///
468/// `hash_reader` can be used to partially read its inner reader's data,
469/// before verifying the hash using this function.
470///
471/// Returns an error containing `name` and `params_source` on failure.
472fn verify_hash<R: io::Read, W: io::Write>(
473    mut hash_reader: hashreader::HashReader<R>,
474    mut sink: W,
475    expected_hash: &str,
476    expected_bytes: u64,
477    name: &str,
478    params_source: &str,
479) -> Result<(), io::Error> {
480    let read_result = io::copy(&mut hash_reader, &mut sink);
481
482    if let Err(read_error) = read_result {
483        return Err(io::Error::new(
484            read_error.kind(),
485            format!(
486                "{} failed reading:\n\
487                 expected: {} bytes,\n\
488                 actual:   {} bytes from {:?},\n\
489                 error: {:?}",
490                name,
491                expected_bytes,
492                hash_reader.byte_count(),
493                params_source,
494                read_error,
495            ),
496        ));
497    }
498
499    let byte_count = hash_reader.byte_count();
500    let hash = hash_reader.into_hash();
501    if hash != expected_hash {
502        return Err(io::Error::new(
503            io::ErrorKind::InvalidData,
504            format!(
505                "{} failed validation:\n\
506                 expected: {} hashing {} bytes,\n\
507                 actual:   {} hashing {} bytes from {:?}",
508                name, expected_hash, expected_bytes, hash, byte_count, params_source,
509            ),
510        ));
511    }
512
513    Ok(())
514}