use std::collections::HashMap;
use incrementalmerkletree::Position;
use orchard::{
keys::{FullViewingKey, IncomingViewingKey},
note_encryption::OrchardDomain,
};
use sapling_crypto::{
self as sapling, NullifierDerivingKey, SaplingIvk, note_encryption::SaplingDomain,
};
use zcash_address::{ZcashAddress, unified::ParseError};
use zcash_keys::{address::UnifiedAddress, keys::UnifiedFullViewingKey};
use zcash_note_encryption::Domain;
use zcash_protocol::consensus;
use zip32::Scope;
use crate::wallet::KeyIdInterface;
pub mod transparent;
pub type AddressIndex = u32;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct KeyId {
pub account_id: zip32::AccountId,
pub scope: Scope,
}
impl KeyId {
pub(crate) fn from_parts(account_id: zip32::AccountId, scope: Scope) -> Self {
Self { account_id, scope }
}
}
impl KeyIdInterface for KeyId {
fn account_id(&self) -> zip32::AccountId {
self.account_id
}
}
impl memuse::DynamicUsage for KeyId {
fn dynamic_usage(&self) -> usize {
self.scope.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.scope.dynamic_usage_bounds()
}
}
pub trait ScanningKeyOps<D: Domain, Nf> {
fn prepare(&self) -> D::IncomingViewingKey;
fn account_id(&self) -> &zip32::AccountId;
fn key_scope(&self) -> Option<Scope>;
fn nf(&self, note: &D::Note, note_position: Position) -> Option<Nf>;
}
impl<D: Domain, Nf, K: ScanningKeyOps<D, Nf>> ScanningKeyOps<D, Nf> for &K {
fn prepare(&self) -> D::IncomingViewingKey {
(*self).prepare()
}
fn account_id(&self) -> &zip32::AccountId {
(*self).account_id()
}
fn key_scope(&self) -> Option<Scope> {
(*self).key_scope()
}
fn nf(&self, note: &D::Note, note_position: Position) -> Option<Nf> {
(*self).nf(note, note_position)
}
}
pub(crate) struct ScanningKey<Ivk, Nk> {
key_id: KeyId,
ivk: Ivk,
nk: Option<Nk>,
}
impl ScanningKeyOps<SaplingDomain, sapling::Nullifier>
for ScanningKey<SaplingIvk, NullifierDerivingKey>
{
fn prepare(&self) -> sapling::note_encryption::PreparedIncomingViewingKey {
sapling_crypto::note_encryption::PreparedIncomingViewingKey::new(&self.ivk)
}
fn nf(&self, note: &sapling::Note, position: Position) -> Option<sapling::Nullifier> {
self.nk.as_ref().map(|key| note.nf(key, position.into()))
}
fn account_id(&self) -> &zip32::AccountId {
&self.key_id.account_id
}
fn key_scope(&self) -> Option<Scope> {
Some(self.key_id.scope)
}
}
impl ScanningKeyOps<OrchardDomain, orchard::note::Nullifier>
for ScanningKey<IncomingViewingKey, FullViewingKey>
{
fn prepare(&self) -> orchard::keys::PreparedIncomingViewingKey {
orchard::keys::PreparedIncomingViewingKey::new(&self.ivk)
}
fn nf(
&self,
note: &orchard::note::Note,
_position: Position,
) -> Option<orchard::note::Nullifier> {
self.nk.as_ref().map(|key| note.nullifier(key))
}
fn account_id(&self) -> &zip32::AccountId {
&self.key_id.account_id
}
fn key_scope(&self) -> Option<Scope> {
Some(self.key_id.scope)
}
}
pub(crate) struct ScanningKeys {
pub(crate) sapling: HashMap<KeyId, ScanningKey<SaplingIvk, NullifierDerivingKey>>,
pub(crate) orchard: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>>,
}
impl ScanningKeys {
pub(crate) fn from_account_ufvks(
ufvks: impl IntoIterator<Item = (zip32::AccountId, UnifiedFullViewingKey)>,
) -> Self {
#![allow(clippy::type_complexity)]
let mut sapling: HashMap<KeyId, ScanningKey<SaplingIvk, NullifierDerivingKey>> =
HashMap::new();
let mut orchard: HashMap<KeyId, ScanningKey<IncomingViewingKey, FullViewingKey>> =
HashMap::new();
for (account_id, ufvk) in ufvks {
if let Some(dfvk) = ufvk.sapling() {
for scope in [Scope::External, Scope::Internal] {
let key_id = KeyId::from_parts(account_id, scope);
sapling.insert(
key_id,
ScanningKey {
key_id,
ivk: dfvk.to_ivk(scope),
nk: Some(dfvk.to_nk(scope)),
},
);
}
}
if let Some(fvk) = ufvk.orchard() {
for scope in [Scope::External, Scope::Internal] {
let key_id = KeyId::from_parts(account_id, scope);
orchard.insert(
key_id,
ScanningKey {
key_id,
ivk: fvk.to_ivk(scope),
nk: Some(fvk.clone()),
},
);
}
}
}
Self { sapling, orchard }
}
}
pub(crate) fn encode_orchard_receiver(
parameters: &impl consensus::Parameters,
orchard_address: &orchard::Address,
) -> Result<String, ParseError> {
Ok(zcash_address::unified::Encoding::encode(
&<zcash_address::unified::Address as zcash_address::unified::Encoding>::try_from_items(
vec![zcash_address::unified::Receiver::Orchard(
orchard_address.to_raw_address_bytes(),
)],
)?,
¶meters.network_type(),
))
}
pub fn decode_unified_address(
consensus_parameters: &impl consensus::Parameters,
encoded_address: &str,
) -> std::io::Result<UnifiedAddress> {
if let zcash_keys::address::Address::Unified(unified_address) =
decode_address(consensus_parameters, encoded_address)?
{
Ok(unified_address)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"failed to decode unified address. incorrect address type.".to_string(),
))
}
}
pub fn decode_address(
consensus_parameters: &impl consensus::Parameters,
encoded_address: &str,
) -> std::io::Result<zcash_keys::address::Address> {
ZcashAddress::try_from_encoded(encoded_address)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("failed to decode address. {e}"),
)
})?
.convert_if_network::<zcash_keys::address::Address>(consensus_parameters.network_type())
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("failed to decode address. {e}"),
)
})
}