From 6996853168a3025d6c197c57123f4207186986f8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 14 Nov 2018 16:39:33 +0000 Subject: [PATCH] Trial Sapling note decryption --- sapling-crypto/src/jubjub/mod.rs | 1 + sapling-crypto/src/primitives/mod.rs | 21 +++++- zcash_primitives/src/note_encryption.rs | 88 +++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/sapling-crypto/src/jubjub/mod.rs b/sapling-crypto/src/jubjub/mod.rs index c925877..71e1875 100644 --- a/sapling-crypto/src/jubjub/mod.rs +++ b/sapling-crypto/src/jubjub/mod.rs @@ -47,6 +47,7 @@ pub mod tests; pub enum Unknown { } /// Point of prime order. +#[derive(Debug)] pub enum PrimeOrder { } /// Fixed generators of the Jubjub curve of unknown diff --git a/sapling-crypto/src/primitives/mod.rs b/sapling-crypto/src/primitives/mod.rs index 77b81c1..402bf4b 100644 --- a/sapling-crypto/src/primitives/mod.rs +++ b/sapling-crypto/src/primitives/mod.rs @@ -116,7 +116,7 @@ impl ViewingKey { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct Diversifier(pub [u8; 11]); impl Diversifier { @@ -129,12 +129,18 @@ impl Diversifier { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct PaymentAddress { pub pk_d: edwards::Point, pub diversifier: Diversifier } +impl PartialEq for PaymentAddress { + fn eq(&self, other: &Self) -> bool { + self.pk_d == other.pk_d && self.diversifier == other.diversifier + } +} + impl PaymentAddress { pub fn g_d( &self, @@ -162,7 +168,7 @@ impl PaymentAddress { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Note { /// The value of the note pub value: u64, @@ -174,6 +180,15 @@ pub struct Note { pub r: E::Fs } +impl PartialEq for Note { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + && self.g_d == other.g_d + && self.pk_d == other.pk_d + && self.r == other.r + } +} + impl Note { pub fn uncommitted() -> E::Fr { // The smallest u-coordinate that is not on the curve diff --git a/zcash_primitives/src/note_encryption.rs b/zcash_primitives/src/note_encryption.rs index 9d97467..6bd26a5 100644 --- a/zcash_primitives/src/note_encryption.rs +++ b/zcash_primitives/src/note_encryption.rs @@ -1,12 +1,16 @@ use blake2_rfc::blake2b::{Blake2b, Blake2bResult}; -use byteorder::{LittleEndian, WriteBytesExt}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use chacha20_poly1305_aead; use ff::{PrimeField, PrimeFieldRepr}; use pairing::bls12_381::{Bls12, Fr}; use rand::{OsRng, Rng}; use sapling_crypto::{ - jubjub::{edwards, fs::Fs, PrimeOrder, ToUniform, Unknown}, - primitives::{Note, PaymentAddress}, + jubjub::{ + edwards, + fs::{Fs, FsRepr}, + PrimeOrder, ToUniform, Unknown, + }, + primitives::{Diversifier, Note, PaymentAddress}, }; use crate::{keys::OutgoingViewingKey, JUBJUB}; @@ -162,6 +166,64 @@ impl SaplingNoteEncryption { } } +/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ivk`. +/// If successful, the corresponding Sapling note and memo are returned, along with the +/// `PaymentAddress` to which the note was sent. +/// +/// Implements section 4.17.2 of the Zcash Protocol Specification. +pub fn try_sapling_note_decryption( + ivk: &Fs, + epk: &edwards::Point, + cmu: &Fr, + enc_ciphertext: &[u8], +) -> Option<(Note, PaymentAddress, Memo)> { + let shared_secret = sapling_ka_agree(&ivk, &epk); + let key = kdf_sapling(&shared_secret, &epk); + + let mut plaintext = Vec::with_capacity(564); + let nonce = [0u8; 12]; + chacha20_poly1305_aead::decrypt( + key.as_bytes(), + &nonce, + &[], + &enc_ciphertext[..564], + &enc_ciphertext[564..], + &mut plaintext, + ) + .ok()?; + + let mut d = [0u8; 11]; + d.copy_from_slice(&plaintext[1..12]); + + let v = (&plaintext[12..20]).read_u64::().ok()?; + + let mut rcm = FsRepr::default(); + rcm.read_le(&plaintext[20..52]).ok()?; + let rcm = Fs::from_repr(rcm).ok()?; + + let mut memo = [0u8; 512]; + memo.copy_from_slice(&plaintext[52..564]); + + let diversifier = Diversifier(d); + let pk_d = match diversifier.g_d::(&JUBJUB) { + Some(g_d) => g_d.mul(ivk.into_repr(), &JUBJUB), + None => { + // Invalid diversifier in note plaintext + return None; + } + }; + + let to = PaymentAddress { pk_d, diversifier }; + let note = to.create_note(v, rcm, &JUBJUB).unwrap(); + + if note.cm(&JUBJUB) != *cmu { + // Published commitment doesn't match calculated commitment + return None; + } + + Some((note, to, Memo(memo))) +} + #[cfg(test)] mod tests { use ff::{PrimeField, PrimeFieldRepr}; @@ -174,7 +236,10 @@ mod tests { primitives::{Diversifier, PaymentAddress}, }; - use super::{kdf_sapling, prf_ock, sapling_ka_agree, Memo, SaplingNoteEncryption}; + use super::{ + kdf_sapling, prf_ock, sapling_ka_agree, try_sapling_note_decryption, Memo, + SaplingNoteEncryption, + }; use crate::{keys::OutgoingViewingKey, JUBJUB}; #[test] @@ -208,6 +273,7 @@ mod tests { // Load the test vector components // + let ivk = read_fs!(tv.ivk); let pk_d = read_point!(tv.default_pk_d) .as_prime_order(&JUBJUB) .unwrap(); @@ -238,6 +304,20 @@ mod tests { let note = to.create_note(tv.v, rcm, &JUBJUB).unwrap(); assert_eq!(note.cm(&JUBJUB), cmu); + // + // Test decryption + // (Tested first because it only requires immutable references.) + // + + match try_sapling_note_decryption(&ivk, &epk, &cmu, &tv.c_enc) { + Some((decrypted_note, decrypted_to, decrypted_memo)) => { + assert_eq!(decrypted_note, note); + assert_eq!(decrypted_to, to); + assert_eq!(&decrypted_memo.0[..], &tv.memo[..]); + } + None => panic!("Note decryption failed"), + } + // // Test encryption //