1use std::collections::HashMap;
4
5use incrementalmerkletree::Position;
6use orchard::{
7 keys::{FullViewingKey, IncomingViewingKey},
8 note_encryption::OrchardDomain,
9};
10use sapling_crypto::{
11 self as sapling, NullifierDerivingKey, SaplingIvk, note_encryption::SaplingDomain,
12};
13use zcash_address::{ZcashAddress, unified::ParseError};
14use zcash_keys::{address::UnifiedAddress, keys::UnifiedFullViewingKey};
15use zcash_note_encryption::Domain;
16use zcash_protocol::consensus;
17use zip32::Scope;
18
19use crate::wallet::KeyIdInterface;
20
21pub mod transparent;
22
23pub type AddressIndex = u32;
25
26#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
28pub struct KeyId {
29 pub account_id: zcash_primitives::zip32::AccountId,
31 pub scope: Scope,
33}
34
35impl KeyId {
36 pub(crate) fn from_parts(account_id: zcash_primitives::zip32::AccountId, scope: Scope) -> Self {
37 Self { account_id, scope }
38 }
39}
40
41impl KeyIdInterface for KeyId {
42 fn account_id(&self) -> zip32::AccountId {
43 self.account_id
44 }
45}
46
47impl memuse::DynamicUsage for KeyId {
48 fn dynamic_usage(&self) -> usize {
49 self.scope.dynamic_usage()
50 }
51
52 fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
53 self.scope.dynamic_usage_bounds()
54 }
55}
56
57pub trait ScanningKeyOps<D: Domain, Nf> {
60 fn prepare(&self) -> D::IncomingViewingKey;
62
63 fn account_id(&self) -> &zcash_primitives::zip32::AccountId;
68
69 fn key_scope(&self) -> Option<Scope>;
71
72 fn nf(&self, note: &D::Note, note_position: Position) -> Option<Nf>;
77}
78impl<D: Domain, Nf, K: ScanningKeyOps<D, Nf>> ScanningKeyOps<D, Nf> for &K {
79 fn prepare(&self) -> D::IncomingViewingKey {
80 (*self).prepare()
81 }
82
83 fn account_id(&self) -> &zcash_primitives::zip32::AccountId {
84 (*self).account_id()
85 }
86
87 fn key_scope(&self) -> Option<Scope> {
88 (*self).key_scope()
89 }
90
91 fn nf(&self, note: &D::Note, note_position: Position) -> Option<Nf> {
92 (*self).nf(note, note_position)
93 }
94}
95
96pub(crate) struct ScanningKey<Ivk, Nk> {
97 key_id: KeyId,
98 ivk: Ivk,
99 nk: Option<Nk>,
100}
101
102impl ScanningKeyOps<SaplingDomain, sapling::Nullifier>
103 for ScanningKey<SaplingIvk, NullifierDerivingKey>
104{
105 fn prepare(&self) -> sapling::note_encryption::PreparedIncomingViewingKey {
106 sapling_crypto::note_encryption::PreparedIncomingViewingKey::new(&self.ivk)
107 }
108
109 fn nf(&self, note: &sapling::Note, position: Position) -> Option<sapling::Nullifier> {
110 self.nk.as_ref().map(|key| note.nf(key, position.into()))
111 }
112
113 fn account_id(&self) -> &zcash_primitives::zip32::AccountId {
114 &self.key_id.account_id
115 }
116
117 fn key_scope(&self) -> Option<Scope> {
118 Some(self.key_id.scope)
119 }
120}
121
122impl ScanningKeyOps<OrchardDomain, orchard::note::Nullifier>
123 for ScanningKey<IncomingViewingKey, FullViewingKey>
124{
125 fn prepare(&self) -> orchard::keys::PreparedIncomingViewingKey {
126 orchard::keys::PreparedIncomingViewingKey::new(&self.ivk)
127 }
128
129 fn nf(
130 &self,
131 note: &orchard::note::Note,
132 _position: Position,
133 ) -> Option<orchard::note::Nullifier> {
134 self.nk.as_ref().map(|key| note.nullifier(key))
135 }
136
137 fn account_id(&self) -> &zcash_primitives::zip32::AccountId {
138 &self.key_id.account_id
139 }
140
141 fn key_scope(&self) -> Option<Scope> {
142 Some(self.key_id.scope)
143 }
144}
145
146pub(crate) struct ScanningKeys {
148 pub(crate) sapling: HashMap<KeyId, ScanningKey<SaplingIvk, NullifierDerivingKey>>,
149 pub(crate) orchard: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>>,
150}
151
152impl ScanningKeys {
153 pub(crate) fn from_account_ufvks(
156 ufvks: impl IntoIterator<Item = (zcash_primitives::zip32::AccountId, UnifiedFullViewingKey)>,
157 ) -> Self {
158 #![allow(clippy::type_complexity)]
159
160 let mut sapling: HashMap<KeyId, ScanningKey<SaplingIvk, NullifierDerivingKey>> =
161 HashMap::new();
162 let mut orchard: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>> =
163 HashMap::new();
164
165 for (account_id, ufvk) in ufvks {
166 if let Some(dfvk) = ufvk.sapling() {
167 for scope in [Scope::External, Scope::Internal] {
168 let key_id = KeyId::from_parts(account_id, scope);
169 sapling.insert(
170 key_id,
171 ScanningKey {
172 key_id,
173 ivk: dfvk.to_ivk(scope),
174 nk: Some(dfvk.to_nk(scope)),
175 },
176 );
177 }
178 }
179
180 if let Some(fvk) = ufvk.orchard() {
181 for scope in [Scope::External, Scope::Internal] {
182 let key_id = KeyId::from_parts(account_id, scope);
183 orchard.insert(
184 key_id,
185 ScanningKey {
186 key_id,
187 ivk: fvk.to_ivk(scope),
188 nk: Some(fvk.clone()),
189 },
190 );
191 }
192 }
193 }
194
195 Self { sapling, orchard }
196 }
197}
198
199pub(crate) fn encode_orchard_receiver(
200 parameters: &impl consensus::Parameters,
201 orchard_address: &orchard::Address,
202) -> Result<String, ParseError> {
203 Ok(zcash_address::unified::Encoding::encode(
204 &<zcash_address::unified::Address as zcash_address::unified::Encoding>::try_from_items(
205 vec![zcash_address::unified::Receiver::Orchard(
206 orchard_address.to_raw_address_bytes(),
207 )],
208 )?,
209 ¶meters.network_type(),
210 ))
211}
212
213pub fn decode_unified_address(
216 consensus_parameters: &impl consensus::Parameters,
217 encoded_address: &str,
218) -> std::io::Result<UnifiedAddress> {
219 if let zcash_keys::address::Address::Unified(unified_address) =
220 decode_address(consensus_parameters, encoded_address)?
221 {
222 Ok(unified_address)
223 } else {
224 Err(std::io::Error::new(
225 std::io::ErrorKind::InvalidData,
226 "failed to decode unified address. incorrect address type.".to_string(),
227 ))
228 }
229}
230
231pub fn decode_address(
233 consensus_parameters: &impl consensus::Parameters,
234 encoded_address: &str,
235) -> std::io::Result<zcash_keys::address::Address> {
236 ZcashAddress::try_from_encoded(encoded_address)
237 .map_err(|e| {
238 std::io::Error::new(
239 std::io::ErrorKind::InvalidData,
240 format!("failed to decode address. {e}"),
241 )
242 })?
243 .convert_if_network::<zcash_keys::address::Address>(consensus_parameters.network_type())
244 .map_err(|e| {
245 std::io::Error::new(
246 std::io::ErrorKind::InvalidData,
247 format!("failed to decode address. {e}"),
248 )
249 })
250}