pepper_sync/
keys.rs

1//! Copied and modified from LRZ due to thread safety limitations and missing OVK
2
3use 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
23/// Child index for the `address_index` path level in the BIP44 hierarchy.
24pub type AddressIndex = u32;
25
26/// Unique ID for shielded keys.
27#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
28pub struct KeyId {
29    /// Account ID
30    pub account_id: zcash_primitives::zip32::AccountId,
31    /// Scope
32    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
57/// A key that can be used to perform trial decryption and nullifier
58/// computation for a `CompactSaplingOutput` or `CompactOrchardAction`.
59pub trait ScanningKeyOps<D: Domain, Nf> {
60    /// Prepare the key for use in batch trial decryption.
61    fn prepare(&self) -> D::IncomingViewingKey;
62
63    /// Returns the account identifier for this key. An account identifier corresponds
64    /// to at most a single unified spending key's worth of spend authority, such that
65    /// both received notes and change spendable by that spending authority will be
66    /// interpreted as belonging to that account.
67    fn account_id(&self) -> &zcash_primitives::zip32::AccountId;
68
69    /// Returns the [`zip32::Scope`] for which this key was derived, if known.
70    fn key_scope(&self) -> Option<Scope>;
71
72    /// Produces the nullifier for the specified note and witness, if possible.
73    ///
74    /// IVK-based implementations of this trait cannot successfully derive
75    /// nullifiers, in which this function will always return `None`.
76    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
146/// A set of keys to be used in scanning for decryptable transaction outputs.
147pub(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    /// Constructs a [`ScanningKeys`] from an iterator of [`zcash_keys::keys::UnifiedFullViewingKey`]s,
154    /// along with the account identifiers corresponding to those UFVKs.
155    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        &parameters.network_type(),
210    ))
211}
212
213/// Decode string to unified address.
214// TODO: return custom error type
215pub 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
231/// Decode string to [`zcash_keys::address::Address`] enum.
232pub 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}