#![doc = document_features::document_features!()]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![deny(rustdoc::broken_intra_doc_links)]
#![allow(clippy::result_unit_err)]
use bellman::groth16::{prepare_verifying_key, PreparedVerifyingKey, VerifyingKey};
use bls12_381::Bls12;
use sapling::circuit::{
OutputParameters, PreparedOutputVerifyingKey, PreparedSpendVerifyingKey, SpendParameters,
};
use std::fs::File;
use std::io::{self, BufReader};
use std::path::Path;
#[cfg(feature = "directories")]
use std::path::PathBuf;
pub mod circuit;
mod hashreader;
pub mod sprout;
#[cfg(any(feature = "local-prover", feature = "bundled-prover"))]
pub mod prover;
#[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")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SaplingParameterPaths {
pub spend: PathBuf,
pub output: PathBuf,
}
#[cfg(feature = "directories")]
pub fn default_params_folder() -> Option<PathBuf> {
#[cfg(windows)]
{
use known_folders::{get_known_folder_path, KnownFolder};
get_known_folder_path(KnownFolder::RoamingAppData).map(|base| base.join("ZcashParams"))
}
#[cfg(target_os = "macos")]
{
xdg::BaseDirectories::new()
.ok()
.map(|base_dirs| base_dirs.get_data_home().join("ZcashParams"))
}
#[cfg(not(any(windows, target_os = "macos")))]
{
home::home_dir().map(|base| base.join(".zcash-params"))
}
}
#[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")]
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")]
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")]
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::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")]
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!("{DOWNLOAD_URL}/{name}.part.1");
let params_url_2 = format!("{DOWNLOAD_URL}/{name}.part.2");
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: SpendParameters,
pub spend_vk: PreparedSpendVerifyingKey,
pub output_params: OutputParameters,
pub output_vk: PreparedOutputVerifyingKey,
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 = SpendParameters::read(&mut spend_fs, false)
.expect("couldn't deserialize Sapling spend parameters");
let output_params = OutputParameters::read(&mut output_fs, false)
.expect("couldn't deserialize Sapling spend parameters");
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 = spend_params.prepared_verifying_key();
let output_vk = output_params.prepared_verifying_key();
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!(
"{name} failed validation:\n\
expected: {expected_bytes} bytes,\n\
actual: {file_size} bytes from {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!(
"{name} failed validation:\n\
expected: {expected_hash} hashing {expected_bytes} bytes,\n\
actual: {hash} hashing {byte_count} bytes from {params_source:?}",
),
));
}
Ok(())
}