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()
168        .ok_or_else(|| io::Error::other("Could not load default params folder"))?;
169    std::fs::create_dir_all(&params_dir)?;
170
171    let params_path = params_dir.join(name);
172
173    // Download parameters if needed.
174    // TODO: use try_exists when it stabilises, to exit early on permissions errors (#83186)
175    if !params_path.exists() {
176        let result = stream_params_downloads_to_disk(
177            &params_path,
178            name,
179            expected_hash,
180            expected_bytes,
181            timeout,
182        );
183
184        // Remove the file on error, and return the download or hash error.
185        if result.is_err() {
186            let _ = std::fs::remove_file(&params_path);
187            result?;
188        }
189    } else {
190        // TODO: avoid reading the files twice
191        // Either:
192        // - return Ok if the paths exist, or
193        // - always load and return the parameters, for newly downloaded and existing files.
194
195        let file_path_string = params_path.to_string_lossy();
196
197        // Check the file size is correct before hashing large amounts of data.
198        verify_file_size(&params_path, expected_bytes, name, &file_path_string).expect(
199            "parameter file size is not correct, \
200             please clean your Zcash parameters directory and re-run `fetch-params`.",
201        );
202
203        // Read the file to verify the hash,
204        // discarding bytes after they're hashed.
205        let params_file = File::open(&params_path)?;
206        let params_file = BufReader::with_capacity(1024 * 1024, params_file);
207        let params_file = hashreader::HashReader::new(params_file);
208
209        verify_hash(
210            params_file,
211            io::sink(),
212            expected_hash,
213            expected_bytes,
214            name,
215            &file_path_string,
216        )?;
217    }
218
219    Ok(params_path)
220}
221
222/// Download the specified parameter file, stream it to `params_path`, and check its hash.
223///
224/// See [`download_sapling_parameters`] for details.
225#[cfg(feature = "download-params")]
226fn stream_params_downloads_to_disk(
227    params_path: &Path,
228    name: &str,
229    expected_hash: &str,
230    expected_bytes: u64,
231    timeout: Option<u64>,
232) -> Result<(), minreq::Error> {
233    use downloadreader::ResponseLazyReader;
234    use std::io::{BufWriter, Read};
235
236    // Fail early if the directory isn't writeable.
237    let new_params_file = File::create(params_path)?;
238    let new_params_file = BufWriter::with_capacity(1024 * 1024, new_params_file);
239
240    // Set up the download requests.
241    //
242    // It's necessary for us to host these files in two parts,
243    // because of CloudFlare's maximum cached file size limit of 512 MB.
244    // The files must fit in the cache to prevent "denial of wallet" attacks.
245    let params_url_1 = format!("{}/{}.part.1", DOWNLOAD_URL, name);
246    // TODO: skip empty part.2 files when downloading sapling spend and sapling output
247    let params_url_2 = format!("{}/{}.part.2", DOWNLOAD_URL, name);
248
249    let mut params_download_1 = minreq::get(&params_url_1);
250    let mut params_download_2 = minreq::get(&params_url_2);
251    if let Some(timeout) = timeout {
252        params_download_1 = params_download_1.with_timeout(timeout);
253        params_download_2 = params_download_2.with_timeout(timeout);
254    }
255
256    // Download the responses and write them to a new file,
257    // verifying the hash as bytes are read.
258    let params_download_1 = ResponseLazyReader::from(params_download_1);
259    let params_download_2 = ResponseLazyReader::from(params_download_2);
260
261    // Limit the download size to avoid DoS.
262    // This also avoids launching the second request, if the first request provides enough bytes.
263    let params_download = params_download_1
264        .chain(params_download_2)
265        .take(expected_bytes);
266    let params_download = BufReader::with_capacity(1024 * 1024, params_download);
267    let params_download = hashreader::HashReader::new(params_download);
268
269    verify_hash(
270        params_download,
271        new_params_file,
272        expected_hash,
273        expected_bytes,
274        name,
275        &format!("{} + {}", params_url_1, params_url_2),
276    )?;
277
278    Ok(())
279}
280
281/// Zcash Sprout and Sapling groth16 circuit parameters.
282pub struct ZcashParameters {
283    pub spend_params: SpendParameters,
284    pub spend_vk: PreparedSpendVerifyingKey,
285    pub output_params: OutputParameters,
286    pub output_vk: PreparedOutputVerifyingKey,
287    pub sprout_vk: Option<PreparedVerifyingKey<Bls12>>,
288}
289
290/// Load the specified parameters, checking the sizes and hashes of the files.
291///
292/// Returns the loaded parameters.
293pub fn load_parameters(
294    spend_path: &Path,
295    output_path: &Path,
296    sprout_path: Option<&Path>,
297) -> ZcashParameters {
298    // Check the file sizes are correct before hashing large amounts of data.
299    verify_file_size(
300        spend_path,
301        SAPLING_SPEND_BYTES,
302        "sapling spend",
303        &spend_path.to_string_lossy(),
304    )
305    .expect(
306        "parameter file size is not correct, \
307         please clean your Zcash parameters directory and re-run `fetch-params`.",
308    );
309
310    verify_file_size(
311        output_path,
312        SAPLING_OUTPUT_BYTES,
313        "sapling output",
314        &output_path.to_string_lossy(),
315    )
316    .expect(
317        "parameter file size is not correct, \
318         please clean your Zcash parameters directory and re-run `fetch-params`.",
319    );
320
321    if let Some(sprout_path) = sprout_path {
322        verify_file_size(
323            sprout_path,
324            SPROUT_BYTES,
325            "sprout groth16",
326            &sprout_path.to_string_lossy(),
327        )
328        .expect(
329            "parameter file size is not correct, \
330             please clean your Zcash parameters directory and re-run `fetch-params`.",
331        );
332    }
333
334    // Load from each of the paths
335    let spend_fs = File::open(spend_path).expect("couldn't load Sapling spend parameters file");
336    let output_fs = File::open(output_path).expect("couldn't load Sapling output parameters file");
337    let sprout_fs =
338        sprout_path.map(|p| File::open(p).expect("couldn't load Sprout groth16 parameters file"));
339
340    parse_parameters(
341        BufReader::with_capacity(1024 * 1024, spend_fs),
342        BufReader::with_capacity(1024 * 1024, output_fs),
343        sprout_fs.map(|fs| BufReader::with_capacity(1024 * 1024, fs)),
344    )
345}
346
347/// Parse Bls12 keys from bytes as serialized by [`groth16::Parameters::write`].
348///
349/// This function will panic if it encounters unparsable data.
350///
351/// [`groth16::Parameters::write`]: bellman::groth16::Parameters::write
352pub fn parse_parameters<R: io::Read>(
353    spend_fs: R,
354    output_fs: R,
355    sprout_fs: Option<R>,
356) -> ZcashParameters {
357    let mut spend_fs = hashreader::HashReader::new(spend_fs);
358    let mut output_fs = hashreader::HashReader::new(output_fs);
359    let mut sprout_fs = sprout_fs.map(hashreader::HashReader::new);
360
361    // Deserialize params
362    let spend_params = SpendParameters::read(&mut spend_fs, false)
363        .expect("couldn't deserialize Sapling spend parameters");
364    let output_params = OutputParameters::read(&mut output_fs, false)
365        .expect("couldn't deserialize Sapling spend parameters");
366
367    // We only deserialize the verifying key for the Sprout parameters, which
368    // appears at the beginning of the parameter file. The rest is loaded
369    // during proving time.
370    let sprout_vk = sprout_fs.as_mut().map(|fs| {
371        VerifyingKey::<Bls12>::read(fs).expect("couldn't deserialize Sprout Groth16 verifying key")
372    });
373
374    // There is extra stuff (the transcript) at the end of the parameter file which is
375    // used to verify the parameter validity, but we're not interested in that. We do
376    // want to read it, though, so that the BLAKE2b computed afterward is consistent
377    // with `b2sum` on the files.
378    let mut sink = io::sink();
379
380    // TODO: use the correct paths for Windows and macOS
381    //       use the actual file paths supplied by the caller
382    verify_hash(
383        spend_fs,
384        &mut sink,
385        SAPLING_SPEND_HASH,
386        SAPLING_SPEND_BYTES,
387        SAPLING_SPEND_NAME,
388        "a file",
389    )
390    .expect(
391        "Sapling spend parameter file is not correct, \
392         please clean your `~/.zcash-params/` and re-run `fetch-params`.",
393    );
394
395    verify_hash(
396        output_fs,
397        &mut sink,
398        SAPLING_OUTPUT_HASH,
399        SAPLING_OUTPUT_BYTES,
400        SAPLING_OUTPUT_NAME,
401        "a file",
402    )
403    .expect(
404        "Sapling output parameter file is not correct, \
405         please clean your `~/.zcash-params/` and re-run `fetch-params`.",
406    );
407
408    if let Some(sprout_fs) = sprout_fs {
409        verify_hash(
410            sprout_fs,
411            &mut sink,
412            SPROUT_HASH,
413            SPROUT_BYTES,
414            SPROUT_NAME,
415            "a file",
416        )
417        .expect(
418            "Sprout groth16 parameter file is not correct, \
419             please clean your `~/.zcash-params/` and re-run `fetch-params`.",
420        );
421    }
422
423    // Prepare verifying keys
424    let spend_vk = spend_params.prepared_verifying_key();
425    let output_vk = output_params.prepared_verifying_key();
426    let sprout_vk = sprout_vk.map(|vk| prepare_verifying_key(&vk));
427
428    ZcashParameters {
429        spend_params,
430        spend_vk,
431        output_params,
432        output_vk,
433        sprout_vk,
434    }
435}
436
437/// Check if the size of the file at `params_path` matches `expected_bytes`,
438/// using filesystem metadata.
439///
440/// Returns an error containing `name` and `params_source` on failure.
441fn verify_file_size(
442    params_path: &Path,
443    expected_bytes: u64,
444    name: &str,
445    params_source: &str,
446) -> Result<(), io::Error> {
447    let file_size = std::fs::metadata(params_path)?.len();
448
449    if file_size != expected_bytes {
450        return Err(io::Error::new(
451            io::ErrorKind::InvalidData,
452            format!(
453                "{} failed validation:\n\
454                 expected: {} bytes,\n\
455                 actual:   {} bytes from {:?}",
456                name, expected_bytes, file_size, params_source,
457            ),
458        ));
459    }
460
461    Ok(())
462}
463
464/// Check if the Blake2b hash from `hash_reader` matches `expected_hash`,
465/// while streaming from `hash_reader` into `sink`.
466///
467/// `hash_reader` can be used to partially read its inner reader's data,
468/// before verifying the hash using this function.
469///
470/// Returns an error containing `name` and `params_source` on failure.
471fn verify_hash<R: io::Read, W: io::Write>(
472    mut hash_reader: hashreader::HashReader<R>,
473    mut sink: W,
474    expected_hash: &str,
475    expected_bytes: u64,
476    name: &str,
477    params_source: &str,
478) -> Result<(), io::Error> {
479    let read_result = io::copy(&mut hash_reader, &mut sink);
480
481    if let Err(read_error) = read_result {
482        return Err(io::Error::new(
483            read_error.kind(),
484            format!(
485                "{} failed reading:\n\
486                 expected: {} bytes,\n\
487                 actual:   {} bytes from {:?},\n\
488                 error: {:?}",
489                name,
490                expected_bytes,
491                hash_reader.byte_count(),
492                params_source,
493                read_error,
494            ),
495        ));
496    }
497
498    let byte_count = hash_reader.byte_count();
499    let hash = hash_reader.into_hash();
500    if hash != expected_hash {
501        return Err(io::Error::new(
502            io::ErrorKind::InvalidData,
503            format!(
504                "{} failed validation:\n\
505                 expected: {} hashing {} bytes,\n\
506                 actual:   {} hashing {} bytes from {:?}",
507                name, expected_hash, expected_bytes, hash, byte_count, params_source,
508            ),
509        ));
510    }
511
512    Ok(())
513}