zcash_client_sqlite: Store UFVK/UA instead of Sapling ExtFVK/address

This is a breaking change to the database format. We don't have support
for migrations yet, so existing wallets won't work after this commit
until zcash/librustzcash#489 is done.
This commit is contained in:
Jack Grigg 2022-06-13 17:54:32 +00:00
parent e86ba927af
commit 0d0527dbf3
6 changed files with 66 additions and 56 deletions

View File

@ -108,6 +108,11 @@ impl UnifiedAddress {
self.sapling.as_ref()
}
/// Returns the transparent receiver within this Unified Address, if any.
pub fn transparent(&self) -> Option<&TransparentAddress> {
self.transparent.as_ref()
}
fn to_address(&self, net: Network) -> ZcashAddress {
let ua = unified::Address::try_from_items(
self.unknown
@ -137,6 +142,11 @@ impl UnifiedAddress {
.expect("UnifiedAddress should only be constructed safely");
ZcashAddress::from_unified(net, ua)
}
/// Returns the string encoding of this `UnifiedAddress` for the given network.
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
self.to_address(params_to_network(params)).to_string()
}
}
/// An address that funds can be sent to.

View File

@ -115,6 +115,7 @@ pub trait WalletRead {
///
/// This will return `Ok(None)` if the account identifier does not correspond
/// to a known account.
// TODO: This does not appear to be the case.
fn get_address(&self, account: AccountId) -> Result<Option<PaymentAddress>, Self::Error>;
/// Returns all extended full viewing keys known about by this wallet.

View File

@ -18,6 +18,16 @@ and this library adheres to Rust's notion of
rewinds exceed supported bounds.
### Changed
- Various **BREAKING CHANGES** have been made to the database tables. These will
require migrations, which may need to be performed in multiple steps.
- The `extfvk` column in the `accounts` table has been replaced by a `ufvk`
column. Values for this column should be derived from the wallet's seed and
the account number; the Sapling component of the resulting Unified Full
Viewing Key should match the old value in the `extfvk` column.
- A new non-null column, `output_pool` has been added to the `sent_notes`
table to enable distinguishing between Sapling and transparent outputs
(and in the future, outputs to other pools). Values for this column should
be assigned by inference from the address type in the stored data.
- MSRV is now 1.56.1.
- Bumped dependencies to `ff 0.12`, `group 0.12`, `jubjub 0.9`.
- Renamed the following to use lower-case abbreviations (matching Rust
@ -38,11 +48,6 @@ and this library adheres to Rust's notion of
method to be used in contexts where a transaction has just been
constructed, rather than only in the case that a transaction has
been decrypted after being retrieved from the network.
- A new non-null column, `output_pool` has been added to the `sent_notes`
table to enable distinguishing between Sapling and transparent outputs
(and in the future, outputs to other pools). This will require a migration,
which may need to be performed in multiple steps. Values for this column
should be assigned by inference from the address type in the stored data.
### Deprecated
- A number of public API methods that are used internally to support the

View File

@ -53,7 +53,6 @@ use zcash_client_backend::{
data_api::{
BlockSource, DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite,
},
encoding::encode_payment_address,
proto::compact_formats::CompactBlock,
wallet::SpendableNote,
};
@ -721,14 +720,6 @@ impl BlockSource for BlockDb {
}
}
fn address_from_extfvk<P: consensus::Parameters>(
params: &P,
extfvk: &ExtendedFullViewingKey,
) -> String {
let addr = extfvk.default_address().1;
encode_payment_address(params.hrp_sapling_payment_address(), &addr)
}
#[cfg(test)]
mod tests {
use ff::PrimeField;

View File

@ -23,11 +23,10 @@ use zcash_primitives::{
};
use zcash_client_backend::{
address::RecipientAddress,
data_api::error::Error,
encoding::{
decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key,
encode_payment_address_p, encode_transparent_address_p,
},
encoding::{encode_payment_address_p, encode_transparent_address_p},
keys::UnifiedFullViewingKey,
wallet::{WalletShieldedOutput, WalletTx},
DecryptedOutput,
};
@ -162,8 +161,15 @@ pub fn get_address<P: consensus::Parameters>(
|row| row.get(0),
)?;
decode_payment_address(wdb.params.hrp_sapling_payment_address(), &addr)
.map_err(SqliteClientError::Bech32)
RecipientAddress::decode(&wdb.params, &addr)
.ok_or_else(|| {
SqliteClientError::CorruptedData("Not a valid Zcash recipient address".to_owned())
})
.map(|addr| match addr {
// TODO: Return the UA, not its Sapling component.
RecipientAddress::Unified(ua) => ua.sapling().cloned(),
_ => None,
})
}
/// Returns the [`ExtendedFullViewingKey`]s for the wallet.
@ -178,21 +184,20 @@ pub fn get_extended_full_viewing_keys<P: consensus::Parameters>(
// Fetch the ExtendedFullViewingKeys we are tracking
let mut stmt_fetch_accounts = wdb
.conn
.prepare("SELECT account, extfvk FROM accounts ORDER BY account ASC")?;
.prepare("SELECT account, ufvk FROM accounts ORDER BY account ASC")?;
let rows = stmt_fetch_accounts
.query_map(NO_PARAMS, |row| {
let acct: u32 = row.get(0)?;
let extfvk = row.get(1).map(|extfvk: String| {
decode_extended_full_viewing_key(
wdb.params.hrp_sapling_extended_full_viewing_key(),
&extfvk,
)
.map_err(SqliteClientError::Bech32)
.and_then(|k| k.ok_or(SqliteClientError::IncorrectHrpExtFvk))
})?;
let account = AccountId::from(acct);
let ufvk_str: String = row.get(1)?;
let ufvk = UnifiedFullViewingKey::decode(&wdb.params, &ufvk_str, account)
.map_err(SqliteClientError::CorruptedData);
// TODO: Return the UFVK, not its Sapling component.
let extfvk =
ufvk.map(|ufvk| ufvk.sapling().cloned().expect("TODO: Add Orchard support"));
Ok((AccountId::from(acct), extfvk))
Ok((account, extfvk))
})
.map_err(SqliteClientError::from)?;
@ -218,16 +223,22 @@ pub fn is_valid_account_extfvk<P: consensus::Parameters>(
extfvk: &ExtendedFullViewingKey,
) -> Result<bool, SqliteClientError> {
wdb.conn
.prepare("SELECT * FROM accounts WHERE account = ? AND extfvk = ?")?
.exists(&[
u32::from(account).to_sql()?,
encode_extended_full_viewing_key(
wdb.params.hrp_sapling_extended_full_viewing_key(),
extfvk,
)
.to_sql()?,
])
.prepare("SELECT ufvk FROM accounts WHERE account = ?")?
.query_row(&[u32::from(account).to_sql()?], |row| {
row.get(0).map(|ufvk_str: String| {
UnifiedFullViewingKey::decode(&wdb.params, &ufvk_str, account)
.map_err(SqliteClientError::CorruptedData)
})
})
.optional()
.map_err(SqliteClientError::from)
.and_then(|row| {
if let Some(ufvk) = row {
ufvk.map(|ufvk| ufvk.sapling() == Some(extfvk))
} else {
Ok(false)
}
})
}
/// Returns the balance for the account, including all mined unspent notes that we know

View File

@ -7,11 +7,9 @@ use zcash_primitives::{
consensus::{self, BlockHeight},
};
use zcash_client_backend::{
encoding::encode_extended_full_viewing_key, keys::UnifiedFullViewingKey,
};
use zcash_client_backend::keys::UnifiedFullViewingKey;
use crate::{address_from_extfvk, error::SqliteClientError, WalletDb};
use crate::{error::SqliteClientError, WalletDb};
#[cfg(feature = "transparent-inputs")]
use {
@ -44,10 +42,12 @@ use {
/// init_wallet_db(&db).unwrap();
/// ```
pub fn init_wallet_db<P>(wdb: &WalletDb<P>) -> Result<(), rusqlite::Error> {
// TODO: Add migrations (https://github.com/zcash/librustzcash/issues/489)
// - extfvk column -> ufvk column
wdb.conn.execute(
"CREATE TABLE IF NOT EXISTS accounts (
account INTEGER PRIMARY KEY,
extfvk TEXT,
ufvk TEXT,
address TEXT,
transparent_address TEXT
)",
@ -200,16 +200,8 @@ pub fn init_accounts_table<P: consensus::Parameters>(
// Insert accounts atomically
wdb.conn.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
for key in keys.iter() {
let extfvk_str: Option<String> = key.sapling().map(|extfvk| {
encode_extended_full_viewing_key(
wdb.params.hrp_sapling_extended_full_viewing_key(),
extfvk,
)
});
let address_str: Option<String> = key
.sapling()
.map(|extfvk| address_from_extfvk(&wdb.params, extfvk));
let ufvk_str: String = key.encode(&wdb.params);
let address_str: String = key.default_address().0.encode(&wdb.params);
#[cfg(feature = "transparent-inputs")]
let taddress_str: Option<String> = key.transparent().and_then(|k| {
k.derive_external_ivk()
@ -220,11 +212,11 @@ pub fn init_accounts_table<P: consensus::Parameters>(
let taddress_str: Option<String> = None;
wdb.conn.execute(
"INSERT INTO accounts (account, extfvk, address, transparent_address)
"INSERT INTO accounts (account, ufvk, address, transparent_address)
VALUES (?, ?, ?, ?)",
params![
u32::from(key.account()),
extfvk_str,
ufvk_str,
address_str,
taddress_str,
],