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 18, 2024
1 parent 34b5a8a commit bf9d396
Show file tree
Hide file tree
Showing 14 changed files with 737 additions and 210 deletions.
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
156 changes: 139 additions & 17 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,6 +87,7 @@ use crate::{
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
legacy::TransparentAddress,
memo::{Memo, MemoBytes},
transaction::{
components::amount::{BalanceError, NonNegativeAmount},
Expand All @@ -95,8 +97,7 @@ use zcash_primitives::{

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

#[cfg(feature = "test-dependencies")]
Expand Down Expand Up @@ -1514,8 +1515,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,17 +1612,104 @@ pub trait WalletCommitmentTrees {
) -> Result<(), ShardTreeError<Self::Error>>;
}

/// An error related to tracking of ephemeral transparent addresses.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AddressTrackingError {
/// Only ZIP 32 accounts are supported for ZIP 320 (TEX Addresses).
UnsupportedAccountType,

/// 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::UnsupportedAccountType => write!(
f,
"Only ZIP 32 accounts are supported for ZIP 320 (TEX Addresses)."
),
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 {
/// 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,
uivk: &UnifiedIncomingViewingKey,
) -> 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 last reserved address from
/// the given account.
///
/// Returns an error if the account identifier does not correspond to a known
/// account.
fn unreserve_addresses(
&self,
address: &[TransparentAddress],
) -> Result<(), AddressTrackingError>;

/// Mark addresses as having been used.
fn mark_addresses_as_used(
&self,
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,
addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError>;
}

#[cfg(feature = "test-dependencies")]
pub mod testing {
use incrementalmerkletree::Address;
use secrecy::{ExposeSecret, SecretVec};
use shardtree::{error::ShardTreeError, store::memory::MemoryShardStore, ShardTree};
use std::{collections::HashMap, convert::Infallible, num::NonZeroU32};
use zcash_keys::keys::UnifiedIncomingViewingKey;
use zip32::fingerprint::SeedFingerprint;

use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network},
legacy::TransparentAddress,
memo::Memo,
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
};
Expand All @@ -1633,13 +1724,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 +2018,36 @@ pub mod testing {
}
}

impl WalletAddressTracking for MockWalletDb {
fn reserve_next_address(
&self,
_uivk: &UnifiedIncomingViewingKey,
) -> Result<TransparentAddress, AddressTrackingError> {
Err(AddressTrackingError::ReachedGapLimit)
}

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

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

fn mark_addresses_as_mined(
&self,
_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 @@ -20,6 +20,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 @@ -83,6 +85,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 @@ -149,6 +154,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 @@ -198,6 +206,7 @@ impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE>
required,
},
InputSelectorError::SyncRequired => Error::ScanRequired,
InputSelectorError::AddressTracking(e) => Error::AddressTracking(e),
}
}
}
Expand Down
Loading

0 comments on commit bf9d396

Please sign in to comment.