diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index c654d31ad..8e7a61d68 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -257,7 +257,7 @@ mod tests { rseed, }; let encryptor = SaplingNoteEncryption::new( - extfvk.fvk.ovk, + Some(extfvk.fvk.ovk), note.clone(), to.clone(), Memo::default(), diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 87327ee6a..9ecaafeec 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -133,7 +133,7 @@ mod tests { rseed, }; let encryptor = SaplingNoteEncryption::new( - extfvk.fvk.ovk, + Some(extfvk.fvk.ovk), note.clone(), to.clone(), Memo::default(), @@ -193,7 +193,7 @@ mod tests { rseed, }; let encryptor = SaplingNoteEncryption::new( - extfvk.fvk.ovk, + Some(extfvk.fvk.ovk), note.clone(), to, Memo::default(), @@ -221,7 +221,7 @@ mod tests { rseed, }; let encryptor = SaplingNoteEncryption::new( - extfvk.fvk.ovk, + Some(extfvk.fvk.ovk), note.clone(), change_addr, Memo::default(), diff --git a/zcash_client_sqlite/src/transact.rs b/zcash_client_sqlite/src/transact.rs index a7488eaa3..fe8897d98 100644 --- a/zcash_client_sqlite/src/transact.rs +++ b/zcash_client_sqlite/src/transact.rs @@ -1,7 +1,7 @@ //! Functions for creating transactions. use ff::PrimeField; -use rand_core::{OsRng, RngCore}; +use rand_core::OsRng; use rusqlite::{types::ToSql, Connection, NO_PARAMS}; use std::convert::TryInto; use std::path::Path; @@ -148,16 +148,9 @@ pub fn create_to_address>( // Apply the outgoing viewing key policy. let ovk = match ovk_policy { - OvkPolicy::Sender => extfvk.fvk.ovk, - OvkPolicy::Custom(ovk) => ovk, - OvkPolicy::Discard => { - // Generate a random outgoing viewing key that the caller does not know. - // The probability of this colliding with a legitimate outgoing viewing - // key is negligible. - let mut ovk = [0; 32]; - OsRng.fill_bytes(&mut ovk); - OutgoingViewingKey(ovk) - } + OvkPolicy::Sender => Some(extfvk.fvk.ovk), + OvkPolicy::Custom(ovk) => Some(ovk), + OvkPolicy::Discard => None, }; // Target the next block, assuming we are up-to-date. diff --git a/zcash_primitives/src/note_encryption.rs b/zcash_primitives/src/note_encryption.rs index b5937e2ff..bb25defdf 100644 --- a/zcash_primitives/src/note_encryption.rs +++ b/zcash_primitives/src/note_encryption.rs @@ -221,7 +221,7 @@ pub fn prf_ock( /// let diversifier = Diversifier([0; 11]); /// let pk_d = diversifier.g_d().unwrap(); /// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap(); -/// let ovk = OutgoingViewingKey([0; 32]); +/// let ovk = Some(OutgoingViewingKey([0; 32])); /// /// let value = 1000; /// let rcv = jubjub::Fr::random(&mut rng); @@ -233,29 +233,34 @@ pub fn prf_ock( /// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap(); /// let cmu = note.cmu(); /// -/// let enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default(), &mut rng); +/// let mut enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default(), &mut rng); /// let encCiphertext = enc.encrypt_note_plaintext(); /// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu); /// ``` -pub struct SaplingNoteEncryption { +pub struct SaplingNoteEncryption { epk: jubjub::SubgroupPoint, esk: jubjub::Fr, note: Note, to: PaymentAddress, memo: Memo, - ovk: OutgoingViewingKey, + /// `None` represents the `ovk = ⊥` case. + ovk: Option, + rng: R, } -impl SaplingNoteEncryption { +impl SaplingNoteEncryption { /// Creates a new encryption context for the given note. - pub fn new( - ovk: OutgoingViewingKey, + /// + /// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be + /// recovered by the sender. + pub fn new( + ovk: Option, note: Note, to: PaymentAddress, memo: Memo, - rng: &mut R, - ) -> SaplingNoteEncryption { - let esk = note.generate_or_derive_esk(rng); + mut rng: R, + ) -> Self { + let esk = note.generate_or_derive_esk(&mut rng); let epk = note.g_d * esk; SaplingNoteEncryption { @@ -265,6 +270,7 @@ impl SaplingNoteEncryption { to, memo, ovk, + rng, } } @@ -317,15 +323,28 @@ impl SaplingNoteEncryption { /// Generates `outCiphertext` for this note. pub fn encrypt_outgoing_plaintext( - &self, + &mut self, cv: &jubjub::ExtendedPoint, cmu: &bls12_381::Scalar, ) -> [u8; OUT_CIPHERTEXT_SIZE] { - let ock = prf_ock(&self.ovk, &cv, &cmu, &self.epk); + let (ock, input) = if let Some(ovk) = &self.ovk { + let ock = prf_ock(ovk, &cv, &cmu, &self.epk); - let mut input = [0u8; OUT_PLAINTEXT_SIZE]; - input[0..32].copy_from_slice(&self.note.pk_d.to_bytes()); - input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(self.esk.to_repr().as_ref()); + let mut input = [0u8; OUT_PLAINTEXT_SIZE]; + input[0..32].copy_from_slice(&self.note.pk_d.to_bytes()); + input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(self.esk.to_repr().as_ref()); + + (ock, input) + } else { + // ovk = ⊥ + let mut ock = OutgoingCipherKey([0; 32]); + let mut input = [0u8; OUT_PLAINTEXT_SIZE]; + + self.rng.fill_bytes(&mut ock.0); + self.rng.fill_bytes(&mut input); + + (ock, input) + }; let mut output = [0u8; OUT_CIPHERTEXT_SIZE]; assert_eq!( @@ -847,22 +866,13 @@ mod tests { let cmu = note.cmu(); let ovk = OutgoingViewingKey([0; 32]); - let ne = SaplingNoteEncryption::new(ovk, note, pa, Memo([0; 512]), &mut rng); - let epk = ne.epk(); + let mut ne = SaplingNoteEncryption::new(Some(ovk), note, pa, Memo([0; 512]), &mut rng); + let epk = ne.epk().clone(); let enc_ciphertext = ne.encrypt_note_plaintext(); let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu); let ock = prf_ock(&ovk, &cv, &cmu, &epk); - ( - ovk, - ock, - ivk, - cv, - cmu, - epk.clone(), - enc_ciphertext, - out_ciphertext, - ) + (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext) } fn reencrypt_enc_ciphertext( @@ -1845,7 +1855,7 @@ mod tests { // Test encryption // - let mut ne = SaplingNoteEncryption::new(ovk, note, to, Memo(tv.memo), &mut OsRng); + let mut ne = SaplingNoteEncryption::new(Some(ovk), note, to, Memo(tv.memo), OsRng); // Swap in the ephemeral keypair from the test vectors ne.esk = esk; ne.epk = epk; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 302656edf..616179245 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -76,7 +76,8 @@ struct SpendDescriptionInfo { } pub struct SaplingOutput { - ovk: OutgoingViewingKey, + /// `None` represents the `ovk = ⊥` case. + ovk: Option, to: PaymentAddress, note: Note, memo: Memo, @@ -86,7 +87,7 @@ impl SaplingOutput { pub fn new( height: u32, rng: &mut R, - ovk: OutgoingViewingKey, + ovk: Option, to: PaymentAddress, value: Amount, memo: Option, @@ -122,7 +123,7 @@ impl SaplingOutput { ctx: &mut P::SaplingProvingContext, rng: &mut R, ) -> OutputDescription { - let encryptor = SaplingNoteEncryption::new( + let mut encryptor = SaplingNoteEncryption::new( self.ovk, self.note.clone(), self.to.clone(), @@ -396,7 +397,7 @@ impl Builder { /// Adds a Sapling address to send funds to. pub fn add_sapling_output( &mut self, - ovk: OutgoingViewingKey, + ovk: Option, to: PaymentAddress, value: Amount, memo: Option, @@ -502,7 +503,7 @@ impl Builder { return Err(Error::NoChangeAddress); }; - self.add_sapling_output(change_address.0, change_address.1, change, None)?; + self.add_sapling_output(Some(change_address.0), change_address.1, change, None)?; } // @@ -728,7 +729,7 @@ mod tests { let mut builder = Builder::::new(0); assert_eq!( - builder.add_sapling_output(ovk, to, Amount::from_i64(-1).unwrap(), None), + builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None), Err(Error::InvalidAmount) ); } @@ -840,7 +841,7 @@ mod tests { } let extfvk = ExtendedFullViewingKey::from(&extsk); - let ovk = extfvk.fvk.ovk; + let ovk = Some(extfvk.fvk.ovk); let to = extfvk.default_address().unwrap().1; // Fail if there is only a Sapling output