diff --git a/Cargo.lock b/Cargo.lock index d9cbf75..3ab00a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,7 +362,6 @@ dependencies = [ "bellman 0.2.0", "blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "blake2s_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "ff 0.5.0", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/librustzcash/Cargo.toml b/librustzcash/Cargo.toml index 432222b..8f33896 100644 --- a/librustzcash/Cargo.toml +++ b/librustzcash/Cargo.toml @@ -27,7 +27,6 @@ ff = { version = "0.5.0", path = "../ff" } libc = "0.2" pairing = { version = "0.15.0", path = "../pairing" } lazy_static = "1" -byteorder = "1" rand_core = "0.5.1" zcash_primitives = { version = "0.1.0", path = "../zcash_primitives" } zcash_proofs = { version = "0.1.0", path = "../zcash_proofs" } diff --git a/librustzcash/src/rustzcash.rs b/librustzcash/src/rustzcash.rs index fc8d3ef..9c199dd 100644 --- a/librustzcash/src/rustzcash.rs +++ b/librustzcash/src/rustzcash.rs @@ -1,38 +1,34 @@ +//! FFI between the C++ zcashd codebase and the Rust Zcash crates. +//! +//! This is internal to zcashd and is not an officially-supported API. + // Catch documentation errors caused by code changes. #![deny(intra_doc_link_resolution_failure)] +// Clippy has a default-deny lint to prevent dereferencing raw pointer arguments +// in a non-unsafe function. However, declaring a function as unsafe has the +// side-effect that the entire function body is treated as an unsafe {} block, +// and rustc will not enforce full safety checks on the parts of the function +// that would otherwise be safe. +// +// The functions in this crate are all for FFI usage, so it's obvious to the +// caller (which is only ever zcashd) that the arguments must satisfy the +// necessary assumptions. We therefore ignore this lint to retain the benefit of +// explicitly annotating the parts of each function that must themselves satisfy +// assumptions of underlying code. +// +// See https://github.com/rust-lang/rfcs/pull/2585 for more background. +#![allow(clippy::not_unsafe_ptr_arg_deref)] -use lazy_static; - -use ff::{PrimeField, PrimeFieldRepr}; -use pairing::bls12_381::{Bls12, Fr, FrRepr}; - -use zcash_primitives::{ - constants::CRH_IVK_PERSONALIZATION, - jubjub::{ - edwards, - fs::{Fs, FsRepr}, - FixedGenerators, JubjubEngine, JubjubParams, PrimeOrder, ToUniform, Unknown, - }, -}; - -use zcash_proofs::circuit::sapling::TREE_DEPTH as SAPLING_TREE_DEPTH; -use zcash_proofs::circuit::sprout::{self, TREE_DEPTH as SPROUT_TREE_DEPTH}; - -use bellman::gadgets::multipack; -use bellman::groth16::{ - create_random_proof, verify_proof, Parameters, PreparedVerifyingKey, Proof, -}; - +use bellman::groth16::{Parameters, PreparedVerifyingKey, Proof}; use blake2s_simd::Params as Blake2sParams; - -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; - -use rand_core::{OsRng, RngCore}; -use std::io::BufReader; - +use ff::{PrimeField, PrimeFieldRepr}; +use lazy_static; use libc::{c_char, c_uchar, size_t}; +use pairing::bls12_381::{Bls12, Fr, FrRepr}; +use rand_core::{OsRng, RngCore}; use std::ffi::CStr; use std::fs::File; +use std::io::BufReader; use std::path::{Path, PathBuf}; use std::slice; @@ -48,6 +44,12 @@ use std::os::windows::ffi::OsStringExt; use zcash_primitives::{ block::equihash, + constants::CRH_IVK_PERSONALIZATION, + jubjub::{ + edwards, + fs::{Fs, FsRepr}, + FixedGenerators, JubjubEngine, JubjubParams, PrimeOrder, ToUniform, Unknown, + }, merkle_tree::CommitmentTreeWitness, note_encryption::sapling_ka_agree, primitives::{Diversifier, Note, PaymentAddress, ProofGenerationKey, ViewingKey}, @@ -57,8 +59,10 @@ use zcash_primitives::{ zip32, JUBJUB, }; use zcash_proofs::{ + circuit::sapling::TREE_DEPTH as SAPLING_TREE_DEPTH, load_parameters, sapling::{SaplingProvingContext, SaplingVerificationContext}, + sprout, }; #[cfg(test)] @@ -72,50 +76,33 @@ static mut SAPLING_SPEND_PARAMS: Option> = None; static mut SAPLING_OUTPUT_PARAMS: Option> = None; static mut SPROUT_GROTH16_PARAMS_PATH: Option = None; -/// Writes an FrRepr to [u8] of length 32 -fn write_le(f: FrRepr, to: &mut [u8]) { - assert_eq!(to.len(), 32); - - f.write_le(to).expect("length is 32 bytes"); -} - -/// Reads an FrRepr from a [u8] of length 32. -/// This will panic (abort) if length provided is -/// not correct. -fn read_le(from: &[u8]) -> FrRepr { - assert_eq!(from.len(), 32); - +/// Reads an FrRepr from a [u8; 32]. +fn read_fr(from: &[u8; 32]) -> FrRepr { let mut f = FrRepr::default(); - f.read_le(from).expect("length is 32 bytes"); - + f.read_le(&from[..]).expect("length is 32 bytes"); f } -/// Reads an FsRepr from [u8] of length 32 -/// This will panic (abort) if length provided is -/// not correct -fn read_fs(from: &[u8]) -> FsRepr { - assert_eq!(from.len(), 32); - +/// Reads an FsRepr from a [u8; 32]. +fn read_fs(from: &[u8; 32]) -> FsRepr { let mut f = <::Fs as PrimeField>::Repr::default(); - f.read_le(from).expect("length is 32 bytes"); - + f.read_le(&from[..]).expect("length is 32 bytes"); f } -/// Reads an FsRepr from [u8] of length 32 +/// Reads an FsRepr from a [u8; 32] /// and multiplies it by the given base. -/// This will panic (abort) if length provided is -/// not correct -fn fixed_scalar_mult(from: &[u8], p_g: FixedGenerators) -> edwards::Point { +fn fixed_scalar_mult(from: &[u8; 32], p_g: FixedGenerators) -> edwards::Point { let f = read_fs(from); JUBJUB.generator(p_g).mul(f, &JUBJUB) } +/// Loads the zk-SNARK parameters into memory and saves paths as necessary. +/// Only called once. #[cfg(not(target_os = "windows"))] #[no_mangle] -pub extern "system" fn librustzcash_init_zksnark_params( +pub extern "C" fn librustzcash_init_zksnark_params( spend_path: *const u8, spend_path_len: usize, spend_hash: *const c_char, @@ -150,9 +137,11 @@ pub extern "system" fn librustzcash_init_zksnark_params( ) } +/// Loads the zk-SNARK parameters into memory and saves paths as necessary. +/// Only called once. #[cfg(target_os = "windows")] #[no_mangle] -pub extern "system" fn librustzcash_init_zksnark_params( +pub extern "C" fn librustzcash_init_zksnark_params( spend_path: *const u16, spend_path_len: usize, spend_hash: *const c_char, @@ -237,19 +226,28 @@ fn init_zksnark_params( } } +/// Writes the "uncommitted" note value for empty leaves of the Merkle tree. +/// +/// `result` must be a valid pointer to 32 bytes which will be written. #[no_mangle] -pub extern "system" fn librustzcash_tree_uncommitted(result: *mut [c_uchar; 32]) { +pub extern "C" fn librustzcash_tree_uncommitted(result: *mut [c_uchar; 32]) { let tmp = Note::::uncommitted().into_repr(); // Should be okay, caller is responsible for ensuring the pointer // is a valid pointer to 32 bytes that can be mutated. let result = unsafe { &mut *result }; - - write_le(tmp, &mut result[..]); + tmp.write_le(&mut result[..]).expect("length is 32 bytes"); } +/// Computes a merkle tree hash for a given depth. The `depth` parameter should +/// not be larger than 62. +/// +/// `a` and `b` each must be of length 32, and must each be scalars of BLS12-381. +/// +/// The result of the merkle tree hash is placed in `result`, which must also be +/// of length 32. #[no_mangle] -pub extern "system" fn librustzcash_merkle_hash( +pub extern "C" fn librustzcash_merkle_hash( depth: size_t, a: *const [c_uchar; 32], b: *const [c_uchar; 32], @@ -258,27 +256,23 @@ pub extern "system" fn librustzcash_merkle_hash( // Should be okay, because caller is responsible for ensuring // the pointer is a valid pointer to 32 bytes, and that is the // size of the representation - let a_repr = read_le(unsafe { &(&*a)[..] }); + let a_repr = read_fr(unsafe { &*a }); // Should be okay, because caller is responsible for ensuring // the pointer is a valid pointer to 32 bytes, and that is the // size of the representation - let b_repr = read_le(unsafe { &(&*b)[..] }); + let b_repr = read_fr(unsafe { &*b }); let tmp = merkle_hash(depth, &a_repr, &b_repr); // Should be okay, caller is responsible for ensuring the pointer // is a valid pointer to 32 bytes that can be mutated. let result = unsafe { &mut *result }; - - write_le(tmp, &mut result[..]); + tmp.write_le(&mut result[..]).expect("length is 32 bytes"); } #[no_mangle] // ToScalar -pub extern "system" fn librustzcash_to_scalar( - input: *const [c_uchar; 64], - result: *mut [c_uchar; 32], -) { +pub extern "C" fn librustzcash_to_scalar(input: *const [c_uchar; 64], result: *mut [c_uchar; 32]) { // Should be okay, because caller is responsible for ensuring // the pointer is a valid pointer to 32 bytes, and that is the // size of the representation @@ -292,10 +286,7 @@ pub extern "system" fn librustzcash_to_scalar( } #[no_mangle] -pub extern "system" fn librustzcash_ask_to_ak( - ask: *const [c_uchar; 32], - result: *mut [c_uchar; 32], -) { +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, FixedGenerators::SpendingKeyGenerator); @@ -305,10 +296,7 @@ pub extern "system" fn librustzcash_ask_to_ak( } #[no_mangle] -pub extern "system" fn librustzcash_nsk_to_nk( - nsk: *const [c_uchar; 32], - result: *mut [c_uchar; 32], -) { +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, FixedGenerators::ProofGenerationKey); @@ -318,7 +306,7 @@ pub extern "system" fn librustzcash_nsk_to_nk( } #[no_mangle] -pub extern "system" fn librustzcash_crh_ivk( +pub extern "C" fn librustzcash_crh_ivk( ak: *const [c_uchar; 32], nk: *const [c_uchar; 32], result: *mut [c_uchar; 32], @@ -343,13 +331,13 @@ pub extern "system" fn librustzcash_crh_ivk( } #[no_mangle] -pub extern "system" fn librustzcash_check_diversifier(diversifier: *const [c_uchar; 11]) -> bool { +pub extern "C" fn librustzcash_check_diversifier(diversifier: *const [c_uchar; 11]) -> bool { let diversifier = Diversifier(unsafe { *diversifier }); diversifier.g_d::(&JUBJUB).is_some() } #[no_mangle] -pub extern "system" fn librustzcash_ivk_to_pkd( +pub extern "C" fn librustzcash_ivk_to_pkd( ivk: *const [c_uchar; 32], diversifier: *const [c_uchar; 11], result: *mut [c_uchar; 32], @@ -388,9 +376,9 @@ fn test_gen_r() { let _ = Fs::from_repr(repr).unwrap(); } -/// Return 32 byte random scalar, uniformly. +/// Generate uniformly random scalar in Jubjub. The result is of length 32. #[no_mangle] -pub extern "system" fn librustzcash_sapling_generate_r(result: *mut [c_uchar; 32]) { +pub extern "C" fn librustzcash_sapling_generate_r(result: *mut [c_uchar; 32]) { // create random 64 byte buffer let mut rng = OsRng; let mut buffer = [0u8; 64]; @@ -428,7 +416,7 @@ fn priv_get_note( }; // Deserialize randomness - let r = match Fs::from_repr(read_fs(&(unsafe { &*r })[..])) { + let r = match Fs::from_repr(read_fs(unsafe { &*r })) { Ok(r) => r, Err(_) => return Err(()), }; @@ -443,9 +431,14 @@ fn priv_get_note( Ok(note) } -/// Compute Sapling note nullifier. +/// Compute a Sapling nullifier. +/// +/// The `diversifier` parameter must be 11 bytes in length. +/// The `pk_d`, `r`, `ak` and `nk` parameters must be of length 32. +/// The result is also of length 32 and placed in `result`. +/// Returns false if `diversifier` or `pk_d` is not valid. #[no_mangle] -pub extern "system" fn librustzcash_sapling_compute_nf( +pub extern "C" fn librustzcash_sapling_compute_nf( diversifier: *const [c_uchar; 11], pk_d: *const [c_uchar; 32], value: u64, @@ -488,9 +481,14 @@ pub extern "system" fn librustzcash_sapling_compute_nf( true } -/// Compute Sapling note commitment. +/// Compute a Sapling commitment. +/// +/// The `diversifier` parameter must be 11 bytes in length. +/// The `pk_d` and `r` parameters must be of length 32. +/// The result is also of length 32 and placed in `result`. +/// Returns false if `diversifier` or `pk_d` is not valid. #[no_mangle] -pub extern "system" fn librustzcash_sapling_compute_cm( +pub extern "C" fn librustzcash_sapling_compute_cm( diversifier: *const [c_uchar; 11], pk_d: *const [c_uchar; 32], value: u64, @@ -503,13 +501,20 @@ pub extern "system" fn librustzcash_sapling_compute_cm( }; let result = unsafe { &mut *result }; - write_le(note.cm(&JUBJUB).into_repr(), &mut result[..]); + note.cm(&JUBJUB) + .into_repr() + .write_le(&mut result[..]) + .expect("length is 32 bytes"); true } +/// Computes \[sk\] \[8\] P for some 32-byte point P, and 32-byte Fs. +/// +/// If P or sk are invalid, returns false. Otherwise, the result is written to +/// the 32-byte `result` buffer. #[no_mangle] -pub extern "system" fn librustzcash_sapling_ka_agree( +pub extern "C" fn librustzcash_sapling_ka_agree( p: *const [c_uchar; 32], sk: *const [c_uchar; 32], result: *mut [c_uchar; 32], @@ -521,7 +526,7 @@ pub extern "system" fn librustzcash_sapling_ka_agree( }; // Deserialize sk - let sk = match Fs::from_repr(read_fs(&(unsafe { &*sk })[..])) { + let sk = match Fs::from_repr(read_fs(unsafe { &*sk })) { Ok(p) => p, Err(_) => return false, }; @@ -536,8 +541,11 @@ pub extern "system" fn librustzcash_sapling_ka_agree( true } +/// Compute g_d = GH(diversifier) and returns false if the diversifier is +/// invalid. Computes \[esk\] g_d and writes the result to the 32-byte `result` +/// buffer. Returns false if `esk` is not a valid scalar. #[no_mangle] -pub extern "system" fn librustzcash_sapling_ka_derivepublic( +pub extern "C" fn librustzcash_sapling_ka_derivepublic( diversifier: *const [c_uchar; 11], esk: *const [c_uchar; 32], result: *mut [c_uchar; 32], @@ -551,7 +559,7 @@ pub extern "system" fn librustzcash_sapling_ka_derivepublic( }; // Deserialize esk - let esk = match Fs::from_repr(read_fs(&(unsafe { &*esk })[..])) { + let esk = match Fs::from_repr(read_fs(unsafe { &*esk })) { Ok(p) => p, Err(_) => return false, }; @@ -564,8 +572,10 @@ pub extern "system" fn librustzcash_sapling_ka_derivepublic( true } +/// Validates the provided Equihash solution against the given parameters, input +/// and nonce. #[no_mangle] -pub extern "system" fn librustzcash_eh_isvalid( +pub extern "C" fn librustzcash_eh_isvalid( n: u32, k: u32, input: *const c_uchar, @@ -584,18 +594,18 @@ pub extern "system" fn librustzcash_eh_isvalid( equihash::is_valid_solution(n, k, rs_input, rs_nonce, rs_soln) } +/// Creates a Sapling verification context. Please free this when you're done. #[no_mangle] -pub extern "system" fn librustzcash_sapling_verification_ctx_init( -) -> *mut SaplingVerificationContext { +pub extern "C" fn librustzcash_sapling_verification_ctx_init() -> *mut SaplingVerificationContext { let ctx = Box::new(SaplingVerificationContext::new()); Box::into_raw(ctx) } +/// Frees a Sapling verification context returned from +/// [`librustzcash_sapling_verification_ctx_init`]. #[no_mangle] -pub extern "system" fn librustzcash_sapling_verification_ctx_free( - ctx: *mut SaplingVerificationContext, -) { +pub extern "C" fn librustzcash_sapling_verification_ctx_free(ctx: *mut SaplingVerificationContext) { drop(unsafe { Box::from_raw(ctx) }); } @@ -603,8 +613,10 @@ const GROTH_PROOF_SIZE: usize = 48 // π_A + 96 // π_B + 48; // π_C +/// Check the validity of a Sapling Spend description, accumulating the value +/// commitment into the context. #[no_mangle] -pub extern "system" fn librustzcash_sapling_check_spend( +pub extern "C" fn librustzcash_sapling_check_spend( ctx: *mut SaplingVerificationContext, cv: *const [c_uchar; 32], anchor: *const [c_uchar; 32], @@ -622,7 +634,7 @@ pub extern "system" fn librustzcash_sapling_check_spend( // Deserialize the anchor, which should be an element // of Fr. - let anchor = match Fr::from_repr(read_le(&(unsafe { &*anchor })[..])) { + let anchor = match Fr::from_repr(read_fr(unsafe { &*anchor })) { Ok(a) => a, Err(_) => return false, }; @@ -658,8 +670,10 @@ pub extern "system" fn librustzcash_sapling_check_spend( ) } +/// Check the validity of a Sapling Output description, accumulating the value +/// commitment into the context. #[no_mangle] -pub extern "system" fn librustzcash_sapling_check_output( +pub extern "C" fn librustzcash_sapling_check_output( ctx: *mut SaplingVerificationContext, cv: *const [c_uchar; 32], cm: *const [c_uchar; 32], @@ -674,7 +688,7 @@ pub extern "system" fn librustzcash_sapling_check_output( // Deserialize the commitment, which should be an element // of Fr. - let cm = match Fr::from_repr(read_le(&(unsafe { &*cm })[..])) { + let cm = match Fr::from_repr(read_fr(unsafe { &*cm })) { Ok(a) => a, Err(_) => return false, }; @@ -701,8 +715,10 @@ pub extern "system" fn librustzcash_sapling_check_output( ) } +/// Finally checks the validity of the entire Sapling transaction given +/// valueBalance and the binding signature. #[no_mangle] -pub extern "system" fn librustzcash_sapling_final_check( +pub extern "C" fn librustzcash_sapling_final_check( ctx: *mut SaplingVerificationContext, value_balance: i64, binding_sig: *const [c_uchar; 64], @@ -727,8 +743,9 @@ pub extern "system" fn librustzcash_sapling_final_check( ) } +/// Sprout JoinSplit proof generation. #[no_mangle] -pub extern "system" fn librustzcash_sprout_prove( +pub extern "C" fn librustzcash_sprout_prove( proof_out: *mut [c_uchar; GROTH_PROOF_SIZE], phi: *const [c_uchar; 32], @@ -740,14 +757,14 @@ pub extern "system" fn librustzcash_sprout_prove( in_value1: u64, in_rho1: *const [c_uchar; 32], in_r1: *const [c_uchar; 32], - in_auth1: *const [c_uchar; 1 + 33 * SPROUT_TREE_DEPTH + 8], + in_auth1: *const [c_uchar; sprout::WITNESS_PATH_SIZE], // Second input 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; 1 + 33 * SPROUT_TREE_DEPTH + 8], + in_auth2: *const [c_uchar; sprout::WITNESS_PATH_SIZE], // First output out_pk1: *const [c_uchar; 32], @@ -763,94 +780,6 @@ pub extern "system" fn librustzcash_sprout_prove( vpub_old: u64, vpub_new: u64, ) { - let phi = unsafe { *phi }; - let rt = unsafe { *rt }; - let h_sig = unsafe { *h_sig }; - let in_sk1 = unsafe { *in_sk1 }; - let in_rho1 = unsafe { *in_rho1 }; - let in_r1 = unsafe { *in_r1 }; - let in_auth1 = unsafe { *in_auth1 }; - let in_sk2 = unsafe { *in_sk2 }; - let in_rho2 = unsafe { *in_rho2 }; - let in_r2 = unsafe { *in_r2 }; - let in_auth2 = unsafe { *in_auth2 }; - let out_pk1 = unsafe { *out_pk1 }; - let out_r1 = unsafe { *out_r1 }; - let out_pk2 = unsafe { *out_pk2 }; - let out_r2 = unsafe { *out_r2 }; - - let mut inputs = Vec::with_capacity(2); - { - let mut handle_input = |sk, value, rho, r, mut auth: &[u8]| { - let value = Some(value); - let rho = Some(sprout::UniqueRandomness(rho)); - let r = Some(sprout::CommitmentRandomness(r)); - let a_sk = Some(sprout::SpendingKey(sk)); - - // skip the first byte - assert_eq!(auth[0], SPROUT_TREE_DEPTH as u8); - auth = &auth[1..]; - - let mut auth_path = [None; SPROUT_TREE_DEPTH]; - for i in (0..SPROUT_TREE_DEPTH).rev() { - // skip length of inner vector - assert_eq!(auth[0], 32); - auth = &auth[1..]; - - let mut sibling = [0u8; 32]; - sibling.copy_from_slice(&auth[0..32]); - auth = &auth[32..]; - - auth_path[i] = Some((sibling, false)); - } - - let mut position = auth - .read_u64::() - .expect("should have had index at the end"); - - for i in 0..SPROUT_TREE_DEPTH { - auth_path[i].as_mut().map(|p| p.1 = (position & 1) == 1); - - position >>= 1; - } - - inputs.push(sprout::JSInput { - value: value, - a_sk: a_sk, - rho: rho, - r: r, - auth_path: auth_path, - }); - }; - - handle_input(in_sk1, in_value1, in_rho1, in_r1, &in_auth1[..]); - handle_input(in_sk2, in_value2, in_rho2, in_r2, &in_auth2[..]); - } - - let mut outputs = Vec::with_capacity(2); - { - let mut handle_output = |a_pk, value, r| { - outputs.push(sprout::JSOutput { - value: Some(value), - a_pk: Some(sprout::PayingKey(a_pk)), - r: Some(sprout::CommitmentRandomness(r)), - }); - }; - - handle_output(out_pk1, out_value1, out_r1); - handle_output(out_pk2, out_value2, out_r2); - } - - let js = sprout::JoinSplit { - vpub_old: Some(vpub_old), - vpub_new: Some(vpub_new), - h_sig: Some(h_sig), - phi: Some(phi), - inputs: inputs, - outputs: outputs, - rt: Some(rt), - }; - // Load parameters from disk let sprout_fs = File::open( unsafe { &SPROUT_GROTH16_PARAMS_PATH } @@ -866,18 +795,39 @@ pub extern "system" fn librustzcash_sprout_prove( drop(sprout_fs); - // Initialize secure RNG - let mut rng = OsRng; - - let proof = create_random_proof(js, ¶ms, &mut rng).expect("proving should not fail"); + 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"); } +/// Sprout JoinSplit proof verification. #[no_mangle] -pub extern "system" fn librustzcash_sprout_verify( +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], @@ -890,43 +840,26 @@ pub extern "system" fn librustzcash_sprout_verify( vpub_old: u64, vpub_new: u64, ) -> bool { - // Prepare the public input for the verifier - let mut public_input = Vec::with_capacity((32 * 8) + (8 * 2)); - public_input.extend(unsafe { &(&*rt)[..] }); - public_input.extend(unsafe { &(&*h_sig)[..] }); - public_input.extend(unsafe { &(&*nf1)[..] }); - public_input.extend(unsafe { &(&*mac1)[..] }); - public_input.extend(unsafe { &(&*nf2)[..] }); - public_input.extend(unsafe { &(&*mac2)[..] }); - public_input.extend(unsafe { &(&*cm1)[..] }); - public_input.extend(unsafe { &(&*cm2)[..] }); - public_input.write_u64::(vpub_old).unwrap(); - public_input.write_u64::(vpub_new).unwrap(); - - let public_input = multipack::bytes_to_bits(&public_input); - let public_input = multipack::compute_multipacking::(&public_input); - - let proof = match Proof::read(unsafe { &(&*proof)[..] }) { - Ok(p) => p, - Err(_) => return false, - }; - - // Verify the proof - match verify_proof( + 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"), - &proof, - &public_input[..], - ) { - // No error, and proof verification successful - Ok(true) => true, - - // Any other case - _ => false, - } + ) } +/// This function (using the proving context) constructs an Output proof given +/// the necessary witness information. It outputs `cv` and the `zkproof`. #[no_mangle] -pub extern "system" fn librustzcash_sapling_output_proof( +pub extern "C" fn librustzcash_sapling_output_proof( ctx: *mut SaplingProvingContext, esk: *const [c_uchar; 32], payment_address: *const [c_uchar; 43], @@ -936,7 +869,7 @@ pub extern "system" fn librustzcash_sapling_output_proof( zkproof: *mut [c_uchar; GROTH_PROOF_SIZE], ) -> bool { // Grab `esk`, which the caller should have constructed for the DH key exchange. - let esk = match Fs::from_repr(read_fs(&(unsafe { &*esk })[..])) { + let esk = match Fs::from_repr(read_fs(unsafe { &*esk })) { Ok(p) => p, Err(_) => return false, }; @@ -949,7 +882,7 @@ pub extern "system" fn librustzcash_sapling_output_proof( }; // The caller provides the commitment randomness for the output note - let rcm = match Fs::from_repr(read_fs(&(unsafe { &*rcm })[..])) { + let rcm = match Fs::from_repr(read_fs(unsafe { &*rcm })) { Ok(p) => p, Err(_) => return false, }; @@ -977,15 +910,20 @@ pub extern "system" fn librustzcash_sapling_output_proof( true } +/// Computes the signature for each Spend description, given the key `ask`, the +/// re-randomization `ar`, the 32-byte sighash `sighash`, and an output `result` +/// buffer of 64-bytes for the signature. +/// +/// This function will fail if the provided `ask` or `ar` are invalid. #[no_mangle] -pub extern "system" fn librustzcash_sapling_spend_sig( +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 { // The caller provides the re-randomization of `ak`. - let ar = match Fs::from_repr(read_fs(&(unsafe { &*ar })[..])) { + let ar = match Fs::from_repr(read_fs(unsafe { &*ar })) { Ok(p) => p, Err(_) => return false, }; @@ -1009,8 +947,12 @@ pub extern "system" fn librustzcash_sapling_spend_sig( true } +/// This function (using the proving context) constructs a binding signature. +/// +/// You must provide the intended valueBalance so that we can internally check +/// consistency. #[no_mangle] -pub extern "system" fn librustzcash_sapling_binding_sig( +pub extern "C" fn librustzcash_sapling_binding_sig( ctx: *const SaplingProvingContext, value_balance: i64, sighash: *const [c_uchar; 32], @@ -1034,8 +976,11 @@ pub extern "system" fn librustzcash_sapling_binding_sig( true } +/// This function (using the proving context) constructs a Spend proof given the +/// necessary witness information. It outputs `cv` (the value commitment) and +/// `rk` (so that you don't have to compute it) along with the proof. #[no_mangle] -pub extern "system" fn librustzcash_sapling_spend_proof( +pub extern "C" fn librustzcash_sapling_spend_proof( ctx: *mut SaplingProvingContext, ak: *const [c_uchar; 32], nsk: *const [c_uchar; 32], @@ -1062,7 +1007,7 @@ pub extern "system" fn librustzcash_sapling_spend_proof( }; // Grab `nsk` from the caller - let nsk = match Fs::from_repr(read_fs(&(unsafe { &*nsk })[..])) { + let nsk = match Fs::from_repr(read_fs(unsafe { &*nsk })) { Ok(p) => p, Err(_) => return false, }; @@ -1077,19 +1022,19 @@ pub extern "system" fn librustzcash_sapling_spend_proof( let diversifier = Diversifier(unsafe { *diversifier }); // The caller chooses the note randomness - let rcm = match Fs::from_repr(read_fs(&(unsafe { &*rcm })[..])) { + let rcm = match Fs::from_repr(read_fs(unsafe { &*rcm })) { Ok(p) => p, Err(_) => return false, }; // The caller also chooses the re-randomization of ak - let ar = match Fs::from_repr(read_fs(&(unsafe { &*ar })[..])) { + let ar = match Fs::from_repr(read_fs(unsafe { &*ar })) { Ok(p) => p, Err(_) => return false, }; // We need to compute the anchor of the Spend. - let anchor = match Fr::from_repr(read_le(unsafe { &(&*anchor)[..] })) { + let anchor = match Fr::from_repr(read_fr(unsafe { &*anchor })) { Ok(p) => p, Err(_) => return false, }; @@ -1134,20 +1079,24 @@ pub extern "system" fn librustzcash_sapling_spend_proof( true } +/// Creates a Sapling proving context. Please free this when you're done. #[no_mangle] -pub extern "system" fn librustzcash_sapling_proving_ctx_init() -> *mut SaplingProvingContext { +pub extern "C" fn librustzcash_sapling_proving_ctx_init() -> *mut SaplingProvingContext { let ctx = Box::new(SaplingProvingContext::new()); Box::into_raw(ctx) } +/// Frees a Sapling proving context returned from +/// [`librustzcash_sapling_proving_ctx_init`]. #[no_mangle] -pub extern "system" fn librustzcash_sapling_proving_ctx_free(ctx: *mut SaplingProvingContext) { +pub extern "C" fn librustzcash_sapling_proving_ctx_free(ctx: *mut SaplingProvingContext) { drop(unsafe { Box::from_raw(ctx) }); } +/// Derive the master ExtendedSpendingKey from a seed. #[no_mangle] -pub extern "system" fn librustzcash_zip32_xsk_master( +pub extern "C" fn librustzcash_zip32_xsk_master( seed: *const c_uchar, seedlen: size_t, xsk_master: *mut [c_uchar; 169], @@ -1160,8 +1109,9 @@ pub extern "system" fn librustzcash_zip32_xsk_master( .expect("should be able to serialize an ExtendedSpendingKey"); } +/// Derive a child ExtendedSpendingKey from a parent. #[no_mangle] -pub extern "system" fn librustzcash_zip32_xsk_derive( +pub extern "C" fn librustzcash_zip32_xsk_derive( xsk_parent: *const [c_uchar; 169], i: u32, xsk_i: *mut [c_uchar; 169], @@ -1176,8 +1126,9 @@ pub extern "system" fn librustzcash_zip32_xsk_derive( .expect("should be able to serialize an ExtendedSpendingKey"); } +/// Derive a child ExtendedFullViewingKey from a parent. #[no_mangle] -pub extern "system" fn librustzcash_zip32_xfvk_derive( +pub extern "C" fn librustzcash_zip32_xfvk_derive( xfvk_parent: *const [c_uchar; 169], i: u32, xfvk_i: *mut [c_uchar; 169], @@ -1197,8 +1148,9 @@ pub extern "system" fn librustzcash_zip32_xfvk_derive( true } +/// Derive a PaymentAddress from an ExtendedFullViewingKey. #[no_mangle] -pub extern "system" fn librustzcash_zip32_xfvk_address( +pub extern "C" fn librustzcash_zip32_xfvk_address( xfvk: *const [c_uchar; 169], j: *const [c_uchar; 11], j_ret: *mut [c_uchar; 11], diff --git a/zcash_proofs/src/lib.rs b/zcash_proofs/src/lib.rs index 0a1f9f2..1e8ceb2 100644 --- a/zcash_proofs/src/lib.rs +++ b/zcash_proofs/src/lib.rs @@ -15,6 +15,7 @@ use std::path::Path; pub mod circuit; mod hashreader; pub mod sapling; +pub mod sprout; #[cfg(feature = "local-prover")] pub mod prover; diff --git a/zcash_proofs/src/sprout.rs b/zcash_proofs/src/sprout.rs new file mode 100644 index 0000000..30ec821 --- /dev/null +++ b/zcash_proofs/src/sprout.rs @@ -0,0 +1,179 @@ +//! APIs for creating and verifying Sprout proofs. + +use bellman::{ + gadgets::multipack, + groth16::{self, create_random_proof, Parameters, PreparedVerifyingKey, Proof}, +}; +use pairing::bls12_381::Bls12; +use rand_core::OsRng; + +use crate::circuit::sprout::*; + +const GROTH_PROOF_SIZE: usize = 48 // π_A + + 96 // π_B + + 48; // π_C +pub const WITNESS_PATH_SIZE: usize = 1 + 33 * TREE_DEPTH + 8; + +/// Sprout JoinSplit proof generation. +pub fn create_proof( + phi: [u8; 32], + rt: [u8; 32], + h_sig: [u8; 32], + + // First input + in_sk1: [u8; 32], + in_value1: u64, + in_rho1: [u8; 32], + in_r1: [u8; 32], + in_auth1: &[u8; WITNESS_PATH_SIZE], + + // Second input + in_sk2: [u8; 32], + in_value2: u64, + in_rho2: [u8; 32], + in_r2: [u8; 32], + in_auth2: &[u8; WITNESS_PATH_SIZE], + + // First output + out_pk1: [u8; 32], + out_value1: u64, + out_r1: [u8; 32], + + // Second output + out_pk2: [u8; 32], + out_value2: u64, + out_r2: [u8; 32], + + // Public value + vpub_old: u64, + vpub_new: u64, + + proving_key: &Parameters, +) -> Proof { + let mut inputs = Vec::with_capacity(2); + { + let mut handle_input = |sk, value, rho, r, mut auth: &[u8]| { + let value = Some(value); + let rho = Some(UniqueRandomness(rho)); + let r = Some(CommitmentRandomness(r)); + let a_sk = Some(SpendingKey(sk)); + + // skip the first byte + assert_eq!(auth[0], TREE_DEPTH as u8); + auth = &auth[1..]; + + let mut auth_path = [None; TREE_DEPTH]; + for i in (0..TREE_DEPTH).rev() { + // skip length of inner vector + assert_eq!(auth[0], 32); + auth = &auth[1..]; + + let mut sibling = [0u8; 32]; + sibling.copy_from_slice(&auth[0..32]); + auth = &auth[32..]; + + auth_path[i] = Some((sibling, false)); + } + + let mut position = { + let mut bytes = [0; 8]; + bytes.copy_from_slice(&auth[0..8]); + u64::from_le_bytes(bytes) + }; + + for entry in auth_path.iter_mut() { + if let Some(p) = entry { + p.1 = (position & 1) == 1; + } + + position >>= 1; + } + + inputs.push(JSInput { + value, + a_sk, + rho, + r, + auth_path, + }); + }; + + handle_input(in_sk1, in_value1, in_rho1, in_r1, &in_auth1[..]); + handle_input(in_sk2, in_value2, in_rho2, in_r2, &in_auth2[..]); + } + + let mut outputs = Vec::with_capacity(2); + { + let mut handle_output = |a_pk, value, r| { + outputs.push(JSOutput { + value: Some(value), + a_pk: Some(PayingKey(a_pk)), + r: Some(CommitmentRandomness(r)), + }); + }; + + handle_output(out_pk1, out_value1, out_r1); + handle_output(out_pk2, out_value2, out_r2); + } + + let js = JoinSplit { + vpub_old: Some(vpub_old), + vpub_new: Some(vpub_new), + h_sig: Some(h_sig), + phi: Some(phi), + inputs, + outputs, + rt: Some(rt), + }; + + // Initialize secure RNG + let mut rng = OsRng; + + create_random_proof(js, proving_key, &mut rng).expect("proving should not fail") +} + +/// Sprout JoinSplit proof verification. +pub fn verify_proof( + proof: &[u8; GROTH_PROOF_SIZE], + rt: &[u8; 32], + h_sig: &[u8; 32], + mac1: &[u8; 32], + mac2: &[u8; 32], + nf1: &[u8; 32], + nf2: &[u8; 32], + cm1: &[u8; 32], + cm2: &[u8; 32], + vpub_old: u64, + vpub_new: u64, + verifying_key: &PreparedVerifyingKey, +) -> bool { + // Prepare the public input for the verifier + let mut public_input = Vec::with_capacity((32 * 8) + (8 * 2)); + public_input.extend(rt); + public_input.extend(h_sig); + public_input.extend(nf1); + public_input.extend(mac1); + public_input.extend(nf2); + public_input.extend(mac2); + public_input.extend(cm1); + public_input.extend(cm2); + public_input.extend(&vpub_old.to_le_bytes()); + public_input.extend(&vpub_new.to_le_bytes()); + + let public_input = multipack::bytes_to_bits(&public_input); + let public_input = multipack::compute_multipacking::(&public_input); + + let proof = match Proof::read(&proof[..]) { + Ok(p) => p, + Err(_) => return false, + }; + + // Verify the proof + match groth16::verify_proof(verifying_key, &proof, &public_input[..]) { + // No error, and proof verification successful + Ok(true) => true, + + // Any other case + _ => false, + } +}