Skip to content

Commit

Permalink
Work-in-progress implementation of ZIP 320.
Browse files Browse the repository at this point in the history
Co-authored-by: Kris Nuttycombe <[email protected]>
Co-authored-by: Jack Grigg <[email protected]>
Signed-off-by: Daira-Emma Hopwood <[email protected]>
  • Loading branch information
3 people committed May 30, 2024
1 parent e0e0064 commit 7c8e775
Show file tree
Hide file tree
Showing 16 changed files with 974 additions and 278 deletions.
29 changes: 27 additions & 2 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,31 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Notable changes
`zcash_client_backend` now supports TEX (transparent-source-only) addresses as specified
in ZIP 320. Sending to one or more TEX addresses will automatically create a multi-step
proposal that uses two transactions. This is intended to be used in conjunction with
`zcash_client_sqlite` 0.11 or later.

In order to take advantage of this support, client wallets will need to be able to send
multiple transactions created from `zcash_client_backend::data_api::wallet::create_proposed_transactions`.
This API was added in `zcash_client_backend` 0.11.0 but previously could only return a
single transaction.

**Note:** This feature changes the use of transparent addresses in ways that are relevant
to security and access to funds, and that may interact with other wallet behaviour. In
particular it exposes new ephemeral transparent addresses belonging to the wallet, which
need to be scanned in order to recover funds if the first transaction of the proposal is
mined but the second is not, or if someone (e.g. the TEX-address recipient) sends back
funds to those addresses. See [ZIP 320](https://zips.z.cash/zip-0320) for details.

### Added
- `zcash_client_backend::data_api`:
- `chain::BlockCache` trait, behind the `sync` feature flag.
- `AddressTrackingError`
- `impl From<AddressTrackingError> for wallet::input_selection::InputSelectorError`
- `WalletAddressTracking` trait, implemented for types that implement the other
`Wallet*` traits.
- `zcash_client_backend::scanning`:
- `testing` module
- `zcash_client_backend::sync` module, behind the `sync` feature flag.
Expand All @@ -21,12 +42,15 @@ and this library adheres to Rust's notion of
CHANGELOG for details.
- `zcash_client_backend::data_api`:
- `error::Error` has a new `Address` variant.
- `wallet::input_selection::InputSelectorError` has a new `Address` variant.
- `wallet::input_selection::InputSelectorError` has new `Address` and
`AddressTracking` variants.
- `zcash_client_backend::proto::proposal::Proposal::{from_standard_proposal,
try_into_standard_proposal}` each no longer require a `consensus::Parameters`
argument.
- `zcash_client_backend::wallet`:
- `Recipient` variants have changed. Instead of wrapping protocol-address
- `Recipient` variants have changed. It has a new `EphemeralTransparent`
variant, and an extra generic parameter giving the type of metadata about
an ephemeral transparent outpoint. Instead of wrapping protocol-address
types, the `External` and `InternalAccount` variants now wrap a
`zcash_address::ZcashAddress`. This simplifies the process of tracking
the original address to which value was sent.
Expand Down Expand Up @@ -127,6 +151,7 @@ and this library adheres to Rust's notion of
- `fn with_orchard_tree_mut`
- `fn put_orchard_subtree_roots`
- Removed `Error::AccountNotFound` variant.
- Added `Error::AddressTracking` variant.
- `WalletSummary::new` now takes an additional `next_orchard_subtree_index`
argument when the `orchard` feature flag is enabled.
- `zcash_client_backend::decrypt`:
Expand Down
2 changes: 2 additions & 0 deletions zcash_client_backend/proto/proposal.proto
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ enum FeeRule {
// The proposed change outputs and fee value.
message TransactionBalance {
// A list of change output values.
// Any `ChangeValue`s for the transparent value pool represent ephemeral
// outputs that will each be given a unique t-address.
repeated ChangeValue proposedChange = 1;
// The fee to be paid by the proposed transaction, in zatoshis.
uint64 feeRequired = 2;
Expand Down
181 changes: 157 additions & 24 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,19 @@
//!
//! ## Core Traits
//!
//! The utility functions described above depend upon four important traits defined in this
//! The utility functions described above depend upon five important traits defined in this
//! module, which between them encompass the data storage requirements of a light wallet.
//! The relevant traits are [`InputSource`], [`WalletRead`], [`WalletWrite`], and
//! [`WalletCommitmentTrees`]. A complete implementation of the data storage layer for a wallet
//! will include an implementation of all four of these traits. See the [`zcash_client_sqlite`]
//! crate for a complete example of the implementation of these traits.
//! The relevant traits are [`InputSource`], [`WalletRead`], [`WalletWrite`],
//! [`WalletCommitmentTrees`], and [`WalletAddressTracking`]. A complete implementation of the
//! data storage layer for a wallet will include an implementation of all five of these traits.
//! See the [`zcash_client_sqlite`] crate for a complete example of the implementation of these
//! traits.
//!
//! ## Accounts
//!
//! The operation of the [`InputSource`], [`WalletRead`] and [`WalletWrite`] traits is built around
//! the concept of a wallet having one or more accounts, with a unique `AccountId` for each
//! account.
//! The operation of the [`InputSource`], [`WalletRead`], [`WalletWrite`], and
//! [`WalletAddressTracking`] traits is built around the concept of a wallet having one or more
//! accounts, with a unique `AccountId` for each account.
//!
//! An account identifier corresponds to at most a single [`UnifiedSpendingKey`]'s worth of spend
//! authority, with the received and spent notes of that account tracked via the corresponding
Expand All @@ -57,7 +58,7 @@
use std::{
collections::HashMap,
fmt::Debug,
fmt::{self, Debug, Display},
hash::Hash,
io,
num::{NonZeroU32, TryFromIntError},
Expand Down Expand Up @@ -86,18 +87,19 @@ use crate::{
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
legacy::TransparentAddress,
memo::{Memo, MemoBytes},
transaction::{
components::amount::{BalanceError, NonNegativeAmount},
components::{
amount::{BalanceError, NonNegativeAmount},
OutPoint,
},
Transaction, TxId,
},
};

#[cfg(feature = "transparent-inputs")]
use {
crate::wallet::TransparentAddressMetadata,
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
};
use crate::wallet::TransparentAddressMetadata;

#[cfg(any(test, feature = "test-dependencies"))]
use zcash_primitives::consensus::NetworkUpgrade;
Expand Down Expand Up @@ -1239,7 +1241,7 @@ impl<'a, AccountId> SentTransaction<'a, AccountId> {
/// This type is capable of representing both shielded and transparent outputs.
pub struct SentTransactionOutput<AccountId> {
output_index: usize,
recipient: Recipient<AccountId, Note>,
recipient: Recipient<AccountId, Note, OutPoint>,
value: NonNegativeAmount,
memo: Option<MemoBytes>,
}
Expand All @@ -1256,7 +1258,7 @@ impl<AccountId> SentTransactionOutput<AccountId> {
/// * `memo` - the memo that was sent with this output
pub fn from_parts(
output_index: usize,
recipient: Recipient<AccountId, Note>,
recipient: Recipient<AccountId, Note, OutPoint>,
value: NonNegativeAmount,
memo: Option<MemoBytes>,
) -> Self {
Expand All @@ -1278,8 +1280,8 @@ impl<AccountId> SentTransactionOutput<AccountId> {
self.output_index
}
/// Returns the recipient address of the transaction, or the account id and
/// resulting note for wallet-internal outputs.
pub fn recipient(&self) -> &Recipient<AccountId, Note> {
/// resulting note/outpoint for wallet-internal outputs.
pub fn recipient(&self) -> &Recipient<AccountId, Note, OutPoint> {
&self.recipient
}
/// Returns the value of the newly created output.
Expand Down Expand Up @@ -1514,8 +1516,11 @@ pub trait WalletWrite: WalletRead {
received_tx: DecryptedTransaction<Self::AccountId>,
) -> Result<(), Self::Error>;

/// Saves information about a transaction that was constructed and sent by the wallet to the
/// persistent wallet store.
/// Saves information about a transaction constructed by the wallet to the persistent
/// wallet store.
///
/// The name `store_sent_tx` is somewhat misleading; this must be called *before* the
/// transaction is sent to the network.
fn store_sent_tx(
&mut self,
sent_tx: &SentTransaction<Self::AccountId>,
Expand Down Expand Up @@ -1608,6 +1613,97 @@ pub trait WalletCommitmentTrees {
) -> Result<(), ShardTreeError<Self::Error>>;
}

/// An error related to tracking of ephemeral transparent addresses.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AddressTrackingError {
/// The account id could not be found.
AccountNotFound,

/// The proposal cannot be constructed until transactions with previously reserved
/// ephemeral address outputs have been mined.
ReachedGapLimit,

/// Internal error.
Internal(String),
}

impl Display for AddressTrackingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AddressTrackingError::AccountNotFound => write!(
f,
"The account id could not be found."
),
AddressTrackingError::ReachedGapLimit => write!(
f,
"The proposal cannot be constructed until transactions with previously reserved ephemeral address outputs have been mined."
),
AddressTrackingError::Internal(e) => write!(
f,
"Internal address tracking error: {}", e
),
}
}
}

/// Wallet operations required for tracking of ephemeral transparent addresses.
///
/// This trait serves to allow the corresponding wallet functions to be abstracted
/// away from any particular data storage substrate.
pub trait WalletAddressTracking {
/// The type of the account identifier.
type AccountId: Copy + Debug + Eq + Hash;

/// Reserves the next available address.
///
/// To ensure that sufficient information is stored on-chain to allow recovering
/// funds sent back to any of the used addresses, a "gap limit" of 20 addresses
/// should be observed as described in [BIP 44]. An implementation should record
/// the index of the first unmined address, and update it for addresses that have
/// been observed as outputs in mined transactions when `addresses_are_mined` is
/// called.
///
/// Returns an error if all addresses within the gap limit have already been
/// reserved.
///
/// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#user-content-Address_gap_limit
fn reserve_next_address(
&self,
account_id: Self::AccountId,
) -> Result<TransparentAddress, AddressTrackingError>;

/// Frees previously reserved ephemeral transparent addresses.
///
/// This should only be used in the case that an error occurs in transaction
/// construction after the address was reserved. It is sufficient for an
/// implementation to only be able to unreserve the addresses that were last
/// reserved in the given account.
///
/// Returns an error if the account identifier does not correspond to a known
/// account.
fn unreserve_addresses(
&self,
account_id: Self::AccountId,
address: &[TransparentAddress],
) -> Result<(), AddressTrackingError>;

/// Mark addresses as having been used.
fn mark_addresses_as_used(
&self,
account_id: Self::AccountId,
address: &[TransparentAddress],
) -> Result<(), AddressTrackingError>;

/// Checks the set of ephemeral transparent addresses within the gap limit for the
/// given mined t-addresses, in order to update the first unmined ephemeral t-address
/// index if necessary.
fn mark_addresses_as_mined(
&self,
account_id: Self::AccountId,
addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError>;
}

#[cfg(feature = "test-dependencies")]
pub mod testing {
use incrementalmerkletree::Address;
Expand All @@ -1619,6 +1715,7 @@ pub mod testing {
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network},
legacy::TransparentAddress,
memo::Memo,
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
};
Expand All @@ -1633,13 +1730,14 @@ pub mod testing {
use super::{
chain::{ChainState, CommitmentTreeRoot},
scanning::ScanRange,
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
ScannedBlock, SeedRelevance, SentTransaction, SpendableNotes, WalletCommitmentTrees,
WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
AccountBirthday, AddressTrackingError, BlockMetadata, DecryptedTransaction, InputSource,
NullifierQuery, ScannedBlock, SeedRelevance, SentTransaction, SpendableNotes,
WalletAddressTracking, WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite,
SAPLING_SHARD_HEIGHT,
};

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

#[cfg(feature = "orchard")]
use super::ORCHARD_SHARD_HEIGHT;
Expand Down Expand Up @@ -1926,6 +2024,41 @@ pub mod testing {
}
}

impl WalletAddressTracking for MockWalletDb {
type AccountId = u32;

fn reserve_next_address(
&self,
_account_id: Self::AccountId,
) -> Result<TransparentAddress, AddressTrackingError> {
Err(AddressTrackingError::ReachedGapLimit)
}

fn unreserve_addresses(
&self,
_account_id: Self::AccountId,
_addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError> {
Ok(())
}

fn mark_addresses_as_used(
&self,
_account_id: Self::AccountId,
_addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError> {
Ok(())
}

fn mark_addresses_as_mined(
&self,
_account_id: Self::AccountId,
_addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError> {
Ok(())
}
}

impl WalletCommitmentTrees for MockWalletDb {
type Error = Infallible;
type SaplingShardStore<'a> = MemoryShardStore<sapling::Node, BlockHeight>;
Expand Down
9 changes: 9 additions & 0 deletions zcash_client_backend/src/data_api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use zcash_primitives::legacy::TransparentAddress;

use crate::wallet::NoteId;

use super::AddressTrackingError;

/// Errors that can occur as a consequence of wallet operations.
#[derive(Debug)]
pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
Expand Down Expand Up @@ -87,6 +89,9 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {

#[cfg(feature = "transparent-inputs")]
AddressNotRecognized(TransparentAddress),

/// An error related to tracking of ephemeral transparent addresses.
AddressTracking(AddressTrackingError),
}

impl<DE, CE, SE, FE> fmt::Display for Error<DE, CE, SE, FE>
Expand Down Expand Up @@ -156,6 +161,9 @@ where
Error::AddressNotRecognized(_) => {
write!(f, "The specified transparent address was not recognized as belonging to the wallet.")
}
Error::AddressTracking(e) => {
write!(f, "Error related to tracking of ephemeral transparent addresses: {}", e)
}
}
}
}
Expand Down Expand Up @@ -212,6 +220,7 @@ impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE>
},
InputSelectorError::SyncRequired => Error::ScanRequired,
InputSelectorError::Address(e) => Error::Address(e),
InputSelectorError::AddressTracking(e) => Error::AddressTracking(e),
}
}
}
Expand Down
Loading

0 comments on commit 7c8e775

Please sign in to comment.