From 5ad432ad6e4fdb6215a340c0170849619fc8cf01 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 30 Aug 2024 16:47:09 -0600 Subject: [PATCH] zcash_client_sqlite: Do not require a transparent key for migrations. The `reserve_until` method for generating ephemeral addresses is used in database migrations, but it makes no sense to use this in the case that the UFVK for an account contains no transparent component; such accounts can never be used to make ZIP 320 spends. --- .../src/wallet/transparent/ephemeral.rs | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs b/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs index e8811bd37..9e24bc413 100644 --- a/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs +++ b/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs @@ -113,7 +113,7 @@ pub(crate) fn get_ephemeral_ivk( conn: &rusqlite::Connection, params: &P, account_id: AccountId, -) -> Result { +) -> Result, SqliteClientError> { let ufvk = conn .query_row( "SELECT ufvk FROM accounts WHERE id = :account_id", @@ -133,8 +133,8 @@ pub(crate) fn get_ephemeral_ivk( let eivk = ufvk .as_ref() .and_then(|ufvk| ufvk.transparent()) - .ok_or(SqliteClientError::UnknownZip32Derivation)? - .derive_ephemeral_ivk()?; + .map(|t| t.derive_ephemeral_ivk()) + .transpose()?; Ok(eivk) } @@ -265,11 +265,11 @@ pub(crate) fn init_account( reserve_until(conn, params, account_id, 0) } -/// Extend the range of stored addresses in an account if necessary so that the -/// index of the next address to reserve will be *at least* `next_to_reserve`. -/// If it would already have been at least `next_to_reserve`, then do nothing. +/// Extend the range of stored addresses in an account if necessary so that the index of the next +/// address to reserve will be *at least* `next_to_reserve`. If no transparent key exists for the +/// given account or it would already have been at least `next_to_reserve`, then do nothing. /// -/// Note that this is called from db migration code. +/// Note that this is called from database migration code. /// /// # Panics /// @@ -282,39 +282,40 @@ fn reserve_until( ) -> Result<(), SqliteClientError> { assert!(next_to_reserve <= 1 << 31); - let first_unstored = first_unstored_index(conn, account_id)?; - let range_to_store = first_unstored..(next_to_reserve.checked_add(GAP_LIMIT).unwrap()); - if range_to_store.is_empty() { - return Ok(()); + if let Some(ephemeral_ivk) = get_ephemeral_ivk(conn, params, account_id)? { + let first_unstored = first_unstored_index(conn, account_id)?; + let range_to_store = first_unstored..(next_to_reserve.checked_add(GAP_LIMIT).unwrap()); + if range_to_store.is_empty() { + return Ok(()); + } + + // used_in_tx and seen_in_tx are initially NULL + let mut stmt_insert_ephemeral_address = conn.prepare_cached( + "INSERT INTO ephemeral_addresses (account_id, address_index, address) + VALUES (:account_id, :address_index, :address)", + )?; + + for raw_index in range_to_store { + // The range to store may contain indicies that are out of the valid range of non hardened + // child indices; we still store explicit rows in the ephemeral_addresses table for these + // so that it's possible to find the first unused address using dead reckoning with the gap + // limit. + let address_str_opt = NonHardenedChildIndex::from_index(raw_index) + .map(|address_index| { + ephemeral_ivk + .derive_ephemeral_address(address_index) + .map(|addr| addr.encode(params)) + }) + .transpose()?; + + stmt_insert_ephemeral_address.execute(named_params![ + ":account_id": account_id.0, + ":address_index": raw_index, + ":address": address_str_opt, + ])?; + } } - let ephemeral_ivk = get_ephemeral_ivk(conn, params, account_id)?; - - // used_in_tx and seen_in_tx are initially NULL - let mut stmt_insert_ephemeral_address = conn.prepare_cached( - "INSERT INTO ephemeral_addresses (account_id, address_index, address) - VALUES (:account_id, :address_index, :address)", - )?; - - for raw_index in range_to_store { - // The range to store may contain indicies that are out of the valid range of non hardened - // child indices; we still store explicit rows in the ephemeral_addresses table for these - // so that it's possible to find the first unused address using dead reckoning with the gap - // limit. - let address_str_opt = NonHardenedChildIndex::from_index(raw_index) - .map(|address_index| { - ephemeral_ivk - .derive_ephemeral_address(address_index) - .map(|addr| addr.encode(params)) - }) - .transpose()?; - - stmt_insert_ephemeral_address.execute(named_params![ - ":account_id": account_id.0, - ":address_index": raw_index, - ":address": address_str_opt, - ])?; - } Ok(()) }