1use std::collections::HashMap;
4
5use incrementalmerkletree::Position;
6use orchard::{
7 keys::{FullViewingKey, IncomingViewingKey},
8 note_encryption::{IronwoodDomain, 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: zip32::AccountId,
31 pub scope: Scope,
33}
34
35impl KeyId {
36 pub(crate) fn from_parts(account_id: 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) -> &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) -> &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) -> &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) -> &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
146impl ScanningKeyOps<IronwoodDomain, orchard::note::Nullifier>
147 for ScanningKey<IncomingViewingKey, FullViewingKey>
148{
149 fn prepare(&self) -> orchard::keys::PreparedIncomingViewingKey {
150 orchard::keys::PreparedIncomingViewingKey::new(&self.ivk)
151 }
152
153 fn nf(
154 &self,
155 note: &orchard::note::Note,
156 _position: Position,
157 ) -> Option<orchard::note::Nullifier> {
158 self.nk.as_ref().map(|key| note.nullifier(key))
159 }
160
161 fn account_id(&self) -> &zip32::AccountId {
162 &self.key_id.account_id
163 }
164
165 fn key_scope(&self) -> Option<Scope> {
166 Some(self.key_id.scope)
167 }
168}
169
170pub(crate) struct ScanningKeys {
172 pub(crate) sapling: HashMap<KeyId, ScanningKey<SaplingIvk, NullifierDerivingKey>>,
173 pub(crate) orchard: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>>,
174 pub(crate) ironwood: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>>,
177}
178
179impl ScanningKeys {
180 pub(crate) fn from_account_ufvks(
183 ufvks: impl IntoIterator<Item = (zip32::AccountId, UnifiedFullViewingKey)>,
184 ) -> Self {
185 #![allow(clippy::type_complexity)]
186
187 let mut sapling: HashMap<KeyId, ScanningKey<SaplingIvk, NullifierDerivingKey>> =
188 HashMap::new();
189 let mut orchard: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>> =
190 HashMap::new();
191 let mut ironwood: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>> =
192 HashMap::new();
193
194 for (account_id, ufvk) in ufvks {
195 if let Some(dfvk) = ufvk.sapling() {
196 for scope in [Scope::External, Scope::Internal] {
197 let key_id = KeyId::from_parts(account_id, scope);
198 sapling.insert(
199 key_id,
200 ScanningKey {
201 key_id,
202 ivk: dfvk.to_ivk(scope),
203 nk: Some(dfvk.to_nk(scope)),
204 },
205 );
206 }
207 }
208
209 if let Some(fvk) = ufvk.orchard() {
210 for scope in [Scope::External, Scope::Internal] {
211 let key_id = KeyId::from_parts(account_id, scope);
212 orchard.insert(
213 key_id,
214 ScanningKey {
215 key_id,
216 ivk: fvk.to_ivk(scope),
217 nk: Some(fvk.clone()),
218 },
219 );
220 ironwood.insert(
221 key_id,
222 ScanningKey {
223 key_id,
224 ivk: fvk.to_ivk(scope),
225 nk: Some(fvk.clone()),
226 },
227 );
228 }
229 }
230 }
231
232 Self {
233 sapling,
234 orchard,
235 ironwood,
236 }
237 }
238}
239
240pub(crate) fn encode_orchard_receiver(
241 parameters: &impl consensus::Parameters,
242 orchard_address: &orchard::Address,
243) -> Result<String, ParseError> {
244 Ok(zcash_address::unified::Encoding::encode(
245 &<zcash_address::unified::Address as zcash_address::unified::Encoding>::try_from_items(
246 zcash_address::unified::Revision::R0,
247 vec![zcash_address::unified::Uitem::Data(
248 zcash_address::unified::Receiver::Orchard(orchard_address.to_raw_address_bytes()),
249 )],
250 )?,
251 ¶meters.network_type(),
252 ))
253}
254
255pub fn decode_unified_address(
258 consensus_parameters: &impl consensus::Parameters,
259 encoded_address: &str,
260) -> std::io::Result<UnifiedAddress> {
261 if let zcash_keys::address::Address::Unified(unified_address) =
262 decode_address(consensus_parameters, encoded_address)?
263 {
264 Ok(*unified_address)
265 } else {
266 Err(std::io::Error::new(
267 std::io::ErrorKind::InvalidData,
268 "failed to decode unified address. incorrect address type.".to_string(),
269 ))
270 }
271}
272
273pub fn decode_address(
275 consensus_parameters: &impl consensus::Parameters,
276 encoded_address: &str,
277) -> std::io::Result<zcash_keys::address::Address> {
278 ZcashAddress::try_from_encoded(encoded_address)
279 .map_err(|e| {
280 std::io::Error::new(
281 std::io::ErrorKind::InvalidData,
282 format!("failed to decode address. {e}"),
283 )
284 })?
285 .convert_if_network::<zcash_keys::address::Address>(consensus_parameters.network_type())
286 .map_err(|e| {
287 std::io::Error::new(
288 std::io::ErrorKind::InvalidData,
289 format!("failed to decode address. {e}"),
290 )
291 })
292}