Skip to content

Commit

Permalink
Merge pull request #1267 from zcash/account-apis
Browse files Browse the repository at this point in the history
Account APIs needed for the mobile SDKs
  • Loading branch information
str4d authored Mar 13, 2024
2 parents 05259ef + b161472 commit 1c72b0b
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 244 deletions.
8 changes: 5 additions & 3 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this library adheres to Rust's notion of
- `Account`
- `AccountBalance::with_orchard_balance_mut`
- `AccountBirthday::orchard_frontier`
- `AccountKind`
- `BlockMetadata::orchard_tree_size`
- `DecryptedTransaction::{new, tx(), orchard_outputs()}`
- `ScannedBlock::orchard`
Expand Down Expand Up @@ -53,10 +54,11 @@ and this library adheres to Rust's notion of
- Arguments to `ScannedBlock::from_parts` have changed.
- Changes to the `WalletRead` trait:
- Added `Account` associated type.
- Added `get_account` method.
- Added `get_derived_account` method.
- `get_account_for_ufvk` now returns `Self::Account` instead of a bare
`AccountId`.
- Added `get_orchard_nullifiers` method.
- `get_account_for_ufvk` now returns an `Self::Account` instead of a bare
`AccountId`
- Added `get_seed_account` method.
- Changes to the `InputSource` trait:
- `select_spendable_notes` now takes its `target_value` argument as a
`NonNegativeAmount`. Also, the values of the returned map are also
Expand Down
45 changes: 43 additions & 2 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,12 +315,32 @@ impl AccountBalance {
}
}

/// The kinds of accounts supported by `zcash_client_backend`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AccountKind {
/// An account derived from a known seed.
Derived {
seed_fingerprint: HdSeedFingerprint,
account_index: zip32::AccountId,
},

/// An account imported from a viewing key.
Imported,
}

/// A set of capabilities that a client account must provide.
pub trait Account<AccountId: Copy> {
/// Returns the unique identifier for the account.
fn id(&self) -> AccountId;

/// Returns whether this account is derived or imported, and the derivation parameters
/// if applicable.
fn kind(&self) -> AccountKind;

/// Returns the UFVK that the wallet backend has stored for the account, if any.
///
/// Accounts for which this returns `None` cannot be used in wallet contexts, because
/// they are unable to maintain an accurate balance.
fn ufvk(&self) -> Option<&UnifiedFullViewingKey>;
}

Expand All @@ -329,6 +349,10 @@ impl<A: Copy> Account<A> for (A, UnifiedFullViewingKey) {
self.0
}

fn kind(&self) -> AccountKind {
AccountKind::Imported
}

fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
Some(&self.1)
}
Expand All @@ -339,6 +363,10 @@ impl<A: Copy> Account<A> for (A, Option<UnifiedFullViewingKey>) {
self.0
}

fn kind(&self) -> AccountKind {
AccountKind::Imported
}

fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
self.1.as_ref()
}
Expand Down Expand Up @@ -546,9 +574,15 @@ pub trait WalletRead {
/// Returns a vector with the IDs of all accounts known to this wallet.
fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error>;

/// Returns the account corresponding to the given ID, if any.
fn get_account(
&self,
account_id: Self::AccountId,
) -> Result<Option<Self::Account>, Self::Error>;

/// Returns the account corresponding to a given [`HdSeedFingerprint`] and
/// [`zip32::AccountId`], if any.
fn get_seed_account(
fn get_derived_account(
&self,
seed: &HdSeedFingerprint,
account_id: zip32::AccountId,
Expand Down Expand Up @@ -1525,7 +1559,14 @@ pub mod testing {
Ok(Vec::new())
}

fn get_seed_account(
fn get_account(
&self,
_account_id: Self::AccountId,
) -> Result<Option<Self::Account>, Self::Error> {
Ok(None)
}

fn get_derived_account(
&self,
_seed: &HdSeedFingerprint,
_account_id: zip32::AccountId,
Expand Down
3 changes: 2 additions & 1 deletion zcash_client_sqlite/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ and this library adheres to Rust's notion of
### Added
- A new `orchard` feature flag has been added to make it possible to
build client code without `orchard` dependendencies.
- `zcash_client_sqlite::AccountId`
- `zcash_client_sqlite::AccountId`
- `zcash_client_sqlite::wallet::Account`
- `impl From<zcash_keys::keys::AddressGenerationError> for SqliteClientError`

### Changed
Expand Down
54 changes: 36 additions & 18 deletions zcash_client_sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ use zcash_client_backend::{
self,
chain::{BlockSource, ChainState, CommitmentTreeRoot},
scanning::{ScanPriority, ScanRange},
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead, WalletSummary,
WalletWrite, SAPLING_SHARD_HEIGHT,
Account, AccountBirthday, AccountKind, BlockMetadata, DecryptedTransaction, InputSource,
NullifierQuery, ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead,
WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
},
keys::{
AddressGenerationError, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey,
Expand Down Expand Up @@ -100,7 +100,7 @@ pub mod error;
pub mod wallet;
use wallet::{
commitment_tree::{self, put_shard_roots},
Account, HdSeedAccount, SubtreeScanProgress,
SubtreeScanProgress,
};

#[cfg(test)]
Expand Down Expand Up @@ -287,36 +287,46 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> InputSource for
impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for WalletDb<C, P> {
type Error = SqliteClientError;
type AccountId = AccountId;
type Account = (AccountId, Option<UnifiedFullViewingKey>);
type Account = wallet::Account;

fn get_account_ids(&self) -> Result<Vec<AccountId>, Self::Error> {
wallet::get_account_ids(self.conn.borrow())
}

fn get_seed_account(
fn get_account(
&self,
account_id: Self::AccountId,
) -> Result<Option<Self::Account>, Self::Error> {
wallet::get_account(self.conn.borrow(), &self.params, account_id)
}

fn get_derived_account(
&self,
seed: &HdSeedFingerprint,
account_id: zip32::AccountId,
) -> Result<Option<Self::Account>, Self::Error> {
wallet::get_seed_account(self.conn.borrow(), &self.params, seed, account_id)
wallet::get_derived_account(self.conn.borrow(), &self.params, seed, account_id)
}

fn validate_seed(
&self,
account_id: Self::AccountId,
seed: &SecretVec<u8>,
) -> Result<bool, Self::Error> {
if let Some(account) = wallet::get_account(self, account_id)? {
if let Account::Zip32(hdaccount) = account {
let seed_fingerprint_match =
HdSeedFingerprint::from_seed(seed) == *hdaccount.hd_seed_fingerprint();
if let Some(account) = self.get_account(account_id)? {
if let AccountKind::Derived {
seed_fingerprint,
account_index,
} = account.kind()
{
let seed_fingerprint_match = HdSeedFingerprint::from_seed(seed) == seed_fingerprint;

let usk = UnifiedSpendingKey::from_seed(
&self.params,
&seed.expose_secret()[..],
hdaccount.account_index(),
account_index,
)
.map_err(|_| SqliteClientError::KeyDerivationError(hdaccount.account_index()))?;
.map_err(|_| SqliteClientError::KeyDerivationError(account_index))?;

// Keys are not comparable with `Eq`, but addresses are, so we derive what should
// be equivalent addresses for each key and use those to check for key equality.
Expand All @@ -326,7 +336,7 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
Ok(usk
.to_unified_full_viewing_key()
.default_address(ua_request)?
== hdaccount.ufvk().default_address(ua_request)?)
== account.default_address(ua_request)?)
},
)?;

Expand Down Expand Up @@ -490,8 +500,8 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
birthday: AccountBirthday,
) -> Result<(AccountId, UnifiedSpendingKey), Self::Error> {
self.transactionally(|wdb| {
let seed_id = HdSeedFingerprint::from_seed(seed);
let account_index = wallet::max_zip32_account_index(wdb.conn.0, &seed_id)?
let seed_fingerprint = HdSeedFingerprint::from_seed(seed);
let account_index = wallet::max_zip32_account_index(wdb.conn.0, &seed_fingerprint)?
.map(|a| a.next().ok_or(SqliteClientError::AccountIdOutOfRange))
.transpose()?
.unwrap_or(zip32::AccountId::ZERO);
Expand All @@ -501,8 +511,16 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
.map_err(|_| SqliteClientError::KeyDerivationError(account_index))?;
let ufvk = usk.to_unified_full_viewing_key();

let account = Account::Zip32(HdSeedAccount::new(seed_id, account_index, ufvk));
let account_id = wallet::add_account(wdb.conn.0, &wdb.params, account, birthday)?;
let account_id = wallet::add_account(
wdb.conn.0,
&wdb.params,
AccountKind::Derived {
seed_fingerprint,
account_index,
},
wallet::ViewingKey::Full(Box::new(ufvk)),
birthday,
)?;

Ok((account_id, usk))
})
Expand Down
Loading

0 comments on commit 1c72b0b

Please sign in to comment.