#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(rustdoc::broken_intra_doc_links)]
#![allow(clippy::result_unit_err)]
use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey, VerifyingKey};
use bls12_381::Bls12;
use std::fs::File;
use std::io::{self, BufReader};
use std::path::Path;
#[cfg(feature = "directories")]
use directories::BaseDirs;
#[cfg(feature = "directories")]
use std::path::PathBuf;
pub mod circuit;
pub mod constants;
mod hashreader;
pub mod sapling;
pub mod sprout;
#[cfg(any(feature = "local-prover", feature = "bundled-prover"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "local-prover", feature = "bundled-prover")))
)]
pub mod prover;
#[cfg(feature = "download-params")]
#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))]
mod downloadreader;
pub const SAPLING_SPEND_NAME: &str = "sapling-spend.params";
pub const SAPLING_OUTPUT_NAME: &str = "sapling-output.params";
pub const SPROUT_NAME: &str = "sprout-groth16.params";
const SAPLING_SPEND_HASH: &str = "8270785a1a0d0bc77196f000ee6d221c9c9894f55307bd9357c3f0105d31ca63991ab91324160d8f53e2bbd3c2633a6eb8bdf5205d822e7f3f73edac51b2b70c";
const SAPLING_OUTPUT_HASH: &str = "657e3d38dbb5cb5e7dd2970e8b03d69b4787dd907285b5a7f0790dcc8072f60bf593b32cc2d1c030e00ff5ae64bf84c5c3beb84ddc841d48264b4a171744d028";
const SPROUT_HASH: &str = "e9b238411bd6c0ec4791e9d04245ec350c9c5744f5610dfcce4365d5ca49dfefd5054e371842b3f88fa1b9d7e8e075249b3ebabd167fa8b0f3161292d36c180a";
const SAPLING_SPEND_BYTES: u64 = 47958396;
const SAPLING_OUTPUT_BYTES: u64 = 3592860;
const SPROUT_BYTES: u64 = 725523612;
#[cfg(feature = "download-params")]
const DOWNLOAD_URL: &str = "https://download.z.cash/downloads";
#[cfg(feature = "download-params")]
#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SaplingParameterPaths {
pub spend: PathBuf,
pub output: PathBuf,
}
#[cfg(feature = "directories")]
#[cfg_attr(docsrs, doc(cfg(feature = "directories")))]
pub fn default_params_folder() -> Option<PathBuf> {
BaseDirs::new().map(|base_dirs| {
if cfg!(any(windows, target_os = "macos")) {
base_dirs.data_dir().join("ZcashParams")
} else {
base_dirs.home_dir().join(".zcash-params")
}
})
}
#[cfg(feature = "download-params")]
#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))]
#[deprecated(
since = "0.6.0",
note = "please replace with `download_sapling_parameters`, and use `download_sprout_parameters` if needed"
)]
pub fn download_parameters() -> Result<(), minreq::Error> {
download_sapling_parameters(None).map(|_sapling_paths| ())
}
#[cfg(feature = "download-params")]
#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))]
pub fn download_sapling_parameters(
timeout: Option<u64>,
) -> Result<SaplingParameterPaths, minreq::Error> {
let spend = fetch_params(
SAPLING_SPEND_NAME,
SAPLING_SPEND_HASH,
SAPLING_SPEND_BYTES,
timeout,
)?;
let output = fetch_params(
SAPLING_OUTPUT_NAME,
SAPLING_OUTPUT_HASH,
SAPLING_OUTPUT_BYTES,
timeout,
)?;
Ok(SaplingParameterPaths { spend, output })
}
#[cfg(feature = "download-params")]
#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))]
pub fn download_sprout_parameters(timeout: Option<u64>) -> Result<PathBuf, minreq::Error> {
fetch_params(SPROUT_NAME, SPROUT_HASH, SPROUT_BYTES, timeout)
}
#[cfg(feature = "download-params")]
#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))]
fn fetch_params(
name: &str,
expected_hash: &str,
expected_bytes: u64,
timeout: Option<u64>,
) -> Result<PathBuf, minreq::Error> {
let params_dir = default_params_folder().ok_or_else(|| {
io::Error::new(io::ErrorKind::Other, "Could not load default params folder")
})?;
std::fs::create_dir_all(¶ms_dir)?;
let params_path = params_dir.join(name);
if !params_path.exists() {
let result = stream_params_downloads_to_disk(
¶ms_path,
name,
expected_hash,
expected_bytes,
timeout,
);
if result.is_err() {
let _ = std::fs::remove_file(¶ms_path);
result?;
}
} else {
let file_path_string = params_path.to_string_lossy();
verify_file_size(¶ms_path, expected_bytes, name, &file_path_string).expect(
"parameter file size is not correct, \
please clean your Zcash parameters directory and re-run `fetch-params`.",
);
let params_file = File::open(¶ms_path)?;
let params_file = BufReader::with_capacity(1024 * 1024, params_file);
let params_file = hashreader::HashReader::new(params_file);
verify_hash(
params_file,
io::sink(),
expected_hash,
expected_bytes,
name,
&file_path_string,
)?;
}
Ok(params_path)
}
#[cfg(feature = "download-params")]
#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))]
fn stream_params_downloads_to_disk(
params_path: &Path,
name: &str,
expected_hash: &str,
expected_bytes: u64,
timeout: Option<u64>,
) -> Result<(), minreq::Error> {
use downloadreader::ResponseLazyReader;
use std::io::{BufWriter, Read};
let new_params_file = File::create(params_path)?;
let new_params_file = BufWriter::with_capacity(1024 * 1024, new_params_file);
let params_url_1 = format!("{}/{}.part.1", DOWNLOAD_URL, name);
let params_url_2 = format!("{}/{}.part.2", DOWNLOAD_URL, name);
let mut params_download_1 = minreq::get(¶ms_url_1);
let mut params_download_2 = minreq::get(¶ms_url_2);
if let Some(timeout) = timeout {
params_download_1 = params_download_1.with_timeout(timeout);
params_download_2 = params_download_2.with_timeout(timeout);
}
let params_download_1 = ResponseLazyReader::from(params_download_1);
let params_download_2 = ResponseLazyReader::from(params_download_2);
let params_download = params_download_1
.chain(params_download_2)
.take(expected_bytes);
let params_download = BufReader::with_capacity(1024 * 1024, params_download);
let params_download = hashreader::HashReader::new(params_download);
verify_hash(
params_download,
new_params_file,
expected_hash,
expected_bytes,
name,
&format!("{} + {}", params_url_1, params_url_2),
)?;
Ok(())
}
pub struct ZcashParameters {
pub spend_params: Parameters<Bls12>,
pub spend_vk: PreparedVerifyingKey<Bls12>,
pub output_params: Parameters<Bls12>,
pub output_vk: PreparedVerifyingKey<Bls12>,
pub sprout_vk: Option<PreparedVerifyingKey<Bls12>>,
}
pub fn load_parameters(
spend_path: &Path,
output_path: &Path,
sprout_path: Option<&Path>,
) -> ZcashParameters {
verify_file_size(
spend_path,
SAPLING_SPEND_BYTES,
"sapling spend",
&spend_path.to_string_lossy(),
)
.expect(
"parameter file size is not correct, \
please clean your Zcash parameters directory and re-run `fetch-params`.",
);
verify_file_size(
output_path,
SAPLING_OUTPUT_BYTES,
"sapling output",
&output_path.to_string_lossy(),
)
.expect(
"parameter file size is not correct, \
please clean your Zcash parameters directory and re-run `fetch-params`.",
);
if let Some(sprout_path) = sprout_path {
verify_file_size(
sprout_path,
SPROUT_BYTES,
"sprout groth16",
&sprout_path.to_string_lossy(),
)
.expect(
"parameter file size is not correct, \
please clean your Zcash parameters directory and re-run `fetch-params`.",
);
}
let spend_fs = File::open(spend_path).expect("couldn't load Sapling spend parameters file");
let output_fs = File::open(output_path).expect("couldn't load Sapling output parameters file");
let sprout_fs =
sprout_path.map(|p| File::open(p).expect("couldn't load Sprout groth16 parameters file"));
parse_parameters(
BufReader::with_capacity(1024 * 1024, spend_fs),
BufReader::with_capacity(1024 * 1024, output_fs),
sprout_fs.map(|fs| BufReader::with_capacity(1024 * 1024, fs)),
)
}
pub fn parse_parameters<R: io::Read>(
spend_fs: R,
output_fs: R,
sprout_fs: Option<R>,
) -> ZcashParameters {
let mut spend_fs = hashreader::HashReader::new(spend_fs);
let mut output_fs = hashreader::HashReader::new(output_fs);
let mut sprout_fs = sprout_fs.map(hashreader::HashReader::new);
let spend_params = Parameters::<Bls12>::read(&mut spend_fs, false)
.expect("couldn't deserialize Sapling spend parameters file");
let output_params = Parameters::<Bls12>::read(&mut output_fs, false)
.expect("couldn't deserialize Sapling spend parameters file");
let sprout_vk = sprout_fs.as_mut().map(|fs| {
VerifyingKey::<Bls12>::read(fs).expect("couldn't deserialize Sprout Groth16 verifying key")
});
let mut sink = io::sink();
verify_hash(
spend_fs,
&mut sink,
SAPLING_SPEND_HASH,
SAPLING_SPEND_BYTES,
SAPLING_SPEND_NAME,
"a file",
)
.expect(
"Sapling spend parameter file is not correct, \
please clean your `~/.zcash-params/` and re-run `fetch-params`.",
);
verify_hash(
output_fs,
&mut sink,
SAPLING_OUTPUT_HASH,
SAPLING_OUTPUT_BYTES,
SAPLING_OUTPUT_NAME,
"a file",
)
.expect(
"Sapling output parameter file is not correct, \
please clean your `~/.zcash-params/` and re-run `fetch-params`.",
);
if let Some(sprout_fs) = sprout_fs {
verify_hash(
sprout_fs,
&mut sink,
SPROUT_HASH,
SPROUT_BYTES,
SPROUT_NAME,
"a file",
)
.expect(
"Sprout groth16 parameter file is not correct, \
please clean your `~/.zcash-params/` and re-run `fetch-params`.",
);
}
let spend_vk = prepare_verifying_key(&spend_params.vk);
let output_vk = prepare_verifying_key(&output_params.vk);
let sprout_vk = sprout_vk.map(|vk| prepare_verifying_key(&vk));
ZcashParameters {
spend_params,
spend_vk,
output_params,
output_vk,
sprout_vk,
}
}
fn verify_file_size(
params_path: &Path,
expected_bytes: u64,
name: &str,
params_source: &str,
) -> Result<(), io::Error> {
let file_size = std::fs::metadata(params_path)?.len();
if file_size != expected_bytes {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"{} failed validation:\n\
expected: {} bytes,\n\
actual: {} bytes from {:?}",
name, expected_bytes, file_size, params_source,
),
));
}
Ok(())
}
fn verify_hash<R: io::Read, W: io::Write>(
mut hash_reader: hashreader::HashReader<R>,
mut sink: W,
expected_hash: &str,
expected_bytes: u64,
name: &str,
params_source: &str,
) -> Result<(), io::Error> {
let read_result = io::copy(&mut hash_reader, &mut sink);
if let Err(read_error) = read_result {
return Err(io::Error::new(
read_error.kind(),
format!(
"{} failed reading:\n\
expected: {} bytes,\n\
actual: {} bytes from {:?},\n\
error: {:?}",
name,
expected_bytes,
hash_reader.byte_count(),
params_source,
read_error,
),
));
}
let byte_count = hash_reader.byte_count();
let hash = hash_reader.into_hash();
if hash != expected_hash {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"{} failed validation:\n\
expected: {} hashing {} bytes,\n\
actual: {} hashing {} bytes from {:?}",
name, expected_hash, expected_bytes, hash, byte_count, params_source,
),
));
}
Ok(())
}