1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use std::collections::HashMap;
use zcash_primitives::{
consensus::{self, BlockHeight},
memo::MemoBytes,
sapling::{
note_encryption::{
try_sapling_note_decryption, try_sapling_output_recovery, PreparedIncomingViewingKey,
},
Note, PaymentAddress,
},
transaction::Transaction,
zip32::{AccountId, Scope},
};
use crate::keys::UnifiedFullViewingKey;
/// An enumeration of the possible relationships a TXO can have to the wallet.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TransferType {
/// The output was received on one of the wallet's external addresses via decryption using the
/// associated incoming viewing key, or at one of the wallet's transparent addresses.
Incoming,
/// The output was received on one of the wallet's internal-only shielded addresses via trial
/// decryption using one of the wallet's internal incoming viewing keys.
WalletInternal,
/// The output was decrypted using one of the wallet's outgoing viewing keys, or was created
/// in a transaction constructed by this wallet.
Outgoing,
}
/// A decrypted shielded output.
pub struct DecryptedOutput {
/// The index of the output within [`shielded_outputs`].
///
/// [`shielded_outputs`]: zcash_primitives::transaction::TransactionData
pub index: usize,
/// The note within the output.
pub note: Note,
/// The account that decrypted the note.
pub account: AccountId,
/// The address the note was sent to.
pub to: PaymentAddress,
/// The memo bytes included with the note.
pub memo: MemoBytes,
/// True if this output was recovered using an [`OutgoingViewingKey`], meaning that
/// this is a logical output of the transaction.
///
/// [`OutgoingViewingKey`]: zcash_primitives::keys::OutgoingViewingKey
pub transfer_type: TransferType,
}
/// Scans a [`Transaction`] for any information that can be decrypted by the set of
/// [`UnifiedFullViewingKey`]s.
pub fn decrypt_transaction<P: consensus::Parameters>(
params: &P,
height: BlockHeight,
tx: &Transaction,
ufvks: &HashMap<AccountId, UnifiedFullViewingKey>,
) -> Vec<DecryptedOutput> {
tx.sapling_bundle()
.iter()
.flat_map(|bundle| {
ufvks
.iter()
.flat_map(move |(account, ufvk)| {
ufvk.sapling().into_iter().map(|dfvk| (*account, dfvk))
})
.flat_map(move |(account, dfvk)| {
let ivk_external =
PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::External));
let ivk_internal =
PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
let ovk = dfvk.fvk().ovk;
bundle
.shielded_outputs()
.iter()
.enumerate()
.flat_map(move |(index, output)| {
try_sapling_note_decryption(params, height, &ivk_external, output)
.map(|ret| (ret, TransferType::Incoming))
.or_else(|| {
try_sapling_note_decryption(
params,
height,
&ivk_internal,
output,
)
.map(|ret| (ret, TransferType::WalletInternal))
})
.or_else(|| {
try_sapling_output_recovery(params, height, &ovk, output)
.map(|ret| (ret, TransferType::Outgoing))
})
.into_iter()
.map(move |((note, to, memo), transfer_type)| DecryptedOutput {
index,
note,
account,
to,
memo,
transfer_type,
})
})
})
})
.collect()
}