#![deny(broken_intra_doc_links)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
use bellman::groth16::{self, Parameters, PreparedVerifyingKey};
use blake2s_simd::Params as Blake2sParams;
use bls12_381::Bls12;
use group::{cofactor::CofactorGroup, GroupEncoding};
use libc::{c_uchar, size_t};
use rand_core::{OsRng, RngCore};
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::slice;
use std::sync::Once;
use subtle::CtOption;
use tracing::info;
#[cfg(not(target_os = "windows"))]
use std::ffi::OsStr;
#[cfg(not(target_os = "windows"))]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "windows")]
use std::ffi::OsString;
#[cfg(target_os = "windows")]
use std::os::windows::ffi::OsStringExt;
use zcash_primitives::{
constants::{CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
sapling::{
keys::FullViewingKey, merkle_hash, note_encryption::sapling_ka_agree, redjubjub, spend_sig,
Diversifier, Note, NullifierDerivingKey, Rseed,
},
zip32::{self, sapling_address, sapling_derive_internal_fvk, sapling_find_address},
};
use zcash_proofs::{load_parameters, sprout};
mod blake2b;
mod ed25519;
mod equihash;
mod metrics_ffi;
mod streams_ffi;
mod tracing_ffi;
mod zcashd_orchard;
mod address_ffi;
mod builder_ffi;
mod bundlecache;
mod history_ffi;
mod incremental_merkle_tree;
mod incremental_merkle_tree_ffi;
mod init_ffi;
mod orchard_bundle;
mod orchard_ffi;
mod orchard_keys_ffi;
mod sapling;
mod transaction_ffi;
mod unified_keys_ffi;
mod wallet;
mod wallet_scanner;
mod zip339_ffi;
mod test_harness_ffi;
#[cfg(test)]
mod tests;
static PROOF_PARAMETERS_LOADED: Once = Once::new();
static mut SAPLING_SPEND_VK: Option<groth16::VerifyingKey<Bls12>> = None;
static mut SAPLING_OUTPUT_VK: Option<groth16::VerifyingKey<Bls12>> = None;
static mut SPROUT_GROTH16_VK: Option<PreparedVerifyingKey<Bls12>> = None;
static mut SAPLING_SPEND_PARAMS: Option<Parameters<Bls12>> = None;
static mut SAPLING_OUTPUT_PARAMS: Option<Parameters<Bls12>> = None;
static mut SPROUT_GROTH16_PARAMS_PATH: Option<PathBuf> = None;
static mut ORCHARD_PK: Option<orchard::circuit::ProvingKey> = None;
static mut ORCHARD_VK: Option<orchard::circuit::VerifyingKey> = None;
fn de_ct<T>(ct: CtOption<T>) -> Option<T> {
if ct.is_some().into() {
Some(ct.unwrap())
} else {
None
}
}
fn fixed_scalar_mult(from: &[u8; 32], p_g: &jubjub::SubgroupPoint) -> jubjub::SubgroupPoint {
let f = jubjub::Scalar::from_bytes(from).unwrap();
p_g * f
}
#[no_mangle]
pub extern "C" fn librustzcash_init_zksnark_params(
#[cfg(not(target_os = "windows"))] spend_path: *const u8,
#[cfg(target_os = "windows")] spend_path: *const u16,
spend_path_len: usize,
#[cfg(not(target_os = "windows"))] output_path: *const u8,
#[cfg(target_os = "windows")] output_path: *const u16,
output_path_len: usize,
#[cfg(not(target_os = "windows"))] sprout_path: *const u8,
#[cfg(target_os = "windows")] sprout_path: *const u16,
sprout_path_len: usize,
load_proving_keys: bool,
) {
PROOF_PARAMETERS_LOADED.call_once(|| {
#[cfg(not(target_os = "windows"))]
let (spend_path, output_path, sprout_path) = {
(
OsStr::from_bytes(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
OsStr::from_bytes(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
if sprout_path.is_null() {
None
} else {
Some(OsStr::from_bytes(unsafe {
slice::from_raw_parts(sprout_path, sprout_path_len)
}))
},
)
};
#[cfg(target_os = "windows")]
let (spend_path, output_path, sprout_path) = {
(
OsString::from_wide(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
OsString::from_wide(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
if sprout_path.is_null() {
None
} else {
Some(OsString::from_wide(unsafe {
slice::from_raw_parts(sprout_path, sprout_path_len)
}))
},
)
};
let (spend_path, output_path, sprout_path) = (
Path::new(&spend_path),
Path::new(&output_path),
sprout_path.as_ref().map(Path::new),
);
let params = load_parameters(spend_path, output_path, sprout_path);
let sapling_spend_params = params.spend_params;
let sapling_output_params = params.output_params;
let sapling_spend_vk = sapling_spend_params.vk.clone();
let sapling_output_vk = sapling_output_params.vk.clone();
info!(target: "main", "Loading Orchard parameters");
let orchard_pk = load_proving_keys.then(orchard::circuit::ProvingKey::build);
let orchard_vk = orchard::circuit::VerifyingKey::build();
unsafe {
SAPLING_SPEND_PARAMS = load_proving_keys.then_some(sapling_spend_params);
SAPLING_OUTPUT_PARAMS = load_proving_keys.then_some(sapling_output_params);
SPROUT_GROTH16_PARAMS_PATH = sprout_path.map(|p| p.to_owned());
SAPLING_SPEND_VK = Some(sapling_spend_vk);
SAPLING_OUTPUT_VK = Some(sapling_output_vk);
SPROUT_GROTH16_VK = params.sprout_vk;
ORCHARD_PK = orchard_pk;
ORCHARD_VK = Some(orchard_vk);
}
});
}
#[no_mangle]
pub extern "C" fn librustzcash_tree_uncommitted(result: *mut [c_uchar; 32]) {
let tmp = Note::uncommitted().to_bytes();
let result = unsafe { &mut *result };
*result = tmp;
}
#[no_mangle]
pub extern "C" fn librustzcash_merkle_hash(
depth: size_t,
a: *const [c_uchar; 32],
b: *const [c_uchar; 32],
result: *mut [c_uchar; 32],
) {
let tmp = merkle_hash(depth, unsafe { &*a }, unsafe { &*b });
let result = unsafe { &mut *result };
*result = tmp;
}
#[no_mangle] pub extern "C" fn librustzcash_to_scalar(input: *const [c_uchar; 64], result: *mut [c_uchar; 32]) {
let scalar = jubjub::Scalar::from_bytes_wide(unsafe { &*input });
let result = unsafe { &mut *result };
*result = scalar.to_bytes();
}
#[no_mangle]
pub extern "C" fn librustzcash_ask_to_ak(ask: *const [c_uchar; 32], result: *mut [c_uchar; 32]) {
let ask = unsafe { &*ask };
let ak = fixed_scalar_mult(ask, &SPENDING_KEY_GENERATOR);
let result = unsafe { &mut *result };
*result = ak.to_bytes();
}
#[no_mangle]
pub extern "C" fn librustzcash_nsk_to_nk(nsk: *const [c_uchar; 32], result: *mut [c_uchar; 32]) {
let nsk = unsafe { &*nsk };
let nk = fixed_scalar_mult(nsk, &PROOF_GENERATION_KEY_GENERATOR);
let result = unsafe { &mut *result };
*result = nk.to_bytes();
}
#[no_mangle]
pub extern "C" fn librustzcash_crh_ivk(
ak: *const [c_uchar; 32],
nk: *const [c_uchar; 32],
result: *mut [c_uchar; 32],
) {
let ak = unsafe { &*ak };
let nk = unsafe { &*nk };
let mut h = Blake2sParams::new()
.hash_length(32)
.personal(CRH_IVK_PERSONALIZATION)
.to_state();
h.update(ak);
h.update(nk);
let mut h = h.finalize().as_ref().to_vec();
h[31] &= 0b0000_0111;
let result = unsafe { &mut *result };
result.copy_from_slice(&h);
}
#[no_mangle]
pub extern "C" fn librustzcash_check_diversifier(diversifier: *const [c_uchar; 11]) -> bool {
let diversifier = Diversifier(unsafe { *diversifier });
diversifier.g_d().is_some()
}
#[no_mangle]
pub extern "C" fn librustzcash_ivk_to_pkd(
ivk: *const [c_uchar; 32],
diversifier: *const [c_uchar; 11],
result: *mut [c_uchar; 32],
) -> bool {
let ivk = de_ct(jubjub::Scalar::from_bytes(unsafe { &*ivk }));
let diversifier = Diversifier(unsafe { *diversifier });
if let (Some(ivk), Some(g_d)) = (ivk, diversifier.g_d()) {
let pk_d = g_d * ivk;
let result = unsafe { &mut *result };
*result = pk_d.to_bytes();
true
} else {
false
}
}
#[test]
fn test_gen_r() {
let mut r1 = [0u8; 32];
let mut r2 = [0u8; 32];
librustzcash_sapling_generate_r(&mut r1);
librustzcash_sapling_generate_r(&mut r2);
assert_ne!(r1, r2);
let _ = jubjub::Scalar::from_bytes(&r1).unwrap();
let _ = jubjub::Scalar::from_bytes(&r2).unwrap();
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_generate_r(result: *mut [c_uchar; 32]) {
let mut rng = OsRng;
let mut buffer = [0u8; 64];
rng.fill_bytes(&mut buffer);
let r = jubjub::Scalar::from_bytes_wide(&buffer);
let result = unsafe { &mut *result };
*result = r.to_bytes();
}
fn priv_get_note(
zip216_enabled: bool,
diversifier: *const [c_uchar; 11],
pk_d: *const [c_uchar; 32],
value: u64,
rcm: *const [c_uchar; 32],
) -> Result<Note, ()> {
let diversifier = Diversifier(unsafe { *diversifier });
let g_d = diversifier.g_d().ok_or(())?;
let pk_d = de_ct(if zip216_enabled {
jubjub::ExtendedPoint::from_bytes(unsafe { &*pk_d })
} else {
jubjub::AffinePoint::from_bytes_pre_zip216_compatibility(unsafe { *pk_d }).map(|p| p.into())
})
.ok_or(())?;
let pk_d = de_ct(pk_d.into_subgroup()).ok_or(())?;
let rseed = Rseed::BeforeZip212(de_ct(jubjub::Scalar::from_bytes(unsafe { &*rcm })).ok_or(())?);
let note = Note {
value,
g_d,
pk_d,
rseed,
};
Ok(note)
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_compute_nf(
diversifier: *const [c_uchar; 11],
pk_d: *const [c_uchar; 32],
value: u64,
rcm: *const [c_uchar; 32],
nk: *const [c_uchar; 32],
position: u64,
result: *mut [c_uchar; 32],
) -> bool {
let note = match priv_get_note(true, diversifier, pk_d, value, rcm) {
Ok(p) => p,
Err(_) => return false,
};
let nk = match de_ct(jubjub::ExtendedPoint::from_bytes(unsafe { &*nk })) {
Some(p) => p,
None => return false,
};
let nk = match de_ct(nk.into_subgroup()) {
Some(nk) => NullifierDerivingKey(nk),
None => return false,
};
let nf = note.nf(&nk, position);
let result = unsafe { &mut *result };
result.copy_from_slice(&nf.0);
true
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_compute_cmu(
zip216_enabled: bool,
diversifier: *const [c_uchar; 11],
pk_d: *const [c_uchar; 32],
value: u64,
rcm: *const [c_uchar; 32],
result: *mut [c_uchar; 32],
) -> bool {
let note = match priv_get_note(zip216_enabled, diversifier, pk_d, value, rcm) {
Ok(p) => p,
Err(_) => return false,
};
let result = unsafe { &mut *result };
*result = note.cmu().to_bytes();
true
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_ka_agree(
zip216_enabled: bool,
p: *const [c_uchar; 32],
sk: *const [c_uchar; 32],
result: *mut [c_uchar; 32],
) -> bool {
let p = match de_ct(if zip216_enabled {
jubjub::ExtendedPoint::from_bytes(unsafe { &*p })
} else {
jubjub::AffinePoint::from_bytes_pre_zip216_compatibility(unsafe { *p }).map(|p| p.into())
}) {
Some(p) => p,
None => return false,
};
let sk = match de_ct(jubjub::Scalar::from_bytes(unsafe { &*sk })) {
Some(p) => p,
None => return false,
};
let ka = sapling_ka_agree(&sk, &p);
let result = unsafe { &mut *result };
*result = ka.to_bytes();
true
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_ka_derivepublic(
diversifier: *const [c_uchar; 11],
esk: *const [c_uchar; 32],
result: *mut [c_uchar; 32],
) -> bool {
let diversifier = Diversifier(unsafe { *diversifier });
let g_d = match diversifier.g_d() {
Some(g) => g,
None => return false,
};
let esk = match de_ct(jubjub::Scalar::from_bytes(unsafe { &*esk })) {
Some(p) => p,
None => return false,
};
let p = g_d * esk;
let result = unsafe { &mut *result };
*result = p.to_bytes();
true
}
const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
#[no_mangle]
pub extern "C" fn librustzcash_sprout_prove(
proof_out: *mut [c_uchar; GROTH_PROOF_SIZE],
phi: *const [c_uchar; 32],
rt: *const [c_uchar; 32],
h_sig: *const [c_uchar; 32],
in_sk1: *const [c_uchar; 32],
in_value1: u64,
in_rho1: *const [c_uchar; 32],
in_r1: *const [c_uchar; 32],
in_auth1: *const [c_uchar; sprout::WITNESS_PATH_SIZE],
in_sk2: *const [c_uchar; 32],
in_value2: u64,
in_rho2: *const [c_uchar; 32],
in_r2: *const [c_uchar; 32],
in_auth2: *const [c_uchar; sprout::WITNESS_PATH_SIZE],
out_pk1: *const [c_uchar; 32],
out_value1: u64,
out_r1: *const [c_uchar; 32],
out_pk2: *const [c_uchar; 32],
out_value2: u64,
out_r2: *const [c_uchar; 32],
vpub_old: u64,
vpub_new: u64,
) {
let sprout_fs = File::open(
unsafe { &SPROUT_GROTH16_PARAMS_PATH }
.as_ref()
.expect("parameters should have been initialized"),
)
.expect("couldn't load Sprout groth16 parameters file");
let mut sprout_fs = BufReader::with_capacity(1024 * 1024, sprout_fs);
let params = Parameters::read(&mut sprout_fs, false)
.expect("couldn't deserialize Sprout JoinSplit parameters file");
drop(sprout_fs);
let proof = sprout::create_proof(
unsafe { *phi },
unsafe { *rt },
unsafe { *h_sig },
unsafe { *in_sk1 },
in_value1,
unsafe { *in_rho1 },
unsafe { *in_r1 },
unsafe { &*in_auth1 },
unsafe { *in_sk2 },
in_value2,
unsafe { *in_rho2 },
unsafe { *in_r2 },
unsafe { &*in_auth2 },
unsafe { *out_pk1 },
out_value1,
unsafe { *out_r1 },
unsafe { *out_pk2 },
out_value2,
unsafe { *out_r2 },
vpub_old,
vpub_new,
¶ms,
);
proof
.write(&mut (unsafe { &mut *proof_out })[..])
.expect("should be able to serialize a proof");
}
#[no_mangle]
pub extern "C" fn librustzcash_sprout_verify(
proof: *const [c_uchar; GROTH_PROOF_SIZE],
rt: *const [c_uchar; 32],
h_sig: *const [c_uchar; 32],
mac1: *const [c_uchar; 32],
mac2: *const [c_uchar; 32],
nf1: *const [c_uchar; 32],
nf2: *const [c_uchar; 32],
cm1: *const [c_uchar; 32],
cm2: *const [c_uchar; 32],
vpub_old: u64,
vpub_new: u64,
) -> bool {
sprout::verify_proof(
unsafe { &*proof },
unsafe { &*rt },
unsafe { &*h_sig },
unsafe { &*mac1 },
unsafe { &*mac2 },
unsafe { &*nf1 },
unsafe { &*nf2 },
unsafe { &*cm1 },
unsafe { &*cm2 },
vpub_old,
vpub_new,
unsafe { SPROUT_GROTH16_VK.as_ref() }.expect("parameters should have been initialized"),
)
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_spend_sig(
ask: *const [c_uchar; 32],
ar: *const [c_uchar; 32],
sighash: *const [c_uchar; 32],
result: *mut [c_uchar; 64],
) -> bool {
let ar = match de_ct(jubjub::Scalar::from_bytes(unsafe { &*ar })) {
Some(p) => p,
None => return false,
};
let ask = match redjubjub::PrivateKey::read(&(unsafe { &*ask })[..]) {
Ok(p) => p,
Err(_) => return false,
};
let mut rng = OsRng;
let sig = spend_sig(ask, ar, unsafe { &*sighash }, &mut rng);
sig.write(&mut (unsafe { &mut *result })[..])
.expect("result should be 64 bytes");
true
}
#[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_xsk_master(
seed: *const c_uchar,
seedlen: size_t,
xsk_master: *mut [c_uchar; 169],
) {
let seed = unsafe { std::slice::from_raw_parts(seed, seedlen) };
let xsk = zip32::ExtendedSpendingKey::master(seed);
xsk.write(&mut (unsafe { &mut *xsk_master })[..])
.expect("should be able to serialize an ExtendedSpendingKey");
}
#[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_xsk_derive(
xsk_parent: *const [c_uchar; 169],
i: u32,
xsk_i: *mut [c_uchar; 169],
) {
let xsk_parent = zip32::ExtendedSpendingKey::read(&unsafe { *xsk_parent }[..])
.expect("valid ExtendedSpendingKey");
let i = zip32::ChildIndex::from_index(i);
let xsk = xsk_parent.derive_child(i);
xsk.write(&mut (unsafe { &mut *xsk_i })[..])
.expect("should be able to serialize an ExtendedSpendingKey");
}
#[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_xsk_derive_internal(
xsk_external: *const [c_uchar; 169],
xsk_internal_ret: *mut [c_uchar; 169],
) {
let xsk_external = zip32::ExtendedSpendingKey::read(&unsafe { *xsk_external }[..])
.expect("valid ExtendedSpendingKey");
let xsk_internal = xsk_external.derive_internal();
xsk_internal
.write(&mut (unsafe { &mut *xsk_internal_ret })[..])
.expect("should be able to serialize an ExtendedSpendingKey");
}
#[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_xfvk_derive(
xfvk_parent: *const [c_uchar; 169],
i: u32,
xfvk_i: *mut [c_uchar; 169],
) -> bool {
let xfvk_parent = zip32::ExtendedFullViewingKey::read(&unsafe { *xfvk_parent }[..])
.expect("valid ExtendedFullViewingKey");
let i = zip32::ChildIndex::from_index(i);
let xfvk = match xfvk_parent.derive_child(i) {
Ok(xfvk) => xfvk,
Err(_) => return false,
};
xfvk.write(&mut (unsafe { &mut *xfvk_i })[..])
.expect("should be able to serialize an ExtendedFullViewingKey");
true
}
#[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_derive_internal_fvk(
fvk: *const [c_uchar; 96],
dk: *const [c_uchar; 32],
fvk_ret: *mut [c_uchar; 96],
dk_ret: *mut [c_uchar; 32],
) {
let fvk = FullViewingKey::read(&unsafe { *fvk }[..]).expect("valid Sapling FullViewingKey");
let dk = zip32::sapling::DiversifierKey::from_bytes(unsafe { *dk });
let (fvk_internal, dk_internal) = sapling_derive_internal_fvk(&fvk, &dk);
let fvk_ret = unsafe { &mut *fvk_ret };
let dk_ret = unsafe { &mut *dk_ret };
fvk_ret.copy_from_slice(&fvk_internal.to_bytes());
dk_ret.copy_from_slice(dk_internal.as_bytes());
}
#[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_address(
fvk: *const [c_uchar; 96],
dk: *const [c_uchar; 32],
j: *const [c_uchar; 11],
addr_ret: *mut [c_uchar; 43],
) -> bool {
let fvk = FullViewingKey::read(&unsafe { *fvk }[..]).expect("valid Sapling FullViewingKey");
let dk = zip32::sapling::DiversifierKey::from_bytes(unsafe { *dk });
let j = zip32::DiversifierIndex(unsafe { *j });
match sapling_address(&fvk, &dk, j) {
Some(addr) => {
let addr_ret = unsafe { &mut *addr_ret };
addr_ret.copy_from_slice(&addr.to_bytes());
true
}
None => false,
}
}
#[no_mangle]
pub extern "C" fn librustzcash_zip32_find_sapling_address(
fvk: *const [c_uchar; 96],
dk: *const [c_uchar; 32],
j: *const [c_uchar; 11],
j_ret: *mut [c_uchar; 11],
addr_ret: *mut [c_uchar; 43],
) -> bool {
let fvk = FullViewingKey::read(&unsafe { *fvk }[..]).expect("valid Sapling FullViewingKey");
let dk = zip32::sapling::DiversifierKey::from_bytes(unsafe { *dk });
let j = zip32::DiversifierIndex(unsafe { *j });
match sapling_find_address(&fvk, &dk, j) {
Some((j, addr)) => {
let j_ret = unsafe { &mut *j_ret };
let addr_ret = unsafe { &mut *addr_ret };
j_ret.copy_from_slice(&j.0);
addr_ret.copy_from_slice(&addr.to_bytes());
true
}
None => false,
}
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_diversifier_index(
dk: *const [c_uchar; 32],
d: *const [c_uchar; 11],
j_ret: *mut [c_uchar; 11],
) {
let dk = zip32::sapling::DiversifierKey::from_bytes(unsafe { *dk });
let diversifier = Diversifier(unsafe { *d });
let j_ret = unsafe { &mut *j_ret };
let j = dk.diversifier_index(&diversifier);
j_ret.copy_from_slice(&j.0);
}
#[no_mangle]
pub extern "C" fn librustzcash_getrandom(buf: *mut u8, buf_len: usize) {
let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };
OsRng.fill_bytes(buf);
}