zcash_keys: Remove `UnifiedAddressRequest::DEFAULT`
This default only made sense in the context of what was supported by `zcash_client_sqlite`, and not in any other context. Unified address requests no longer have their parts conditioned by what feature flags are available; instead, if a request is constructed for which the required key parts are not supported under a particular selection of feature flags, address generation will raise a runtime error.
This commit is contained in:
parent
0fa76ff726
commit
ea1d3a35db
|
@ -22,6 +22,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
zcash_address.workspace = true
|
||||
zcash_client_backend = { workspace = true, features = ["unstable-serialization", "unstable-spanning-tree"] }
|
||||
zcash_encoding.workspace = true
|
||||
zcash_keys = { workspace = true, features = ["orchard"] }
|
||||
zcash_primitives.workspace = true
|
||||
|
||||
# Dependencies exposed in a public API:
|
||||
|
|
|
@ -107,6 +107,9 @@ pub(crate) const VERIFY_LOOKAHEAD: u32 = 10;
|
|||
|
||||
pub(crate) const SAPLING_TABLES_PREFIX: &str = "sapling";
|
||||
|
||||
pub const DEFAULT_UA_REQUEST: UnifiedAddressRequest =
|
||||
UnifiedAddressRequest::unsafe_new(false, true, true);
|
||||
|
||||
/// A newtype wrapper for received note identifiers.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ReceivedNoteId(pub(crate) i64);
|
||||
|
@ -1116,12 +1119,9 @@ extern crate assert_matches;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zcash_client_backend::{
|
||||
data_api::{AccountBirthday, WalletRead, WalletWrite},
|
||||
keys::UnifiedAddressRequest,
|
||||
};
|
||||
use zcash_client_backend::data_api::{AccountBirthday, WalletRead, WalletWrite};
|
||||
|
||||
use crate::{testing::TestBuilder, AccountId};
|
||||
use crate::{testing::TestBuilder, AccountId, DEFAULT_UA_REQUEST};
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
use {
|
||||
|
@ -1145,7 +1145,7 @@ mod tests {
|
|||
// TODO: Add Orchard
|
||||
let addr2 = st
|
||||
.wallet_mut()
|
||||
.get_next_available_address(account, UnifiedAddressRequest::DEFAULT)
|
||||
.get_next_available_address(account, DEFAULT_UA_REQUEST)
|
||||
.unwrap();
|
||||
assert!(addr2.is_some());
|
||||
assert_ne!(current_addr, addr2);
|
||||
|
@ -1173,7 +1173,12 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
// The receiver for the default UA should be in the set.
|
||||
assert!(receivers.contains_key(ufvk.default_address().0.transparent().unwrap()));
|
||||
assert!(receivers.contains_key(
|
||||
ufvk.default_address(DEFAULT_UA_REQUEST)
|
||||
.0
|
||||
.transparent()
|
||||
.unwrap()
|
||||
));
|
||||
|
||||
// The default t-addr should be in the set.
|
||||
assert!(receivers.contains_key(&taddr));
|
||||
|
|
|
@ -99,6 +99,7 @@ use zcash_client_backend::{
|
|||
};
|
||||
|
||||
use crate::wallet::commitment_tree::{get_max_checkpointed_height, SqliteShardStore};
|
||||
use crate::DEFAULT_UA_REQUEST;
|
||||
use crate::{
|
||||
error::SqliteClientError, SqlTransaction, WalletCommitmentTrees, WalletDb, PRUNING_DEPTH,
|
||||
SAPLING_TABLES_PREFIX,
|
||||
|
@ -260,7 +261,7 @@ pub(crate) fn add_account<P: consensus::Parameters>(
|
|||
}
|
||||
|
||||
// Always derive the default Unified Address for the account.
|
||||
let (address, d_idx) = key.default_address();
|
||||
let (address, d_idx) = key.default_address(DEFAULT_UA_REQUEST);
|
||||
insert_address(conn, params, account, d_idx, &address)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -181,7 +181,9 @@ mod tests {
|
|||
zip32::AccountId,
|
||||
};
|
||||
|
||||
use crate::{testing::TestBuilder, wallet::scanning::priority_code, WalletDb};
|
||||
use crate::{
|
||||
testing::TestBuilder, wallet::scanning::priority_code, WalletDb, DEFAULT_UA_REQUEST,
|
||||
};
|
||||
|
||||
use super::init_wallet_db;
|
||||
|
||||
|
@ -1005,7 +1007,8 @@ mod tests {
|
|||
)?;
|
||||
|
||||
let ufvk_str = ufvk.encode(&wdb.params);
|
||||
let address_str = Address::Unified(ufvk.default_address().0).encode(&wdb.params);
|
||||
let address_str =
|
||||
Address::Unified(ufvk.default_address(DEFAULT_UA_REQUEST).0).encode(&wdb.params);
|
||||
wdb.conn.execute(
|
||||
"INSERT INTO accounts (account, ufvk, address, transparent_address)
|
||||
VALUES (?, ?, ?, '')",
|
||||
|
@ -1019,8 +1022,14 @@ mod tests {
|
|||
// add a transparent "sent note"
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
{
|
||||
let taddr = Address::Transparent(*ufvk.default_address().0.transparent().unwrap())
|
||||
.encode(&wdb.params);
|
||||
let taddr = Address::Transparent(
|
||||
*ufvk
|
||||
.default_address(DEFAULT_UA_REQUEST)
|
||||
.0
|
||||
.transparent()
|
||||
.unwrap(),
|
||||
)
|
||||
.encode(&wdb.params);
|
||||
wdb.conn.execute(
|
||||
"INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'000000')",
|
||||
[],
|
||||
|
@ -1060,7 +1069,7 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn account_produces_expected_ua_sequence() {
|
||||
use zcash_client_backend::{data_api::AccountBirthday, keys::UnifiedAddressRequest};
|
||||
use zcash_client_backend::data_api::AccountBirthday;
|
||||
|
||||
let network = Network::MainNetwork;
|
||||
let data_file = NamedTempFile::new().unwrap();
|
||||
|
@ -1090,7 +1099,7 @@ mod tests {
|
|||
assert_eq!(tv.unified_addr, ua.encode(&Network::MainNetwork));
|
||||
|
||||
db_data
|
||||
.get_next_available_address(account, UnifiedAddressRequest::DEFAULT)
|
||||
.get_next_available_address(account, DEFAULT_UA_REQUEST)
|
||||
.unwrap()
|
||||
.expect("get_next_available_address generated an address");
|
||||
} else {
|
||||
|
|
|
@ -395,6 +395,7 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn migrate_from_wm2() {
|
||||
use zcash_client_backend::keys::UnifiedAddressRequest;
|
||||
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
||||
|
||||
let network = Network::TestNetwork;
|
||||
|
@ -437,7 +438,7 @@ mod tests {
|
|||
|
||||
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
|
||||
let ufvk = usk.to_unified_full_viewing_key();
|
||||
let (ua, _) = ufvk.default_address();
|
||||
let (ua, _) = ufvk.default_address(UnifiedAddressRequest::unsafe_new(false, true, true));
|
||||
let taddr = ufvk
|
||||
.transparent()
|
||||
.and_then(|k| {
|
||||
|
|
|
@ -5,6 +5,7 @@ use schemer;
|
|||
use schemer_rusqlite::RusqliteMigration;
|
||||
use uuid::Uuid;
|
||||
use zcash_client_backend::{address::Address, keys::UnifiedFullViewingKey};
|
||||
use zcash_keys::keys::UnifiedAddressRequest;
|
||||
use zcash_primitives::{consensus, zip32::AccountId};
|
||||
|
||||
use crate::wallet::{init::WalletMigrationError, insert_address};
|
||||
|
@ -84,7 +85,8 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
"Address in accounts table was not a Unified Address.".to_string(),
|
||||
));
|
||||
};
|
||||
let (expected_address, idx) = ufvk.default_address();
|
||||
let (expected_address, idx) =
|
||||
ufvk.default_address(UnifiedAddressRequest::unsafe_new(false, true, true));
|
||||
if decoded_address != expected_address {
|
||||
return Err(WalletMigrationError::CorruptedData(format!(
|
||||
"Decoded UA {} does not match the UFVK's default address {} at {:?}.",
|
||||
|
@ -154,7 +156,8 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
],
|
||||
)?;
|
||||
|
||||
let (address, d_idx) = ufvk.default_address();
|
||||
let (address, d_idx) =
|
||||
ufvk.default_address(UnifiedAddressRequest::unsafe_new(false, true, true));
|
||||
insert_address(transaction, &self.params, account, d_idx, &address)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use uuid::Uuid;
|
|||
use zcash_client_backend::{
|
||||
address::Address, keys::UnifiedSpendingKey, PoolType, ShieldedProtocol,
|
||||
};
|
||||
use zcash_keys::keys::UnifiedAddressRequest;
|
||||
use zcash_primitives::{consensus, zip32::AccountId};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
|
@ -64,6 +65,8 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
let mut stmt_fetch_accounts =
|
||||
transaction.prepare("SELECT account, address FROM accounts")?;
|
||||
|
||||
let ua_request = UnifiedAddressRequest::new(false, true, true)
|
||||
.expect("A shielded receiver type is requested.");
|
||||
let mut rows = stmt_fetch_accounts.query([])?;
|
||||
while let Some(row) = rows.next()? {
|
||||
// We only need to check for the presence of the seed if we have keys that
|
||||
|
@ -105,7 +108,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
"Address field value decoded to a transparent address; should have been Sapling or unified.".to_string()));
|
||||
}
|
||||
Address::Unified(decoded_address) => {
|
||||
let (expected_address, idx) = ufvk.default_address();
|
||||
let (expected_address, idx) = ufvk.default_address(ua_request);
|
||||
if decoded_address != expected_address {
|
||||
return Err(WalletMigrationError::CorruptedData(
|
||||
format!("Decoded unified address {} does not match the ufvk's default address {} at {:?}.",
|
||||
|
@ -117,7 +120,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
}
|
||||
|
||||
let ufvk_str: String = ufvk.encode(&self.params);
|
||||
let address_str: String = ufvk.default_address().0.encode(&self.params);
|
||||
let address_str: String = ufvk.default_address(ua_request).0.encode(&self.params);
|
||||
|
||||
// This migration, and the wallet behaviour before it, stored the default
|
||||
// transparent address in the `accounts` table. This does not necessarily
|
||||
|
|
|
@ -380,8 +380,11 @@ impl UnifiedSpendingKey {
|
|||
}
|
||||
|
||||
#[cfg(feature = "test-dependencies")]
|
||||
pub fn default_address(&self) -> (UnifiedAddress, DiversifierIndex) {
|
||||
self.to_unified_full_viewing_key().default_address()
|
||||
pub fn default_address(
|
||||
&self,
|
||||
request: UnifiedAddressRequest,
|
||||
) -> (UnifiedAddress, DiversifierIndex) {
|
||||
self.to_unified_full_viewing_key().default_address(request)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "test-dependencies", feature = "transparent-inputs"))]
|
||||
|
@ -405,6 +408,9 @@ pub enum AddressGenerationError {
|
|||
/// A requested address typecode was not recognized, so we are unable to generate the address
|
||||
/// as requested.
|
||||
ReceiverTypeNotSupported(Typecode),
|
||||
/// A requested address typecode was recognized, but the unified key being used to generate the
|
||||
/// address lacks an item of the requested type.
|
||||
KeyNotAvailable(Typecode),
|
||||
/// A Unified address cannot be generated without at least one shielded receiver being
|
||||
/// included.
|
||||
ShieldedReceiverRequired,
|
||||
|
@ -413,44 +419,41 @@ pub enum AddressGenerationError {
|
|||
/// Specification for how a unified address should be generated from a unified viewing key.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UnifiedAddressRequest {
|
||||
#[cfg(feature = "orchard")]
|
||||
has_orchard: bool,
|
||||
_has_orchard: bool,
|
||||
has_sapling: bool,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
has_p2pkh: bool,
|
||||
}
|
||||
|
||||
impl UnifiedAddressRequest {
|
||||
pub const DEFAULT: UnifiedAddressRequest = Self {
|
||||
#[cfg(feature = "orchard")]
|
||||
has_orchard: false, // FIXME: Always request Orchard receivers once we can receive Orchard funds
|
||||
has_sapling: true,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
has_p2pkh: true,
|
||||
};
|
||||
|
||||
pub fn new(
|
||||
#[cfg(feature = "orchard")] has_orchard: bool,
|
||||
has_sapling: bool,
|
||||
#[cfg(feature = "transparent-inputs")] has_p2pkh: bool,
|
||||
) -> Option<Self> {
|
||||
#[cfg(feature = "orchard")]
|
||||
/// Construct a new unified address request from its constituent parts
|
||||
pub fn new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Option<Self> {
|
||||
let has_shielded_receiver = has_orchard || has_sapling;
|
||||
#[cfg(not(feature = "orchard"))]
|
||||
let has_shielded_receiver = has_sapling;
|
||||
|
||||
if !has_shielded_receiver {
|
||||
None
|
||||
} else {
|
||||
Some(Self {
|
||||
#[cfg(feature = "orchard")]
|
||||
has_orchard,
|
||||
_has_orchard: has_orchard,
|
||||
has_sapling,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
has_p2pkh,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new unified address request from its constituent parts.
|
||||
///
|
||||
/// Panics: at least one of `has_orchard` or `has_sapling` must be `true`.
|
||||
pub const fn unsafe_new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Self {
|
||||
if !(has_orchard || has_sapling) {
|
||||
panic!("At least one shielded receiver must be requested.")
|
||||
}
|
||||
|
||||
Self {
|
||||
_has_orchard: has_orchard,
|
||||
has_sapling,
|
||||
has_p2pkh,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [ZIP 316](https://zips.z.cash/zip-0316) unified full viewing key.
|
||||
|
@ -626,51 +629,65 @@ impl UnifiedFullViewingKey {
|
|||
request: UnifiedAddressRequest,
|
||||
) -> Result<UnifiedAddress, AddressGenerationError> {
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard = {
|
||||
let orchard_j = orchard::keys::DiversifierIndex::from(*j.as_bytes());
|
||||
self.orchard
|
||||
.as_ref()
|
||||
.filter(|_| request.has_orchard)
|
||||
.map(|ofvk| ofvk.address_at(orchard_j, Scope::External))
|
||||
};
|
||||
|
||||
let sapling = if let Some(extfvk) = self.sapling.as_ref().filter(|_| request.has_sapling) {
|
||||
// If a Sapling receiver type is requested, we must be able to construct an
|
||||
// address; if we're unable to do so, then no Unified Address exists at this
|
||||
// diversifier and we use `?` to early-return from this method.
|
||||
Some(
|
||||
extfvk
|
||||
.address(j)
|
||||
.ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(j))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let transparent = if let Some(tfvk) =
|
||||
self.transparent.as_ref().filter(|_| request.has_p2pkh)
|
||||
{
|
||||
// If a transparent receiver type is requested, we must be able to construct an
|
||||
// address; if we're unable to do so, then no Unified Address exists at this
|
||||
// diversifier.
|
||||
match to_transparent_child_index(j) {
|
||||
Some(transparent_j) => match tfvk
|
||||
.derive_external_ivk()
|
||||
.and_then(|tivk| tivk.derive_address(transparent_j))
|
||||
{
|
||||
Ok(taddr) => Some(taddr),
|
||||
Err(_) => return Err(AddressGenerationError::InvalidTransparentChildIndex(j)),
|
||||
},
|
||||
// Diversifier doesn't generate a valid transparent child index, so we eagerly
|
||||
// return `None`.
|
||||
None => return Err(AddressGenerationError::InvalidTransparentChildIndex(j)),
|
||||
let orchard = if request._has_orchard {
|
||||
if let Some(ofvk) = &self.orchard {
|
||||
let orchard_j = orchard::keys::DiversifierIndex::from(*j.as_bytes());
|
||||
Some(ofvk.address_at(orchard_j, Scope::External))
|
||||
} else {
|
||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let sapling = if request.has_sapling {
|
||||
if let Some(extfvk) = &self.sapling {
|
||||
// If a Sapling receiver type is requested, we must be able to construct an
|
||||
// address; if we're unable to do so, then no Unified Address exists at this
|
||||
// diversifier and we use `?` to early-return from this method.
|
||||
Some(
|
||||
extfvk
|
||||
.address(j)
|
||||
.ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(j))?,
|
||||
)
|
||||
} else {
|
||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let transparent = if request.has_p2pkh {
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
||||
Typecode::P2pkh,
|
||||
));
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
if let Some(tfvk) = self.transparent.as_ref() {
|
||||
// If a transparent receiver type is requested, we must be able to construct an
|
||||
// address; if we're unable to do so, then no Unified Address exists at this
|
||||
// diversifier.
|
||||
match to_transparent_child_index(j) {
|
||||
Some(transparent_j) => match tfvk
|
||||
.derive_external_ivk()
|
||||
.and_then(|tivk| tivk.derive_address(transparent_j))
|
||||
{
|
||||
Ok(taddr) => Some(taddr),
|
||||
Err(_) => {
|
||||
return Err(AddressGenerationError::InvalidTransparentChildIndex(j))
|
||||
}
|
||||
},
|
||||
// Diversifier doesn't generate a valid transparent child index, so we eagerly
|
||||
// return `None`.
|
||||
None => return Err(AddressGenerationError::InvalidTransparentChildIndex(j)),
|
||||
}
|
||||
} else {
|
||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let transparent = None;
|
||||
|
||||
UnifiedAddress::from_receivers(
|
||||
#[cfg(feature = "orchard")]
|
||||
|
@ -720,9 +737,11 @@ impl UnifiedFullViewingKey {
|
|||
|
||||
/// Returns the Unified Address corresponding to the smallest valid diversifier index,
|
||||
/// along with that index.
|
||||
pub fn default_address(&self) -> (UnifiedAddress, DiversifierIndex) {
|
||||
// FIXME: Enable Orchard keys
|
||||
self.find_address(DiversifierIndex::new(), UnifiedAddressRequest::DEFAULT)
|
||||
pub fn default_address(
|
||||
&self,
|
||||
request: UnifiedAddressRequest,
|
||||
) -> (UnifiedAddress, DiversifierIndex) {
|
||||
self.find_address(DiversifierIndex::new(), request)
|
||||
.expect("UFVK should have at least one valid diversifier")
|
||||
}
|
||||
}
|
||||
|
@ -913,7 +932,10 @@ mod tests {
|
|||
}
|
||||
|
||||
let ua = ufvk
|
||||
.address(d_idx, UnifiedAddressRequest::DEFAULT)
|
||||
.address(
|
||||
d_idx,
|
||||
UnifiedAddressRequest::unsafe_new(false, true, true),
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"unified address generation failed for account {}: {:?}",
|
||||
|
|
Loading…
Reference in New Issue