diff --git a/include/librustzcash.h b/include/librustzcash.h index 114994a..2a222a8 100644 --- a/include/librustzcash.h +++ b/include/librustzcash.h @@ -100,6 +100,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 4ae7696..619955c 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,6 +340,93 @@ pub extern "system" fn librustzcash_sapling_generate_r(result: *mut [c_uchar; 32 .expect("result must be 32 bytes"); } +// 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, ()> { + let diversifier = sapling_crypto::primitives::Diversifier(unsafe { *diversifier }); + let g_d = match diversifier.g_d::(&JUBJUB) { + Some(g_d) => g_d, + None => return Err(()), + }; + + let pk_d = match edwards::Point::::read(&(unsafe { &*pk_d })[..], &JUBJUB) { + Ok(p) => p, + Err(_) => return Err(()), + }; + + let pk_d = match pk_d.as_prime_order(&JUBJUB) { + Some(pk_d) => pk_d, + None => return Err(()), + }; + + // Deserialize randomness + let r = unsafe { *r }; + let mut repr = FsRepr::default(); + repr.read_le(&r[..]).expect("length is not 32 bytes"); + let r = match Fs::from_repr(repr) { + Ok(p) => p, + Err(_) => return Err(()), + }; + + let note = sapling_crypto::primitives::Note { + value, + g_d, + pk_d, + 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( @@ -347,38 +436,11 @@ pub extern "system" fn librustzcash_sapling_compute_cm( r: *const [c_uchar; 32], result: *mut [c_uchar; 32], ) -> bool { - let diversifier = sapling_crypto::primitives::Diversifier(unsafe { *diversifier }); - let g_d = match diversifier.g_d::(&JUBJUB) { - Some(g_d) => g_d, - None => return false, - }; - - let pk_d = match edwards::Point::::read(&(unsafe { &*pk_d })[..], &JUBJUB) { + let note = match priv_get_note(diversifier, pk_d, value, r) { Ok(p) => p, Err(_) => return false, }; - let pk_d = match pk_d.as_prime_order(&JUBJUB) { - Some(pk_d) => pk_d, - None => return false, - }; - - // Deserialize randomness - let r = unsafe { *r }; - let mut repr = FsRepr::default(); - repr.read_le(&r[..]).expect("length is not 32 bytes"); - let r = match Fs::from_repr(repr) { - Ok(p) => p, - Err(_) => return false, - }; - - let note = sapling_crypto::primitives::Note { - value, - g_d, - pk_d, - r, - }; - 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 420f23f..ad5ca41 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 e7116c4..c9fde9a 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); } }