Skip to content

Commit 85dd964

Browse files
dairanuttycomJack Grigg
committed
Work-in-progress implementation of ZIP 320.
Co-authored-by: Kris Nuttycombe <[email protected]> Co-authored-by: Jack Grigg <[email protected]> Signed-off-by: Daira-Emma Hopwood <[email protected]>
1 parent 38f9841 commit 85dd964

File tree

16 files changed

+1113
-290
lines changed

16 files changed

+1113
-290
lines changed

components/zcash_address/src/lib.rs

+35
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,41 @@ impl ZcashAddress {
302302
_ => false,
303303
}
304304
}
305+
306+
/// Returns a copy of the address without source restriction. Currently, this means
307+
/// to convert a TEX address to the underlying P2PKH address, and leave other address
308+
/// types unchanged.
309+
///
310+
/// The caller is responsible for complying with any requirements of ZIP 320 in its
311+
/// use of the underlying address; in particular, funds should not be sent to it from
312+
/// shielded sources.
313+
pub fn without_source_restriction(&self) -> Self {
314+
match &self.kind {
315+
AddressKind::Tex(data) => ZcashAddress {
316+
net: self.net,
317+
kind: AddressKind::P2pkh(*data),
318+
},
319+
_ => self.clone(),
320+
}
321+
}
322+
}
323+
324+
#[cfg(test)]
325+
mod tests {
326+
use crate::ZcashAddress;
327+
328+
#[test]
329+
fn without_source_restriction() {
330+
// from https://zips.z.cash/zip-0320#reference-implementation
331+
let tex_addr: ZcashAddress = "tex1s2rt77ggv6q989lr49rkgzmh5slsksa9khdgte"
332+
.parse()
333+
.expect("valid address");
334+
let p2pkh_addr: ZcashAddress = "t1VmmGiyjVNeCjxDZzg7vZmd99WyzVby9yC"
335+
.parse()
336+
.expect("valid address");
337+
assert_eq!(tex_addr.without_source_restriction(), p2pkh_addr);
338+
assert_eq!(p2pkh_addr.without_source_restriction(), p2pkh_addr);
339+
}
305340
}
306341

307342
#[cfg(feature = "test-dependencies")]

