diff --git a/include/librustzcash.h b/include/librustzcash.h index f877eefc25..34f346d484 100644 --- a/include/librustzcash.h +++ b/include/librustzcash.h @@ -102,6 +102,23 @@ extern "C" { /// `librustzcash_sapling_verification_ctx_init`. void librustzcash_sapling_verification_ctx_free(void *); + /// 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 the diversifier or pk_d is not valid + bool librustzcash_sapling_compute_nf( + const unsigned char *diversifier, + const unsigned char *pk_d, + const uint64_t value, + const unsigned char *r, + const unsigned char *ak, + const unsigned char *nk, + const uint64_t position, + unsigned char *result + ); + /// Compute a Sapling commitment. /// /// The `diversifier` parameter must be 11 bytes in length. diff --git a/src/rustzcash.rs b/src/rustzcash.rs index 8ff895f189..ac76a358e4 100644 --- a/src/rustzcash.rs +++ b/src/rustzcash.rs @@ -33,6 +33,8 @@ use std::ffi::CStr; use std::fs::File; use std::slice; +use sapling_crypto::primitives::ViewingKey; + pub mod equihash; #[cfg(test)] @@ -338,29 +340,27 @@ pub extern "system" fn librustzcash_sapling_generate_r(result: *mut [c_uchar; 32 .expect("result must be 32 bytes"); } -/// Compute Sapling note commitment. -#[no_mangle] -pub extern "system" fn librustzcash_sapling_compute_cm( +// Private utility function to get Note from C parameters +fn priv_get_note( diversifier: *const [c_uchar; 11], pk_d: *const [c_uchar; 32], value: uint64_t, r: *const [c_uchar; 32], - result: *mut [c_uchar; 32], -) -> bool { +) -> Result, ()> { let diversifier = sapling_crypto::primitives::Diversifier(unsafe { *diversifier }); let g_d = match diversifier.g_d::(&JUBJUB) { Some(g_d) => g_d, - None => return false, + None => return Err(()), }; let pk_d = match edwards::Point::::read(&(unsafe { &*pk_d })[..], &JUBJUB) { Ok(p) => p, - Err(_) => return false, + Err(_) => return Err(()), }; let pk_d = match pk_d.as_prime_order(&JUBJUB) { Some(pk_d) => pk_d, - None => return false, + None => return Err(()), }; // Deserialize randomness @@ -369,7 +369,7 @@ pub extern "system" fn librustzcash_sapling_compute_cm( repr.read_le(&r[..]).expect("length is not 32 bytes"); let r = match Fs::from_repr(repr) { Ok(p) => p, - Err(_) => return false, + Err(_) => return Err(()), }; let note = sapling_crypto::primitives::Note { @@ -379,6 +379,68 @@ pub extern "system" fn librustzcash_sapling_compute_cm( r, }; + Ok(note) +} + +/// Compute Sapling note nullifier. +#[no_mangle] +pub extern "system" fn librustzcash_sapling_compute_nf( + diversifier: *const [c_uchar; 11], + pk_d: *const [c_uchar; 32], + value: uint64_t, + r: *const [c_uchar; 32], + ak: *const [c_uchar; 32], + nk: *const [c_uchar; 32], + position: uint64_t, + result: *mut [c_uchar; 32], +) -> bool { + let note = match priv_get_note(diversifier, pk_d, value, r) { + Ok(p) => p, + Err(_) => return false, + }; + + let ak = match edwards::Point::::read(&(unsafe { &*ak })[..], &JUBJUB) { + Ok(p) => p, + Err(_) => return false, + }; + + let ak = match ak.as_prime_order(&JUBJUB) { + Some(ak) => ak, + None => return false, + }; + + let nk = match edwards::Point::::read(&(unsafe { &*nk })[..], &JUBJUB) { + Ok(p) => p, + Err(_) => return false, + }; + + let nk = match nk.as_prime_order(&JUBJUB) { + Some(nk) => nk, + None => return false, + }; + + let vk = ViewingKey { ak, nk }; + let nf = note.nf(&vk, position, &JUBJUB); + let result = unsafe { &mut *result }; + result.copy_from_slice(&nf); + + true +} + +/// Compute Sapling note commitment. +#[no_mangle] +pub extern "system" fn librustzcash_sapling_compute_cm( + diversifier: *const [c_uchar; 11], + pk_d: *const [c_uchar; 32], + value: uint64_t, + r: *const [c_uchar; 32], + result: *mut [c_uchar; 32], +) -> bool { + let note = match priv_get_note(diversifier, pk_d, value, r) { + Ok(p) => p, + Err(_) => return false, + }; + let result = unsafe { &mut *result }; write_le(note.cm(&JUBJUB).into_repr(), &mut result[..]); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 420f23fc69..ad5ca41fd4 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -2,7 +2,7 @@ use sapling_crypto::jubjub::{FixedGenerators, JubjubParams}; use super::JUBJUB; -mod commitments; +mod notes; mod key_components; mod signatures; diff --git a/src/tests/commitments.rs b/src/tests/notes.rs similarity index 98% rename from src/tests/commitments.rs rename to src/tests/notes.rs index e7116c476e..c9fde9a289 100644 --- a/src/tests/commitments.rs +++ b/src/tests/notes.rs @@ -1,7 +1,8 @@ use librustzcash_sapling_compute_cm; +use librustzcash_sapling_compute_nf; #[test] -fn commitments() { +fn notes() { #![allow(dead_code)] struct TestVector { sk: [u8; 32], @@ -655,5 +656,18 @@ fn commitments() { &mut result )); assert_eq!(&result, &tv.note_cm); + + // Compute nullifier and compare with test vector + assert!(librustzcash_sapling_compute_nf( + &tv.default_d, + &tv.default_pk_d, + tv.note_v, + &tv.note_r, + &tv.ak, + &tv.nk, + tv.note_pos, + &mut result + )); + assert_eq!(&result, &tv.note_nf); } }