1#![doc = document_features::document_features!()]
8#![cfg_attr(docsrs, feature(doc_cfg))]
11#![cfg_attr(docsrs, feature(doc_auto_cfg))]
12#![deny(rustdoc::broken_intra_doc_links)]
14#![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
40pub const SAPLING_SPEND_NAME: &str = "sapling-spend.params";
44
45pub const SAPLING_OUTPUT_NAME: &str = "sapling-output.params";
47
48pub const SPROUT_NAME: &str = "sprout-groth16.params";
50
51const SAPLING_SPEND_HASH: &str = "8270785a1a0d0bc77196f000ee6d221c9c9894f55307bd9357c3f0105d31ca63991ab91324160d8f53e2bbd3c2633a6eb8bdf5205d822e7f3f73edac51b2b70c";
53const SAPLING_OUTPUT_HASH: &str = "657e3d38dbb5cb5e7dd2970e8b03d69b4787dd907285b5a7f0790dcc8072f60bf593b32cc2d1c030e00ff5ae64bf84c5c3beb84ddc841d48264b4a171744d028";
54const SPROUT_HASH: &str = "e9b238411bd6c0ec4791e9d04245ec350c9c5744f5610dfcce4365d5ca49dfefd5054e371842b3f88fa1b9d7e8e075249b3ebabd167fa8b0f3161292d36c180a";
55
56const 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#[cfg(feature = "download-params")]
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct SaplingParameterPaths {
68 pub spend: PathBuf,
70
71 pub output: PathBuf,
73}
74
75#[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#[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#[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#[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#[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 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(¶ms_dir)?;
171
172 let params_path = params_dir.join(name);
173
174 if !params_path.exists() {
177 let result = stream_params_downloads_to_disk(
178 ¶ms_path,
179 name,
180 expected_hash,
181 expected_bytes,
182 timeout,
183 );
184
185 if result.is_err() {
187 let _ = std::fs::remove_file(¶ms_path);
188 result?;
189 }
190 } else {
191 let file_path_string = params_path.to_string_lossy();
197
198 verify_file_size(¶ms_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 let params_file = File::open(¶ms_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#[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 let new_params_file = File::create(params_path)?;
239 let new_params_file = BufWriter::with_capacity(1024 * 1024, new_params_file);
240
241 let params_url_1 = format!("{}/{}.part.1", DOWNLOAD_URL, name);
247 let params_url_2 = format!("{}/{}.part.2", DOWNLOAD_URL, name);
249
250 let mut params_download_1 = minreq::get(¶ms_url_1);
251 let mut params_download_2 = minreq::get(¶ms_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 let params_download_1 = ResponseLazyReader::from(params_download_1);
260 let params_download_2 = ResponseLazyReader::from(params_download_2);
261
262 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
282pub 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
291pub fn load_parameters(
295 spend_path: &Path,
296 output_path: &Path,
297 sprout_path: Option<&Path>,
298) -> ZcashParameters {
299 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 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
348pub 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 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 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 let mut sink = io::sink();
380
381 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 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
438fn 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
465fn 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}