zcash_client_backend/proto/proposal.proto

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ enum FeeRule {
113113
// The proposed change outputs and fee value.
114114
message TransactionBalance {
115115
// A list of change output values.
116+
// Any `ChangeValue`s for the transparent value pool represent ephemeral
117+
// outputs that will each be given a unique t-address.
116118
repeated ChangeValue proposedChange = 1;
117119
// The fee to be paid by the proposed transaction, in zatoshis.
118120
uint64 feeRequired = 2;

zcash_client_backend/src/data_api.rs

+157-24
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,19 @@
3030
//!
3131
//! ## Core Traits
3232
//!
33-
//! The utility functions described above depend upon four important traits defined in this
33+
//! The utility functions described above depend upon five important traits defined in this
3434
//! module, which between them encompass the data storage requirements of a light wallet.
35-
//! The relevant traits are [`InputSource`], [`WalletRead`], [`WalletWrite`], and
36-
//! [`WalletCommitmentTrees`]. A complete implementation of the data storage layer for a wallet
37-
//! will include an implementation of all four of these traits. See the [`zcash_client_sqlite`]
38-
//! crate for a complete example of the implementation of these traits.
35+
//! The relevant traits are [`InputSource`], [`WalletRead`], [`WalletWrite`],
36+
//! [`WalletCommitmentTrees`], and [`WalletAddressTracking`]. A complete implementation of the
37+
//! data storage layer for a wallet will include an implementation of all five of these traits.
38+
//! See the [`zcash_client_sqlite`] crate for a complete example of the implementation of these
39+
//! traits.
3940
//!
4041
//! ## Accounts
4142
//!
42-
//! The operation of the [`InputSource`], [`WalletRead`] and [`WalletWrite`] traits is built around
43-
//! the concept of a wallet having one or more accounts, with a unique `AccountId` for each
44-
//! account.
43+
//! The operation of the [`InputSource`], [`WalletRead`], [`WalletWrite`], and
44+
//! [`WalletAddressTracking`] traits is built around the concept of a wallet having one or more
45+
//! accounts, with a unique `AccountId` for each account.
4546
//!
4647
//! An account identifier corresponds to at most a single [`UnifiedSpendingKey`]'s worth of spend
4748
//! authority, with the received and spent notes of that account tracked via the corresponding
@@ -57,7 +58,7 @@
5758
5859
use std::{
5960
collections::HashMap,
60-
fmt::Debug,
61+
fmt::{self, Debug, Display},
6162
hash::Hash,
6263
io,
6364
num::{NonZeroU32, TryFromIntError},
@@ -86,18 +87,19 @@ use crate::{
8687
use zcash_primitives::{
8788
block::BlockHash,
8889
consensus::BlockHeight,
90+
legacy::TransparentAddress,
8991
memo::{Memo, MemoBytes},
9092
transaction::{
91-
components::amount::{BalanceError, NonNegativeAmount},
93+
components::{
94+
amount::{BalanceError, NonNegativeAmount},
95+
OutPoint,
96+
},
9297
Transaction, TxId,
9398
},
9499
};
95100

96101
#[cfg(feature = "transparent-inputs")]
97-
use {
98-
crate::wallet::TransparentAddressMetadata,
99-
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
100-
};
102+
use crate::wallet::TransparentAddressMetadata;
101103

102104
#[cfg(any(test, feature = "test-dependencies"))]
103105
use zcash_primitives::consensus::NetworkUpgrade;
@@ -1239,7 +1241,7 @@ impl<'a, AccountId> SentTransaction<'a, AccountId> {
12391241
/// This type is capable of representing both shielded and transparent outputs.
12401242
pub struct SentTransactionOutput<AccountId> {
12411243
output_index: usize,
1242-
recipient: Recipient<AccountId, Note>,
1244+
recipient: Recipient<AccountId, Note, OutPoint>,
12431245
value: NonNegativeAmount,
12441246
memo: Option<MemoBytes>,
12451247
}
@@ -1256,7 +1258,7 @@ impl<AccountId> SentTransactionOutput<AccountId> {
12561258
/// * `memo` - the memo that was sent with this output
12571259
pub fn from_parts(
12581260
output_index: usize,
1259-
recipient: Recipient<AccountId, Note>,
1261+
recipient: Recipient<AccountId, Note, OutPoint>,
12601262
value: NonNegativeAmount,
12611263
memo: Option<MemoBytes>,
12621264
) -> Self {
@@ -1278,8 +1280,8 @@ impl<AccountId> SentTransactionOutput<AccountId> {
12781280
self.output_index
12791281
}
12801282
/// Returns the recipient address of the transaction, or the account id and
1281-
/// resulting note for wallet-internal outputs.
1282-
pub fn recipient(&self) -> &Recipient<AccountId, Note> {
1283+
/// resulting note/outpoint for wallet-internal outputs.
1284+
pub fn recipient(&self) -> &Recipient<AccountId, Note, OutPoint> {
12831285
&self.recipient
12841286
}
12851287
/// Returns the value of the newly created output.
@@ -1514,8 +1516,11 @@ pub trait WalletWrite: WalletRead {
15141516
received_tx: DecryptedTransaction<Self::AccountId>,
15151517
) -> Result<(), Self::Error>;
15161518

1517-
/// Saves information about a transaction that was constructed and sent by the wallet to the
1518-
/// persistent wallet store.
1519+
/// Saves information about a transaction constructed by the wallet to the persistent
1520+
/// wallet store.
1521+
///
1522+
/// The name `store_sent_tx` is somewhat misleading; this must be called *before* the
1523+
/// transaction is sent to the network.
15191524
fn store_sent_tx(
15201525
&mut self,
15211526
sent_tx: &SentTransaction<Self::AccountId>,
@@ -1608,6 +1613,97 @@ pub trait WalletCommitmentTrees {
16081613
) -> Result<(), ShardTreeError<Self::Error>>;
16091614
}
16101615

1616+
/// An error related to tracking of ephemeral transparent addresses.
1617+
#[derive(Debug, Clone, PartialEq, Eq)]
1618+
pub enum AddressTrackingError {
1619+
/// The account id could not be found.
1620+
AccountNotFound,
1621+
1622+
/// The proposal cannot be constructed until transactions with previously reserved
1623+
/// ephemeral address outputs have been mined.
1624+
ReachedGapLimit,
1625+
1626+
/// Internal error.
1627+
Internal(String),
1628+
}
1629+
1630+
impl Display for AddressTrackingError {
1631+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1632+
match self {
1633+
AddressTrackingError::AccountNotFound => write!(
1634+
f,
1635+
"The account id could not be found."
1636+
),
1637+
AddressTrackingError::ReachedGapLimit => write!(
1638+
f,
1639+
"The proposal cannot be constructed until transactions with previously reserved ephemeral address outputs have been mined."
1640+
),
1641+
AddressTrackingError::Internal(e) => write!(
1642+
f,
1643+
"Internal address tracking error: {}", e
1644+
),
1645+
}
1646+
}
1647+
}
1648+
1649+
/// Wallet operations required for tracking of ephemeral transparent addresses.
1650+
///
1651+
/// This trait serves to allow the corresponding wallet functions to be abstracted
1652+
/// away from any particular data storage substrate.
1653+
pub trait WalletAddressTracking {
1654+
/// The type of the account identifier.
1655+
type AccountId: Copy + Debug + Eq + Hash;
1656+
1657+
/// Reserves the next available address.
1658+
///
1659+
/// To ensure that sufficient information is stored on-chain to allow recovering
1660+
/// funds sent back to any of the used addresses, a "gap limit" of 20 addresses
1661+
/// should be observed as described in [BIP 44]. An implementation should record
1662+
/// the index of the first unmined address, and update it for addresses that have
1663+
/// been observed as outputs in mined transactions when `addresses_are_mined` is
1664+
/// called.
1665+
///
1666+
/// Returns an error if all addresses within the gap limit have already been
1667+
/// reserved.
1668+
///
1669+
/// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#user-content-Address_gap_limit
1670+
fn reserve_next_address(
1671+
&self,
1672+
account_id: Self::AccountId,
1673+
) -> Result<TransparentAddress, AddressTrackingError>;
1674+
1675+
/// Frees previously reserved ephemeral transparent addresses.
1676+
///
1677+
/// This should only be used in the case that an error occurs in transaction
1678+
/// construction after the address was reserved. It is sufficient for an
1679+
/// implementation to only be able to unreserve the addresses that were last
1680+
/// reserved in the given account.
1681+
///
1682+
/// Returns an error if the account identifier does not correspond to a known
1683+
/// account.
1684+
fn unreserve_addresses(
1685+
&self,
1686+
account_id: Self::AccountId,
1687+
address: &[TransparentAddress],
1688+
) -> Result<(), AddressTrackingError>;
1689+
1690+
/// Mark addresses as having been used.
1691+
fn mark_addresses_as_used(
1692+
&self,
1693+
account_id: Self::AccountId,
1694+
address: &[TransparentAddress],
1695+
) -> Result<(), AddressTrackingError>;
1696+
1697+
/// Checks the set of ephemeral transparent addresses within the gap limit for the
1698+
/// given mined t-addresses, in order to update the first unmined ephemeral t-address
1699+
/// index if necessary.
1700+
fn mark_addresses_as_mined(
1701+
&self,
1702+
account_id: Self::AccountId,
1703+
addresses: &[TransparentAddress],
1704+
) -> Result<(), AddressTrackingError>;
1705+
}
1706+
16111707
#[cfg(feature = "test-dependencies")]
16121708
pub mod testing {
16131709
use incrementalmerkletree::Address;
@@ -1619,6 +1715,7 @@ pub mod testing {
16191715
use zcash_primitives::{
16201716
block::BlockHash,
16211717
consensus::{BlockHeight, Network},
1718+
legacy::TransparentAddress,
16221719
memo::Memo,
16231720
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
16241721
};
@@ -1633,13 +1730,14 @@ pub mod testing {
16331730
use super::{
16341731
chain::{ChainState, CommitmentTreeRoot},
16351732
scanning::ScanRange,
1636-
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
1637-
ScannedBlock, SeedRelevance, SentTransaction, SpendableNotes, WalletCommitmentTrees,
1638-
WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
1733+
AccountBirthday, AddressTrackingError, BlockMetadata, DecryptedTransaction, InputSource,
1734+
NullifierQuery, ScannedBlock, SeedRelevance, SentTransaction, SpendableNotes,
1735+
WalletAddressTracking, WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite,
1736+
SAPLING_SHARD_HEIGHT,
16391737
};
16401738

16411739
#[cfg(feature = "transparent-inputs")]
1642-
use {crate::wallet::TransparentAddressMetadata, zcash_primitives::legacy::TransparentAddress};
1740+
use crate::wallet::TransparentAddressMetadata;
16431741

16441742
#[cfg(feature = "orchard")]
16451743
use super::ORCHARD_SHARD_HEIGHT;
@@ -1926,6 +2024,41 @@ pub mod testing {
19262024
}
19272025
}
19282026

2027+
impl WalletAddressTracking for MockWalletDb {
2028+
type AccountId = u32;
2029+
2030+
fn reserve_next_address(
2031+
&self,
2032+
_account_id: Self::AccountId,
2033+
) -> Result<TransparentAddress, AddressTrackingError> {
2034+
Err(AddressTrackingError::ReachedGapLimit)
2035+
}
2036+
2037+
fn unreserve_addresses(
2038+
&self,
2039+
_account_id: Self::AccountId,
2040+
_addresses: &[TransparentAddress],
2041+
) -> Result<(), AddressTrackingError> {
2042+
Ok(())
2043+
}
2044+
2045+
fn mark_addresses_as_used(
2046+
&self,
2047+
_account_id: Self::AccountId,
2048+
_addresses: &[TransparentAddress],
2049+
) -> Result<(), AddressTrackingError> {
2050+
Ok(())
2051+
}
2052+
2053+
fn mark_addresses_as_mined(
2054+
&self,
2055+
_account_id: Self::AccountId,
2056+
_addresses: &[TransparentAddress],
2057+
) -> Result<(), AddressTrackingError> {
2058+
Ok(())
2059+
}
2060+
}
2061+
19292062
impl WalletCommitmentTrees for MockWalletDb {
19302063
type Error = Infallible;
19312064
type SaplingShardStore<'a> = MemoryShardStore<sapling::Node, BlockHeight>;

zcash_client_backend/src/data_api/error.rs

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use zcash_primitives::legacy::TransparentAddress;
2121

2222
use crate::wallet::NoteId;
2323

24+
use super::AddressTrackingError;
25+
2426
/// Errors that can occur as a consequence of wallet operations.
2527
#[derive(Debug)]
2628
pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
@@ -87,6 +89,9 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
8789

8890
#[cfg(feature = "transparent-inputs")]
8991
AddressNotRecognized(TransparentAddress),
92+
93+
/// An error related to tracking of ephemeral transparent addresses.
94+
AddressTracking(AddressTrackingError),
9095
}
9196

9297
impl<DE, CE, SE, FE> fmt::Display for Error<DE, CE, SE, FE>
@@ -156,6 +161,9 @@ where
156161
Error::AddressNotRecognized(_) => {
157162
write!(f, "The specified transparent address was not recognized as belonging to the wallet.")
158163
}
164+
Error::AddressTracking(e) => {
165+
write!(f, "Error related to tracking of ephemeral transparent addresses: {}", e)
166+
}
159167
}
160168
}
161169
}
@@ -212,6 +220,7 @@ impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE>
212220
},
213221
InputSelectorError::SyncRequired => Error::ScanRequired,
214222
InputSelectorError::Address(e) => Error::Address(e),
223+
InputSelectorError::AddressTracking(e) => Error::AddressTracking(e),
215224
}
216225
}
217226
}

0 commit comments

Comments
 (0)