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