diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index c96aa413f..1f6125412 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -9,7 +9,7 @@ use zcash_primitives::{ consensus::BlockHeight, merkle_tree::{CommitmentTree, IncrementalWitness}, note_encryption::Memo, - primitives::{Note, Nullifier, PaymentAddress}, + primitives::{Nullifier, PaymentAddress}, sapling::Node, transaction::{components::Amount, Transaction, TxId}, zip32::ExtendedFullViewingKey, @@ -20,7 +20,7 @@ use crate::{ data_api::wallet::ANCHOR_OFFSET, decrypt::DecryptedOutput, proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote, WalletShieldedOutput, WalletTx}, + wallet::{AccountId, SpendableNote, WalletTx}, }; pub mod chain; @@ -184,7 +184,7 @@ pub struct PrunedBlock<'a> { pub block_hash: BlockHash, pub block_time: u32, pub commitment_tree: &'a CommitmentTree, - pub transactions: &'a Vec, + pub transactions: &'a Vec>, } pub struct ReceivedTransaction<'a> { @@ -205,6 +205,7 @@ pub struct SentTransaction<'a> { /// This trait encapsulates the write capabilities required to update stored /// wallet data. pub trait WalletWrite: WalletRead { + #[allow(clippy::type_complexity)] fn insert_pruned_block( &mut self, block: &PrunedBlock, @@ -251,70 +252,6 @@ pub trait BlockSource { F: FnMut(CompactBlock) -> Result<(), Self::Error>; } -/// This trait provides a generalization over shielded output representations -/// that allows a wallet to avoid coupling to a specific one. -// TODO: it'd probably be better not to unify the definitions of -// `WalletShieldedOutput` and `DecryptedOutput` via a compositional -// approach, if possible. -pub trait ShieldedOutput { - fn index(&self) -> usize; - fn account(&self) -> AccountId; - fn to(&self) -> &PaymentAddress; - fn note(&self) -> &Note; - fn memo(&self) -> Option<&Memo>; - fn is_change(&self) -> Option; - fn nullifier(&self) -> Option; -} - -impl ShieldedOutput for WalletShieldedOutput { - fn index(&self) -> usize { - self.index - } - fn account(&self) -> AccountId { - self.account - } - fn to(&self) -> &PaymentAddress { - &self.to - } - fn note(&self) -> &Note { - &self.note - } - fn memo(&self) -> Option<&Memo> { - None - } - fn is_change(&self) -> Option { - Some(self.is_change) - } - - fn nullifier(&self) -> Option { - self.nf.clone() - } -} - -impl ShieldedOutput for DecryptedOutput { - fn index(&self) -> usize { - self.index - } - fn account(&self) -> AccountId { - self.account - } - fn to(&self) -> &PaymentAddress { - &self.to - } - fn note(&self) -> &Note { - &self.note - } - fn memo(&self) -> Option<&Memo> { - Some(&self.memo) - } - fn is_change(&self) -> Option { - None - } - fn nullifier(&self) -> Option { - None - } -} - #[cfg(feature = "test-dependencies")] pub mod testing { use std::collections::HashMap; @@ -447,6 +384,7 @@ pub mod testing { } impl WalletWrite for MockWalletDB { + #[allow(clippy::type_complexity)] fn insert_pruned_block( &mut self, _block: &PrunedBlock, diff --git a/zcash_client_backend/src/data_api/chain.rs b/zcash_client_backend/src/data_api/chain.rs index c8986487a..c033fdbd7 100644 --- a/zcash_client_backend/src/data_api/chain.rs +++ b/zcash_client_backend/src/data_api/chain.rs @@ -291,7 +291,7 @@ where let block_hash = BlockHash::from_slice(&block.hash); let block_time = block.time; - let txs: Vec = { + let txs: Vec> = { let mut witness_refs: Vec<_> = witnesses.iter_mut().map(|w| &mut w.1).collect(); scan_block( @@ -344,11 +344,10 @@ where .flat_map(|tx| tx.shielded_spends.iter().map(|spend| spend.nf)) .collect(); nullifiers.retain(|(_, nf)| !spent_nf.contains(nf)); - nullifiers.extend(txs.iter().flat_map(|tx| { - tx.shielded_outputs - .iter() - .flat_map(|out| out.nf.map(|nf| (out.account, nf))) - })); + nullifiers.extend( + txs.iter() + .flat_map(|tx| tx.shielded_outputs.iter().map(|out| (out.account, out.nf))), + ); witnesses.extend(new_witnesses); diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 399fe85bd..d2170d93b 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -30,13 +30,13 @@ impl ConditionallySelectable for AccountId { /// A subset of a [`Transaction`] relevant to wallets and light clients. /// /// [`Transaction`]: zcash_primitives::transaction::Transaction -pub struct WalletTx { +pub struct WalletTx { pub txid: TxId, pub index: usize, pub num_spends: usize, pub num_outputs: usize, pub shielded_spends: Vec, - pub shielded_outputs: Vec, + pub shielded_outputs: Vec>, } /// A subset of a [`SpendDescription`] relevant to wallets and light clients. @@ -51,7 +51,7 @@ pub struct WalletShieldedSpend { /// A subset of an [`OutputDescription`] relevant to wallets and light clients. /// /// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription -pub struct WalletShieldedOutput { +pub struct WalletShieldedOutput { pub index: usize, pub cmu: bls12_381::Scalar, pub epk: jubjub::ExtendedPoint, @@ -60,7 +60,7 @@ pub struct WalletShieldedOutput { pub to: PaymentAddress, pub is_change: bool, pub witness: IncrementalWitness, - pub nf: Option, + pub nf: N, } pub struct SpendableNote { diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 7bb103bdf..fdc39142a 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -7,7 +7,7 @@ use zcash_primitives::{ consensus::{self, BlockHeight}, merkle_tree::{CommitmentTree, IncrementalWitness}, note_encryption::try_sapling_compact_note_decryption, - primitives::{Nullifier, ViewingKey}, + primitives::{Note, Nullifier, PaymentAddress, SaplingIvk, ViewingKey}, sapling::Node, transaction::TxId, }; @@ -25,17 +25,17 @@ use crate::wallet::{AccountId, WalletShieldedOutput, WalletShieldedSpend, Wallet /// /// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey #[allow(clippy::too_many_arguments)] -fn scan_output( +fn scan_output( params: &P, height: BlockHeight, (index, output): (usize, CompactOutput), - vks: &[(AccountId, &ViewingKey)], + vks: &[(AccountId, &K)], spent_from_accounts: &HashSet, tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], block_witnesses: &mut [&mut IncrementalWitness], new_witnesses: &mut [&mut IncrementalWitness], -) -> Option { +) -> Option> { let cmu = output.cmu().ok()?; let epk = output.epk().ok()?; let ct = output.ciphertext; @@ -54,12 +54,10 @@ fn scan_output( tree.append(node).unwrap(); for (account, vk) in vks.iter() { - let ivk = vk.ivk(); - let (note, to) = - match try_sapling_compact_note_decryption(params, height, &ivk, &epk, &cmu, &ct) { - Some(ret) => ret, - None => continue, - }; + let (note, to) = match vk.try_decryption(params, height, &epk, &cmu, &ct) { + Some(ret) => ret, + None => continue, + }; // A note is marked as "change" if the account that received it // also spent notes in the same transaction. This will catch, @@ -70,7 +68,7 @@ fn scan_output( let is_change = spent_from_accounts.contains(&account); let witness = IncrementalWitness::from_tree(tree); - let nf = note.nf(&vk, witness.position() as u64); + let nf = vk.nf(¬e, &witness); return Some(WalletShieldedOutput { index, @@ -81,30 +79,81 @@ fn scan_output( to, is_change, witness, - nf: Some(nf), + nf, }); } + None } -/// Scans a [`CompactBlock`] with a set of [`ViewingKey`]s. +pub trait ScanningKey { + type Nf; + + fn try_decryption( + &self, + params: &P, + height: BlockHeight, + epk: &jubjub::ExtendedPoint, + cmu: &bls12_381::Scalar, + ct: &[u8], + ) -> Option<(Note, PaymentAddress)>; + + fn nf(&self, note: &Note, witness: &IncrementalWitness) -> Self::Nf; +} + +impl ScanningKey for ViewingKey { + type Nf = Nullifier; + + fn try_decryption( + &self, + params: &P, + height: BlockHeight, + epk: &jubjub::ExtendedPoint, + cmu: &bls12_381::Scalar, + ct: &[u8], + ) -> Option<(Note, PaymentAddress)> { + try_sapling_compact_note_decryption(params, height, &self.ivk(), &epk, &cmu, &ct) + } + + fn nf(&self, note: &Note, witness: &IncrementalWitness) -> Self::Nf { + note.nf(self, witness.position() as u64) + } +} + +impl ScanningKey for SaplingIvk { + type Nf = (); + + fn try_decryption( + &self, + params: &P, + height: BlockHeight, + epk: &jubjub::ExtendedPoint, + cmu: &bls12_381::Scalar, + ct: &[u8], + ) -> Option<(Note, PaymentAddress)> { + try_sapling_compact_note_decryption(params, height, self, &epk, &cmu, &ct) + } + + fn nf(&self, _note: &Note, _witness: &IncrementalWitness) {} +} + +/// Scans a [`CompactBlock`] with a set of [`ScanningKeys`]s. /// /// Returns a vector of [`WalletTx`]s belonging to any of the given -/// [`ViewingKey`]s. +/// [`ScanningKey`]s. If scanning with a full viewing key, the nullifiers +/// of the resulting [`WalletShieldedOutput`]s will also be computed. /// /// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are /// incremented appropriately. -/// -/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey -pub fn scan_block( +pub fn scan_block( params: &P, block: CompactBlock, - vks: &[(AccountId, &ViewingKey)], + vks: &[(AccountId, &K)], nullifiers: &[(AccountId, Nullifier)], tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], -) -> Vec { - let mut wtxs: Vec = vec![]; +) -> Vec> { + let mut wtxs: Vec> = vec![]; let block_height = block.height(); for tx in block.vtx.into_iter() { @@ -145,7 +194,7 @@ pub fn scan_block( shielded_spends.iter().map(|spend| spend.account).collect(); // Check for incoming notes while incrementing tree and witnesses - let mut shielded_outputs: Vec = vec![]; + let mut shielded_outputs: Vec> = vec![]; { // Grab mutable references to new witnesses from previous transactions // in this block so that we can update them. Scoped so we don't hold @@ -211,7 +260,7 @@ mod tests { constants::SPENDING_KEY_GENERATOR, merkle_tree::CommitmentTree, note_encryption::{Memo, SaplingNoteEncryption}, - primitives::{Note, Nullifier}, + primitives::{Note, Nullifier, SaplingIvk}, transaction::components::Amount, util::generate_random_rseed, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, @@ -408,12 +457,13 @@ mod tests { let cb = fake_compact_block(1u32.into(), nf, extfvk, Amount::from_u64(5).unwrap(), false); assert_eq!(cb.vtx.len(), 2); + let vks: Vec<(AccountId, &SaplingIvk)> = vec![]; let mut tree = CommitmentTree::empty(); let txs = scan_block( &Network::TestNetwork, cb, - &[], + &vks[..], &[(account, nf)], &mut tree, &mut [], diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index a33632193..5d429a601 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -388,6 +388,7 @@ impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> { } impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { + #[allow(clippy::type_complexity)] fn insert_pruned_block( &mut self, block: &PrunedBlock, diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 459f88c3b..bf86ddef0 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -9,7 +9,7 @@ use zcash_primitives::{ consensus::{self, BlockHeight, NetworkUpgrade}, merkle_tree::{CommitmentTree, IncrementalWitness}, note_encryption::Memo, - primitives::{Nullifier, PaymentAddress}, + primitives::{Note, Nullifier, PaymentAddress}, sapling::Node, transaction::{components::Amount, Transaction, TxId}, zip32::ExtendedFullViewingKey, @@ -17,12 +17,12 @@ use zcash_primitives::{ use zcash_client_backend::{ address::RecipientAddress, - data_api::{error::Error, ShieldedOutput}, + data_api::error::Error, encoding::{ decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key, encode_payment_address, }, - wallet::{AccountId, WalletTx}, + wallet::{AccountId, WalletShieldedOutput, WalletTx}, DecryptedOutput, }; @@ -31,6 +31,70 @@ use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDB}; pub mod init; pub mod transact; +/// This trait provides a generalization over shielded output representations +/// that allows a wallet to avoid coupling to a specific one. +// TODO: it'd probably be better not to unify the definitions of +// `WalletShieldedOutput` and `DecryptedOutput` via a compositional +// approach, if possible. +pub trait ShieldedOutput { + fn index(&self) -> usize; + fn account(&self) -> AccountId; + fn to(&self) -> &PaymentAddress; + fn note(&self) -> &Note; + fn memo(&self) -> Option<&Memo>; + fn is_change(&self) -> Option; + fn nullifier(&self) -> Option; +} + +impl ShieldedOutput for WalletShieldedOutput { + fn index(&self) -> usize { + self.index + } + fn account(&self) -> AccountId { + self.account + } + fn to(&self) -> &PaymentAddress { + &self.to + } + fn note(&self) -> &Note { + &self.note + } + fn memo(&self) -> Option<&Memo> { + None + } + fn is_change(&self) -> Option { + Some(self.is_change) + } + + fn nullifier(&self) -> Option { + Some(self.nf) + } +} + +impl ShieldedOutput for DecryptedOutput { + fn index(&self) -> usize { + self.index + } + fn account(&self) -> AccountId { + self.account + } + fn to(&self) -> &PaymentAddress { + &self.to + } + fn note(&self) -> &Note { + &self.note + } + fn memo(&self) -> Option<&Memo> { + Some(&self.memo) + } + fn is_change(&self) -> Option { + None + } + fn nullifier(&self) -> Option { + None + } +} + /// Returns the address for the account. /// /// # Examples @@ -458,9 +522,9 @@ pub fn insert_block<'a, P>( Ok(()) } -pub fn put_tx_meta<'a, P>( +pub fn put_tx_meta<'a, P, N>( stmts: &mut DataConnStmtCache<'a, P>, - tx: &WalletTx, + tx: &WalletTx, height: BlockHeight, ) -> Result { let txid = tx.txid.0.to_vec();