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 38f9841 commit 85dd964
Show file tree
Hide file tree
Showing 16 changed files with 1,113 additions and 290 deletions.
35 changes: 35 additions & 0 deletions components/zcash_address/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,41 @@ impl ZcashAddress {
_ => false,
}
}

/// Returns a copy of the address without source restriction. Currently, this means
/// to convert a TEX address to the underlying P2PKH address, and leave other address
/// types unchanged.
///
/// The caller is responsible for complying with any requirements of ZIP 320 in its
/// use of the underlying address; in particular, funds should not be sent to it from
/// shielded sources.
pub fn without_source_restriction(&self) -> Self {
match &self.kind {
AddressKind::Tex(data) => ZcashAddress {
net: self.net,
kind: AddressKind::P2pkh(*data),
},
_ => self.clone(),
}
}
}

#[cfg(test)]
mod tests {
use crate::ZcashAddress;

#[test]
fn without_source_restriction() {
// from https://zips.z.cash/zip-0320#reference-implementation
let tex_addr: ZcashAddress = "tex1s2rt77ggv6q989lr49rkgzmh5slsksa9khdgte"
.parse()
.expect("valid address");
let p2pkh_addr: ZcashAddress = "t1VmmGiyjVNeCjxDZzg7vZmd99WyzVby9yC"
.parse()
.expect("valid address");
assert_eq!(tex_addr.without_source_restriction(), p2pkh_addr);
assert_eq!(p2pkh_addr.without_source_restriction(), p2pkh_addr);
}
}

#[cfg(feature = "test-dependencies")]
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,

Check warning on line 1634 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L1631-L1634

Added lines #L1631 - L1634 were not covered by tests
"The account id could not be found."
),
AddressTrackingError::ReachedGapLimit => write!(
f,

Check warning on line 1638 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L1637-L1638

Added lines #L1637 - L1638 were not covered by tests
"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

Check warning on line 1643 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L1641-L1643

Added lines #L1641 - L1643 were not covered by tests
),
}
}
}

/// 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(

Check warning on line 2030 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2030

Added line #L2030 was not covered by tests
&self,
_account_id: Self::AccountId,
) -> Result<TransparentAddress, AddressTrackingError> {
Err(AddressTrackingError::ReachedGapLimit)

Check warning on line 2034 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2034

Added line #L2034 was not covered by tests
}

fn unreserve_addresses(

Check warning on line 2037 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2037

Added line #L2037 was not covered by tests
&self,
_account_id: Self::AccountId,
_addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError> {
Ok(())

Check warning on line 2042 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2042

Added line #L2042 was not covered by tests
}

fn mark_addresses_as_used(

Check warning on line 2045 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2045

Added line #L2045 was not covered by tests
&self,
_account_id: Self::AccountId,
_addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError> {
Ok(())

Check warning on line 2050 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2050

Added line #L2050 was not covered by tests
}

fn mark_addresses_as_mined(

Check warning on line 2053 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2053

Added line #L2053 was not covered by tests
&self,
_account_id: Self::AccountId,
_addresses: &[TransparentAddress],
) -> Result<(), AddressTrackingError> {
Ok(())

Check warning on line 2058 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L2058

Added line #L2058 was not covered by tests
}
}

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)

Check warning on line 165 in zcash_client_backend/src/data_api/error.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/error.rs#L164-L165

Added lines #L164 - L165 were not covered by tests
}
}
}
}
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),

Check warning on line 223 in zcash_client_backend/src/data_api/error.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/error.rs#L223

Added line #L223 was not covered by tests
}
}
}
Expand Down
Loading

0 comments on commit 85dd964

Please sign in to comment.