Skip to main content

pepper_sync/keys/
transparent.rs

1//! Transparent keys and addresses
2
3use zcash_address::{ToAddress as _, ZcashAddress};
4use zcash_protocol::consensus;
5use zcash_transparent::address::TransparentAddress;
6use zcash_transparent::keys::{NonHardenedChildIndex, TransparentKeyScope};
7use zip32::AccountId;
8
9#[cfg(not(feature = "darkside_test"))]
10use zcash_transparent::keys::{AccountPubKey, IncomingViewingKey as _};
11
12use crate::wallet::KeyIdInterface;
13
14/// Unique ID for transparent addresses.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16pub struct TransparentAddressId {
17    account_id: AccountId,
18    scope: TransparentScope,
19    address_index: NonHardenedChildIndex,
20}
21
22impl TransparentAddressId {
23    /// Construct from parts
24    #[must_use]
25    pub fn new(
26        account_id: zip32::AccountId,
27        scope: TransparentScope,
28        address_index: NonHardenedChildIndex,
29    ) -> Self {
30        Self {
31            account_id,
32            scope,
33            address_index,
34        }
35    }
36
37    /// Gets address index
38    #[must_use]
39    pub fn address_index(&self) -> NonHardenedChildIndex {
40        self.address_index
41    }
42
43    /// Gets address scope
44    #[must_use]
45    pub fn scope(&self) -> TransparentScope {
46        self.scope
47    }
48}
49
50impl KeyIdInterface for TransparentAddressId {
51    fn account_id(&self) -> AccountId {
52        self.account_id
53    }
54}
55
56/// Child index for the `change` path level in the BIP44 hierarchy (a.k.a. scope/chain).
57#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
58pub enum TransparentScope {
59    /// External scope
60    External,
61    /// Internal scope (a.k.a. change)
62    Internal,
63    /// Refund scope (a.k.a. ephemeral)
64    Refund,
65}
66
67impl std::fmt::Display for TransparentScope {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(
70            f,
71            "{}",
72            match self {
73                TransparentScope::External => "external",
74                TransparentScope::Internal => "internal",
75                TransparentScope::Refund => "refund",
76            }
77        )
78    }
79}
80
81impl From<TransparentScope> for TransparentKeyScope {
82    fn from(value: TransparentScope) -> Self {
83        match value {
84            TransparentScope::External => TransparentKeyScope::EXTERNAL,
85            TransparentScope::Internal => TransparentKeyScope::INTERNAL,
86            TransparentScope::Refund => TransparentKeyScope::EPHEMERAL,
87        }
88    }
89}
90
91impl TryFrom<u8> for TransparentScope {
92    type Error = std::io::Error;
93
94    fn try_from(value: u8) -> std::io::Result<Self> {
95        match value {
96            0 => Ok(TransparentScope::External),
97            1 => Ok(TransparentScope::Internal),
98            2 => Ok(TransparentScope::Refund),
99            _ => Err(std::io::Error::new(
100                std::io::ErrorKind::InvalidData,
101                "invalid scope value",
102            )),
103        }
104    }
105}
106
107#[cfg(not(feature = "darkside_test"))]
108pub(crate) fn derive_address(
109    consensus_parameters: &impl consensus::Parameters,
110    account_pubkey: &AccountPubKey,
111    address_id: TransparentAddressId,
112) -> Result<String, bip32::Error> {
113    let address = match address_id.scope() {
114        TransparentScope::External => {
115            derive_external_address(account_pubkey, address_id.address_index())?
116        }
117        TransparentScope::Internal => {
118            derive_internal_address(account_pubkey, address_id.address_index())?
119        }
120        TransparentScope::Refund => {
121            derive_refund_address(account_pubkey, address_id.address_index())?
122        }
123    };
124
125    Ok(encode_address(consensus_parameters, address))
126}
127
128#[cfg(not(feature = "darkside_test"))]
129fn derive_external_address(
130    account_pubkey: &AccountPubKey,
131    address_index: NonHardenedChildIndex,
132) -> Result<TransparentAddress, bip32::Error> {
133    account_pubkey
134        .derive_external_ivk()?
135        .derive_address(address_index)
136}
137
138#[cfg(not(feature = "darkside_test"))]
139fn derive_internal_address(
140    account_pubkey: &AccountPubKey,
141    address_index: NonHardenedChildIndex,
142) -> Result<TransparentAddress, bip32::Error> {
143    account_pubkey
144        .derive_internal_ivk()?
145        .derive_address(address_index)
146}
147
148#[cfg(not(feature = "darkside_test"))]
149fn derive_refund_address(
150    account_pubkey: &AccountPubKey,
151    address_index: NonHardenedChildIndex,
152) -> Result<TransparentAddress, bip32::Error> {
153    account_pubkey
154        .derive_ephemeral_ivk()?
155        .derive_ephemeral_address(address_index)
156}
157
158/// Encodes transparent address
159pub fn encode_address(
160    consensus_parameters: &impl consensus::Parameters,
161    address: TransparentAddress,
162) -> String {
163    let zcash_address = match address {
164        TransparentAddress::PublicKeyHash(data) => {
165            ZcashAddress::from_transparent_p2pkh(consensus_parameters.network_type(), data)
166        }
167        TransparentAddress::ScriptHash(data) => {
168            ZcashAddress::from_transparent_p2sh(consensus_parameters.network_type(), data)
169        }
170    };
171    zcash_address.to_string()
172}