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()
168 .ok_or_else(|| io::Error::other("Could not load default params folder"))?;
169 std::fs::create_dir_all(¶ms_dir)?;
170
171 let params_path = params_dir.join(name);
172
173 if !params_path.exists() {
176 let result = stream_params_downloads_to_disk(
177 ¶ms_path,
178 name,
179 expected_hash,
180 expected_bytes,
181 timeout,
182 );
183
184 if result.is_err() {
186 let _ = std::fs::remove_file(¶ms_path);
187 result?;
188 }
189 } else {
190 let file_path_string = params_path.to_string_lossy();
196
197 verify_file_size(¶ms_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 let params_file = File::open(¶ms_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#[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 let new_params_file = File::create(params_path)?;
238 let new_params_file = BufWriter::with_capacity(1024 * 1024, new_params_file);
239
240 let params_url_1 = format!("{}/{}.part.1", DOWNLOAD_URL, name);
246 let params_url_2 = format!("{}/{}.part.2", DOWNLOAD_URL, name);
248
249 let mut params_download_1 = minreq::get(¶ms_url_1);
250 let mut params_download_2 = minreq::get(¶ms_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 let params_download_1 = ResponseLazyReader::from(params_download_1);
259 let params_download_2 = ResponseLazyReader::from(params_download_2);
260
261 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
281pub 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
290pub fn load_parameters(
294 spend_path: &Path,
295 output_path: &Path,
296 sprout_path: Option<&Path>,
297) -> ZcashParameters {
298 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 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
347pub 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 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 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 let mut sink = io::sink();
379
380 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 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
437fn 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
464fn 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}