From 313e45cc017d216aae4bf04274193c7c36946e3c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 7 May 2018 19:22:07 -0600 Subject: [PATCH] Implementation of Sapling transaction verification. --- include/librustzcash.h | 40 ++++++ src/rustzcash.rs | 292 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 326 insertions(+), 6 deletions(-) diff --git a/include/librustzcash.h b/include/librustzcash.h index d5a596c..8146196 100644 --- a/include/librustzcash.h +++ b/include/librustzcash.h @@ -36,6 +36,46 @@ extern "C" { const unsigned char *b, unsigned char *result ); + + /// Creates a Sapling verification context. Please free this + /// when you're done. + void * librustzcash_sapling_verification_ctx_init(); + + /// Check the validity of a Sapling Spend description, + /// accumulating the value commitment into the context. + bool librustzcash_sapling_check_spend( + void *ctx, + const unsigned char *cv, + const unsigned char *anchor, + const unsigned char *nullifier, + const unsigned char *rk, + const unsigned char *zkproof, + const unsigned char *spendAuthSig, + const unsigned char *sighashValue + ); + + /// Check the validity of a Sapling Output description, + /// accumulating the value commitment into the context. + bool librustzcash_sapling_check_output( + void *ctx, + const unsigned char *cv, + const unsigned char *cm, + const unsigned char *ephemeralKey, + const unsigned char *zkproof + ); + + /// Finally checks the validity of the entire Sapling + /// transaction given valueBalance and the binding signature. + bool librustzcash_sapling_final_check( + void *ctx, + int64_t valueBalance, + const unsigned char *bindingSig, + const unsigned char *sighashValue + ); + + /// Frees a Sapling verification context returned from + /// `librustzcash_sapling_verification_ctx_init`. + void librustzcash_sapling_verification_ctx_free(void *); } #endif // LIBRUSTZCASH_INCLUDE_H_ diff --git a/src/rustzcash.rs b/src/rustzcash.rs index 754a507..8a1e001 100644 --- a/src/rustzcash.rs +++ b/src/rustzcash.rs @@ -6,13 +6,18 @@ extern crate sapling_crypto; #[macro_use] extern crate lazy_static; -use pairing::{BitIterator, PrimeField, PrimeFieldRepr, bls12_381::{Bls12, Fr, FrRepr}}; +use pairing::{BitIterator, Field, PrimeField, PrimeFieldRepr, bls12_381::{Bls12, Fr, FrRepr}}; -use sapling_crypto::{jubjub::JubjubBls12, pedersen_hash::{pedersen_hash, Personalization}, util::swap_bits_u64}; +use sapling_crypto::{circuit::multipack, + jubjub::{edwards, FixedGenerators, JubjubBls12, JubjubParams, Unknown, + fs::FsRepr}, + pedersen_hash::{pedersen_hash, Personalization}, + redjubjub::{self, Signature}, util::swap_bits_u64}; -use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey, VerifyingKey}; +use bellman::groth16::{prepare_verifying_key, verify_proof, Parameters, PreparedVerifyingKey, + Proof, VerifyingKey}; -use libc::{c_char, c_uchar, size_t, uint64_t}; +use libc::{c_char, c_uchar, size_t, int64_t, uint64_t}; use std::ffi::CStr; use std::fs::File; @@ -43,8 +48,7 @@ fn write_le(mut f: FrRepr, to: &mut [u8]) { /// 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 -{ +fn read_le(from: &[u8]) -> FrRepr { assert_eq!(from.len(), 32); let mut f = FrRepr::default(); @@ -187,3 +191,279 @@ fn test_xor() { 0x1e1e1e1e1e1e1e1e ); } + +pub struct SaplingVerificationContext { + bvk: edwards::Point, +} + +#[no_mangle] +pub extern "system" fn librustzcash_sapling_verification_ctx_init( +) -> *mut SaplingVerificationContext { + let ctx = Box::new(SaplingVerificationContext { + bvk: edwards::Point::zero(), + }); + + Box::into_raw(ctx) +} + +#[no_mangle] +pub extern "system" fn librustzcash_sapling_verification_ctx_free( + ctx: *mut SaplingVerificationContext, +) { + drop(unsafe { Box::from_raw(ctx) }); +} + +const GROTH_PROOF_SIZE: usize = 48 // π_A + + 96 // π_B + + 48; // π_C + +#[no_mangle] +pub extern "system" fn librustzcash_sapling_check_spend( + ctx: *mut SaplingVerificationContext, + cv: *const [c_uchar; 32], + anchor: *const [c_uchar; 32], + nullifier: *const [c_uchar; 32], + rk: *const [c_uchar; 32], + zkproof: *const [c_uchar; GROTH_PROOF_SIZE], + spend_auth_sig: *const [c_uchar; 64], + sighash_value: *const [c_uchar; 32], +) -> bool { + // Deserialize the value commitment + let cv = match edwards::Point::::read(&(unsafe { &*cv })[..], &JUBJUB) { + Ok(p) => p, + Err(_) => return false, + }; + + // Accumulate the value commitment in the context + { + let mut tmp = cv.clone(); + tmp = tmp.add(&unsafe { &*ctx }.bvk, &JUBJUB); + + // Update the context + unsafe { &mut *ctx }.bvk = tmp; + } + + // Deserialize the anchor, which should be an element + // of Fr. + let anchor = match Fr::from_repr(read_le(&(unsafe { &*anchor })[..])) { + Ok(a) => a, + Err(_) => return false, + }; + + // Grab the nullifier as a sequence of bytes + let nullifier = &unsafe { &*nullifier }[..]; + + // Compute the signature's message for rk/spend_auth_sig + let mut data_to_be_signed = [0u8; 64]; + (&mut data_to_be_signed[0..32]).copy_from_slice(&(unsafe { &*rk })[..]); + (&mut data_to_be_signed[32..64]).copy_from_slice(&(unsafe { &*sighash_value })[..]); + + // Deserialize rk + let rk = match redjubjub::PublicKey::::read(&(unsafe { &*rk })[..], &JUBJUB) { + Ok(p) => p, + Err(_) => return false, + }; + + // Deserialize the signature + let spend_auth_sig = match Signature::read(&(unsafe { &*spend_auth_sig })[..]) { + Ok(sig) => sig, + Err(_) => return false, + }; + + // Verify the spend_auth_sig + if !rk.verify( + &data_to_be_signed, + &spend_auth_sig, + FixedGenerators::SpendingKeyGenerator, + &JUBJUB, + ) { + return false; + } + + // Construct public input for circuit + let mut public_input = [Fr::zero(); 7]; + { + let (x, y) = rk.0.into_xy(); + public_input[0] = x; + public_input[1] = y; + } + { + let (x, y) = cv.into_xy(); + public_input[2] = x; + public_input[3] = y; + } + public_input[4] = anchor; + + // Add the nullifier through multiscalar packing + { + let nullifier = multipack::bytes_to_bits(nullifier); + let nullifier = multipack::compute_multipacking::(&nullifier); + + assert_eq!(nullifier.len(), 2); + + public_input[5] = nullifier[0]; + public_input[6] = nullifier[1]; + } + + // Deserialize the proof + let zkproof = match Proof::::read(&(unsafe { &*zkproof })[..]) { + Ok(p) => p, + Err(_) => return false, + }; + + // Verify the proof + match verify_proof( + unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap(), + &zkproof, + &public_input[..], + ) { + // No error, and proof verification successful + Ok(true) => true, + + // Any other case + _ => false, + } +} + +#[no_mangle] +pub extern "system" fn librustzcash_sapling_check_output( + ctx: *mut SaplingVerificationContext, + cv: *const [c_uchar; 32], + cm: *const [c_uchar; 32], + epk: *const [c_uchar; 32], + zkproof: *const [c_uchar; GROTH_PROOF_SIZE], +) -> bool { + // Deserialize the value commitment + let cv = match edwards::Point::::read(&(unsafe { &*cv })[..], &JUBJUB) { + Ok(p) => p, + Err(_) => return false, + }; + + // Accumulate the value commitment in the context + { + let mut tmp = cv.clone(); + tmp.negate(); // Outputs subtract from the total. + tmp = tmp.add(&unsafe { &*ctx }.bvk, &JUBJUB); + + // Update the context + unsafe { &mut *ctx }.bvk = tmp; + } + + // Deserialize the commitment, which should be an element + // of Fr. + let cm = match Fr::from_repr(read_le(&(unsafe { &*cm })[..])) { + Ok(a) => a, + Err(_) => return false, + }; + + // Deserialize the ephemeral key + let epk = match edwards::Point::::read(&(unsafe { &*epk })[..], &JUBJUB) { + Ok(p) => p, + Err(_) => return false, + }; + + // Construct public input for circuit + let mut public_input = [Fr::zero(); 5]; + { + let (x, y) = cv.into_xy(); + public_input[0] = x; + public_input[1] = y; + } + { + let (x, y) = epk.into_xy(); + public_input[2] = x; + public_input[3] = y; + } + public_input[4] = cm; + + // Deserialize the proof + let zkproof = match Proof::::read(&(unsafe { &*zkproof })[..]) { + Ok(p) => p, + Err(_) => return false, + }; + + // Verify the proof + match verify_proof( + unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap(), + &zkproof, + &public_input[..], + ) { + // No error, and proof verification successful + Ok(true) => true, + + // Any other case + _ => false, + } +} + +// This function computes `value` in the exponent of the value commitment base +fn compute_value_balance(value: int64_t) -> Option> { + // Compute the absolute value (failing if -i64::MAX is + // the value) + let abs = match value.checked_abs() { + Some(a) => a as u64, + None => return None, + }; + + // Is it negative? We'll have to negate later if so. + let is_negative = value.is_negative(); + + // Compute it in the exponent + let mut value_balance = JUBJUB + .generator(FixedGenerators::ValueCommitmentValue) + .mul(FsRepr::from(abs), &JUBJUB); + + // Negate if necessary + if is_negative { + value_balance = value_balance.negate(); + } + + // Convert to unknown order point + Some(value_balance.into()) +} + +#[no_mangle] +pub extern "system" fn librustzcash_sapling_final_check( + ctx: *mut SaplingVerificationContext, + value_balance: int64_t, + binding_sig: *const [c_uchar; 64], + sighash_value: *const [c_uchar; 32], +) -> bool { + // Obtain current bvk from the context + let mut bvk = redjubjub::PublicKey(unsafe { &*ctx }.bvk.clone()); + + // Compute value balance + let mut value_balance = match compute_value_balance(value_balance) { + Some(a) => a, + None => return false, + }; + + // Subtract value_balance from current bvk to get final bvk + value_balance = value_balance.negate(); + bvk.0 = bvk.0.add(&value_balance, &JUBJUB); + + // Compute the signature's message for bvk/binding_sig + let mut data_to_be_signed = [0u8; 64]; + bvk.0 + .write(&mut data_to_be_signed[0..32]) + .expect("bvk is 32 bytes"); + (&mut data_to_be_signed[32..64]).copy_from_slice(&(unsafe { &*sighash_value })[..]); + + // Deserialize the signature + let binding_sig = match Signature::read(&(unsafe { &*binding_sig })[..]) { + Ok(sig) => sig, + Err(_) => return false, + }; + + // Verify the binding_sig + if !bvk.verify( + &data_to_be_signed, + &binding_sig, + FixedGenerators::ValueCommitmentRandomness, + &JUBJUB, + ) { + return false; + } + + true +}