From d2fb0c853b42c651f74a9c33025fa0374cf79ffd Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 7 Nov 2024 19:34:42 +0100 Subject: [PATCH 01/11] registration ease --- .../identity/register_dpns_name.rs | 2 +- src/database/wallet.rs | 4 +- src/model/wallet/mod.rs | 23 +++---- .../identities/add_new_identity_screen/mod.rs | 63 +++++++++++------- src/ui/wallet/add_new_wallet_screen.rs | 66 +++++++++++-------- 5 files changed, 93 insertions(+), 65 deletions(-) diff --git a/src/backend_task/identity/register_dpns_name.rs b/src/backend_task/identity/register_dpns_name.rs index 2da823646..91544cff2 100644 --- a/src/backend_task/identity/register_dpns_name.rs +++ b/src/backend_task/identity/register_dpns_name.rs @@ -197,7 +197,7 @@ impl AppContext { qualified_identity.dpns_names = owned_dpns_names; // Insert qualified identity into the database - self.insert_local_qualified_identity(&qualified_identity) + self.update_local_qualified_identity(&qualified_identity) .map_err(|e| format!("Database error: {}", e))?; Ok(BackendTaskSuccessResult::Message( diff --git a/src/database/wallet.rs b/src/database/wallet.rs index 769b65569..4d3b13157 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -71,7 +71,7 @@ impl Database { is_main: bool, ) -> rusqlite::Result<()> { self.execute( - "UPDATE wallet SET alias = ?, is_main = ? WHERE seed = ?", + "UPDATE wallet SET alias = ?, is_main = ? WHERE seed_hash = ?", params![new_alias, is_main as i32, seed_hash], )?; Ok(()) @@ -148,7 +148,7 @@ impl Database { let rows_affected = self.execute( "UPDATE wallet_addresses SET balance = balance + ? - WHERE seed = ? AND address = ?", + WHERE seed_hash = ? AND address = ?", params![additional_balance, seed_hash, address.to_string()], )?; diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index 87004a619..3027c3b82 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -283,18 +283,18 @@ impl Wallet { let mut address_index = 0; let mut found_unused_derivation_path = None; let mut known_public_key = None; - let derivation_path_extension = DerivationPath::from( - [ - ChildNumber::Normal { - index: change.into(), - }, - ChildNumber::Normal { - index: address_index, - }, - ] - .as_slice(), - ); while found_unused_derivation_path.is_none() { + let derivation_path_extension = DerivationPath::from( + [ + ChildNumber::Normal { + index: change.into(), + }, + ChildNumber::Normal { + index: address_index, + }, + ] + .as_slice(), + ); let derivation_path = DerivationPath::bip_44_payment_path(network, 0, change, address_index); @@ -351,6 +351,7 @@ impl Wallet { Some(false), ) .map_err(|e| e.to_string())?; + println!("adding address {} at {}", &address, &derivation_path); app_context .db .add_address( diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index c16d8ad53..6dfea03e9 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -89,6 +89,8 @@ pub struct AddNewIdentityScreen { identity_keys: IdentityKeys, balance_check_handle: Option<(Arc, thread::JoinHandle<()>)>, error_message: Option, + show_password: bool, + wallet_password: String, show_pop_up_info: Option, in_key_selection_advanced_mode: bool, pub app_context: Arc, @@ -148,6 +150,8 @@ impl AddNewIdentityScreen { }, balance_check_handle: None, error_message: None, + show_password: false, + wallet_password: "".to_string(), show_pop_up_info: None, in_key_selection_advanced_mode: false, app_context: app_context.clone(), @@ -273,32 +277,43 @@ impl AddNewIdentityScreen { ui.add_space(10.0); ui.label("This wallet is locked. Please enter the password to unlock it:"); - let mut password = String::new(); - let password_input = ui.add( - egui::TextEdit::singleline(&mut password) - .password(true) - .hint_text("Enter password"), - ); + let mut unlocked = false; + ui.horizontal(|ui| { + let password_input = ui.add( + egui::TextEdit::singleline(&mut self.wallet_password) + .password(!self.show_password) + .hint_text("Enter password"), + ); - let unlocked = if password_input.lost_focus() - && ui.input(|i| i.key_pressed(egui::Key::Enter)) - { - let unlocked = match wallet.wallet_seed.open(&password) { - Ok(_) => { - self.error_message = None; // Clear any previous error - true - } - Err(e) => { - self.error_message = Some(e); // Store the error message - false - } + ui.checkbox(&mut self.show_password, "Show Password"); + + unlocked = if password_input.lost_focus() + && ui.input(|i| i.key_pressed(egui::Key::Enter)) + { + let unlocked = match wallet.wallet_seed.open(&self.wallet_password) { + Ok(_) => { + self.error_message = None; // Clear any previous error + true + } + Err(_) => { + if let Some(hint) = wallet.password_hint() { + self.error_message = Some(format!( + "Incorrect Password, password hint is {}", + hint + )); + } else { + self.error_message = Some("Incorrect Password".to_string()); + } + false + } + }; + // Clear the password field after submission + self.wallet_password.zeroize(); + unlocked + } else { + false }; - // Clear the password field after submission - password.zeroize(); - unlocked - } else { - false - }; + }); // Display error message if the password was incorrect if let Some(error_message) = &self.error_message { diff --git a/src/ui/wallet/add_new_wallet_screen.rs b/src/ui/wallet/add_new_wallet_screen.rs index c1b8acea1..46752ef2a 100644 --- a/src/ui/wallet/add_new_wallet_screen.rs +++ b/src/ui/wallet/add_new_wallet_screen.rs @@ -161,18 +161,20 @@ impl AddNewWalletScreen { fn render_seed_phrase_input(&mut self, ui: &mut Ui) { ui.add_space(15.0); // Add spacing from the top - ui.vertical(|ui| { + ui.vertical_centered(|ui| { // Allocate a full-width container to center align the elements let available_width = ui.available_width(); ui.allocate_ui_with_layout( Vec2::new(available_width, 0.0), - egui::Layout::top_down(egui::Align::Min), + egui::Layout::top_down(egui::Align::Center), |ui| { ui.horizontal(|ui| { // Add spacing to align the combo box to the left of the center let half_width = available_width / 2.0 - 400.0; // Adjust half-width with padding - ui.add_space(half_width); + if half_width > 0.0 { + ui.add_space(half_width); + } let style = ui.style_mut(); @@ -235,8 +237,8 @@ impl AddNewWalletScreen { // Create a container with a fixed width (72% of the available width) let frame_width = available_width * 0.72; ui.allocate_ui_with_layout( - Vec2::new(frame_width, 300.0), // Set width and height of the container - egui::Layout::top_down(egui::Align::Min), + Vec2::new(frame_width, 260.0), // Set width and height of the container + egui::Layout::top_down(egui::Align::Center), |ui| { Frame::none() .fill(Color32::WHITE) @@ -249,15 +251,15 @@ impl AddNewWalletScreen { // Calculate the size of each grid cell let column_width = frame_width / columns as f32; - let row_height = 300.0 / rows as f32; - - Grid::new("seed_phrase_grid") - .num_columns(columns) - .spacing((0.0, 0.0)) - .min_col_width(column_width) - .min_row_height(row_height) - .show(ui, |ui| { - if let Some(mnemonic) = &self.seed_phrase { + let row_height = 260.0 / rows as f32; + + if let Some(mnemonic) = &self.seed_phrase { + Grid::new("seed_phrase_grid") + .num_columns(columns) + .spacing((0.0, 0.0)) + .min_col_width(column_width) + .min_row_height(row_height) + .show(ui, |ui| { for (i, word) in mnemonic.words().enumerate() { let word_text = RichText::new(word) .size(row_height * 0.5) @@ -276,18 +278,17 @@ impl AddNewWalletScreen { ui.end_row(); } } - } else { - let word_text = - RichText::new("Seed Phrase").size(40.0).monospace(); - - ui.with_layout( - Layout::centered_and_justified(Direction::LeftToRight), - |ui| { - ui.label(word_text); - }, - ); - } - }); + }); + } else { + let word_text = RichText::new("Seed Phrase").size(40.0).monospace(); + + ui.with_layout( + Layout::centered_and_justified(Direction::LeftToRight), + |ui| { + ui.label(word_text); + }, + ); + } }); }, ); @@ -338,7 +339,18 @@ impl ScreenLike for AddNewWalletScreen { ui.add_space(20.0); - ui.heading("4. Add a password that must be used to unlock the wallet. (Optional but Recommended)"); + ui.heading("4. Select a wallet name to remember it. (This will not go to the blockchain)"); + + ui.add_space(8.0); + + ui.horizontal(|ui| { + ui.label("Wallet Name:"); + ui.text_edit_singleline(&mut self.alias_input); + }); + + ui.add_space(20.0); + + ui.heading("5. Add a password that must be used to unlock the wallet. (Optional but Recommended)"); ui.add_space(8.0); From ce44ff8abb68d5eb0da131d7ba885546996204d7 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 Nov 2024 01:26:06 +0100 Subject: [PATCH 02/11] key storage --- src/app.rs | 28 ++- src/app_dir.rs | 2 +- .../identity/add_key_to_identity.rs | 18 +- src/backend_task/identity/load_identity.rs | 46 +++- src/backend_task/identity/mod.rs | 44 ++-- .../identity/register_identity.rs | 8 +- src/components/core_zmq_listener.rs | 8 +- src/context.rs | 11 +- src/database/initialization.rs | 3 + src/database/settings.rs | 38 +++- src/model/contested_name.rs | 4 +- src/model/mod.rs | 1 + src/model/password_info.rs | 6 + .../encrypted_key_storage.rs | 185 ++++++++++++++++ .../mod.rs} | 63 +++--- .../qualified_identity_public_key.rs | 202 ++++++++++++++++++ src/model/wallet/asset_lock_transaction.rs | 2 +- src/model/wallet/mod.rs | 4 +- .../by_using_unused_asset_lock.rs | 3 +- .../by_using_unused_balance.rs | 8 +- .../by_wallet_qr_code.rs | 3 +- .../identities/add_new_identity_screen/mod.rs | 7 +- src/ui/identities/identities_screen.rs | 56 +++-- src/ui/key_info_screen.rs | 36 ++-- src/ui/mod.rs | 5 +- src/ui/transfers/mod.rs | 15 +- src/ui/wallet/add_new_wallet_screen.rs | 5 +- src/ui/wallet/import_wallet_screen.rs | 4 +- src/ui/wallet/wallets_screen.rs | 51 ++++- src/ui/withdrawals/mod.rs | 13 +- src/ui/withdraws_status_screen.rs | 2 +- 31 files changed, 727 insertions(+), 154 deletions(-) create mode 100644 src/model/password_info.rs create mode 100644 src/model/qualified_identity/encrypted_key_storage.rs rename src/model/{qualified_identity.rs => qualified_identity/mod.rs} (81%) create mode 100644 src/model/qualified_identity/qualified_identity_public_key.rs diff --git a/src/app.rs b/src/app.rs index e6d0ae8fb..56643ea88 100644 --- a/src/app.rs +++ b/src/app.rs @@ -119,16 +119,22 @@ impl AppState { let settings = db.get_settings().expect("expected to get settings"); - let mainnet_app_context = match AppContext::new(Network::Dash, db.clone()) { - Some(context) => context, - None => { - eprintln!( - "Error: Failed to create the AppContext. Expected Dash config for mainnet." - ); - std::process::exit(1); - } - }; - let testnet_app_context = AppContext::new(Network::Testnet, db.clone()); + let password_info = settings + .clone() + .map(|(_, _, password_info)| password_info) + .flatten(); + + let mainnet_app_context = + match AppContext::new(Network::Dash, db.clone(), password_info.clone()) { + Some(context) => context, + None => { + eprintln!( + "Error: Failed to create the AppContext. Expected Dash config for mainnet." + ); + std::process::exit(1); + } + }; + let testnet_app_context = AppContext::new(Network::Testnet, db.clone(), password_info); let mut identities_screen = IdentitiesScreen::new(&mainnet_app_context); let mut dpns_active_contests_screen = @@ -153,7 +159,7 @@ impl AppState { let mut chosen_network = Network::Dash; - if let Some((network, screen_type)) = settings { + if let Some((network, screen_type, password_info)) = settings { selected_main_screen = screen_type; chosen_network = network; if chosen_network == Network::Testnet && testnet_app_context.is_some() { diff --git a/src/app_dir.rs b/src/app_dir.rs index af9dde98c..69ff100d5 100644 --- a/src/app_dir.rs +++ b/src/app_dir.rs @@ -1,6 +1,6 @@ use directories::ProjectDirs; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; const QUALIFIER: &str = ""; // Typically empty on macOS and Linux const ORGANIZATION: &str = ""; diff --git a/src/backend_task/identity/add_key_to_identity.rs b/src/backend_task/identity/add_key_to_identity.rs index 1041e4cb3..87f169371 100644 --- a/src/backend_task/identity/add_key_to_identity.rs +++ b/src/backend_task/identity/add_key_to_identity.rs @@ -1,5 +1,7 @@ +use super::BackendTaskSuccessResult; use crate::context::AppContext; -use crate::model::qualified_identity::EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity; +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; +use crate::model::qualified_identity::PrivateKeyTarget::PrivateKeyOnMainIdentity; use crate::model::qualified_identity::QualifiedIdentity; use dash_sdk::dpp::identity::accessors::{IdentityGettersV0, IdentitySettersV0}; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::{ @@ -13,8 +15,6 @@ use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::{Fetch, Identity, IdentityPublicKey}; use dash_sdk::Sdk; -use super::BackendTaskSuccessResult; - impl AppContext { pub(super) async fn add_key_to_identity( &self, @@ -30,7 +30,7 @@ impl AppContext { let Some(master_key) = qualified_identity.can_sign_with_master_key() else { return Err("Master key not found".to_string()); }; - let master_key_id = master_key.id(); + let master_key_id = master_key.identity_public_key.id(); let identity = Identity::fetch_by_identifier(sdk, qualified_identity.identity.id()) .await .map_err(|e| format!("Fetch nonce error: {}", e))? @@ -38,10 +38,14 @@ impl AppContext { qualified_identity.identity = identity; qualified_identity.identity.bump_revision(); public_key_to_add.set_id(qualified_identity.identity.get_public_key_max_id() + 1); - qualified_identity.encrypted_private_keys.insert( - (PrivateKeyOnMainIdentity, public_key_to_add.id()), - (public_key_to_add.clone(), private_key.clone()), + let qualified_key = QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + public_key_to_add.clone(), + self.wallets.read().unwrap().as_slice(), ); + qualified_identity.private_keys.insert_non_encrypted( + (PrivateKeyOnMainIdentity, public_key_to_add.id()), + (qualified_key, private_key), + )?; let state_transition = IdentityUpdateTransition::try_from_identity_with_signer( &qualified_identity.identity, &master_key_id, diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index 314f152c3..82076f453 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -1,6 +1,8 @@ +use super::BackendTaskSuccessResult; use crate::backend_task::identity::{verify_key_input, IdentityInputToLoad}; use crate::context::AppContext; -use crate::model::qualified_identity::EncryptedPrivateKeyTarget::{ +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; +use crate::model::qualified_identity::PrivateKeyTarget::{ self, PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, QualifiedIdentity}; @@ -17,8 +19,6 @@ use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identifier, use dash_sdk::Sdk; use std::collections::BTreeMap; -use super::BackendTaskSuccessResult; - impl AppContext { pub(super) async fn load_identity( &self, @@ -61,13 +61,21 @@ impl AppContext { let mut encrypted_private_keys = BTreeMap::new(); + let wallets = self.wallets.read().unwrap(); + if identity_type != IdentityType::User && owner_private_key_bytes.is_some() { let owner_private_key_bytes = owner_private_key_bytes.unwrap(); let key = self.verify_owner_key_exists_on_identity(&identity, &owner_private_key_bytes)?; + let key_id = key.id(); + let qualified_key = + QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + key, + wallets.as_slice(), + ); encrypted_private_keys.insert( - (PrivateKeyOnMainIdentity, key.id()), - (key.clone(), owner_private_key_bytes), + (PrivateKeyOnMainIdentity, key_id), + (qualified_key, owner_private_key_bytes), ); } @@ -77,9 +85,15 @@ impl AppContext { &identity, &payout_address_private_key_bytes, )?; + let key_id = key.id(); + let qualified_key = + QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + key, + wallets.as_slice(), + ); encrypted_private_keys.insert( - (PrivateKeyOnMainIdentity, key.id()), - (key.clone(), payout_address_private_key_bytes), + (PrivateKeyOnMainIdentity, key_id), + (qualified_key, payout_address_private_key_bytes), ); } @@ -108,9 +122,14 @@ impl AppContext { &voter_identity, &voting_private_key_bytes, )?; + let qualified_key = + QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + key.clone(), + wallets.as_slice(), + ); encrypted_private_keys.insert( (PrivateKeyOnVoterIdentity, key.id()), - (key.clone(), voting_private_key_bytes), + (qualified_key, voting_private_key_bytes), ); Some((voter_identity, key)) } else { @@ -136,9 +155,14 @@ impl AppContext { return Err("Private key input length is 0 for key id {key_id}".to_string()) } }; + let qualified_key = + QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + public_key.clone(), + wallets.as_slice(), + ); encrypted_private_keys.insert( - (EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity, key_id), - (public_key.clone(), private_key_bytes), + (PrivateKeyTarget::PrivateKeyOnMainIdentity, key_id), + (qualified_key, private_key_bytes), ); } } @@ -198,7 +222,7 @@ impl AppContext { } else { Some(alias_input) }, - encrypted_private_keys, + private_keys: encrypted_private_keys.into(), dpns_names: maybe_owned_dpns_names, }; diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index 6dfbafbd2..0dc548c74 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -6,11 +6,12 @@ mod register_identity; mod transfer; mod withdraw_from_identity; +use super::BackendTaskSuccessResult; use crate::app::TaskResult; use crate::context::AppContext; -use crate::model::qualified_identity::{ - EncryptedPrivateKeyTarget, IdentityType, QualifiedIdentity, -}; +use crate::model::qualified_identity::encrypted_key_storage::KeyStorage; +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; +use crate::model::qualified_identity::{IdentityType, PrivateKeyTarget, QualifiedIdentity}; use crate::model::wallet::Wallet; use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::dashcore::{Address, PrivateKey, TxOut}; @@ -30,8 +31,6 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::sync::{Arc, RwLock}; use tokio::sync::mpsc; -use super::BackendTaskSuccessResult; - #[derive(Debug, Clone, PartialEq)] pub struct IdentityInputToLoad { pub identity_id_input: String, @@ -51,9 +50,7 @@ pub struct IdentityKeys { } impl IdentityKeys { - pub fn to_encrypted_private_keys( - &self, - ) -> BTreeMap<(EncryptedPrivateKeyTarget, KeyID), (IdentityPublicKey, [u8; 32])> { + pub fn to_key_storage(&self, context: &AppContext) -> KeyStorage { let Self { master_private_key, master_private_key_type, @@ -61,6 +58,9 @@ impl IdentityKeys { } = self; let secp = Secp256k1::new(); let mut key_map = BTreeMap::new(); + + let wallets = context.wallets.read().unwrap(); + if let Some(master_private_key) = master_private_key { let key = IdentityPublicKey::V0(IdentityPublicKeyV0 { id: 0, @@ -73,11 +73,20 @@ impl IdentityKeys { disabled_at: None, }); + let qualified_identity_public_key = + QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + key, + wallets.as_slice(), + ); key_map.insert( - (EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity, 0), - (key, master_private_key.inner.secret_bytes()), + (PrivateKeyTarget::PrivateKeyOnMainIdentity, 0), + ( + qualified_identity_public_key, + master_private_key.inner.secret_bytes(), + ), ); } + key_map.extend(keys_input.iter().enumerate().map( |(i, (private_key, key_type, purpose, security_level))| { let id = (i + 1) as KeyID; @@ -91,14 +100,23 @@ impl IdentityKeys { data: private_key.public_key(&secp).to_bytes().into(), disabled_at: None, }); + + let qualified_identity_public_key = + QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + identity_public_key, + wallets.as_slice(), + ); ( - (EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity, id), - (identity_public_key, private_key.inner.secret_bytes()), + (PrivateKeyTarget::PrivateKeyOnMainIdentity, id), + ( + qualified_identity_public_key, + private_key.inner.secret_bytes(), + ), ) }, )); - key_map + KeyStorage::Open(key_map.into()) } pub fn to_public_keys_map(&self) -> BTreeMap { let Self { diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index a1d5944e3..46a919809 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -121,7 +121,7 @@ impl AppContext { .await .map_err(|e| e.to_string())?; - let mut wallet_id; + let wallet_id; let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = match identity_registration_method { @@ -207,7 +207,7 @@ impl AppContext { .send_raw_transaction(&asset_lock_transaction) .map_err(|e| e.to_string())?; - let mut asset_lock_proof; + let asset_lock_proof; loop { { @@ -259,7 +259,7 @@ impl AppContext { .send_raw_transaction(&asset_lock_transaction) .map_err(|e| e.to_string())?; - let mut asset_lock_proof; + let asset_lock_proof; loop { { @@ -298,7 +298,7 @@ impl AppContext { associated_owner_key_id: None, identity_type: IdentityType::User, alias: None, - encrypted_private_keys: keys.to_encrypted_private_keys(), + private_keys: keys.to_key_storage(self), dpns_names: vec![], }; diff --git a/src/components/core_zmq_listener.rs b/src/components/core_zmq_listener.rs index 7ee5a03cd..c264d568e 100644 --- a/src/components/core_zmq_listener.rs +++ b/src/components/core_zmq_listener.rs @@ -79,8 +79,8 @@ impl CoreZMQListener { match topic { "rawchainlock" => { - println!("Received raw chain locked block:"); - println!("Data (hex): {}", hex::encode(data_bytes)); + // println!("Received raw chain locked block:"); + // println!("Data (hex): {}", hex::encode(data_bytes)); // Create a cursor over the data_bytes let mut cursor = Cursor::new(data_bytes); @@ -108,8 +108,8 @@ impl CoreZMQListener { } } "rawtxlocksig" => { - println!("Received rawtxlocksig for InstantSend:"); - println!("Data (hex): {}", hex::encode(data_bytes)); + // println!("Received rawtxlocksig for InstantSend:"); + // println!("Data (hex): {}", hex::encode(data_bytes)); // Create a cursor over the data_bytes let mut cursor = Cursor::new(data_bytes); diff --git a/src/context.rs b/src/context.rs index 2ff9d3461..ed2677cdb 100644 --- a/src/context.rs +++ b/src/context.rs @@ -2,6 +2,7 @@ use crate::config::{Config, NetworkConfig}; use crate::context_provider::Provider; use crate::database::Database; use crate::model::contested_name::ContestedName; +use crate::model::password_info::PasswordInfo; use crate::model::qualified_contract::QualifiedContract; use crate::model::qualified_identity::{DPNSNameInfo, QualifiedIdentity}; use crate::model::wallet::Wallet; @@ -39,12 +40,17 @@ pub struct AppContext { pub(crate) core_client: Client, pub(crate) has_wallet: AtomicBool, pub(crate) wallets: RwLock>>>, + pub(crate) password_info: Option, pub(crate) transactions_waiting_for_finality: Mutex>>, pub(crate) platform_version: &'static PlatformVersion, } impl AppContext { - pub fn new(network: Network, db: Arc) -> Option> { + pub fn new( + network: Network, + db: Arc, + password_info: Option, + ) -> Option> { let config = match Config::load() { Ok(config) => config, Err(e) => { @@ -101,6 +107,7 @@ impl AppContext { core_client, has_wallet: (!wallets.is_empty()).into(), wallets: RwLock::new(wallets), + password_info, transactions_waiting_for_finality: Mutex::new(BTreeMap::new()), platform_version: PlatformVersion::latest(), }; @@ -203,7 +210,7 @@ impl AppContext { } /// Retrieves the current `RootScreenType` from the settings - pub fn get_settings(&self) -> Result> { + pub fn get_settings(&self) -> Result)>> { self.db.get_settings() } diff --git a/src/database/initialization.rs b/src/database/initialization.rs index e69fbc984..73122bd90 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -8,6 +8,9 @@ impl Database { self.execute( "CREATE TABLE IF NOT EXISTS settings ( id INTEGER PRIMARY KEY CHECK (id = 1), + password_check BLOB, + main_password_salt BLOB, + main_password_nonce BLOB, network TEXT NOT NULL, start_root_screen INTEGER NOT NULL, database_version INTEGER NOT NULL diff --git a/src/database/settings.rs b/src/database/settings.rs index fe861a6e8..b4b63b705 100644 --- a/src/database/settings.rs +++ b/src/database/settings.rs @@ -1,4 +1,5 @@ use crate::database::Database; +use crate::model::password_info::PasswordInfo; use crate::ui::RootScreenType; use dash_sdk::dpp::dashcore::Network; use rusqlite::{params, Result}; @@ -24,16 +25,47 @@ impl Database { Ok(()) } + pub fn update_main_password( + &self, + salt: &[u8], + nonce: &[u8], + password_check: &[u8], + ) -> Result<()> { + // Update the settings table with the provided salt, nonce, and password_check + self.execute( + "UPDATE settings + SET main_password_salt = ?, + main_password_nonce = ?, + password_check = ?, + WHERE id = 1", + rusqlite::params![salt, nonce, password_check], + )?; + + Ok(()) + } /// Retrieves the settings from the database. - pub fn get_settings(&self) -> Result> { + pub fn get_settings(&self) -> Result)>> { // Query the settings row let conn = self.conn.lock().unwrap(); let mut stmt = - conn.prepare("SELECT network, start_root_screen FROM settings WHERE id = 1")?; + conn.prepare("SELECT network, start_root_screen, password_check, main_password_salt, main_password_nonce FROM settings WHERE id = 1")?; let result = stmt.query_row([], |row| { let network: String = row.get(0)?; let start_root_screen: u32 = row.get(1)?; + let password_check: Option> = row.get(2)?; + let main_password_salt: Option> = row.get(3)?; + let main_password_nonce: Option> = row.get(4)?; + + // Combine the password-related fields if all are present, otherwise set to None + let password_data = match (password_check, main_password_salt, main_password_nonce) { + (Some(password_checker), Some(salt), Some(nonce)) => Some(PasswordInfo { + password_checker, + salt, + nonce, + }), + _ => None, + }; // Convert network from string to enum let parsed_network = @@ -43,7 +75,7 @@ impl Database { let root_screen_type = RootScreenType::from_int(start_root_screen) .ok_or_else(|| rusqlite::Error::InvalidQuery)?; - Ok((parsed_network, root_screen_type)) + Ok((parsed_network, root_screen_type, password_data)) }); match result { diff --git a/src/model/contested_name.rs b/src/model/contested_name.rs index c3d89108a..7036bb2e2 100644 --- a/src/model/contested_name.rs +++ b/src/model/contested_name.rs @@ -1,4 +1,4 @@ -use crate::model::qualified_identity::EncryptedPrivateKeyTarget; +use crate::model::qualified_identity::PrivateKeyTarget; use bincode::{Decode, Encode}; use dash_sdk::dpp::identity::{KeyID, TimestampMillis}; use dash_sdk::dpp::prelude::{BlockHeight, CoreBlockHeight, Identifier}; @@ -33,7 +33,7 @@ pub struct ContestedName { pub end_time: Option, pub state: ContestState, pub last_updated: Option, - pub my_votes: BTreeMap<(Identifier, EncryptedPrivateKeyTarget, KeyID), ResourceVoteChoice>, + pub my_votes: BTreeMap<(Identifier, PrivateKeyTarget, KeyID), ResourceVoteChoice>, } #[derive(Debug, Encode, Decode, Clone)] diff --git a/src/model/mod.rs b/src/model/mod.rs index b263badfa..1e93d702b 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,4 +1,5 @@ pub mod contested_name; +pub mod password_info; pub mod qualified_contract; pub mod qualified_identity; pub mod wallet; diff --git a/src/model/password_info.rs b/src/model/password_info.rs new file mode 100644 index 000000000..ccb17b298 --- /dev/null +++ b/src/model/password_info.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone)] +pub struct PasswordInfo { + pub password_checker: Vec, + pub salt: Vec, + pub nonce: Vec, +} diff --git a/src/model/qualified_identity/encrypted_key_storage.rs b/src/model/qualified_identity/encrypted_key_storage.rs new file mode 100644 index 000000000..ba3826891 --- /dev/null +++ b/src/model/qualified_identity/encrypted_key_storage.rs @@ -0,0 +1,185 @@ +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; +use crate::model::qualified_identity::PrivateKeyTarget; +use bincode::{Decode, Encode}; +use dash_sdk::dpp::identity::KeyID; +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt; + +#[derive(Debug, Encode, Decode, Clone, PartialEq)] +pub enum KeyStorage { + Open(ClearKeyStorage), + Closed(ClosedKeyStorage), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PrivateKeyData { + Clear([u8; 32]), + Encrypted(Vec), +} + +impl fmt::Display for PrivateKeyData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PrivateKeyData::Clear(data) => { + write!(f, "Clear({:?})", hex::encode(data)) + } + PrivateKeyData::Encrypted(data) => { + write!(f, "Encrypted({} bytes)", data.len()) + } + } + } +} + +impl Default for KeyStorage { + fn default() -> Self { + Self::Closed(ClosedKeyStorage::default()) + } +} + +impl From> + for KeyStorage +{ + fn from( + value: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, [u8; 32])>, + ) -> Self { + Self::Open(ClearKeyStorage::from(value)) + } +} + +#[derive(Debug, Encode, Decode, Clone, PartialEq)] +pub struct ClearKeyStorage { + pub private_keys: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, [u8; 32])>, +} + +impl From> + for ClearKeyStorage +{ + fn from( + value: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, [u8; 32])>, + ) -> Self { + Self { + private_keys: value, + } + } +} + +impl ClearKeyStorage { + pub fn get( + &self, + key: &(PrivateKeyTarget, KeyID), + ) -> Option<&(QualifiedIdentityPublicKey, [u8; 32])> { + self.private_keys.get(key) + } + + pub fn insert( + &mut self, + key: (PrivateKeyTarget, KeyID), + value: (QualifiedIdentityPublicKey, [u8; 32]), + ) { + self.private_keys.insert(key, value); + } +} + +#[derive(Debug, Default, Encode, Decode, Clone, PartialEq)] +pub struct ClosedKeyStorage { + pub encrypted_private_keys: + BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, Vec)>, +} + +impl ClosedKeyStorage { + pub fn get( + &self, + key: &(PrivateKeyTarget, KeyID), + ) -> Option<&(QualifiedIdentityPublicKey, Vec)> { + self.encrypted_private_keys.get(key) + } + pub fn insert( + &mut self, + key: (PrivateKeyTarget, KeyID), + value: (QualifiedIdentityPublicKey, Vec), + ) { + self.encrypted_private_keys.insert(key, value); + } +} + +impl KeyStorage { + pub fn get( + &self, + key: &(PrivateKeyTarget, KeyID), + ) -> Result, String> { + match self { + KeyStorage::Open(open) => Ok(open.get(key)), + KeyStorage::Closed(_) => Err("Key is encrypted, please enter password".to_string()), + } + } + + pub fn get_private_key_data(&self, key: &(PrivateKeyTarget, KeyID)) -> Option { + match self { + KeyStorage::Open(open) => open.get(key).map(|(_, k)| PrivateKeyData::Clear(*k)), + KeyStorage::Closed(closed) => closed + .get(key) + .map(|(_, k)| PrivateKeyData::Encrypted(k.clone())), + } + } + + pub fn has(&self, key: &(PrivateKeyTarget, KeyID)) -> bool { + match self { + KeyStorage::Open(open) => open.private_keys.contains_key(key), + KeyStorage::Closed(closed) => closed.encrypted_private_keys.contains_key(key), + } + } + + pub fn keys_set(&self) -> BTreeSet<(PrivateKeyTarget, KeyID)> { + match self { + KeyStorage::Open(open) => open.private_keys.keys().cloned().collect(), + KeyStorage::Closed(closed) => closed.encrypted_private_keys.keys().cloned().collect(), + } + } + + pub fn identity_public_keys(&self) -> Vec<(&PrivateKeyTarget, &QualifiedIdentityPublicKey)> { + match self { + KeyStorage::Open(open) => open + .private_keys + .iter() + .map(|((target, _), (key, _))| (target, key)) + .collect(), + KeyStorage::Closed(closed) => closed + .encrypted_private_keys + .iter() + .map(|((target, _), (key, _))| (target, key)) + .collect(), + } + } + + /// Inserts an unencrypted key into `ClearKeyStorage`. Returns an error if the storage is closed. + pub fn insert_non_encrypted( + &mut self, + key: (PrivateKeyTarget, KeyID), + value: (QualifiedIdentityPublicKey, [u8; 32]), + ) -> Result<(), String> { + match self { + KeyStorage::Open(open) => { + open.insert(key, value); + Ok(()) + } + KeyStorage::Closed(_) => { + Err("Cannot insert non-encrypted key into closed storage".to_string()) + } + } + } + + /// Inserts an encrypted key into `ClosedKeyStorage`. Returns an error if the storage is open. + pub fn insert_encrypted( + &mut self, + key: (PrivateKeyTarget, KeyID), + value: (QualifiedIdentityPublicKey, Vec), + ) -> Result<(), String> { + match self { + KeyStorage::Closed(closed) => { + closed.insert(key, value); + Ok(()) + } + KeyStorage::Open(_) => Err("Cannot insert encrypted key into open storage".to_string()), + } + } +} diff --git a/src/model/qualified_identity.rs b/src/model/qualified_identity/mod.rs similarity index 81% rename from src/model/qualified_identity.rs rename to src/model/qualified_identity/mod.rs index e98f8a2ef..00f19cd9c 100644 --- a/src/model/qualified_identity.rs +++ b/src/model/qualified_identity/mod.rs @@ -1,3 +1,8 @@ +pub mod encrypted_key_storage; +pub mod qualified_identity_public_key; + +use crate::model::qualified_identity::encrypted_key_storage::KeyStorage; +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use bincode::{Decode, Encode}; use dash_sdk::dashcore_rpc::dashcore::{signer, PubkeyHash}; use dash_sdk::dpp::bls_signatures::{Bls12381G2Impl, SignatureSchemes}; @@ -18,7 +23,7 @@ use dash_sdk::dpp::platform_value::BinaryData; use dash_sdk::dpp::state_transition::errors::InvalidIdentityPublicKeyTypeError; use dash_sdk::dpp::{bls_signatures, ed25519_dalek, ProtocolError}; use dash_sdk::platform::IdentityPublicKey; -use std::collections::{BTreeMap, HashSet}; +use std::collections::HashSet; use std::fmt::{Display, Formatter}; #[derive(Debug, Encode, Decode, PartialEq, Clone, Copy)] @@ -57,17 +62,17 @@ impl Display for IdentityType { } #[derive(Debug, Encode, Decode, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub enum EncryptedPrivateKeyTarget { +pub enum PrivateKeyTarget { PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, PrivateKeyOnOperatorIdentity, } -impl From for EncryptedPrivateKeyTarget { +impl From for PrivateKeyTarget { fn from(value: Purpose) -> Self { match value { - Purpose::VOTING => EncryptedPrivateKeyTarget::PrivateKeyOnVoterIdentity, - _ => EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity, + Purpose::VOTING => PrivateKeyTarget::PrivateKeyOnVoterIdentity, + _ => PrivateKeyTarget::PrivateKeyOnMainIdentity, } } } @@ -86,8 +91,7 @@ pub struct QualifiedIdentity { pub associated_owner_key_id: Option, pub identity_type: IdentityType, pub alias: Option, - pub encrypted_private_keys: - BTreeMap<(EncryptedPrivateKeyTarget, KeyID), (IdentityPublicKey, [u8; 32])>, + pub private_keys: KeyStorage, pub dpns_names: Vec, } @@ -98,11 +102,12 @@ impl Signer for QualifiedIdentity { data: &[u8], ) -> Result { let (_, private_key) = self - .encrypted_private_keys + .private_keys .get(&( identity_public_key.purpose().into(), identity_public_key.id(), )) + .map_err(|e| ProtocolError::Generic(e))? .ok_or(ProtocolError::Generic(format!( "{:?} not found in {:?}", identity_public_key, self @@ -144,12 +149,10 @@ impl Signer for QualifiedIdentity { } fn can_sign_with(&self, identity_public_key: &IdentityPublicKey) -> bool { - self.encrypted_private_keys - .get(&( - identity_public_key.purpose().into(), - identity_public_key.id(), - )) - .is_some() + self.private_keys.has(&( + identity_public_key.purpose().into(), + identity_public_key.id(), + )) } } @@ -204,16 +207,16 @@ impl QualifiedIdentity { }) } - pub fn can_sign_with_master_key(&self) -> Option<&IdentityPublicKey> { + pub fn can_sign_with_master_key(&self) -> Option<&QualifiedIdentityPublicKey> { if self.identity_type != IdentityType::User { return None; } // Iterate through the encrypted private keys to check for a valid master key - for ((target, _), (public_key, _)) in &self.encrypted_private_keys { - if *target == EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity - && public_key.purpose() == Purpose::AUTHENTICATION - && public_key.security_level() == SecurityLevel::MASTER + for (target, public_key) in self.private_keys.identity_public_keys() { + if *target == PrivateKeyTarget::PrivateKeyOnMainIdentity + && public_key.identity_public_key.purpose() == Purpose::AUTHENTICATION + && public_key.identity_public_key.security_level() == SecurityLevel::MASTER { return Some(public_key); } @@ -234,23 +237,23 @@ impl QualifiedIdentity { ) } - pub fn available_withdrawal_keys(&self) -> Vec<&IdentityPublicKey> { + pub fn available_withdrawal_keys(&self) -> Vec<&QualifiedIdentityPublicKey> { let mut keys = vec![]; // Check the main identity's public keys - for ((target, _), (public_key, _)) in &self.encrypted_private_keys { + for (target, public_key) in self.private_keys.identity_public_keys() { match (self.identity_type, target) { - (IdentityType::User, EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity) => { - if public_key.purpose() == Purpose::TRANSFER { + (IdentityType::User, PrivateKeyTarget::PrivateKeyOnMainIdentity) => { + if public_key.identity_public_key.purpose() == Purpose::TRANSFER { keys.push(public_key); } } (IdentityType::Masternode | IdentityType::Evonode, target_type) => { - if target_type == &EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity { - if public_key.purpose() == Purpose::OWNER { + if target_type == &PrivateKeyTarget::PrivateKeyOnMainIdentity { + if public_key.identity_public_key.purpose() == Purpose::OWNER { keys.push(public_key); } - if public_key.purpose() == Purpose::TRANSFER { + if public_key.identity_public_key.purpose() == Purpose::TRANSFER { keys.push(public_key); } } @@ -262,12 +265,12 @@ impl QualifiedIdentity { keys } - pub fn available_transfer_keys(&self) -> Vec<&IdentityPublicKey> { + pub fn available_transfer_keys(&self) -> Vec<&QualifiedIdentityPublicKey> { let mut keys = vec![]; // Check the main identity's public keys - for (public_key, _) in self.encrypted_private_keys.values() { - if public_key.purpose() == Purpose::TRANSFER { + for (_, public_key) in self.private_keys.identity_public_keys() { + if public_key.identity_public_key.purpose() == Purpose::TRANSFER { keys.push(public_key); } } @@ -285,7 +288,7 @@ impl From for QualifiedIdentity { associated_owner_key_id: None, identity_type: IdentityType::User, alias: None, - encrypted_private_keys: Default::default(), + private_keys: Default::default(), dpns_names: vec![], } } diff --git a/src/model/qualified_identity/qualified_identity_public_key.rs b/src/model/qualified_identity/qualified_identity_public_key.rs new file mode 100644 index 000000000..0bc0b730a --- /dev/null +++ b/src/model/qualified_identity/qualified_identity_public_key.rs @@ -0,0 +1,202 @@ +use crate::model::wallet::{Wallet, WalletSeedHash}; +use bincode::de::{BorrowDecoder, Decoder}; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{BorrowDecode, Decode, Encode}; +use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; +use dash_sdk::dpp::dashcore::bip32::ChildNumber; +use dash_sdk::platform::IdentityPublicKey; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, Clone, PartialEq)] +pub struct QualifiedIdentityPublicKey { + pub identity_public_key: IdentityPublicKey, + pub in_wallet_at_derivation_path: Option<(WalletSeedHash, DerivationPath)>, +} + +impl Encode for QualifiedIdentityPublicKey { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + // Encode `identity_public_key` + self.identity_public_key.encode(encoder)?; + + // Encode `in_wallet_at_derivation_path` + match &self.in_wallet_at_derivation_path { + Some((hash, derivation_path)) => { + // Indicate that the option is `Some` + true.encode(encoder)?; + + // Encode the `hash` + hash.encode(encoder)?; + + // Encode the length of the `DerivationPath` + derivation_path.len().encode(encoder)?; + + // Encode each `ChildNumber` in the `DerivationPath` + for child in derivation_path.into_iter() { + match child { + ChildNumber::Normal { index } => { + 0u8.encode(encoder)?; // Discriminant for Normal + index.encode(encoder)?; + } + ChildNumber::Hardened { index } => { + 1u8.encode(encoder)?; // Discriminant for Hardened + index.encode(encoder)?; + } + ChildNumber::Normal256 { index } => { + 2u8.encode(encoder)?; // Discriminant for Normal256 + index.encode(encoder)?; + } + ChildNumber::Hardened256 { index } => { + 3u8.encode(encoder)?; // Discriminant for Hardened256 + index.encode(encoder)?; + } + } + } + } + None => { + // Indicate that the option is `None` + false.encode(encoder)?; + } + } + + Ok(()) + } +} + +impl Decode for QualifiedIdentityPublicKey { + fn decode(decoder: &mut D) -> Result { + // Decode `identity_public_key` + let identity_public_key = IdentityPublicKey::decode(decoder)?; + + // Decode `in_wallet_at_derivation_path` + let has_derivation_path = bool::decode(decoder)?; + let in_wallet_at_derivation_path = if has_derivation_path { + // Decode the `hash` + let hash: [u8; 32] = Decode::decode(decoder)?; + + // Decode the length of the `DerivationPath` + let path_len = usize::decode(decoder)?; + + // Decode each `ChildNumber` in the `DerivationPath` + let mut path = Vec::with_capacity(path_len); + for _ in 0..path_len { + let discriminant = u8::decode(decoder)?; + let child_number = match discriminant { + 0 => ChildNumber::Normal { + index: u32::decode(decoder)?, + }, + 1 => ChildNumber::Hardened { + index: u32::decode(decoder)?, + }, + 2 => ChildNumber::Normal256 { + index: <[u8; 32]>::decode(decoder)?, + }, + 3 => ChildNumber::Hardened256 { + index: <[u8; 32]>::decode(decoder)?, + }, + _ => return Err(DecodeError::OtherString("Invalid ChildNumber type".into())), + }; + path.push(child_number); + } + + Some((hash, DerivationPath::from(path))) + } else { + None + }; + + Ok(Self { + identity_public_key, + in_wallet_at_derivation_path, + }) + } +} + +impl<'de> BorrowDecode<'de> for QualifiedIdentityPublicKey { + fn borrow_decode>(decoder: &mut D) -> Result { + // Decode `identity_public_key` + let identity_public_key = IdentityPublicKey::decode(decoder)?; + + // Decode `in_wallet_at_derivation_path` + let has_derivation_path = bool::decode(decoder)?; + let in_wallet_at_derivation_path = if has_derivation_path { + // Decode the `hash` + let hash: [u8; 32] = Decode::decode(decoder)?; + + // Decode the length of the `DerivationPath` + let path_len = usize::decode(decoder)?; + + // Decode each `ChildNumber` in the `DerivationPath` + let mut path = Vec::with_capacity(path_len); + for _ in 0..path_len { + let discriminant = u8::decode(decoder)?; + let child_number = match discriminant { + 0 => ChildNumber::Normal { + index: u32::decode(decoder)?, + }, + 1 => ChildNumber::Hardened { + index: u32::decode(decoder)?, + }, + 2 => ChildNumber::Normal256 { + index: <[u8; 32]>::decode(decoder)?, + }, + 3 => ChildNumber::Hardened256 { + index: <[u8; 32]>::decode(decoder)?, + }, + _ => return Err(DecodeError::OtherString("Invalid ChildNumber type".into())), + }; + path.push(child_number); + } + + Some((hash, DerivationPath::from(path))) + } else { + None + }; + + Ok(Self { + identity_public_key, + in_wallet_at_derivation_path, + }) + } +} + +impl From for QualifiedIdentityPublicKey { + fn from(value: IdentityPublicKey) -> Self { + Self { + identity_public_key: value, + in_wallet_at_derivation_path: None, + } + } +} + +impl QualifiedIdentityPublicKey { + pub fn from_identity_public_key_with_wallets_check( + value: IdentityPublicKey, + _wallets: &[Arc>], + ) -> Self { + // Initialize `in_wallet_at_derivation_path` as `None` + let mut in_wallet_at_derivation_path = None; + + // // Iterate over each wallet to check for matching derivation paths + // for locked_wallet in wallets { + // let wallet = locked_wallet.read().unwrap(); + // for (address, derivation_path) in &wallet.known_addresses { + // // Check if this address corresponds to the identity public key's address + // if wallet.master_bip44_ecdsa_extended_public_key.to_string() == value.public_key_string() { + // // Compute the hash (for example, SHA-256 or any other hash, adjust as needed) + // let hash = some_hash_function(&derivation_path.to_string()); + // + // in_wallet_at_derivation_path = Some((hash, derivation_path.clone())); + // break; + // } + // } + // if in_wallet_at_derivation_path.is_some() { + // break; + // } + // } + + Self { + identity_public_key: value, + in_wallet_at_derivation_path, + } + } +} diff --git a/src/model/wallet/asset_lock_transaction.rs b/src/model/wallet/asset_lock_transaction.rs index ce600f4a4..47f36bc0e 100644 --- a/src/model/wallet/asset_lock_transaction.rs +++ b/src/model/wallet/asset_lock_transaction.rs @@ -194,7 +194,7 @@ impl Wallet { .input .iter() .enumerate() - .map(|(i, input)| { + .map(|(i, _)| { cache .legacy_signature_hash(i, &previous_tx_output.script_pubkey, sighash_u32) .expect("expected sighash") diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index 3027c3b82..134ff55a3 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -117,6 +117,8 @@ pub struct Wallet { pub is_main: bool, } +pub type WalletSeedHash = [u8; 32]; + #[derive(Debug, Clone, PartialEq)] pub enum WalletSeed { Open(OpenWalletSeed), @@ -131,7 +133,7 @@ pub struct OpenWalletSeed { #[derive(Debug, Clone, PartialEq)] pub struct ClosedWalletSeed { - pub seed_hash: [u8; 32], // SHA-256 hash of the seed + pub seed_hash: WalletSeedHash, // SHA-256 hash of the seed pub encrypted_seed: Vec, pub salt: Vec, pub nonce: Vec, diff --git a/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs b/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs index 00270f45b..1d19102c1 100644 --- a/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs +++ b/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs @@ -75,7 +75,7 @@ impl AddNewIdentityScreen { pub fn render_ui_by_using_unused_asset_lock( &mut self, ui: &mut Ui, - mut step_number: u32, + step_number: u32, ) -> AppAction { let mut action = AppAction::None; @@ -91,7 +91,6 @@ impl AddNewIdentityScreen { ); ui.add_space(10.0); self.render_choose_funding_asset_lock(ui); - step_number += 1; if ui.button("Create Identity").clicked() { action |= self.register_identity_clicked(FundingMethod::UseUnusedAssetLock); diff --git a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs index 90a00d249..6beeca6fe 100644 --- a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs +++ b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs @@ -32,15 +32,17 @@ impl AddNewIdentityScreen { step_number += 1; - ui.heading("2. How much of your wallet balance would you like to transfer?"); - step_number += 1; + ui.heading(format!( + "{}. How much of your wallet balance would you like to transfer?", + step_number + )); self.render_funding_amount_input(ui); // Extract the step from the RwLock to minimize borrow scope let step = self.step.read().unwrap().clone(); - let Ok(amount_dash) = self.funding_amount.parse::() else { + let Ok(_) = self.funding_amount.parse::() else { return action; }; diff --git a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs index 764fe3f10..946912cda 100644 --- a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs @@ -113,7 +113,7 @@ impl AddNewIdentityScreen { Ok(()) } - pub fn render_ui_by_wallet_qr_code(&mut self, ui: &mut Ui, mut step_number: u32) -> AppAction { + pub fn render_ui_by_wallet_qr_code(&mut self, ui: &mut Ui, step_number: u32) -> AppAction { let mut action = AppAction::None; // Extract the step from the RwLock to minimize borrow scope @@ -132,7 +132,6 @@ impl AddNewIdentityScreen { ) .as_str(), ); - step_number += 1; ui.add_space(8.0); diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 6dfea03e9..368bee115 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -9,23 +9,22 @@ use crate::backend_task::identity::{ }; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; -use crate::model::wallet::{Wallet, WalletSeed}; +use crate::model::wallet::Wallet; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, ScreenLike}; use arboard::Clipboard; use dash_sdk::dashcore_rpc::dashcore::Address; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::balances::credits::Duffs; -use dash_sdk::dpp::dashcore::{OutPoint, PrivateKey, ScriptBuf, Transaction, TxOut}; +use dash_sdk::dpp::dashcore::{OutPoint, PrivateKey, Transaction, TxOut}; use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; use dash_sdk::dpp::prelude::AssetLockProof; use eframe::egui::Context; -use egui::{Color32, ColorImage, ComboBox, TextureHandle, Ui}; +use egui::{Color32, ColorImage, ComboBox, Ui}; use image::Luma; use qrcode::QrCode; use serde::Deserialize; use std::cmp::PartialEq; -use std::ptr::read; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 8f78b4d86..fc6a2d789 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -2,7 +2,8 @@ use crate::app::{AppAction, DesiredAppAction}; use crate::backend_task::identity::IdentityTask; use crate::backend_task::BackendTask; use crate::context::AppContext; -use crate::model::qualified_identity::EncryptedPrivateKeyTarget::{ +use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; +use crate::model::qualified_identity::PrivateKeyTarget::{ PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; @@ -24,7 +25,6 @@ use eframe::egui::{self, Context}; use eframe::emath::Align; use egui::{Color32, Frame, Margin, RichText, Ui}; use egui_extras::{Column, TableBuilder}; -use std::collections::BTreeMap; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; @@ -102,6 +102,18 @@ impl IdentitiesScreen { .on_hover_text(helper); } + fn show_in_wallet(ui: &mut Ui, qualified_identity: &QualifiedIdentity) { + // Calculate the balance in DASH (10^-11 conversion) + let balance_in_dash = qualified_identity.identity.balance() as f64 * 1e-11; + + // Format the balance with 4 decimal places + let formatted_balance = format!("{:.4} DASH", balance_in_dash); + + // Add the label with hover text + ui.add(egui::Label::new(formatted_balance).sense(egui::Sense::hover())) + .on_hover_text(format!("{}", qualified_identity.identity.balance())); + } + fn show_balance(ui: &mut Ui, qualified_identity: &QualifiedIdentity) { // Calculate the balance in DASH (10^-11 conversion) let balance_in_dash = qualified_identity.identity.balance() as f64 * 1e-11; @@ -119,7 +131,7 @@ impl IdentitiesScreen { ui: &mut Ui, identity: &QualifiedIdentity, key: &IdentityPublicKey, - encrypted_private_key: Option<&[u8; 32]>, + encrypted_private_key: Option, ) -> AppAction { let button_color = if encrypted_private_key.is_some() { Color32::from_rgb(167, 232, 232) @@ -149,7 +161,7 @@ impl IdentitiesScreen { AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( identity.clone(), key.clone(), - encrypted_private_key.cloned(), + encrypted_private_key, &self.app_context, ))) } else { @@ -234,14 +246,15 @@ impl IdentitiesScreen { .resizable(true) .cell_layout(egui::Layout::left_to_right(Align::Center)) // Define columns with resizing and alignment - .column(Column::initial(40.0).resizable(true)) // Name + .column(Column::initial(60.0).resizable(true)) // Name .column(Column::initial(100.0).resizable(true)) // Identity ID + .column(Column::initial(60.0).resizable(true)) // In Wallet .column(Column::initial(100.0).resizable(true)) // Balance .column(Column::initial(80.0).resizable(true)) // Type - .column(Column::initial(80.0).resizable(true)) // Refresh .column(Column::initial(80.0).resizable(true)) // Keys .column(Column::initial(80.0).resizable(true)) // Withdraw .column(Column::initial(80.0).resizable(true)) // Transfer + .column(Column::initial(80.0).resizable(true)) // Actions .header(30.0, |mut header| { header.col(|ui| { ui.heading("Name"); @@ -249,6 +262,9 @@ impl IdentitiesScreen { header.col(|ui| { ui.heading("Identity ID"); }); + header.col(|ui| { + ui.heading("In Wallet"); + }); header.col(|ui| { ui.heading("Balance"); }); @@ -283,6 +299,9 @@ impl IdentitiesScreen { row.col(|ui| { Self::show_identity_id(ui, qualified_identity); }); + row.col(|ui| { + Self::show_in_wallet(ui, qualified_identity); + }); row.col(|ui| { Self::show_balance(ui, qualified_identity); }); @@ -299,9 +318,11 @@ impl IdentitiesScreen { for (key_id, key) in public_keys_vec.iter() { if total_keys_shown < max_keys_to_show { let holding_private_key = qualified_identity - .encrypted_private_keys - .get(&(PrivateKeyOnMainIdentity, **key_id)) - .map(|(_, p)| p); + .private_keys + .get_private_key_data(&( + PrivateKeyOnMainIdentity, + **key_id, + )); action |= self.show_public_key( ui, qualified_identity, @@ -326,12 +347,11 @@ impl IdentitiesScreen { if total_keys_shown < max_keys_to_show { let holding_private_key = qualified_identity - .encrypted_private_keys - .get(&( + .private_keys + .get_private_key_data(&( PrivateKeyOnVoterIdentity, **key_id, - )) - .map(|(_, p)| p); + )); action |= self.show_public_key( ui, qualified_identity, @@ -489,9 +509,8 @@ impl IdentitiesScreen { )); for (key_id, key) in main_identity_rest_keys { let holding_private_key = qualified_identity - .encrypted_private_keys - .get(&(PrivateKeyOnMainIdentity, **key_id)) - .map(|(_, p)| p); + .private_keys + .get_private_key_data(&(PrivateKeyOnMainIdentity, **key_id)); action |= self.show_public_key(ui, qualified_identity, *key, holding_private_key); } @@ -504,9 +523,8 @@ impl IdentitiesScreen { ui.label("Voter Identity Keys:"); for (key_id, key) in voter_public_keys_vec.iter() { let holding_private_key = qualified_identity - .encrypted_private_keys - .get(&(PrivateKeyOnVoterIdentity, **key_id)) - .map(|(_, p)| p); + .private_keys + .get_private_key_data(&(PrivateKeyOnVoterIdentity, **key_id)); action |= self.show_public_key(ui, qualified_identity, *key, holding_private_key); } } diff --git a/src/ui/key_info_screen.rs b/src/ui/key_info_screen.rs index 48c7307e1..0e40b1820 100644 --- a/src/ui/key_info_screen.rs +++ b/src/ui/key_info_screen.rs @@ -1,5 +1,6 @@ use crate::app::AppAction; use crate::context::AppContext; +use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; use crate::model::qualified_identity::QualifiedIdentity; use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; @@ -19,7 +20,7 @@ use std::sync::Arc; pub struct KeyInfoScreen { pub identity: QualifiedIdentity, pub key: IdentityPublicKey, - pub private_key_bytes: Option<[u8; 32]>, + pub private_key_data: Option, pub app_context: Arc, private_key_input: String, error_message: Option, @@ -149,13 +150,20 @@ impl ScreenLike for KeyInfoScreen { ui.separator(); // Display the private key if available - if let Some(private_key) = &self.private_key_bytes { + if let Some(private_key) = self.private_key_data.as_mut() { ui.label("Private Key:"); - let private_key_hex = hex::encode(private_key); - ui.add( - TextEdit::multiline(&mut private_key_hex.as_str().to_owned()) - .desired_width(f32::INFINITY), - ); + match private_key { + PrivateKeyData::Clear(clear) => { + let private_key_hex = hex::encode(clear); + ui.add( + TextEdit::multiline(&mut private_key_hex.as_str().to_owned()) + .desired_width(f32::INFINITY), + ); + } + PrivateKeyData::Encrypted(_) => { + ui.label("key is encrypted"); + } + } } else { ui.label("Enter Private Key:"); ui.text_edit_singleline(&mut self.private_key_input); @@ -179,13 +187,13 @@ impl KeyInfoScreen { pub fn new( identity: QualifiedIdentity, key: IdentityPublicKey, - private_key_bytes: Option<[u8; 32]>, + private_key_bytes: Option, app_context: &Arc, ) -> Self { Self { identity, key, - private_key_bytes, + private_key_data: private_key_bytes, app_context: app_context.clone(), private_key_input: String::new(), error_message: None, @@ -204,11 +212,13 @@ impl KeyInfoScreen { self.error_message = Some(format!("Issue verifying private key {}", err)); } else if validation_result.unwrap() { // If valid, store the private key in the context and reset the input field - self.private_key_bytes = Some(private_key_bytes.clone()); - self.identity.encrypted_private_keys.insert( + self.private_key_data = Some(PrivateKeyData::Clear(private_key_bytes)); + if let Err(e) = self.identity.private_keys.insert_non_encrypted( (self.key.purpose().into(), self.key.id()), - (self.key.clone(), private_key_bytes), - ); + (self.key.clone().into(), private_key_bytes), + ) { + self.error_message = Some(e); + } match self .app_context .insert_local_qualified_identity(&self.identity, None) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index be4d5555f..6aed1fe62 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,7 @@ use crate::app::AppAction; use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; use crate::model::qualified_identity::QualifiedIdentity; use crate::ui::add_key_screen::AddKeyScreen; use crate::ui::document_query_screen::DocumentQueryScreen; @@ -121,7 +122,7 @@ pub enum ScreenType { WithdrawalScreen(QualifiedIdentity), TransferScreen(QualifiedIdentity), AddKeyScreen(QualifiedIdentity), - KeyInfo(QualifiedIdentity, IdentityPublicKey, Option<[u8; 32]>), + KeyInfo(QualifiedIdentity, IdentityPublicKey, Option), Keys(Identity), DocumentQueryScreen, WithdrawsStatus, @@ -283,7 +284,7 @@ impl Screen { Screen::KeyInfoScreen(screen) => ScreenType::KeyInfo( screen.identity.clone(), screen.key.clone(), - screen.private_key_bytes.clone(), + screen.private_key_data.clone(), ), Screen::IdentitiesScreen(_) => ScreenType::Identities, Screen::DPNSContestedNamesScreen(DPNSContestedNamesScreen { diff --git a/src/ui/transfers/mod.rs b/src/ui/transfers/mod.rs index 6b50a23b1..9e49992fb 100644 --- a/src/ui/transfers/mod.rs +++ b/src/ui/transfers/mod.rs @@ -6,7 +6,6 @@ use crate::model::qualified_identity::QualifiedIdentity; use crate::ui::components::top_panel::add_top_panel; use crate::ui::key_info_screen::KeyInfoScreen; use crate::ui::{MessageType, Screen, ScreenLike}; -use dash_sdk::dashcore_rpc::dashcore::Address; use dash_sdk::dpp::fee::Credits; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; @@ -14,7 +13,6 @@ use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::platform::{Identifier, IdentityPublicKey}; use eframe::egui::{self, Context, Ui}; -use std::str::FromStr; use std::sync::Arc; pub struct TransferScreen { @@ -61,9 +59,16 @@ impl TransferScreen { } } else { for key in self.identity.available_transfer_keys() { - let label = - format!("Key ID: {} (Purpose: {:?})", key.id(), key.purpose()); - ui.selectable_value(&mut self.selected_key, Some(key.clone()), label); + let label = format!( + "Key ID: {} (Purpose: {:?})", + key.identity_public_key.id(), + key.identity_public_key.purpose() + ); + ui.selectable_value( + &mut self.selected_key, + Some(key.identity_public_key.clone()), + label, + ); } } }); diff --git a/src/ui/wallet/add_new_wallet_screen.rs b/src/ui/wallet/add_new_wallet_screen.rs index 46752ef2a..abd4b65a0 100644 --- a/src/ui/wallet/add_new_wallet_screen.rs +++ b/src/ui/wallet/add_new_wallet_screen.rs @@ -4,10 +4,7 @@ use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; use eframe::egui::Context; -use crate::model::wallet::{ - ClosedWalletSeed, DerivationPathReference, DerivationPathType, OpenWalletSeed, Wallet, - WalletSeed, -}; +use crate::model::wallet::{ClosedWalletSeed, OpenWalletSeed, Wallet, WalletSeed}; use crate::ui::components::entropy_grid::U256EntropyGrid; use bip39::{Language, Mnemonic}; use dash_sdk::dashcore_rpc::dashcore::bip32::{ChildNumber, DerivationPath}; diff --git a/src/ui/wallet/import_wallet_screen.rs b/src/ui/wallet/import_wallet_screen.rs index 32fdd52fd..90c32b63c 100644 --- a/src/ui/wallet/import_wallet_screen.rs +++ b/src/ui/wallet/import_wallet_screen.rs @@ -10,7 +10,7 @@ use egui::{ Color32, ComboBox, Direction, FontId, Frame, Grid, Layout, Margin, RichText, Stroke, TextStyle, Ui, Vec2, }; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; pub struct ImportWalletScreen { seed_phrase: Option, @@ -183,7 +183,7 @@ impl ImportWalletScreen { impl ScreenLike for ImportWalletScreen { fn ui(&mut self, ctx: &Context) -> AppAction { - let mut action = add_top_panel( + let action = add_top_panel( ctx, &self.app_context, vec![ diff --git a/src/ui/wallet/wallets_screen.rs b/src/ui/wallet/wallets_screen.rs index f686a0582..312d5ee5f 100644 --- a/src/ui/wallet/wallets_screen.rs +++ b/src/ui/wallet/wallets_screen.rs @@ -1,6 +1,6 @@ use crate::app::{AppAction, DesiredAppAction}; use crate::backend_task::core::CoreTask; -use crate::backend_task::BackendTask; +use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; @@ -21,6 +21,7 @@ enum SortColumn { TotalReceived, Type, Index, + DerivationPath, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -41,6 +42,7 @@ pub trait DerivationPathHelpers { fn is_bip44(&self, network: Network) -> bool; fn is_bip44_external(&self, network: Network) -> bool; fn is_bip44_change(&self, network: Network) -> bool; + fn is_asset_lock_funding(&self, network: Network) -> bool; } impl DerivationPathHelpers for DerivationPath { fn is_bip44(&self, network: Network) -> bool { @@ -80,6 +82,21 @@ impl DerivationPathHelpers for DerivationPath { && components[1] == ChildNumber::Hardened { index: coin_type } && components[3] == ChildNumber::Normal { index: 1 } } + + fn is_asset_lock_funding(&self, network: Network) -> bool { + // BIP44 change paths have the form m/44'/coin_type'/account'/1/... + let coin_type = match network { + Network::Dash => 5, + _ => 1, + }; + // Asset lock funding paths have the form m/9'/coin_type'/5'/1'/x + let components = self.as_ref(); + components.len() == 5 + && components[0] == ChildNumber::Hardened { index: 9 } + && components[1] == ChildNumber::Hardened { index: coin_type } + && components[2] == ChildNumber::Hardened { index: 5 } + && components[3] == ChildNumber::Hardened { index: 1 } + } } // Define a struct to hold the address data @@ -90,6 +107,7 @@ struct AddressData { total_received: u64, address_type: String, index: u32, + derivation_path: DerivationPath, } impl WalletsBalancesScreen { @@ -139,6 +157,7 @@ impl WalletsBalancesScreen { SortColumn::TotalReceived => a.total_received.cmp(&b.total_received), SortColumn::Type => a.address_type.cmp(&b.address_type), SortColumn::Index => a.index.cmp(&b.index), + SortColumn::DerivationPath => a.derivation_path.cmp(&b.derivation_path), }; if self.sort_order == SortOrder::Ascending { @@ -272,11 +291,13 @@ impl WalletsBalancesScreen { }; let address_type = if derivation_path.is_bip44_external(self.app_context.network) { - "BIP44 External".to_string() + "Funds".to_string() } else if derivation_path.is_bip44_change(self.app_context.network) { - "BIP44 Change".to_string() + "Change".to_string() + } else if derivation_path.is_asset_lock_funding(self.app_context.network) { + "Identity Creation".to_string() } else { - "Unknown".to_string() + "System".to_string() }; AddressData { @@ -290,6 +311,7 @@ impl WalletsBalancesScreen { total_received, address_type, index, + derivation_path: derivation_path.clone(), } }) .collect::>() @@ -316,6 +338,7 @@ impl WalletsBalancesScreen { .column(Column::initial(150.0)) // Total Received .column(Column::initial(100.0)) // Type .column(Column::initial(60.0)) // Index + .column(Column::remainder()) // Derivation Path .header(30.0, |mut header| { header.col(|ui| { let label = if self.sort_column == SortColumn::Address { @@ -395,6 +418,19 @@ impl WalletsBalancesScreen { self.toggle_sort(SortColumn::Index); } }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::DerivationPath { + match self.sort_order { + SortOrder::Ascending => "Full Path ^", + SortOrder::Descending => "Full Path v", + } + } else { + "Full Path" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::DerivationPath); + } + }); }) .body(|mut body| { for data in &address_data { @@ -419,6 +455,9 @@ impl WalletsBalancesScreen { row.col(|ui| { ui.label(format!("{}", data.index)); }); + row.col(|ui| { + ui.label(format!("{}", data.derivation_path)); + }); }); } }); @@ -573,6 +612,10 @@ impl ScreenLike for WalletsBalancesScreen { action } + fn display_task_result(&mut self, backend_task_success_result: BackendTaskSuccessResult) { + println!("{:?}", backend_task_success_result) + } + fn display_message(&mut self, message: &str, message_type: MessageType) { self.error_message = Some((message.to_string(), message_type)); } diff --git a/src/ui/withdrawals/mod.rs b/src/ui/withdrawals/mod.rs index e166168ea..a7840ead3 100644 --- a/src/ui/withdrawals/mod.rs +++ b/src/ui/withdrawals/mod.rs @@ -60,9 +60,16 @@ impl WithdrawalScreen { } } else { for key in self.identity.available_withdrawal_keys() { - let label = - format!("Key ID: {} (Purpose: {:?})", key.id(), key.purpose()); - ui.selectable_value(&mut self.selected_key, Some(key.clone()), label); + let label = format!( + "Key ID: {} (Purpose: {:?})", + key.identity_public_key.id(), + key.identity_public_key.purpose() + ); + ui.selectable_value( + &mut self.selected_key, + Some(key.identity_public_key.clone()), + label, + ); } } }); diff --git a/src/ui/withdraws_status_screen.rs b/src/ui/withdraws_status_screen.rs index c34566dfe..5ad1b12a0 100644 --- a/src/ui/withdraws_status_screen.rs +++ b/src/ui/withdraws_status_screen.rs @@ -80,7 +80,7 @@ impl WithdrawsStatusScreen { } } - fn show_input_field(&mut self, ui: &mut Ui) {} + fn show_input_field(&mut self, _ui: &mut Ui) {} fn show_output(&mut self, ui: &mut egui::Ui) -> AppAction { let mut app_action = AppAction::None; From 05e104939072e29c7b6a226a2eaca1b550f1b8b5 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 Nov 2024 01:30:22 +0100 Subject: [PATCH 03/11] compiling --- src/backend_task/identity/load_identity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index 82076f453..240cd2fb4 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -61,7 +61,7 @@ impl AppContext { let mut encrypted_private_keys = BTreeMap::new(); - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read().unwrap().clone(); if identity_type != IdentityType::User && owner_private_key_bytes.is_some() { let owner_private_key_bytes = owner_private_key_bytes.unwrap(); From 9c6c9634ebd66455f11d8a13ff908890d87e7969 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 Nov 2024 10:09:43 +0100 Subject: [PATCH 04/11] more work --- src/database/settings.rs | 2 +- .../encrypted_key_storage.rs | 25 +++++- src/model/wallet/encryption.rs | 86 +++++++++++-------- .../add_existing_identity_screen.rs | 2 +- src/ui/identities/identities_screen.rs | 53 ++++++++++-- src/ui/wallet/add_new_wallet_screen.rs | 30 ++++++- src/ui/wallet/import_wallet_screen.rs | 10 ++- 7 files changed, 156 insertions(+), 52 deletions(-) diff --git a/src/database/settings.rs b/src/database/settings.rs index b4b63b705..6dc252dd6 100644 --- a/src/database/settings.rs +++ b/src/database/settings.rs @@ -36,7 +36,7 @@ impl Database { "UPDATE settings SET main_password_salt = ?, main_password_nonce = ?, - password_check = ?, + password_check = ? WHERE id = 1", rusqlite::params![salt, nonce, password_check], )?; diff --git a/src/model/qualified_identity/encrypted_key_storage.rs b/src/model/qualified_identity/encrypted_key_storage.rs index ba3826891..a061d6520 100644 --- a/src/model/qualified_identity/encrypted_key_storage.rs +++ b/src/model/qualified_identity/encrypted_key_storage.rs @@ -1,7 +1,8 @@ use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::PrivateKeyTarget; use bincode::{Decode, Encode}; -use dash_sdk::dpp::identity::KeyID; +use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dash_sdk::dpp::identity::{KeyID, Purpose, SecurityLevel}; use std::collections::{BTreeMap, BTreeSet}; use std::fmt; @@ -122,6 +123,28 @@ impl KeyStorage { } } + pub fn find_master_key(&self) -> Option<&QualifiedIdentityPublicKey> { + match self { + KeyStorage::Open(open) => open + .private_keys + .values() + .find(|(public_key, _)| { + public_key.identity_public_key.purpose() == Purpose::AUTHENTICATION + && public_key.identity_public_key.security_level() == SecurityLevel::MASTER + }) + .map(|(public_key, _)| public_key), + + KeyStorage::Closed(closed) => closed + .encrypted_private_keys + .values() + .find(|(public_key, _)| { + public_key.identity_public_key.purpose() == Purpose::AUTHENTICATION + && public_key.identity_public_key.security_level() == SecurityLevel::MASTER + }) + .map(|(public_key, _)| public_key), + } + } + pub fn has(&self, key: &(PrivateKeyTarget, KeyID)) -> bool { match self { KeyStorage::Open(open) => open.private_keys.contains_key(key), diff --git a/src/model/wallet/encryption.rs b/src/model/wallet/encryption.rs index b36b5ddb8..fae091155 100644 --- a/src/model/wallet/encryption.rs +++ b/src/model/wallet/encryption.rs @@ -7,9 +7,55 @@ use rand::RngCore; const SALT_SIZE: usize = 16; // 128-bit salt const NONCE_SIZE: usize = 12; // 96-bit nonce for AES-GCM +pub const DASH_SECRET_MESSAGE: &[u8; 19] = b"dash_secret_message"; + use crate::model::wallet::ClosedWalletSeed; use sha2::{Digest, Sha256}; +/// Derive a key from the password and salt using Argon2. +pub fn derive_password_key(password: &str, salt: &[u8]) -> Result, String> { + let key_length = 32; // For AES-256, we use a 256-bit key (32 bytes) + + let mut key = vec![0u8; key_length]; + + // Using Argon2 with default parameters + let argon2 = Argon2::default(); + + // Deriving the key + argon2 + .hash_password_into(password.as_bytes(), salt, &mut key) + .map_err(|e| e.to_string())?; + + Ok(key) +} + +/// Encrypt the seed using AES-256-GCM. +pub fn encrypt_message( + message: &[u8], + password: &str, +) -> Result<(Vec, Vec, Vec), String> { + // Generate a random salt + let mut salt = vec![0u8; SALT_SIZE]; + OsRng.fill_bytes(&mut salt); + + // Derive the key + let key = derive_password_key(password, &salt)?; + + // Generate a random nonce + let mut nonce = vec![0u8; NONCE_SIZE]; + OsRng.fill_bytes(&mut nonce); + + // Create cipher instance + let cipher = Aes256Gcm::new_from_slice(&key).map_err(|e| e.to_string())?; + + // Encrypt the seed + let encrypted_seed = cipher + .encrypt(Nonce::from_slice(&nonce), message) + .map_err(|e| e.to_string())?; + + Ok((encrypted_seed, salt, nonce)) +} + impl ClosedWalletSeed { pub fn compute_seed_hash(seed: &[u8]) -> [u8; 32] { let mut hasher = Sha256::new(); @@ -20,54 +66,18 @@ impl ClosedWalletSeed { seed_hash } - /// Derive a key from the password and salt using Argon2. - fn derive_key(password: &str, salt: &[u8]) -> Result, String> { - let key_length = 32; // For AES-256, we use a 256-bit key (32 bytes) - - let mut key = vec![0u8; key_length]; - - // Using Argon2 with default parameters - let argon2 = Argon2::default(); - - // Deriving the key - argon2 - .hash_password_into(password.as_bytes(), salt, &mut key) - .map_err(|e| e.to_string())?; - - Ok(key) - } - /// Encrypt the seed using AES-256-GCM. pub(crate) fn encrypt_seed( seed: &[u8], password: &str, ) -> Result<(Vec, Vec, Vec), String> { - // Generate a random salt - let mut salt = vec![0u8; SALT_SIZE]; - OsRng.fill_bytes(&mut salt); - - // Derive the key - let key = Self::derive_key(password, &salt)?; - - // Generate a random nonce - let mut nonce = vec![0u8; NONCE_SIZE]; - OsRng.fill_bytes(&mut nonce); - - // Create cipher instance - let cipher = Aes256Gcm::new_from_slice(&key).map_err(|e| e.to_string())?; - - // Encrypt the seed - let encrypted_seed = cipher - .encrypt(Nonce::from_slice(&nonce), seed) - .map_err(|e| e.to_string())?; - - Ok((encrypted_seed, salt, nonce)) + encrypt_message(seed, password) } /// Decrypt the seed using AES-256-GCM. pub fn decrypt_seed(&self, password: &str) -> Result<[u8; 64], String> { // Derive the key - let key = Self::derive_key(password, &self.salt)?; + let key = derive_password_key(password, &self.salt)?; // Create cipher instance let cipher = Aes256Gcm::new_from_slice(&key).map_err(|e| e.to_string())?; diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index 28c96b37f..6f1ede3a6 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -276,7 +276,7 @@ impl ScreenLike for AddExistingIdentityScreen { } ui.horizontal(|ui| { - ui.label("Identity ID (Hex or Base58):"); + ui.label("Identity ID / ProTxHash (Hex or Base58):"); ui.text_edit_singleline(&mut self.identity_id_input); }); diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index fc6a2d789..0adf19c2d 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -7,6 +7,7 @@ use crate::model::qualified_identity::PrivateKeyTarget::{ PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; +use crate::model::wallet::WalletSeedHash; use crate::ui::add_key_screen::AddKeyScreen; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; @@ -25,6 +26,7 @@ use eframe::egui::{self, Context}; use eframe::emath::Align; use egui::{Color32, Frame, Margin, RichText, Ui}; use egui_extras::{Column, TableBuilder}; +use std::collections::HashMap; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; @@ -33,6 +35,7 @@ pub struct IdentitiesScreen { pub app_context: Arc, pub show_more_keys_popup: Option, pub identity_to_remove: Option, + pub wallet_seed_hash_cache: HashMap, } impl IdentitiesScreen { @@ -50,6 +53,7 @@ impl IdentitiesScreen { app_context: app_context.clone(), show_more_keys_popup: None, identity_to_remove: None, + wallet_seed_hash_cache: Default::default(), } } @@ -102,15 +106,50 @@ impl IdentitiesScreen { .on_hover_text(helper); } - fn show_in_wallet(ui: &mut Ui, qualified_identity: &QualifiedIdentity) { - // Calculate the balance in DASH (10^-11 conversion) - let balance_in_dash = qualified_identity.identity.balance() as f64 * 1e-11; + fn find_wallet(&mut self, wallet_seed_hash: &WalletSeedHash) -> Option { + if let Some(in_wallet_text) = self.wallet_seed_hash_cache.get(wallet_seed_hash) { + return Some(in_wallet_text.clone()); + } + let wallets = self.app_context.wallets.read().unwrap(); + for wallet in wallets.iter() { + let wallet_guard = wallet.read().unwrap(); + if &wallet_guard.seed_hash() == wallet_seed_hash { + let in_wallet_text = if let Some(alias) = wallet_guard.alias.as_ref() { + alias.clone() + } else { + hex::encode(wallet_guard.seed_hash()) + .split_at(5) + .0 + .to_string() + }; + self.wallet_seed_hash_cache + .insert(*wallet_seed_hash, in_wallet_text.clone()); + return Some(in_wallet_text); + } + } + return None; + } - // Format the balance with 4 decimal places - let formatted_balance = format!("{:.4} DASH", balance_in_dash); + fn show_in_wallet(&mut self, ui: &mut Ui, qualified_identity: &QualifiedIdentity) { + let master_identity_public_key = qualified_identity.private_keys.find_master_key(); + + let message = match master_identity_public_key { + None => "".to_string(), + Some(qualified_identity_public_key) => { + match qualified_identity_public_key + .in_wallet_at_derivation_path + .as_ref() + { + None => "".to_string(), + Some((wallet_seed_hash, _)) => { + self.find_wallet(wallet_seed_hash).unwrap_or_default() + } + } + } + }; // Add the label with hover text - ui.add(egui::Label::new(formatted_balance).sense(egui::Sense::hover())) + ui.add(egui::Label::new(message).sense(egui::Sense::hover())) .on_hover_text(format!("{}", qualified_identity.identity.balance())); } @@ -300,7 +339,7 @@ impl IdentitiesScreen { Self::show_identity_id(ui, qualified_identity); }); row.col(|ui| { - Self::show_in_wallet(ui, qualified_identity); + self.show_in_wallet(ui, qualified_identity); }); row.col(|ui| { Self::show_balance(ui, qualified_identity); diff --git a/src/ui/wallet/add_new_wallet_screen.rs b/src/ui/wallet/add_new_wallet_screen.rs index abd4b65a0..11040261a 100644 --- a/src/ui/wallet/add_new_wallet_screen.rs +++ b/src/ui/wallet/add_new_wallet_screen.rs @@ -4,6 +4,7 @@ use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; use eframe::egui::Context; +use crate::model::wallet::encryption::{encrypt_message, DASH_SECRET_MESSAGE}; use crate::model::wallet::{ClosedWalletSeed, OpenWalletSeed, Wallet, WalletSeed}; use crate::ui::components::entropy_grid::U256EntropyGrid; use bip39::{Language, Mnemonic}; @@ -54,6 +55,7 @@ pub struct AddNewWalletScreen { estimated_time_to_crack: String, error: Option, pub app_context: Arc, + use_password_for_app: bool, } impl AddNewWalletScreen { @@ -69,6 +71,7 @@ impl AddNewWalletScreen { estimated_time_to_crack: "".to_string(), error: None, app_context: app_context.clone(), + use_password_for_app: true, } } @@ -92,6 +95,14 @@ impl AddNewWalletScreen { // Encrypt the seed to obtain encrypted_seed, salt, and nonce let (encrypted_seed, salt, nonce) = ClosedWalletSeed::encrypt_seed(&seed, self.password.as_str())?; + if self.use_password_for_app { + let (encrypted_message, salt, nonce) = + encrypt_message(DASH_SECRET_MESSAGE, self.password.as_str())?; + self.app_context + .db + .update_main_password(&salt, &nonce, &encrypted_message) + .map_err(|e| e.to_string())?; + } (encrypted_seed, salt, nonce, true) }; @@ -321,6 +332,10 @@ impl ScreenLike for AddNewWalletScreen { ui.heading("2. Select your desired seed phrase language and press \"Generate\"."); self.render_seed_phrase_input(ui); + if self.seed_phrase.is_none() { + return; + } + ui.add_space(10.0); ui.heading( @@ -334,6 +349,10 @@ impl ScreenLike for AddNewWalletScreen { ui.checkbox(&mut self.wrote_it_down, "I wrote it down"); }); + if !self.wrote_it_down { + return; + } + ui.add_space(20.0); ui.heading("4. Select a wallet name to remember it. (This will not go to the blockchain)"); @@ -347,7 +366,7 @@ impl ScreenLike for AddNewWalletScreen { ui.add_space(20.0); - ui.heading("5. Add a password that must be used to unlock the wallet. (Optional but Recommended)"); + ui.heading("5. Add a password that must be used to unlock the wallet. (Optional but recommended)"); ui.add_space(8.0); @@ -408,9 +427,14 @@ impl ScreenLike for AddNewWalletScreen { self.estimated_time_to_crack )); + if self.app_context.password_info.is_none() { + ui.add_space(10.0); + ui.checkbox(&mut self.use_password_for_app, "Use password for Dash Evo Tool loose keys (recommended)"); + } + ui.add_space(20.0); - ui.heading("5. Save the wallet."); + ui.heading("6. Save the wallet."); ui.add_space(5.0); // Centered "Save Wallet" button at the bottom @@ -421,7 +445,7 @@ impl ScreenLike for AddNewWalletScreen { .min_size(Vec2::new(300.0, 60.0)) .rounding(10.0) .stroke(Stroke::new(1.5, Color32::WHITE)) - .sense(if self.wrote_it_down { + .sense(if self.wrote_it_down && self.seed_phrase.is_some() { egui::Sense::click() } else { egui::Sense::hover() diff --git a/src/ui/wallet/import_wallet_screen.rs b/src/ui/wallet/import_wallet_screen.rs index 90c32b63c..9f3c9eff0 100644 --- a/src/ui/wallet/import_wallet_screen.rs +++ b/src/ui/wallet/import_wallet_screen.rs @@ -209,6 +209,10 @@ impl ScreenLike for ImportWalletScreen { ui.heading("2. Select your desired seed phrase language and press \"Generate\""); self.render_seed_phrase_input(ui); + if self.seed_phrase.is_none() { + return; + } + ui.add_space(10.0); ui.heading( @@ -222,6 +226,10 @@ impl ScreenLike for ImportWalletScreen { ui.checkbox(&mut self.wrote_it_down, "I wrote it down"); }); + if !self.wrote_it_down { + return; + } + ui.add_space(20.0); ui.heading("4. Add an optional password that must be used to unlock the wallet"); @@ -244,7 +252,7 @@ impl ScreenLike for ImportWalletScreen { .min_size(Vec2::new(300.0, 60.0)) .rounding(10.0) .stroke(Stroke::new(1.5, Color32::WHITE)) - .sense(if self.wrote_it_down { + .sense(if self.wrote_it_down && self.seed_phrase.is_some() { egui::Sense::click() } else { egui::Sense::hover() From 5261983ee97dc8d8e86f92c2208b2492ca8c9211 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 Nov 2024 11:17:02 +0100 Subject: [PATCH 05/11] more work --- .../identity/add_key_to_identity.rs | 19 ++++---- src/backend_task/identity/load_identity.rs | 4 ++ src/backend_task/identity/mod.rs | 35 ++++++++------- .../identity/register_identity.rs | 3 +- .../qualified_identity_public_key.rs | 45 +++++++++++-------- src/model/wallet/mod.rs | 4 +- src/ui/add_key_screen.rs | 7 ++- .../identities/add_new_identity_screen/mod.rs | 4 +- 8 files changed, 73 insertions(+), 48 deletions(-) diff --git a/src/backend_task/identity/add_key_to_identity.rs b/src/backend_task/identity/add_key_to_identity.rs index 87f169371..3b2b9832a 100644 --- a/src/backend_task/identity/add_key_to_identity.rs +++ b/src/backend_task/identity/add_key_to_identity.rs @@ -20,7 +20,7 @@ impl AppContext { &self, sdk: &Sdk, mut qualified_identity: QualifiedIdentity, - mut public_key_to_add: IdentityPublicKey, + mut public_key_to_add: QualifiedIdentityPublicKey, private_key: [u8; 32], ) -> Result { let new_identity_nonce = sdk @@ -37,19 +37,20 @@ impl AppContext { .unwrap(); qualified_identity.identity = identity; qualified_identity.identity.bump_revision(); - public_key_to_add.set_id(qualified_identity.identity.get_public_key_max_id() + 1); - let qualified_key = QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( - public_key_to_add.clone(), - self.wallets.read().unwrap().as_slice(), - ); + public_key_to_add + .identity_public_key + .set_id(qualified_identity.identity.get_public_key_max_id() + 1); qualified_identity.private_keys.insert_non_encrypted( - (PrivateKeyOnMainIdentity, public_key_to_add.id()), - (qualified_key, private_key), + ( + PrivateKeyOnMainIdentity, + public_key_to_add.identity_public_key.id(), + ), + (public_key_to_add.clone(), private_key), )?; let state_transition = IdentityUpdateTransition::try_from_identity_with_signer( &qualified_identity.identity, &master_key_id, - vec![public_key_to_add.clone()], + vec![public_key_to_add.identity_public_key.clone()], vec![], new_identity_nonce, UserFeeIncrease::default(), diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index 240cd2fb4..2f6588120 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -71,6 +71,7 @@ impl AppContext { let qualified_key = QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( key, + self.network, wallets.as_slice(), ); encrypted_private_keys.insert( @@ -89,6 +90,7 @@ impl AppContext { let qualified_key = QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( key, + self.network, wallets.as_slice(), ); encrypted_private_keys.insert( @@ -125,6 +127,7 @@ impl AppContext { let qualified_key = QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( key.clone(), + self.network, wallets.as_slice(), ); encrypted_private_keys.insert( @@ -158,6 +161,7 @@ impl AppContext { let qualified_key = QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( public_key.clone(), + self.network, wallets.as_slice(), ); encrypted_private_keys.insert( diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index 0dc548c74..e8c6ccdb9 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -12,7 +12,8 @@ use crate::context::AppContext; use crate::model::qualified_identity::encrypted_key_storage::KeyStorage; use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::{IdentityType, PrivateKeyTarget, QualifiedIdentity}; -use crate::model::wallet::Wallet; +use crate::model::wallet::{Wallet, WalletSeedHash}; +use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::dashcore::{Address, PrivateKey, TxOut}; use dash_sdk::dpp::balances::credits::Duffs; @@ -44,13 +45,18 @@ pub struct IdentityInputToLoad { #[derive(Debug, Clone, PartialEq)] pub struct IdentityKeys { - pub(crate) master_private_key: Option, + pub(crate) master_private_key: Option<(PrivateKey, DerivationPath)>, pub(crate) master_private_key_type: KeyType, - pub(crate) keys_input: Vec<(PrivateKey, KeyType, Purpose, SecurityLevel)>, + pub(crate) keys_input: Vec<( + (PrivateKey, DerivationPath), + KeyType, + Purpose, + SecurityLevel, + )>, } impl IdentityKeys { - pub fn to_key_storage(&self, context: &AppContext) -> KeyStorage { + pub fn to_key_storage(&self, wallet_seed_hash: WalletSeedHash) -> KeyStorage { let Self { master_private_key, master_private_key_type, @@ -59,9 +65,7 @@ impl IdentityKeys { let secp = Secp256k1::new(); let mut key_map = BTreeMap::new(); - let wallets = context.wallets.read().unwrap(); - - if let Some(master_private_key) = master_private_key { + if let Some((master_private_key, master_private_key_derivation_path)) = master_private_key { let key = IdentityPublicKey::V0(IdentityPublicKeyV0 { id: 0, purpose: Purpose::AUTHENTICATION, @@ -74,9 +78,9 @@ impl IdentityKeys { }); let qualified_identity_public_key = - QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + QualifiedIdentityPublicKey::from_identity_public_key_in_wallet( key, - wallets.as_slice(), + Some((wallet_seed_hash, master_private_key_derivation_path.clone())), ); key_map.insert( (PrivateKeyTarget::PrivateKeyOnMainIdentity, 0), @@ -88,7 +92,7 @@ impl IdentityKeys { } key_map.extend(keys_input.iter().enumerate().map( - |(i, (private_key, key_type, purpose, security_level))| { + |(i, ((private_key, derivation_path), key_type, purpose, security_level))| { let id = (i + 1) as KeyID; let identity_public_key = IdentityPublicKey::V0(IdentityPublicKeyV0 { id, @@ -102,9 +106,9 @@ impl IdentityKeys { }); let qualified_identity_public_key = - QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( + QualifiedIdentityPublicKey::from_identity_public_key_in_wallet( identity_public_key, - wallets.as_slice(), + Some((wallet_seed_hash, derivation_path.clone())), ); ( (PrivateKeyTarget::PrivateKeyOnMainIdentity, id), @@ -123,10 +127,11 @@ impl IdentityKeys { master_private_key, master_private_key_type, keys_input, + .. } = self; let secp = Secp256k1::new(); let mut key_map = BTreeMap::new(); - if let Some(master_private_key) = master_private_key { + if let Some((master_private_key, _)) = master_private_key { let data = match master_private_key_type { KeyType::ECDSA_SECP256K1 => master_private_key.public_key(&secp).to_bytes().into(), KeyType::ECDSA_HASH160 => master_private_key @@ -151,7 +156,7 @@ impl IdentityKeys { key_map.insert(0, key); } key_map.extend(keys_input.iter().enumerate().map( - |(i, (private_key, key_type, purpose, security_level))| { + |(i, ((private_key, _), key_type, purpose, security_level))| { let id = (i + 1) as KeyID; let data = match key_type { KeyType::ECDSA_SECP256K1 => private_key.public_key(&secp).to_bytes().into(), @@ -216,7 +221,7 @@ pub struct RegisterDpnsNameInput { pub(crate) enum IdentityTask { LoadIdentity(IdentityInputToLoad), RegisterIdentity(IdentityRegistrationInfo), - AddKeyToIdentity(QualifiedIdentity, IdentityPublicKey, [u8; 32]), + AddKeyToIdentity(QualifiedIdentity, QualifiedIdentityPublicKey, [u8; 32]), WithdrawFromIdentity(QualifiedIdentity, Option
, Credits, Option), Transfer(QualifiedIdentity, Identifier, Credits, Option), RegisterDpnsName(RegisterDpnsNameInput), diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index 46a919809..c64f847ff 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -291,6 +291,7 @@ impl AppContext { let identity = Identity::new_with_id_and_keys(identity_id, public_keys, sdk.version()) .expect("expected to make identity"); + let wallet_seed_hash = wallet.read().unwrap().seed_hash(); let mut qualified_identity = QualifiedIdentity { identity: identity.clone(), associated_voter_identity: None, @@ -298,7 +299,7 @@ impl AppContext { associated_owner_key_id: None, identity_type: IdentityType::User, alias: None, - private_keys: keys.to_key_storage(self), + private_keys: keys.to_key_storage(wallet_seed_hash), dpns_names: vec![], }; diff --git a/src/model/qualified_identity/qualified_identity_public_key.rs b/src/model/qualified_identity/qualified_identity_public_key.rs index 0bc0b730a..086b3362b 100644 --- a/src/model/qualified_identity/qualified_identity_public_key.rs +++ b/src/model/qualified_identity/qualified_identity_public_key.rs @@ -5,6 +5,9 @@ use bincode::error::{DecodeError, EncodeError}; use bincode::{BorrowDecode, Decode, Encode}; use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::dashcore::bip32::ChildNumber; +use dash_sdk::dpp::dashcore::Network; +use dash_sdk::dpp::identity::hash::IdentityPublicKeyHashMethodsV0; +use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::platform::IdentityPublicKey; use std::sync::{Arc, RwLock}; @@ -169,30 +172,36 @@ impl From for QualifiedIdentityPublicKey { } impl QualifiedIdentityPublicKey { + pub fn from_identity_public_key_in_wallet( + identity_public_key: IdentityPublicKey, + in_wallet_at_derivation_path: Option<(WalletSeedHash, DerivationPath)>, + ) -> Self { + Self { + identity_public_key, + in_wallet_at_derivation_path, + } + } pub fn from_identity_public_key_with_wallets_check( value: IdentityPublicKey, - _wallets: &[Arc>], + network: Network, + wallets: &[Arc>], ) -> Self { // Initialize `in_wallet_at_derivation_path` as `None` let mut in_wallet_at_derivation_path = None; - // // Iterate over each wallet to check for matching derivation paths - // for locked_wallet in wallets { - // let wallet = locked_wallet.read().unwrap(); - // for (address, derivation_path) in &wallet.known_addresses { - // // Check if this address corresponds to the identity public key's address - // if wallet.master_bip44_ecdsa_extended_public_key.to_string() == value.public_key_string() { - // // Compute the hash (for example, SHA-256 or any other hash, adjust as needed) - // let hash = some_hash_function(&derivation_path.to_string()); - // - // in_wallet_at_derivation_path = Some((hash, derivation_path.clone())); - // break; - // } - // } - // if in_wallet_at_derivation_path.is_some() { - // break; - // } - // } + if let Ok(address) = value.address(network) { + // Iterate over each wallet to check for matching derivation paths + for locked_wallet in wallets { + let wallet = locked_wallet.read().unwrap(); + if let Some(derivation_path) = wallet.known_addresses.get(&address) { + in_wallet_at_derivation_path = + Some((wallet.seed_hash(), derivation_path.clone())); + } + if in_wallet_at_derivation_path.is_some() { + break; + } + } + } Self { identity_public_key: value, diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index 134ff55a3..d3c9a44c8 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -411,7 +411,7 @@ impl Wallet { network: Network, identity_index: u32, key_index: u32, - ) -> Result { + ) -> Result<(PrivateKey, DerivationPath), String> { let derivation_path = DerivationPath::identity_authentication_path( network, KeyDerivationType::ECDSA, @@ -421,7 +421,7 @@ impl Wallet { let extended_public_key = derivation_path .derive_priv_ecdsa_for_master_seed(self.seed_bytes()?, network) .expect("derivation should not be able to fail"); - Ok(extended_public_key.to_priv()) + Ok((extended_public_key.to_priv(), derivation_path)) } pub fn identity_registration_ecdsa_public_key( diff --git a/src/ui/add_key_screen.rs b/src/ui/add_key_screen.rs index 22d846140..2b5220802 100644 --- a/src/ui/add_key_screen.rs +++ b/src/ui/add_key_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::backend_task::identity::IdentityTask; use crate::backend_task::BackendTask; use crate::context::AppContext; +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::QualifiedIdentity; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, ScreenLike}; @@ -80,10 +81,14 @@ impl AddKeyScreen { err )); } else if validation_result.unwrap() { + let new_qualified_key = QualifiedIdentityPublicKey { + identity_public_key: new_key.into(), + in_wallet_at_derivation_path: None, + }; app_action = AppAction::BackendTask(BackendTask::IdentityTask( IdentityTask::AddKeyToIdentity( self.identity.clone(), - new_key.into(), + new_qualified_key.into(), private_key_bytes, ), )); diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 368bee115..7df77b525 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -531,7 +531,7 @@ impl AddNewIdentityScreen { // Render additional key options only if "Advanced" mode is selected if self.in_key_selection_advanced_mode { // Render the master key input - if let Some(master_key) = self.identity_keys.master_private_key { + if let Some((master_key, _)) = self.identity_keys.master_private_key { self.render_master_key(ui, master_key); } @@ -545,7 +545,7 @@ impl AddNewIdentityScreen { fn render_keys_input(&mut self, ui: &mut egui::Ui) { let mut keys_to_remove = vec![]; - for (i, (key, key_type, purpose, security_level)) in + for (i, ((key, _), key_type, purpose, security_level)) in self.identity_keys.keys_input.iter_mut().enumerate() { ui.horizontal(|ui| { From cca79a6559150f52f3336464c611251500415a84 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 Nov 2024 14:06:06 +0100 Subject: [PATCH 06/11] shows wallet for keys correctly --- .../encrypted_key_storage.rs | 28 +++++++++++++++++++ src/model/wallet/mod.rs | 1 - src/ui/identities/identities_screen.rs | 12 ++++---- src/ui/key_info_screen.rs | 26 +++++++++++++---- src/ui/mod.rs | 8 +++++- 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/model/qualified_identity/encrypted_key_storage.rs b/src/model/qualified_identity/encrypted_key_storage.rs index a061d6520..adbed75ae 100644 --- a/src/model/qualified_identity/encrypted_key_storage.rs +++ b/src/model/qualified_identity/encrypted_key_storage.rs @@ -1,6 +1,8 @@ use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::PrivateKeyTarget; +use crate::model::wallet::WalletSeedHash; use bincode::{Decode, Encode}; +use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::dpp::identity::{KeyID, Purpose, SecurityLevel}; use std::collections::{BTreeMap, BTreeSet}; @@ -123,6 +125,32 @@ impl KeyStorage { } } + pub fn get_private_key_data_and_wallet_info( + &self, + key: &(PrivateKeyTarget, KeyID), + ) -> Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)> { + match self { + KeyStorage::Open(open) => open.get(key).map(|(qualified_identity_public_key, k)| { + ( + PrivateKeyData::Clear(*k), + qualified_identity_public_key + .in_wallet_at_derivation_path + .clone(), + ) + }), + KeyStorage::Closed(closed) => { + closed.get(key).map(|(qualified_identity_public_key, k)| { + ( + PrivateKeyData::Encrypted(k.clone()), + qualified_identity_public_key + .in_wallet_at_derivation_path + .clone(), + ) + }) + } + } + } + pub fn find_master_key(&self) -> Option<&QualifiedIdentityPublicKey> { match self { KeyStorage::Open(open) => open diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index d3c9a44c8..bdf92f47f 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -353,7 +353,6 @@ impl Wallet { Some(false), ) .map_err(|e| e.to_string())?; - println!("adding address {} at {}", &address, &derivation_path); app_context .db .add_address( diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 0adf19c2d..cfee32d7b 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -3,6 +3,7 @@ use crate::backend_task::identity::IdentityTask; use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::PrivateKeyTarget::{ PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; @@ -15,6 +16,7 @@ use crate::ui::key_info_screen::KeyInfoScreen; use crate::ui::transfers::TransferScreen; use crate::ui::withdrawals::WithdrawalScreen; use crate::ui::{RootScreenType, Screen, ScreenLike, ScreenType}; +use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::dpp::identity::Purpose; @@ -170,7 +172,7 @@ impl IdentitiesScreen { ui: &mut Ui, identity: &QualifiedIdentity, key: &IdentityPublicKey, - encrypted_private_key: Option, + encrypted_private_key: Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, ) -> AppAction { let button_color = if encrypted_private_key.is_some() { Color32::from_rgb(167, 232, 232) @@ -358,7 +360,7 @@ impl IdentitiesScreen { if total_keys_shown < max_keys_to_show { let holding_private_key = qualified_identity .private_keys - .get_private_key_data(&( + .get_private_key_data_and_wallet_info(&( PrivateKeyOnMainIdentity, **key_id, )); @@ -387,7 +389,7 @@ impl IdentitiesScreen { let holding_private_key = qualified_identity .private_keys - .get_private_key_data(&( + .get_private_key_data_and_wallet_info(&( PrivateKeyOnVoterIdentity, **key_id, )); @@ -549,7 +551,7 @@ impl IdentitiesScreen { for (key_id, key) in main_identity_rest_keys { let holding_private_key = qualified_identity .private_keys - .get_private_key_data(&(PrivateKeyOnMainIdentity, **key_id)); + .get_private_key_data_and_wallet_info(&(PrivateKeyOnMainIdentity, **key_id)); action |= self.show_public_key(ui, qualified_identity, *key, holding_private_key); } @@ -563,7 +565,7 @@ impl IdentitiesScreen { for (key_id, key) in voter_public_keys_vec.iter() { let holding_private_key = qualified_identity .private_keys - .get_private_key_data(&(PrivateKeyOnVoterIdentity, **key_id)); + .get_private_key_data_and_wallet_info(&(PrivateKeyOnVoterIdentity, **key_id)); action |= self.show_public_key(ui, qualified_identity, *key, holding_private_key); } } diff --git a/src/ui/key_info_screen.rs b/src/ui/key_info_screen.rs index 0e40b1820..d56752f2c 100644 --- a/src/ui/key_info_screen.rs +++ b/src/ui/key_info_screen.rs @@ -2,8 +2,10 @@ use crate::app::AppAction; use crate::context::AppContext; use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; use crate::model::qualified_identity::QualifiedIdentity; +use crate::model::wallet::WalletSeedHash; use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; +use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::dashcore::address::Payload; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::dashcore::{Address, PubkeyHash, ScriptHash}; @@ -12,7 +14,7 @@ use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicK use dash_sdk::dpp::identity::KeyType; use dash_sdk::dpp::identity::KeyType::BIP13_SCRIPT_HASH; use dash_sdk::dpp::platform_value::string_encoding::Encoding; -use dash_sdk::dpp::prelude::IdentityPublicKey; +use dash_sdk::platform::IdentityPublicKey; use eframe::egui::{self, Context}; use egui::{RichText, TextEdit}; use std::sync::Arc; @@ -20,7 +22,7 @@ use std::sync::Arc; pub struct KeyInfoScreen { pub identity: QualifiedIdentity, pub key: IdentityPublicKey, - pub private_key_data: Option, + pub private_key_data: Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, pub app_context: Arc, private_key_input: String, error_message: Option, @@ -81,6 +83,18 @@ impl ScreenLike for KeyInfoScreen { ui.label("Disabled"); } ui.end_row(); + + if let Some((_, Some((_, derivation_path)))) = self.private_key_data.as_ref() { + // Disabled + ui.label(RichText::new("In local Wallet").strong()); + ui.label( + RichText::new(format!("At derivation path {}", derivation_path)) + .strong(), + ); + ui.end_row(); + } + + ui.end_row(); }); ui.separator(); @@ -150,7 +164,7 @@ impl ScreenLike for KeyInfoScreen { ui.separator(); // Display the private key if available - if let Some(private_key) = self.private_key_data.as_mut() { + if let Some((private_key, _)) = self.private_key_data.as_mut() { ui.label("Private Key:"); match private_key { PrivateKeyData::Clear(clear) => { @@ -187,13 +201,13 @@ impl KeyInfoScreen { pub fn new( identity: QualifiedIdentity, key: IdentityPublicKey, - private_key_bytes: Option, + private_key_data: Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, app_context: &Arc, ) -> Self { Self { identity, key, - private_key_data: private_key_bytes, + private_key_data, app_context: app_context.clone(), private_key_input: String::new(), error_message: None, @@ -212,7 +226,7 @@ impl KeyInfoScreen { self.error_message = Some(format!("Issue verifying private key {}", err)); } else if validation_result.unwrap() { // If valid, store the private key in the context and reset the input field - self.private_key_data = Some(PrivateKeyData::Clear(private_key_bytes)); + self.private_key_data = Some((PrivateKeyData::Clear(private_key_bytes), None)); if let Err(e) = self.identity.private_keys.insert_non_encrypted( (self.key.purpose().into(), self.key.id()), (self.key.clone().into(), private_key_bytes), diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6aed1fe62..361735ca9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,6 +3,7 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; use crate::model::qualified_identity::QualifiedIdentity; +use crate::model::wallet::WalletSeedHash; use crate::ui::add_key_screen::AddKeyScreen; use crate::ui::document_query_screen::DocumentQueryScreen; use crate::ui::dpns_contested_names_screen::DPNSContestedNamesScreen; @@ -15,6 +16,7 @@ use crate::ui::wallet::import_wallet_screen::ImportWalletScreen; use crate::ui::wallet::wallets_screen::WalletsBalancesScreen; use crate::ui::withdrawals::WithdrawalScreen; use crate::ui::withdraws_status_screen::WithdrawsStatusScreen; +use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::identity::Identity; use dash_sdk::dpp::prelude::IdentityPublicKey; use dpns_contested_names_screen::DPNSSubscreen; @@ -122,7 +124,11 @@ pub enum ScreenType { WithdrawalScreen(QualifiedIdentity), TransferScreen(QualifiedIdentity), AddKeyScreen(QualifiedIdentity), - KeyInfo(QualifiedIdentity, IdentityPublicKey, Option), + KeyInfo( + QualifiedIdentity, + IdentityPublicKey, + Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, + ), Keys(Identity), DocumentQueryScreen, WithdrawsStatus, From 860361da23f414ec0c6cb39bc669bab0d9e0c4b8 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 9 Nov 2024 19:20:22 +0100 Subject: [PATCH 07/11] a lot more work --- .../identity/add_key_to_identity.rs | 4 +- .../identity/load_identity_from_wallet.rs | 104 +++++ src/backend_task/identity/mod.rs | 8 +- .../identity/register_identity.rs | 2 +- .../encrypted_key_storage.rs | 418 +++++++++++------- src/model/qualified_identity/mod.rs | 2 +- .../qualified_identity_public_key.rs | 1 - src/model/wallet/mod.rs | 40 ++ .../add_existing_identity_screen.rs | 196 ++++++-- src/ui/identities/identities_screen.rs | 11 +- src/ui/key_info_screen.rs | 11 +- 11 files changed, 599 insertions(+), 198 deletions(-) create mode 100644 src/backend_task/identity/load_identity_from_wallet.rs diff --git a/src/backend_task/identity/add_key_to_identity.rs b/src/backend_task/identity/add_key_to_identity.rs index 3b2b9832a..574b13481 100644 --- a/src/backend_task/identity/add_key_to_identity.rs +++ b/src/backend_task/identity/add_key_to_identity.rs @@ -12,7 +12,7 @@ use dash_sdk::dpp::state_transition::identity_update_transition::methods::Identi use dash_sdk::dpp::state_transition::identity_update_transition::IdentityUpdateTransition; use dash_sdk::dpp::state_transition::proof_result::StateTransitionProofResult; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; -use dash_sdk::platform::{Fetch, Identity, IdentityPublicKey}; +use dash_sdk::platform::{Fetch, Identity}; use dash_sdk::Sdk; impl AppContext { @@ -46,7 +46,7 @@ impl AppContext { public_key_to_add.identity_public_key.id(), ), (public_key_to_add.clone(), private_key), - )?; + ); let state_transition = IdentityUpdateTransition::try_from_identity_with_signer( &qualified_identity.identity, &master_key_id, diff --git a/src/backend_task/identity/load_identity_from_wallet.rs b/src/backend_task/identity/load_identity_from_wallet.rs new file mode 100644 index 000000000..bec16c039 --- /dev/null +++ b/src/backend_task/identity/load_identity_from_wallet.rs @@ -0,0 +1,104 @@ +use super::{BackendTaskSuccessResult, IdentityIndex}; +use crate::backend_task::identity::{verify_key_input, IdentityInputToLoad}; +use crate::context::AppContext; +use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; +use crate::model::qualified_identity::PrivateKeyTarget::{ + self, PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, +}; +use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, QualifiedIdentity}; +use crate::model::wallet::Wallet; +use dash_sdk::dpp::dashcore::hashes::Hash; +use dash_sdk::dpp::document::DocumentV0Getters; +use dash_sdk::dpp::identity::accessors::IdentityGettersV0; +use dash_sdk::dpp::platform_value::Value; +use dash_sdk::drive::query::{WhereClause, WhereOperator}; +use dash_sdk::platform::types::identity::PublicKeyHash; +use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identifier, Identity}; +use dash_sdk::Sdk; + +impl AppContext { + pub(super) async fn load_user_identity_from_wallet( + &self, + sdk: &Sdk, + wallet: Wallet, + identity_index: IdentityIndex, + ) -> Result { + let public_key = + wallet.identity_authentication_ecdsa_public_key(self.network, identity_index, 0)?; + + let Some(identity) = Identity::fetch( + &sdk, + PublicKeyHash(public_key.pubkey_hash().to_byte_array()), + ) + .await + .map_err(|e| e.to_string())? + else { + return Ok(BackendTaskSuccessResult::None); + }; + + let identity_id = identity.id(); + + // Fetch DPNS names using SDK + let dpns_names_document_query = DocumentQuery { + data_contract: self.dpns_contract.clone(), + document_type_name: "domain".to_string(), + where_clauses: vec![WhereClause { + field: "records.identity".to_string(), + operator: WhereOperator::Equal, + value: Value::Identifier(identity_id.into()), + }], + order_by_clauses: vec![], + limit: 100, + start: None, + }; + + let maybe_owned_dpns_names = Document::fetch_many(&self.sdk, dpns_names_document_query) + .await + .map(|document_map| { + document_map + .values() + .filter_map(|maybe_doc| { + maybe_doc.as_ref().and_then(|doc| { + let name = doc + .get("normalizedLabel") + .map(|label| label.to_str().unwrap_or_default()); + let acquired_at = doc + .created_at() + .into_iter() + .chain(doc.transferred_at()) + .max(); + + match (name, acquired_at) { + (Some(name), Some(acquired_at)) => Some(DPNSNameInfo { + name: name.to_string(), + acquired_at, + }), + _ => None, + } + }) + }) + .collect::>() + .into() + }) + .map_err(|e| format!("Error fetching DPNS names: {}", e))?; + + let qualified_identity = QualifiedIdentity { + identity, + associated_voter_identity: None, + associated_operator_identity: None, + associated_owner_key_id: None, + identity_type: IdentityType::User, + alias: None, + private_keys: encrypted_private_keys.into(), + dpns_names: maybe_owned_dpns_names, + }; + + // Insert qualified identity into the database + self.insert_local_qualified_identity(&qualified_identity, None) + .map_err(|e| format!("Database error: {}", e))?; + + Ok(BackendTaskSuccessResult::Message( + "Successfully loaded identity".to_string(), + )) + } +} diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index e8c6ccdb9..86e54a3f7 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -1,5 +1,6 @@ mod add_key_to_identity; mod load_identity; +mod load_identity_from_wallet; mod refresh_identity; mod register_dpns_name; mod register_identity; @@ -120,7 +121,7 @@ impl IdentityKeys { }, )); - KeyStorage::Open(key_map.into()) + key_map.into() } pub fn to_public_keys_map(&self) -> BTreeMap { let Self { @@ -220,6 +221,7 @@ pub struct RegisterDpnsNameInput { #[derive(Debug, Clone, PartialEq)] pub(crate) enum IdentityTask { LoadIdentity(IdentityInputToLoad), + SearchIdentityFromWallet(Wallet, IdentityIndex), RegisterIdentity(IdentityRegistrationInfo), AddKeyToIdentity(QualifiedIdentity, QualifiedIdentityPublicKey, [u8; 32]), WithdrawFromIdentity(QualifiedIdentity, Option
, Credits, Option), @@ -431,6 +433,10 @@ impl AppContext { self.transfer_to_identity(qualified_identity, to_identifier, credits, id) .await } + IdentityTask::SearchIdentityFromWallet(wallet, identity_index) => { + self.load_user_identity_from_wallet(sdk, wallet, identity_index) + .await + } } } } diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index c64f847ff..258433b17 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -166,7 +166,7 @@ impl AppContext { } IdentityRegistrationMethod::FundWithWallet(amount, identity_index) => { // Scope the write lock to avoid holding it across an await. - let (asset_lock_transaction, asset_lock_proof_private_key, change_address) = { + let (asset_lock_transaction, asset_lock_proof_private_key, _) = { let mut wallet = wallet.write().unwrap(); wallet_id = wallet.seed_hash(); match wallet.asset_lock_transaction( diff --git a/src/model/qualified_identity/encrypted_key_storage.rs b/src/model/qualified_identity/encrypted_key_storage.rs index adbed75ae..5239196cf 100644 --- a/src/model/qualified_identity/encrypted_key_storage.rs +++ b/src/model/qualified_identity/encrypted_key_storage.rs @@ -1,205 +1,335 @@ use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::PrivateKeyTarget; -use crate::model::wallet::WalletSeedHash; -use bincode::{Decode, Encode}; +use crate::model::wallet::{Wallet, WalletSeed, WalletSeedHash}; +use bincode::de::{BorrowDecoder, Decoder}; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{BorrowDecode, Decode, Encode}; use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; +use dash_sdk::dpp::dashcore::bip32::ChildNumber; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::dpp::identity::{KeyID, Purpose, SecurityLevel}; use std::collections::{BTreeMap, BTreeSet}; use std::fmt; +use std::sync::{Arc, RwLock}; -#[derive(Debug, Encode, Decode, Clone, PartialEq)] -pub enum KeyStorage { - Open(ClearKeyStorage), - Closed(ClosedKeyStorage), +#[derive(Debug, Clone, PartialEq)] +pub struct WalletDerivationPath { + wallet_seed_hash: WalletSeedHash, + derivation_path: DerivationPath, } -#[derive(Debug, Clone, PartialEq)] +impl Encode for WalletDerivationPath { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + // Encode `wallet_seed_hash` + self.wallet_seed_hash.encode(encoder)?; + + // Encode the length of the `DerivationPath` + self.derivation_path.len().encode(encoder)?; + + // Encode each `ChildNumber` in the `DerivationPath` + for child in &self.derivation_path { + match child { + ChildNumber::Normal { index } => { + 0u8.encode(encoder)?; // Discriminant for Normal + index.encode(encoder)?; + } + ChildNumber::Hardened { index } => { + 1u8.encode(encoder)?; // Discriminant for Hardened + index.encode(encoder)?; + } + ChildNumber::Normal256 { index } => { + 2u8.encode(encoder)?; // Discriminant for Normal256 + index.encode(encoder)?; + } + ChildNumber::Hardened256 { index } => { + 3u8.encode(encoder)?; // Discriminant for Hardened256 + index.encode(encoder)?; + } + } + } + + Ok(()) + } +} + +impl Decode for WalletDerivationPath { + fn decode(decoder: &mut D) -> Result { + // Decode `wallet_seed_hash` + let wallet_seed_hash = WalletSeedHash::decode(decoder)?; + + // Decode the length of the `DerivationPath` + let path_len = usize::decode(decoder)?; + + // Decode each `ChildNumber` in the `DerivationPath` + let mut path = Vec::with_capacity(path_len); + for _ in 0..path_len { + let discriminant = u8::decode(decoder)?; + let child_number = match discriminant { + 0 => ChildNumber::Normal { + index: u32::decode(decoder)?, + }, + 1 => ChildNumber::Hardened { + index: u32::decode(decoder)?, + }, + 2 => ChildNumber::Normal256 { + index: <[u8; 32]>::decode(decoder)?, + }, + 3 => ChildNumber::Hardened256 { + index: <[u8; 32]>::decode(decoder)?, + }, + _ => return Err(DecodeError::OtherString("Invalid ChildNumber type".into())), + }; + path.push(child_number); + } + + let derivation_path = DerivationPath::from(path); + Ok(Self { + wallet_seed_hash, + derivation_path, + }) + } +} + +impl<'de> BorrowDecode<'de> for WalletDerivationPath { + fn borrow_decode>(decoder: &mut D) -> Result { + // Decode `wallet_seed_hash` + let wallet_seed_hash = WalletSeedHash::decode(decoder)?; + + // Decode the length of the `DerivationPath` + let path_len = usize::decode(decoder)?; + + // Decode each `ChildNumber` in the `DerivationPath` + let mut path = Vec::with_capacity(path_len); + for _ in 0..path_len { + let discriminant = u8::decode(decoder)?; + let child_number = match discriminant { + 0 => ChildNumber::Normal { + index: u32::decode(decoder)?, + }, + 1 => ChildNumber::Hardened { + index: u32::decode(decoder)?, + }, + 2 => ChildNumber::Normal256 { + index: <[u8; 32]>::decode(decoder)?, + }, + 3 => ChildNumber::Hardened256 { + index: <[u8; 32]>::decode(decoder)?, + }, + _ => return Err(DecodeError::OtherString("Invalid ChildNumber type".into())), + }; + path.push(child_number); + } + + let derivation_path = DerivationPath::from(path); + Ok(Self { + wallet_seed_hash, + derivation_path, + }) + } +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub enum PrivateKeyData { + AlwaysClear([u8; 32]), // This is for keys that are MEDIUM security level Clear([u8; 32]), Encrypted(Vec), + AtWalletDerivationPath(WalletDerivationPath), } impl fmt::Display for PrivateKeyData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PrivateKeyData::Clear(data) => { - write!(f, "Clear({:?})", hex::encode(data)) + write!(f, "Clear({})", hex::encode(data)) } PrivateKeyData::Encrypted(data) => { write!(f, "Encrypted({} bytes)", data.len()) } + PrivateKeyData::AlwaysClear(data) => { + write!(f, "Clear({})", hex::encode(data)) + } + PrivateKeyData::AtWalletDerivationPath(WalletDerivationPath { + wallet_seed_hash: wallet_seed, + derivation_path, + }) => { + write!( + f, + "AtWalletDerivationPath({}/{})", + hex::encode(wallet_seed), + derivation_path + ) + } } } } -impl Default for KeyStorage { - fn default() -> Self { - Self::Closed(ClosedKeyStorage::default()) - } +#[derive(Debug, Encode, Decode, Clone, PartialEq, Default)] +pub struct KeyStorage { + pub private_keys: + BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, PrivateKeyData)>, } impl From> for KeyStorage -{ - fn from( - value: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, [u8; 32])>, - ) -> Self { - Self::Open(ClearKeyStorage::from(value)) - } -} - -#[derive(Debug, Encode, Decode, Clone, PartialEq)] -pub struct ClearKeyStorage { - pub private_keys: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, [u8; 32])>, -} - -impl From> - for ClearKeyStorage { fn from( value: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, [u8; 32])>, ) -> Self { Self { - private_keys: value, + private_keys: value + .into_iter() + .map(|(key, (qualified_identity_public_key, clear_key))| { + if qualified_identity_public_key + .identity_public_key + .security_level() + == SecurityLevel::MEDIUM + { + ( + key, + ( + qualified_identity_public_key, + PrivateKeyData::AlwaysClear(clear_key), + ), + ) + } else { + ( + key, + ( + qualified_identity_public_key, + PrivateKeyData::Clear(clear_key), + ), + ) + } + }) + .collect(), } } } -impl ClearKeyStorage { +impl KeyStorage { pub fn get( &self, key: &(PrivateKeyTarget, KeyID), - ) -> Option<&(QualifiedIdentityPublicKey, [u8; 32])> { - self.private_keys.get(key) + ) -> Result, String> { + self.private_keys + .get(key) + .map( + |(qualified_identity_public_key_data, private_key_data)| match private_key_data { + PrivateKeyData::AlwaysClear(clear) | PrivateKeyData::Clear(clear) => { + Ok((qualified_identity_public_key_data, *clear)) + } + PrivateKeyData::Encrypted(_) => { + Err("Key is encrypted, please enter password".to_string()) + } + PrivateKeyData::AtWalletDerivationPath(_) => { + Err("Key is not resolved, please enter password".to_string()) + } + }, + ) + .transpose() } - pub fn insert( + pub fn get_resolve( &mut self, - key: (PrivateKeyTarget, KeyID), - value: (QualifiedIdentityPublicKey, [u8; 32]), - ) { - self.private_keys.insert(key, value); - } -} - -#[derive(Debug, Default, Encode, Decode, Clone, PartialEq)] -pub struct ClosedKeyStorage { - pub encrypted_private_keys: - BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, Vec)>, -} - -impl ClosedKeyStorage { - pub fn get( - &self, key: &(PrivateKeyTarget, KeyID), - ) -> Option<&(QualifiedIdentityPublicKey, Vec)> { - self.encrypted_private_keys.get(key) + wallets: &[Arc>], + ) -> Result, String> { + self.private_keys + .get_mut(key) + .map( + |(qualified_identity_public_key_data, private_key_data)| match private_key_data { + PrivateKeyData::AlwaysClear(clear) | PrivateKeyData::Clear(clear) => { + Ok((qualified_identity_public_key_data.clone(), *clear)) + } + PrivateKeyData::Encrypted(_) => { + Err("Key is encrypted, please enter password".to_string()) + } + PrivateKeyData::AtWalletDerivationPath(WalletDerivationPath { + wallet_seed_hash, + derivation_path, + }) => { + let derived_key = Wallet::derive_private_key_in_arc_rw_lock_slice( + wallets, + *wallet_seed_hash, + derivation_path, + )? + .ok_or("Wallet not present".to_string())?; + match qualified_identity_public_key_data + .identity_public_key + .security_level() + { + SecurityLevel::MEDIUM => { + *private_key_data = PrivateKeyData::AlwaysClear(derived_key) + } + _ => *private_key_data = PrivateKeyData::Clear(derived_key), + } + Ok((qualified_identity_public_key_data.clone(), derived_key)) + } + }, + ) + .transpose() } - pub fn insert( - &mut self, - key: (PrivateKeyTarget, KeyID), - value: (QualifiedIdentityPublicKey, Vec), - ) { - self.encrypted_private_keys.insert(key, value); + + pub fn get_private_key_data(&self, key: &(PrivateKeyTarget, KeyID)) -> Option<&PrivateKeyData> { + self.private_keys + .get(key) + .map(|(_, private_key_data)| private_key_data) } -} -impl KeyStorage { - pub fn get( + pub fn get_private_key_data_and_wallet_info( &self, key: &(PrivateKeyTarget, KeyID), - ) -> Result, String> { - match self { - KeyStorage::Open(open) => Ok(open.get(key)), - KeyStorage::Closed(_) => Err("Key is encrypted, please enter password".to_string()), - } - } - - pub fn get_private_key_data(&self, key: &(PrivateKeyTarget, KeyID)) -> Option { - match self { - KeyStorage::Open(open) => open.get(key).map(|(_, k)| PrivateKeyData::Clear(*k)), - KeyStorage::Closed(closed) => closed - .get(key) - .map(|(_, k)| PrivateKeyData::Encrypted(k.clone())), - } + ) -> Option<(&PrivateKeyData, &Option<(WalletSeedHash, DerivationPath)>)> { + self.private_keys + .get(key) + .map(|(qualified_identity_public_key_data, private_key_data)| { + ( + private_key_data, + &qualified_identity_public_key_data.in_wallet_at_derivation_path, + ) + }) } - pub fn get_private_key_data_and_wallet_info( + pub fn get_cloned_private_key_data_and_wallet_info( &self, key: &(PrivateKeyTarget, KeyID), ) -> Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)> { - match self { - KeyStorage::Open(open) => open.get(key).map(|(qualified_identity_public_key, k)| { + self.private_keys + .get(key) + .map(|(qualified_identity_public_key_data, private_key_data)| { ( - PrivateKeyData::Clear(*k), - qualified_identity_public_key + private_key_data.clone(), + qualified_identity_public_key_data .in_wallet_at_derivation_path .clone(), ) - }), - KeyStorage::Closed(closed) => { - closed.get(key).map(|(qualified_identity_public_key, k)| { - ( - PrivateKeyData::Encrypted(k.clone()), - qualified_identity_public_key - .in_wallet_at_derivation_path - .clone(), - ) - }) - } - } + }) } pub fn find_master_key(&self) -> Option<&QualifiedIdentityPublicKey> { - match self { - KeyStorage::Open(open) => open - .private_keys - .values() - .find(|(public_key, _)| { - public_key.identity_public_key.purpose() == Purpose::AUTHENTICATION - && public_key.identity_public_key.security_level() == SecurityLevel::MASTER - }) - .map(|(public_key, _)| public_key), - - KeyStorage::Closed(closed) => closed - .encrypted_private_keys - .values() - .find(|(public_key, _)| { - public_key.identity_public_key.purpose() == Purpose::AUTHENTICATION - && public_key.identity_public_key.security_level() == SecurityLevel::MASTER - }) - .map(|(public_key, _)| public_key), - } + self.private_keys + .values() + .find(|(public_key, _)| { + public_key.identity_public_key.purpose() == Purpose::AUTHENTICATION + && public_key.identity_public_key.security_level() == SecurityLevel::MASTER + }) + .map(|(public_key, _)| public_key) } pub fn has(&self, key: &(PrivateKeyTarget, KeyID)) -> bool { - match self { - KeyStorage::Open(open) => open.private_keys.contains_key(key), - KeyStorage::Closed(closed) => closed.encrypted_private_keys.contains_key(key), - } + self.private_keys.contains_key(key) } pub fn keys_set(&self) -> BTreeSet<(PrivateKeyTarget, KeyID)> { - match self { - KeyStorage::Open(open) => open.private_keys.keys().cloned().collect(), - KeyStorage::Closed(closed) => closed.encrypted_private_keys.keys().cloned().collect(), - } + self.private_keys.keys().cloned().collect() } pub fn identity_public_keys(&self) -> Vec<(&PrivateKeyTarget, &QualifiedIdentityPublicKey)> { - match self { - KeyStorage::Open(open) => open - .private_keys - .iter() - .map(|((target, _), (key, _))| (target, key)) - .collect(), - KeyStorage::Closed(closed) => closed - .encrypted_private_keys - .iter() - .map(|((target, _), (key, _))| (target, key)) - .collect(), - } + self.private_keys + .iter() + .map(|((target, _), (key, _))| (target, key)) + .collect() } /// Inserts an unencrypted key into `ClearKeyStorage`. Returns an error if the storage is closed. @@ -207,30 +337,16 @@ impl KeyStorage { &mut self, key: (PrivateKeyTarget, KeyID), value: (QualifiedIdentityPublicKey, [u8; 32]), - ) -> Result<(), String> { - match self { - KeyStorage::Open(open) => { - open.insert(key, value); - Ok(()) - } - KeyStorage::Closed(_) => { - Err("Cannot insert non-encrypted key into closed storage".to_string()) + ) { + match value.0.identity_public_key.security_level() { + SecurityLevel::MEDIUM => { + self.private_keys + .insert(key, (value.0, PrivateKeyData::AlwaysClear(value.1))); } - } - } - - /// Inserts an encrypted key into `ClosedKeyStorage`. Returns an error if the storage is open. - pub fn insert_encrypted( - &mut self, - key: (PrivateKeyTarget, KeyID), - value: (QualifiedIdentityPublicKey, Vec), - ) -> Result<(), String> { - match self { - KeyStorage::Closed(closed) => { - closed.insert(key, value); - Ok(()) + _ => { + self.private_keys + .insert(key, (value.0, PrivateKeyData::Clear(value.1))); } - KeyStorage::Open(_) => Err("Cannot insert encrypted key into open storage".to_string()), } } } diff --git a/src/model/qualified_identity/mod.rs b/src/model/qualified_identity/mod.rs index 00f19cd9c..3e01eb4b7 100644 --- a/src/model/qualified_identity/mod.rs +++ b/src/model/qualified_identity/mod.rs @@ -114,7 +114,7 @@ impl Signer for QualifiedIdentity { )))?; match identity_public_key.key_type() { KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => { - let signature = signer::sign(data, private_key)?; + let signature = signer::sign(data, &private_key)?; Ok(signature.to_vec().into()) } KeyType::BLS12_381 => { diff --git a/src/model/qualified_identity/qualified_identity_public_key.rs b/src/model/qualified_identity/qualified_identity_public_key.rs index 086b3362b..74bdd9af6 100644 --- a/src/model/qualified_identity/qualified_identity_public_key.rs +++ b/src/model/qualified_identity/qualified_identity_public_key.rs @@ -7,7 +7,6 @@ use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::dashcore::bip32::ChildNumber; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::identity::hash::IdentityPublicKeyHashMethodsV0; -use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::platform::IdentityPublicKey; use std::sync::{Arc, RwLock}; diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index bdf92f47f..ffeb15be3 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -9,6 +9,8 @@ use dash_sdk::dpp::dashcore::{ Address, InstantLock, Network, OutPoint, PrivateKey, PublicKey, Transaction, TxOut, }; use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, RwLock}; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum DerivationPathReference { Unknown = 0, @@ -259,6 +261,44 @@ impl Wallet { } } + pub fn find_in_arc_rw_lock_slice( + slice: &[Arc>], + wallet_seed_hash: WalletSeedHash, + ) -> Option>> { + for wallet in slice { + // Attempt to read the wallet from the RwLock + let wallet_ref = wallet.read().unwrap(); + // Check if the wallet's seed hash matches the provided wallet_seed_hash + if wallet_ref.seed_hash() == wallet_seed_hash { + // Return a clone of the Arc> that matches + return Some(wallet.clone()); + } + } + // Return None if no wallet with the matching seed hash is found + None + } + + pub fn derive_private_key_in_arc_rw_lock_slice( + slice: &[Arc>], + wallet_seed_hash: WalletSeedHash, + derivation_path: &DerivationPath, + ) -> Result, String> { + for wallet in slice { + // Attempt to read the wallet from the RwLock + let wallet_ref = wallet.read().unwrap(); + // Check if this wallet's seed hash matches the target hash + if wallet_ref.seed_hash() == wallet_seed_hash { + // Attempt to derive the private key using the provided derivation path + let extended_private_key = derivation_path + .derive_priv_ecdsa_for_master_seed(wallet_ref.seed_bytes()?, Network::Dash) + .map_err(|e| e.to_string())?; + return Ok(Some(extended_private_key.private_key.secret_bytes())); + } + } + // Return None if no wallet with the matching seed hash is found + Ok(None) + } + pub fn private_key_for_address( &self, address: &Address, diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index 6f1ede3a6..10cf6b9f2 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -3,16 +3,19 @@ use crate::backend_task::identity::{IdentityInputToLoad, IdentityTask}; use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::qualified_identity::IdentityType; +use crate::model::wallet::Wallet; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dashcore_rpc::dashcore::Network; use dash_sdk::dpp::identity::TimestampMillis; use eframe::egui::Context; +use egui::{ComboBox, Ui}; use rand::prelude::IteratorRandom; use rand::thread_rng; use serde::Deserialize; use std::fs; -use std::sync::Arc; +use std::sync::atomic::Ordering; +use std::sync::{Arc, RwLock}; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Debug, Clone, Deserialize)] @@ -78,6 +81,12 @@ pub enum AddIdentityStatus { Complete, } +#[derive(Clone, PartialEq)] +pub enum IdentityLoadMethod { + ByIdentifier, + FromWallet, +} + pub struct AddExistingIdentityScreen { identity_id_input: String, pub identity_type: IdentityType, @@ -88,11 +97,15 @@ pub struct AddExistingIdentityScreen { keys_input: Vec, add_identity_status: AddIdentityStatus, testnet_loaded_nodes: Option, + pub identity_load_method: IdentityLoadMethod, + selected_wallet: Option>>, + pub identity_index_input: String, pub app_context: Arc, } impl AddExistingIdentityScreen { pub fn new(app_context: &Arc) -> Self { + let selected_wallet = app_context.wallets.read().unwrap().first().cloned(); let testnet_loaded_nodes = if app_context.network == Network::Testnet { load_testnet_nodes_from_yml(".testnet_nodes.yml") } else { @@ -108,10 +121,138 @@ impl AddExistingIdentityScreen { keys_input: vec![String::new()], add_identity_status: AddIdentityStatus::NotStarted, testnet_loaded_nodes, + identity_load_method: IdentityLoadMethod::ByIdentifier, + selected_wallet, + identity_index_input: String::new(), app_context: app_context.clone(), } } + fn render_by_identity(&mut self, ui: &mut egui::Ui) -> AppAction { + let mut action = AppAction::None; + if self.app_context.network == Network::Testnet && self.testnet_loaded_nodes.is_some() { + if ui.button("Fill Random HPMN").clicked() { + self.fill_random_hpmn(); + } + + if ui.button("Fill Random Masternode").clicked() { + self.fill_random_masternode(); + } + } + + ui.horizontal(|ui| { + ui.label("Identity ID / ProTxHash (Hex or Base58):"); + ui.text_edit_singleline(&mut self.identity_id_input); + }); + + self.render_identity_type_selection(ui); + + // Input for Alias + ui.horizontal(|ui| { + ui.label("Alias:"); + ui.text_edit_singleline(&mut self.alias_input); + }); + + // Render the keys input based on identity type + self.render_keys_input(ui); + + if ui.button("Load Identity").clicked() { + // Set the status to waiting and capture the current time + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + self.add_identity_status = AddIdentityStatus::WaitingForResult(now); + action = self.load_identity_clicked(); + } + action + } + + fn render_wallet_selection(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + if self.app_context.has_wallet.load(Ordering::Relaxed) { + let wallets = &self.app_context.wallets.read().unwrap(); + let wallet_aliases: Vec = wallets + .iter() + .map(|wallet| { + wallet + .read() + .unwrap() + .alias + .clone() + .unwrap_or_else(|| "Unnamed Wallet".to_string()) + }) + .collect(); + + let selected_wallet_alias = self + .selected_wallet + .as_ref() + .and_then(|wallet| wallet.read().ok()?.alias.clone()) + .unwrap_or_else(|| "Select".to_string()); + + // Display the ComboBox for wallet selection + ComboBox::from_label("") + .selected_text(selected_wallet_alias.clone()) + .show_ui(ui, |ui| { + for (idx, wallet) in wallets.iter().enumerate() { + let wallet_alias = wallet_aliases[idx].clone(); + + let is_selected = self + .selected_wallet + .as_ref() + .map_or(false, |selected| Arc::ptr_eq(selected, wallet)); + + if ui + .selectable_label(is_selected, wallet_alias.clone()) + .clicked() + { + // Update the selected wallet + self.selected_wallet = Some(wallet.clone()); + } + } + }); + + ui.add_space(20.0); + } else { + ui.label("No wallets available."); + } + }); + } + + fn render_from_wallet(&mut self, ui: &mut egui::Ui, wallets_len: usize) -> AppAction { + let mut action = AppAction::None; + + // Wallet selection + if wallets_len > 1 { + self.render_wallet_selection(ui); + } + + // Identity index input + ui.horizontal(|ui| { + ui.label("Identity Index:"); + ui.text_edit_singleline(&mut self.identity_index_input); + }); + + if ui.button("Search For Identity").clicked() { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + self.add_identity_status = AddIdentityStatus::WaitingForResult(now); + action = AppAction::BackendTask(BackendTask::IdentityTask( + IdentityTask::SearchIdentityFromWallet( + self.selected_wallet + .as_ref() + .unwrap() + .read() + .unwrap() + .clone(), + ), + )); + } + action + } + fn render_identity_type_selection(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { ui.label("Identity Type:"); @@ -263,42 +404,33 @@ impl ScreenLike for AddExistingIdentityScreen { ); egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("Add Identity"); - - if self.app_context.network == Network::Testnet && self.testnet_loaded_nodes.is_some() { - if ui.button("Fill Random HPMN").clicked() { - self.fill_random_hpmn(); - } - - if ui.button("Fill Random Masternode").clicked() { - self.fill_random_masternode(); + // Prepare tabs + let mut tabs = vec![("By Identifier", IdentityLoadMethod::ByIdentifier)]; + let wallets_len = { + // Check if there are wallets + let wallets = self.app_context.wallets.read().unwrap(); + let has_wallet = !wallets.is_empty(); + if has_wallet { + tabs.push(("From Wallet", IdentityLoadMethod::FromWallet)); } - } + wallets.len() + }; + // Render tabs ui.horizontal(|ui| { - ui.label("Identity ID / ProTxHash (Hex or Base58):"); - ui.text_edit_singleline(&mut self.identity_id_input); - }); - - self.render_identity_type_selection(ui); - - // Input for Alias - ui.horizontal(|ui| { - ui.label("Alias:"); - ui.text_edit_singleline(&mut self.alias_input); + for (tab_name, tab_method) in &tabs { + let selected = self.identity_load_method == *tab_method; + if ui.selectable_label(selected, *tab_name).clicked() { + self.identity_load_method = tab_method.clone(); + } + } }); - // Render the keys input based on identity type - self.render_keys_input(ui); - - if ui.button("Load Identity").clicked() { - // Set the status to waiting and capture the current time - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs(); - self.add_identity_status = AddIdentityStatus::WaitingForResult(now); - action = self.load_identity_clicked(); + match self.identity_load_method { + IdentityLoadMethod::ByIdentifier => action |= self.render_by_identity(ui), + IdentityLoadMethod::FromWallet => { + action |= self.render_from_wallet(ui, wallets_len) + } } match &self.add_identity_status { diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index cfee32d7b..701553702 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -360,7 +360,7 @@ impl IdentitiesScreen { if total_keys_shown < max_keys_to_show { let holding_private_key = qualified_identity .private_keys - .get_private_key_data_and_wallet_info(&( + .get_cloned_private_key_data_and_wallet_info(&( PrivateKeyOnMainIdentity, **key_id, )); @@ -389,7 +389,7 @@ impl IdentitiesScreen { let holding_private_key = qualified_identity .private_keys - .get_private_key_data_and_wallet_info(&( + .get_cloned_private_key_data_and_wallet_info(&( PrivateKeyOnVoterIdentity, **key_id, )); @@ -551,7 +551,7 @@ impl IdentitiesScreen { for (key_id, key) in main_identity_rest_keys { let holding_private_key = qualified_identity .private_keys - .get_private_key_data_and_wallet_info(&(PrivateKeyOnMainIdentity, **key_id)); + .get_cloned_private_key_data_and_wallet_info(&(PrivateKeyOnMainIdentity, **key_id)); action |= self.show_public_key(ui, qualified_identity, *key, holding_private_key); } @@ -565,7 +565,10 @@ impl IdentitiesScreen { for (key_id, key) in voter_public_keys_vec.iter() { let holding_private_key = qualified_identity .private_keys - .get_private_key_data_and_wallet_info(&(PrivateKeyOnVoterIdentity, **key_id)); + .get_cloned_private_key_data_and_wallet_info(&( + PrivateKeyOnVoterIdentity, + **key_id, + )); action |= self.show_public_key(ui, qualified_identity, *key, holding_private_key); } } diff --git a/src/ui/key_info_screen.rs b/src/ui/key_info_screen.rs index d56752f2c..14d34fe2c 100644 --- a/src/ui/key_info_screen.rs +++ b/src/ui/key_info_screen.rs @@ -167,7 +167,7 @@ impl ScreenLike for KeyInfoScreen { if let Some((private_key, _)) = self.private_key_data.as_mut() { ui.label("Private Key:"); match private_key { - PrivateKeyData::Clear(clear) => { + PrivateKeyData::Clear(clear) | PrivateKeyData::AlwaysClear(clear) => { let private_key_hex = hex::encode(clear); ui.add( TextEdit::multiline(&mut private_key_hex.as_str().to_owned()) @@ -177,6 +177,9 @@ impl ScreenLike for KeyInfoScreen { PrivateKeyData::Encrypted(_) => { ui.label("key is encrypted"); } + PrivateKeyData::AtWalletDerivationPath(_) => { + ui.label("key is in encrypted wallet"); + } } } else { ui.label("Enter Private Key:"); @@ -227,12 +230,10 @@ impl KeyInfoScreen { } else if validation_result.unwrap() { // If valid, store the private key in the context and reset the input field self.private_key_data = Some((PrivateKeyData::Clear(private_key_bytes), None)); - if let Err(e) = self.identity.private_keys.insert_non_encrypted( + self.identity.private_keys.insert_non_encrypted( (self.key.purpose().into(), self.key.id()), (self.key.clone().into(), private_key_bytes), - ) { - self.error_message = Some(e); - } + ); match self .app_context .insert_local_qualified_identity(&self.identity, None) From d915e605706168714a38c24c300d907cdb7b2922 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 9 Nov 2024 23:41:26 +0100 Subject: [PATCH 08/11] a lot more work --- .../identity/load_identity_from_wallet.rs | 41 ++++- src/backend_task/identity/mod.rs | 6 +- .../encrypted_key_storage.rs | 20 ++- .../qualified_identity_public_key.rs | 166 ++---------------- src/model/wallet/mod.rs | 28 +++ src/ui/components/mod.rs | 1 + src/ui/components/wallet_unlock.rs | 74 ++++++++ .../add_existing_identity_screen.rs | 28 +-- .../identities/add_new_identity_screen/mod.rs | 147 ++++++++++------ src/ui/identities/identities_screen.rs | 10 +- src/ui/key_info_screen.rs | 12 +- src/ui/mod.rs | 6 +- 12 files changed, 283 insertions(+), 256 deletions(-) create mode 100644 src/ui/components/wallet_unlock.rs diff --git a/src/backend_task/identity/load_identity_from_wallet.rs b/src/backend_task/identity/load_identity_from_wallet.rs index bec16c039..38053440e 100644 --- a/src/backend_task/identity/load_identity_from_wallet.rs +++ b/src/backend_task/identity/load_identity_from_wallet.rs @@ -1,20 +1,21 @@ +use std::collections::BTreeMap; +use dash_sdk::dpp::dashcore::bip32::{DerivationPath, KeyDerivationType}; use super::{BackendTaskSuccessResult, IdentityIndex}; -use crate::backend_task::identity::{verify_key_input, IdentityInputToLoad}; use crate::context::AppContext; use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; -use crate::model::qualified_identity::PrivateKeyTarget::{ - self, PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, -}; -use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, QualifiedIdentity}; +use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, PrivateKeyTarget, QualifiedIdentity}; use crate::model::wallet::Wallet; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::document::DocumentV0Getters; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; +use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dash_sdk::dpp::identity::{KeyID, KeyType}; use dash_sdk::dpp::platform_value::Value; use dash_sdk::drive::query::{WhereClause, WhereOperator}; use dash_sdk::platform::types::identity::PublicKeyHash; -use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identifier, Identity}; +use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identity}; use dash_sdk::Sdk; +use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; impl AppContext { pub(super) async fn load_user_identity_from_wallet( @@ -82,6 +83,32 @@ impl AppContext { }) .map_err(|e| format!("Error fetching DPNS names: {}", e))?; + let top_bound = identity.public_keys().len() as u32 + 5; + + let (public_key_result_map, public_key_hash_result_map) = wallet.identity_authentication_ecdsa_public_keys_data_map(self.network, identity_index, 0..top_bound)?; + + let wallet_seed_hash = wallet.seed_hash(); + let private_keys = identity.public_keys().values().filter_map(|public_key| { + let index: u32 = match public_key.key_type() { + KeyType::ECDSA_SECP256K1 => { + public_key_result_map.get(public_key.data().as_slice()).cloned() + } + KeyType::ECDSA_HASH160 => { + let hash: [u8;20] = public_key.data().as_slice().try_into().ok()?; + public_key_hash_result_map.get(&hash).cloned() + } + _ => None, + }?; + let derivation_path = DerivationPath::identity_authentication_path( + self.network, + KeyDerivationType::ECDSA, + identity_index, + index, + ); + let wallet_derivation_path = WalletDerivationPath { wallet_seed_hash, derivation_path}; + Some(((PrivateKeyTarget::PrivateKeyOnMainIdentity, public_key.id()), (QualifiedIdentityPublicKey { identity_public_key: public_key.clone(), in_wallet_at_derivation_path: Some(wallet_derivation_path.clone()) }, PrivateKeyData::AtWalletDerivationPath(wallet_derivation_path)))) + }).collect::>().into(); + let qualified_identity = QualifiedIdentity { identity, associated_voter_identity: None, @@ -89,7 +116,7 @@ impl AppContext { associated_owner_key_id: None, identity_type: IdentityType::User, alias: None, - private_keys: encrypted_private_keys.into(), + private_keys, dpns_names: maybe_owned_dpns_names, }; diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index 86e54a3f7..8e82897e1 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -10,7 +10,7 @@ mod withdraw_from_identity; use super::BackendTaskSuccessResult; use crate::app::TaskResult; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::KeyStorage; +use crate::model::qualified_identity::encrypted_key_storage::{KeyStorage, WalletDerivationPath}; use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::{IdentityType, PrivateKeyTarget, QualifiedIdentity}; use crate::model::wallet::{Wallet, WalletSeedHash}; @@ -81,7 +81,7 @@ impl IdentityKeys { let qualified_identity_public_key = QualifiedIdentityPublicKey::from_identity_public_key_in_wallet( key, - Some((wallet_seed_hash, master_private_key_derivation_path.clone())), + Some(WalletDerivationPath { wallet_seed_hash, derivation_path: master_private_key_derivation_path.clone() }), ); key_map.insert( (PrivateKeyTarget::PrivateKeyOnMainIdentity, 0), @@ -109,7 +109,7 @@ impl IdentityKeys { let qualified_identity_public_key = QualifiedIdentityPublicKey::from_identity_public_key_in_wallet( identity_public_key, - Some((wallet_seed_hash, derivation_path.clone())), + Some(WalletDerivationPath { wallet_seed_hash, derivation_path: derivation_path.clone() }), ); ( (PrivateKeyTarget::PrivateKeyOnMainIdentity, id), diff --git a/src/model/qualified_identity/encrypted_key_storage.rs b/src/model/qualified_identity/encrypted_key_storage.rs index 5239196cf..2d9e1e9f1 100644 --- a/src/model/qualified_identity/encrypted_key_storage.rs +++ b/src/model/qualified_identity/encrypted_key_storage.rs @@ -1,6 +1,6 @@ use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::PrivateKeyTarget; -use crate::model::wallet::{Wallet, WalletSeed, WalletSeedHash}; +use crate::model::wallet::{Wallet, WalletSeedHash}; use bincode::de::{BorrowDecoder, Decoder}; use bincode::enc::Encoder; use bincode::error::{DecodeError, EncodeError}; @@ -15,8 +15,8 @@ use std::sync::{Arc, RwLock}; #[derive(Debug, Clone, PartialEq)] pub struct WalletDerivationPath { - wallet_seed_hash: WalletSeedHash, - derivation_path: DerivationPath, + pub(crate) wallet_seed_hash: WalletSeedHash, + pub(crate) derivation_path: DerivationPath, } impl Encode for WalletDerivationPath { @@ -170,6 +170,16 @@ pub struct KeyStorage { BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, PrivateKeyData)>, } +impl From> +for KeyStorage +{ + fn from(value: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, PrivateKeyData)>) -> Self { + Self { + private_keys: value, + } + } +} + impl From> for KeyStorage { @@ -280,7 +290,7 @@ impl KeyStorage { pub fn get_private_key_data_and_wallet_info( &self, key: &(PrivateKeyTarget, KeyID), - ) -> Option<(&PrivateKeyData, &Option<(WalletSeedHash, DerivationPath)>)> { + ) -> Option<(&PrivateKeyData, &Option)> { self.private_keys .get(key) .map(|(qualified_identity_public_key_data, private_key_data)| { @@ -294,7 +304,7 @@ impl KeyStorage { pub fn get_cloned_private_key_data_and_wallet_info( &self, key: &(PrivateKeyTarget, KeyID), - ) -> Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)> { + ) -> Option<(PrivateKeyData, Option)> { self.private_keys .get(key) .map(|(qualified_identity_public_key_data, private_key_data)| { diff --git a/src/model/qualified_identity/qualified_identity_public_key.rs b/src/model/qualified_identity/qualified_identity_public_key.rs index 74bdd9af6..74da76d87 100644 --- a/src/model/qualified_identity/qualified_identity_public_key.rs +++ b/src/model/qualified_identity/qualified_identity_public_key.rs @@ -1,164 +1,15 @@ -use crate::model::wallet::{Wallet, WalletSeedHash}; -use bincode::de::{BorrowDecoder, Decoder}; -use bincode::enc::Encoder; -use bincode::error::{DecodeError, EncodeError}; -use bincode::{BorrowDecode, Decode, Encode}; -use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; -use dash_sdk::dpp::dashcore::bip32::ChildNumber; +use crate::model::wallet::Wallet; +use bincode::{Decode, Encode}; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::identity::hash::IdentityPublicKeyHashMethodsV0; use dash_sdk::platform::IdentityPublicKey; use std::sync::{Arc, RwLock}; +use crate::model::qualified_identity::encrypted_key_storage::WalletDerivationPath; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Encode, Decode, Clone, PartialEq)] pub struct QualifiedIdentityPublicKey { pub identity_public_key: IdentityPublicKey, - pub in_wallet_at_derivation_path: Option<(WalletSeedHash, DerivationPath)>, -} - -impl Encode for QualifiedIdentityPublicKey { - fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { - // Encode `identity_public_key` - self.identity_public_key.encode(encoder)?; - - // Encode `in_wallet_at_derivation_path` - match &self.in_wallet_at_derivation_path { - Some((hash, derivation_path)) => { - // Indicate that the option is `Some` - true.encode(encoder)?; - - // Encode the `hash` - hash.encode(encoder)?; - - // Encode the length of the `DerivationPath` - derivation_path.len().encode(encoder)?; - - // Encode each `ChildNumber` in the `DerivationPath` - for child in derivation_path.into_iter() { - match child { - ChildNumber::Normal { index } => { - 0u8.encode(encoder)?; // Discriminant for Normal - index.encode(encoder)?; - } - ChildNumber::Hardened { index } => { - 1u8.encode(encoder)?; // Discriminant for Hardened - index.encode(encoder)?; - } - ChildNumber::Normal256 { index } => { - 2u8.encode(encoder)?; // Discriminant for Normal256 - index.encode(encoder)?; - } - ChildNumber::Hardened256 { index } => { - 3u8.encode(encoder)?; // Discriminant for Hardened256 - index.encode(encoder)?; - } - } - } - } - None => { - // Indicate that the option is `None` - false.encode(encoder)?; - } - } - - Ok(()) - } -} - -impl Decode for QualifiedIdentityPublicKey { - fn decode(decoder: &mut D) -> Result { - // Decode `identity_public_key` - let identity_public_key = IdentityPublicKey::decode(decoder)?; - - // Decode `in_wallet_at_derivation_path` - let has_derivation_path = bool::decode(decoder)?; - let in_wallet_at_derivation_path = if has_derivation_path { - // Decode the `hash` - let hash: [u8; 32] = Decode::decode(decoder)?; - - // Decode the length of the `DerivationPath` - let path_len = usize::decode(decoder)?; - - // Decode each `ChildNumber` in the `DerivationPath` - let mut path = Vec::with_capacity(path_len); - for _ in 0..path_len { - let discriminant = u8::decode(decoder)?; - let child_number = match discriminant { - 0 => ChildNumber::Normal { - index: u32::decode(decoder)?, - }, - 1 => ChildNumber::Hardened { - index: u32::decode(decoder)?, - }, - 2 => ChildNumber::Normal256 { - index: <[u8; 32]>::decode(decoder)?, - }, - 3 => ChildNumber::Hardened256 { - index: <[u8; 32]>::decode(decoder)?, - }, - _ => return Err(DecodeError::OtherString("Invalid ChildNumber type".into())), - }; - path.push(child_number); - } - - Some((hash, DerivationPath::from(path))) - } else { - None - }; - - Ok(Self { - identity_public_key, - in_wallet_at_derivation_path, - }) - } -} - -impl<'de> BorrowDecode<'de> for QualifiedIdentityPublicKey { - fn borrow_decode>(decoder: &mut D) -> Result { - // Decode `identity_public_key` - let identity_public_key = IdentityPublicKey::decode(decoder)?; - - // Decode `in_wallet_at_derivation_path` - let has_derivation_path = bool::decode(decoder)?; - let in_wallet_at_derivation_path = if has_derivation_path { - // Decode the `hash` - let hash: [u8; 32] = Decode::decode(decoder)?; - - // Decode the length of the `DerivationPath` - let path_len = usize::decode(decoder)?; - - // Decode each `ChildNumber` in the `DerivationPath` - let mut path = Vec::with_capacity(path_len); - for _ in 0..path_len { - let discriminant = u8::decode(decoder)?; - let child_number = match discriminant { - 0 => ChildNumber::Normal { - index: u32::decode(decoder)?, - }, - 1 => ChildNumber::Hardened { - index: u32::decode(decoder)?, - }, - 2 => ChildNumber::Normal256 { - index: <[u8; 32]>::decode(decoder)?, - }, - 3 => ChildNumber::Hardened256 { - index: <[u8; 32]>::decode(decoder)?, - }, - _ => return Err(DecodeError::OtherString("Invalid ChildNumber type".into())), - }; - path.push(child_number); - } - - Some((hash, DerivationPath::from(path))) - } else { - None - }; - - Ok(Self { - identity_public_key, - in_wallet_at_derivation_path, - }) - } + pub in_wallet_at_derivation_path: Option, } impl From for QualifiedIdentityPublicKey { @@ -173,7 +24,7 @@ impl From for QualifiedIdentityPublicKey { impl QualifiedIdentityPublicKey { pub fn from_identity_public_key_in_wallet( identity_public_key: IdentityPublicKey, - in_wallet_at_derivation_path: Option<(WalletSeedHash, DerivationPath)>, + in_wallet_at_derivation_path: Option, ) -> Self { Self { identity_public_key, @@ -194,7 +45,10 @@ impl QualifiedIdentityPublicKey { let wallet = locked_wallet.read().unwrap(); if let Some(derivation_path) = wallet.known_addresses.get(&address) { in_wallet_at_derivation_path = - Some((wallet.seed_hash(), derivation_path.clone())); + Some(WalletDerivationPath { + wallet_seed_hash: wallet.seed_hash(), + derivation_path: derivation_path.clone(), + }); } if in_wallet_at_derivation_path.is_some() { break; diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index ffeb15be3..812b77725 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -9,6 +9,7 @@ use dash_sdk::dpp::dashcore::{ Address, InstantLock, Network, OutPoint, PrivateKey, PublicKey, Transaction, TxOut, }; use std::collections::{BTreeMap, HashMap}; +use std::ops::Range; use std::sync::{Arc, RwLock}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] @@ -65,6 +66,7 @@ use bitflags::bitflags; use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::balances::credits::Duffs; +use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::fee::Credits; use dash_sdk::dpp::prelude::AssetLockProof; use dash_sdk::platform::Identity; @@ -445,6 +447,32 @@ impl Wallet { Ok(extended_public_key.to_pub()) } + pub fn identity_authentication_ecdsa_public_keys_data_map( + &self, + network: Network, + identity_index: u32, + key_index_range: Range, + ) -> Result<(BTreeMap, u32>, BTreeMap<[u8;20], u32>), String> { + let mut public_key_result_map = BTreeMap::new(); + let mut public_key_hash_result_map = BTreeMap::new(); + for key_index in key_index_range { + let derivation_path = DerivationPath::identity_authentication_path( + network, + KeyDerivationType::ECDSA, + identity_index, + key_index, + ); + let extended_public_key = derivation_path + .derive_pub_ecdsa_for_master_seed(self.seed_bytes()?, network) + .map_err(|e| e.to_string())?; + + public_key_result_map.insert(extended_public_key.public_key.serialize().to_vec(), key_index); + public_key_hash_result_map.insert(extended_public_key.to_pub().pubkey_hash().to_byte_array(), key_index); + } + + Ok((public_key_result_map, public_key_hash_result_map)) + } + pub fn identity_authentication_ecdsa_private_key( &self, network: Network, diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index e8e64de30..6d037e21f 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -3,3 +3,4 @@ pub mod dpns_subscreen_chooser_panel; pub mod entropy_grid; pub mod left_panel; pub mod top_panel; +pub mod wallet_unlock; diff --git a/src/ui/components/wallet_unlock.rs b/src/ui/components/wallet_unlock.rs new file mode 100644 index 000000000..9b1f136cb --- /dev/null +++ b/src/ui/components/wallet_unlock.rs @@ -0,0 +1,74 @@ +use std::sync::{Arc, RwLock}; +use eframe::epaint::Color32; +use egui::Ui; +use zeroize::Zeroize; +use crate::model::wallet::Wallet; + +pub trait ScreenWithWalletUnlock { + fn selected_wallet_ref(&self) -> &Option>>; + fn wallet_password_ref(&self) -> &String; + fn wallet_password_mut(&mut self) -> &mut String; + fn show_password(&self) -> bool; + fn show_password_mut(&mut self) -> &mut bool; + fn set_error_message(&mut self, error_message: Option); + + fn error_message(&self) -> Option<&String>; + fn render_wallet_unlock(&mut self, ui: &mut Ui) -> bool { + if let Some(wallet_guard) = self.selected_wallet_ref().as_ref() { + let mut wallet = wallet_guard.write().unwrap(); + + // Only render the unlock prompt if the wallet requires a password and is locked + if wallet.uses_password && !wallet.is_open() { + ui.add_space(10.0); + ui.label("This wallet is locked. Please enter the password to unlock it:"); + + let mut unlocked = false; + ui.horizontal(|ui| { + let password_input = ui.add( + egui::TextEdit::singleline(self.wallet_password_mut()) + .password(!self.show_password()) + .hint_text("Enter password"), + ); + + ui.checkbox(self.show_password_mut(), "Show Password"); + + unlocked = if password_input.lost_focus() + && ui.input(|i| i.key_pressed(egui::Key::Enter)) + { + let unlocked = match wallet.wallet_seed.open(&self.wallet_password_ref()) { + Ok(_) => { + self.set_error_message(None); // Clear any previous error + true + } + Err(_) => { + if let Some(hint) = wallet.password_hint() { + self.set_error_message(Some(format!( + "Incorrect Password, password hint is {}", + hint + ))); + } else { + self.set_error_message(Some("Incorrect Password".to_string())); + } + false + } + }; + // Clear the password field after submission + self.wallet_password_mut().zeroize(); + unlocked + } else { + false + }; + }); + + // Display error message if the password was incorrect + if let Some(error_message) = self.error_message() { + ui.add_space(5.0); + ui.colored_label(Color32::RED, error_message); + } + + return unlocked; + } + } + false + } +} \ No newline at end of file diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index 10cf6b9f2..a75cb7004 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -239,16 +239,24 @@ impl AddExistingIdentityScreen { .expect("Time went backwards") .as_secs(); self.add_identity_status = AddIdentityStatus::WaitingForResult(now); - action = AppAction::BackendTask(BackendTask::IdentityTask( - IdentityTask::SearchIdentityFromWallet( - self.selected_wallet - .as_ref() - .unwrap() - .read() - .unwrap() - .clone(), - ), - )); + + // Parse identity index input + if let Ok(identity_index) = self.identity_index_input.trim().parse::() { + action = AppAction::BackendTask(BackendTask::IdentityTask( + IdentityTask::SearchIdentityFromWallet( + self.selected_wallet + .as_ref() + .unwrap() + .read() + .unwrap() + .clone(), + identity_index, + ), + )); + } else { + // Handle invalid index input (optional) + self.add_identity_status = AddIdentityStatus::ErrorMessage("Invalid identity index".to_string()); + } } action } diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 7df77b525..405a6d45e 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -30,6 +30,7 @@ use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{fmt, thread}; use zeroize::Zeroize; +use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; #[derive(Debug, Clone, Deserialize)] struct KeyInfo { @@ -267,64 +268,64 @@ impl AddNewIdentityScreen { } } - fn render_wallet_unlock(&mut self, ui: &mut Ui) -> bool { - if let Some(wallet_guard) = self.selected_wallet.as_ref() { - let mut wallet = wallet_guard.write().unwrap(); - - // Only render the unlock prompt if the wallet requires a password and is locked - if wallet.uses_password && !wallet.is_open() { - ui.add_space(10.0); - ui.label("This wallet is locked. Please enter the password to unlock it:"); - - let mut unlocked = false; - ui.horizontal(|ui| { - let password_input = ui.add( - egui::TextEdit::singleline(&mut self.wallet_password) - .password(!self.show_password) - .hint_text("Enter password"), - ); - - ui.checkbox(&mut self.show_password, "Show Password"); - - unlocked = if password_input.lost_focus() - && ui.input(|i| i.key_pressed(egui::Key::Enter)) - { - let unlocked = match wallet.wallet_seed.open(&self.wallet_password) { - Ok(_) => { - self.error_message = None; // Clear any previous error - true - } - Err(_) => { - if let Some(hint) = wallet.password_hint() { - self.error_message = Some(format!( - "Incorrect Password, password hint is {}", - hint - )); - } else { - self.error_message = Some("Incorrect Password".to_string()); - } - false - } - }; - // Clear the password field after submission - self.wallet_password.zeroize(); - unlocked - } else { - false - }; - }); - - // Display error message if the password was incorrect - if let Some(error_message) = &self.error_message { - ui.add_space(5.0); - ui.colored_label(Color32::RED, error_message); - } - - return unlocked; - } - } - false - } + // fn render_wallet_unlock(&mut self, ui: &mut Ui) -> bool { + // if let Some(wallet_guard) = self.selected_wallet.as_ref() { + // let mut wallet = wallet_guard.write().unwrap(); + // + // // Only render the unlock prompt if the wallet requires a password and is locked + // if wallet.uses_password && !wallet.is_open() { + // ui.add_space(10.0); + // ui.label("This wallet is locked. Please enter the password to unlock it:"); + // + // let mut unlocked = false; + // ui.horizontal(|ui| { + // let password_input = ui.add( + // egui::TextEdit::singleline(&mut self.wallet_password) + // .password(!self.show_password) + // .hint_text("Enter password"), + // ); + // + // ui.checkbox(&mut self.show_password, "Show Password"); + // + // unlocked = if password_input.lost_focus() + // && ui.input(|i| i.key_pressed(egui::Key::Enter)) + // { + // let unlocked = match wallet.wallet_seed.open(&self.wallet_password) { + // Ok(_) => { + // self.error_message = None; // Clear any previous error + // true + // } + // Err(_) => { + // if let Some(hint) = wallet.password_hint() { + // self.error_message = Some(format!( + // "Incorrect Password, password hint is {}", + // hint + // )); + // } else { + // self.error_message = Some("Incorrect Password".to_string()); + // } + // false + // } + // }; + // // Clear the password field after submission + // self.wallet_password.zeroize(); + // unlocked + // } else { + // false + // }; + // }); + // + // // Display error message if the password was incorrect + // if let Some(error_message) = &self.error_message { + // ui.add_space(5.0); + // ui.colored_label(Color32::RED, error_message); + // } + // + // return unlocked; + // } + // } + // false + // } fn render_wallet_selection(&mut self, ui: &mut Ui) -> bool { if self.app_context.has_wallet.load(Ordering::Relaxed) { @@ -797,6 +798,36 @@ impl AddNewIdentityScreen { } } +impl ScreenWithWalletUnlock for AddNewIdentityScreen { + fn selected_wallet_ref(&self) -> &Option>> { + &self.selected_wallet + } + + fn wallet_password_ref(&self) -> &String { + &self.wallet_password + } + + fn wallet_password_mut(&mut self) -> &mut String { + &mut self.wallet_password + } + + fn show_password(&self) -> bool { + self.show_password + } + + fn show_password_mut(&mut self) -> &mut bool { + &mut self.show_password + } + + fn set_error_message(&mut self, error_message: Option) { + self.error_message = error_message; + } + + fn error_message(&self) -> Option<&String> { + self.error_message.as_ref() + } +} + impl ScreenLike for AddNewIdentityScreen { fn display_message(&mut self, message: &str, _message_type: MessageType) { self.error_message = Some(message.to_string()); diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 701553702..b71d4d1ca 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -2,8 +2,7 @@ use crate::app::{AppAction, DesiredAppAction}; use crate::backend_task::identity::IdentityTask; use crate::backend_task::BackendTask; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; -use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; +use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; use crate::model::qualified_identity::PrivateKeyTarget::{ PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; @@ -16,7 +15,6 @@ use crate::ui::key_info_screen::KeyInfoScreen; use crate::ui::transfers::TransferScreen; use crate::ui::withdrawals::WithdrawalScreen; use crate::ui::{RootScreenType, Screen, ScreenLike, ScreenType}; -use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::dpp::identity::Purpose; @@ -143,8 +141,8 @@ impl IdentitiesScreen { .as_ref() { None => "".to_string(), - Some((wallet_seed_hash, _)) => { - self.find_wallet(wallet_seed_hash).unwrap_or_default() + Some(wallet_derivation_path) => { + self.find_wallet(&wallet_derivation_path.wallet_seed_hash).unwrap_or_default() } } } @@ -172,7 +170,7 @@ impl IdentitiesScreen { ui: &mut Ui, identity: &QualifiedIdentity, key: &IdentityPublicKey, - encrypted_private_key: Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, + encrypted_private_key: Option<(PrivateKeyData, Option)>, ) -> AppAction { let button_color = if encrypted_private_key.is_some() { Color32::from_rgb(167, 232, 232) diff --git a/src/ui/key_info_screen.rs b/src/ui/key_info_screen.rs index 14d34fe2c..fa388ce31 100644 --- a/src/ui/key_info_screen.rs +++ b/src/ui/key_info_screen.rs @@ -1,11 +1,9 @@ use crate::app::AppAction; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; +use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; use crate::model::qualified_identity::QualifiedIdentity; -use crate::model::wallet::WalletSeedHash; use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; -use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::dashcore::address::Payload; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::dashcore::{Address, PubkeyHash, ScriptHash}; @@ -22,7 +20,7 @@ use std::sync::Arc; pub struct KeyInfoScreen { pub identity: QualifiedIdentity, pub key: IdentityPublicKey, - pub private_key_data: Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, + pub private_key_data: Option<(PrivateKeyData, Option)>, pub app_context: Arc, private_key_input: String, error_message: Option, @@ -84,11 +82,11 @@ impl ScreenLike for KeyInfoScreen { } ui.end_row(); - if let Some((_, Some((_, derivation_path)))) = self.private_key_data.as_ref() { + if let Some((_, Some(wallet_derivation_path))) = self.private_key_data.as_ref() { // Disabled ui.label(RichText::new("In local Wallet").strong()); ui.label( - RichText::new(format!("At derivation path {}", derivation_path)) + RichText::new(format!("At derivation path {}", wallet_derivation_path.derivation_path)) .strong(), ); ui.end_row(); @@ -204,7 +202,7 @@ impl KeyInfoScreen { pub fn new( identity: QualifiedIdentity, key: IdentityPublicKey, - private_key_data: Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, + private_key_data: Option<(PrivateKeyData, Option)>, app_context: &Arc, ) -> Self { Self { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 361735ca9..ac4472434 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,9 +1,8 @@ use crate::app::AppAction; use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; +use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; use crate::model::qualified_identity::QualifiedIdentity; -use crate::model::wallet::WalletSeedHash; use crate::ui::add_key_screen::AddKeyScreen; use crate::ui::document_query_screen::DocumentQueryScreen; use crate::ui::dpns_contested_names_screen::DPNSContestedNamesScreen; @@ -16,7 +15,6 @@ use crate::ui::wallet::import_wallet_screen::ImportWalletScreen; use crate::ui::wallet::wallets_screen::WalletsBalancesScreen; use crate::ui::withdrawals::WithdrawalScreen; use crate::ui::withdraws_status_screen::WithdrawsStatusScreen; -use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dpp::identity::Identity; use dash_sdk::dpp::prelude::IdentityPublicKey; use dpns_contested_names_screen::DPNSSubscreen; @@ -127,7 +125,7 @@ pub enum ScreenType { KeyInfo( QualifiedIdentity, IdentityPublicKey, - Option<(PrivateKeyData, Option<(WalletSeedHash, DerivationPath)>)>, + Option<(PrivateKeyData, Option)>, ), Keys(Identity), DocumentQueryScreen, From b466a94181d76d389ca8ac3c77b1f29eb9cdd16c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 9 Nov 2024 23:58:14 +0100 Subject: [PATCH 09/11] a lot more work --- src/ui/components/wallet_unlock.rs | 78 ++++++++++++++----- .../add_existing_identity_screen.rs | 49 ++++++++++++ .../identities/add_new_identity_screen/mod.rs | 19 +---- 3 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/ui/components/wallet_unlock.rs b/src/ui/components/wallet_unlock.rs index 9b1f136cb..a964f4375 100644 --- a/src/ui/components/wallet_unlock.rs +++ b/src/ui/components/wallet_unlock.rs @@ -13,8 +13,35 @@ pub trait ScreenWithWalletUnlock { fn set_error_message(&mut self, error_message: Option); fn error_message(&self) -> Option<&String>; + + fn should_ask_for_password(&mut self) -> bool { + if let Some(wallet_guard) = self.selected_wallet_ref().clone() { + let mut wallet = wallet_guard.write().unwrap(); + if !wallet.uses_password { + if let Err(e) = wallet.wallet_seed.open_no_password() { + self.set_error_message(Some(e)); + } + false + } else if wallet.is_open() { + false + } else { + true + } + } else { + true + } + } + + fn render_wallet_unlock_if_needed(&mut self, ui: &mut Ui) -> (bool, bool) { + if self.should_ask_for_password() { + (true, self.render_wallet_unlock(ui)) + } else { + (false, false) + } + } + fn render_wallet_unlock(&mut self, ui: &mut Ui) -> bool { - if let Some(wallet_guard) = self.selected_wallet_ref().as_ref() { + if let Some(wallet_guard) = self.selected_wallet_ref().clone() { let mut wallet = wallet_guard.write().unwrap(); // Only render the unlock prompt if the wallet requires a password and is locked @@ -23,43 +50,58 @@ pub trait ScreenWithWalletUnlock { ui.label("This wallet is locked. Please enter the password to unlock it:"); let mut unlocked = false; + + // Capture necessary values before the closure + let show_password = self.show_password(); + let mut local_show_password = show_password; // Local copy of show_password + let wallet_password_mut = self.wallet_password_mut(); // Mutable reference to the password + let mut local_error_message = None; // Local variable for error message + ui.horizontal(|ui| { let password_input = ui.add( - egui::TextEdit::singleline(self.wallet_password_mut()) - .password(!self.show_password()) + egui::TextEdit::singleline(wallet_password_mut) + .password(!local_show_password) .hint_text("Enter password"), ); - ui.checkbox(self.show_password_mut(), "Show Password"); + // Checkbox to toggle password visibility + ui.checkbox(&mut local_show_password, "Show Password"); - unlocked = if password_input.lost_focus() + if password_input.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { - let unlocked = match wallet.wallet_seed.open(&self.wallet_password_ref()) { + // Use the password from wallet_password_mut + let wallet_password_ref = &*wallet_password_mut; + + let unlock_result = wallet.wallet_seed.open(wallet_password_ref); + + match unlock_result { Ok(_) => { - self.set_error_message(None); // Clear any previous error - true + local_error_message = None; + unlocked = true; } Err(_) => { if let Some(hint) = wallet.password_hint() { - self.set_error_message(Some(format!( + local_error_message = Some(format!( "Incorrect Password, password hint is {}", hint - ))); + )); } else { - self.set_error_message(Some("Incorrect Password".to_string())); + local_error_message = Some("Incorrect Password".to_string()); } - false } - }; + } // Clear the password field after submission - self.wallet_password_mut().zeroize(); - unlocked - } else { - false - }; + wallet_password_mut.zeroize(); + } }); + // Update `show_password` after the closure + *self.show_password_mut() = local_show_password; + + // Update the error message + self.set_error_message(local_error_message); + // Display error message if the password was incorrect if let Some(error_message) = self.error_message() { ui.add_space(5.0); diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index a75cb7004..ef2884cb8 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -17,6 +17,8 @@ use std::fs; use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; use std::time::{SystemTime, UNIX_EPOCH}; +use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::identities::add_new_identity_screen::AddNewIdentityScreen; #[derive(Debug, Clone, Deserialize)] struct MasternodeInfo { @@ -99,6 +101,9 @@ pub struct AddExistingIdentityScreen { testnet_loaded_nodes: Option, pub identity_load_method: IdentityLoadMethod, selected_wallet: Option>>, + show_password: bool, + wallet_password: String, + error_message: Option, pub identity_index_input: String, pub app_context: Arc, } @@ -123,6 +128,9 @@ impl AddExistingIdentityScreen { testnet_loaded_nodes, identity_load_method: IdentityLoadMethod::ByIdentifier, selected_wallet, + show_password: false, + wallet_password: "".to_string(), + error_message: None, identity_index_input: String::new(), app_context: app_context.clone(), } @@ -227,6 +235,16 @@ impl AddExistingIdentityScreen { self.render_wallet_selection(ui); } + if self.selected_wallet.is_none() { + return action; + }; + + let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); + + if needed_unlock && !just_unlocked { + return action; + } + // Identity index input ui.horizontal(|ui| { ui.label("Identity Index:"); @@ -380,6 +398,37 @@ impl AddExistingIdentityScreen { } } + +impl ScreenWithWalletUnlock for AddExistingIdentityScreen { + fn selected_wallet_ref(&self) -> &Option>> { + &self.selected_wallet + } + + fn wallet_password_ref(&self) -> &String { + &self.wallet_password + } + + fn wallet_password_mut(&mut self) -> &mut String { + &mut self.wallet_password + } + + fn show_password(&self) -> bool { + self.show_password + } + + fn show_password_mut(&mut self) -> &mut bool { + &mut self.show_password + } + + fn set_error_message(&mut self, error_message: Option) { + self.error_message = error_message; + } + + fn error_message(&self) -> Option<&String> { + self.error_message.as_ref() + } +} + impl ScreenLike for AddExistingIdentityScreen { fn display_message(&mut self, message: &str, message_type: MessageType) { match message_type { diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 405a6d45e..92a2225d7 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -877,24 +877,9 @@ impl ScreenLike for AddNewIdentityScreen { return; }; - let should_ask_for_password = if let Some(wallet_guard) = self.selected_wallet.as_ref() { - let mut wallet = wallet_guard.write().unwrap(); - if !wallet.uses_password { - if let Err(e) = wallet.wallet_seed.open_no_password() { - self.error_message = Some(e); - } - false - } else if wallet.is_open() { - false - } else { - true - } - } else { - true - }; + let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); - if should_ask_for_password { - let just_unlocked = self.render_wallet_unlock(ui); + if needed_unlock { if just_unlocked { let wallet_guard = self.selected_wallet.as_ref().unwrap(); let wallet = wallet_guard.read().unwrap(); From 9b0d7c00ce547554a6e61931e617ad42b4c9b568 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 10 Nov 2024 16:59:07 +0100 Subject: [PATCH 10/11] a lot more work --- src/backend_task/identity/load_identity.rs | 106 ++++++++--- .../identity/load_identity_from_wallet.rs | 35 ++-- src/backend_task/identity/mod.rs | 14 +- src/database/wallet.rs | 2 +- .../encrypted_key_storage.rs | 6 +- .../qualified_identity_public_key.rs | 11 +- src/model/wallet/mod.rs | 168 +++++++++++++----- src/ui/components/wallet_unlock.rs | 11 +- .../add_existing_identity_screen.rs | 17 +- .../identities/add_new_identity_screen/mod.rs | 39 +++- src/ui/identities/identities_screen.rs | 10 +- src/ui/key_info_screen.rs | 14 +- src/ui/mod.rs | 4 +- .../mod.rs} | 111 ++++++++++-- 14 files changed, 406 insertions(+), 142 deletions(-) rename src/ui/wallet/{wallets_screen.rs => wallets_screen/mod.rs} (87%) diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index 2f6588120..6d81522c8 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -1,6 +1,7 @@ use super::BackendTaskSuccessResult; use crate::backend_task::identity::{verify_key_input, IdentityInputToLoad}; use crate::context::AppContext; +use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::PrivateKeyTarget::{ self, PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, @@ -8,16 +9,19 @@ use crate::model::qualified_identity::PrivateKeyTarget::{ use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, QualifiedIdentity}; use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::dashcore::PrivateKey; +use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::document::DocumentV0Getters; use dash_sdk::dpp::identifier::MasternodeIdentifiers; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dash_sdk::dpp::identity::{KeyType, SecurityLevel}; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::platform_value::Value; use dash_sdk::drive::query::{WhereClause, WhereOperator}; use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identifier, Identity}; use dash_sdk::Sdk; -use std::collections::BTreeMap; +use egui::ahash::HashMap; +use std::collections::{BTreeMap, HashSet}; impl AppContext { pub(super) async fn load_identity( @@ -76,7 +80,10 @@ impl AppContext { ); encrypted_private_keys.insert( (PrivateKeyOnMainIdentity, key_id), - (qualified_key, owner_private_key_bytes), + ( + qualified_key, + PrivateKeyData::Clear(owner_private_key_bytes), + ), ); } @@ -95,7 +102,10 @@ impl AppContext { ); encrypted_private_keys.insert( (PrivateKeyOnMainIdentity, key_id), - (qualified_key, payout_address_private_key_bytes), + ( + qualified_key, + PrivateKeyData::Clear(payout_address_private_key_bytes), + ), ); } @@ -132,7 +142,10 @@ impl AppContext { ); encrypted_private_keys.insert( (PrivateKeyOnVoterIdentity, key.id()), - (qualified_key, voting_private_key_bytes), + ( + qualified_key, + PrivateKeyData::Clear(voting_private_key_bytes), + ), ); Some((voter_identity, key)) } else { @@ -143,31 +156,78 @@ impl AppContext { }; if identity_type == IdentityType::User { - for (i, private_key_input) in keys_input.into_iter().enumerate() { - let key_id = i as u32; - let public_key = match identity.public_keys().get(&key_id) { - Some(key) => key, - None => return Err("No public key matching key id {key_id}".to_string()), - }; - let private_key_bytes = match verify_key_input( - private_key_input, - &public_key.key_type().to_string(), - )? { - Some(bytes) => bytes, - None => { - return Err("Private key input length is 0 for key id {key_id}".to_string()) - } - }; + let input_private_keys = keys_input + .into_iter() + .filter_map(|key_string| { + Some( + verify_key_input(key_string, "User Key") + .transpose()? + .and_then(|sk| { + PrivateKey::from_slice(sk.as_slice(), self.network) + .map_err(|e| e.to_string()) + }), + ) + }) + .collect::, String>>()?; + + let secp = Secp256k1::new(); + let (public_key_lookup, public_key_hash_lookup): ( + HashMap, [u8; 32]>, + HashMap<[u8; 20], [u8; 32]>, + ) = input_private_keys + .into_iter() + .map(|private_key| { + let public_key = private_key.public_key(&secp); + let public_key_bytes = public_key.to_bytes(); + let pub_key_hash = public_key.pubkey_hash().to_byte_array(); + ( + (public_key_bytes, private_key.inner.secret_bytes()), + (pub_key_hash, private_key.inner.secret_bytes()), + ) + }) + .unzip(); + + for (&key_id, public_key) in identity.public_keys().iter() { let qualified_key = QualifiedIdentityPublicKey::from_identity_public_key_with_wallets_check( public_key.clone(), self.network, wallets.as_slice(), ); - encrypted_private_keys.insert( - (PrivateKeyTarget::PrivateKeyOnMainIdentity, key_id), - (qualified_key, private_key_bytes), - ); + + if let Some(wallet_derivation_path) = + qualified_key.in_wallet_at_derivation_path.clone() + { + encrypted_private_keys.insert( + (PrivateKeyTarget::PrivateKeyOnMainIdentity, key_id), + ( + qualified_key, + PrivateKeyData::AtWalletDerivationPath(wallet_derivation_path), + ), + ); + } else if let Some(private_key_bytes) = + public_key_lookup.get(public_key.data().0.as_slice()) + { + let private_data = match public_key.security_level() { + SecurityLevel::MEDIUM => PrivateKeyData::AlwaysClear(*private_key_bytes), + _ => PrivateKeyData::Clear(*private_key_bytes), + }; + encrypted_private_keys.insert( + (PrivateKeyTarget::PrivateKeyOnMainIdentity, key_id), + (qualified_key, private_data), + ); + } else if let Some(private_key_bytes) = + public_key_hash_lookup.get(public_key.data().0.as_slice()) + { + let private_data = match public_key.security_level() { + SecurityLevel::MEDIUM => PrivateKeyData::AlwaysClear(*private_key_bytes), + _ => PrivateKeyData::Clear(*private_key_bytes), + }; + encrypted_private_keys.insert( + (PrivateKeyTarget::PrivateKeyOnMainIdentity, key_id), + (qualified_key, private_data), + ); + } } } diff --git a/src/backend_task/identity/load_identity_from_wallet.rs b/src/backend_task/identity/load_identity_from_wallet.rs index 38053440e..881db6f6e 100644 --- a/src/backend_task/identity/load_identity_from_wallet.rs +++ b/src/backend_task/identity/load_identity_from_wallet.rs @@ -1,10 +1,14 @@ -use std::collections::BTreeMap; -use dash_sdk::dpp::dashcore::bip32::{DerivationPath, KeyDerivationType}; use super::{BackendTaskSuccessResult, IdentityIndex}; use crate::context::AppContext; +use crate::model::qualified_identity::encrypted_key_storage::{ + PrivateKeyData, WalletDerivationPath, +}; use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; -use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, PrivateKeyTarget, QualifiedIdentity}; -use crate::model::wallet::Wallet; +use crate::model::qualified_identity::{ + DPNSNameInfo, IdentityType, PrivateKeyTarget, QualifiedIdentity, +}; +use crate::model::wallet::{Wallet, WalletArcRef}; +use dash_sdk::dpp::dashcore::bip32::{DerivationPath, KeyDerivationType}; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::document::DocumentV0Getters; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; @@ -15,17 +19,19 @@ use dash_sdk::drive::query::{WhereClause, WhereOperator}; use dash_sdk::platform::types::identity::PublicKeyHash; use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identity}; use dash_sdk::Sdk; -use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; +use std::collections::BTreeMap; impl AppContext { pub(super) async fn load_user_identity_from_wallet( &self, sdk: &Sdk, - wallet: Wallet, + wallet_arc_ref: WalletArcRef, identity_index: IdentityIndex, ) -> Result { - let public_key = - wallet.identity_authentication_ecdsa_public_key(self.network, identity_index, 0)?; + let public_key = { + let mut wallet = wallet_arc_ref.wallet.write().unwrap(); + wallet.identity_authentication_ecdsa_public_key(self.network, identity_index, 0)? + }; let Some(identity) = Identity::fetch( &sdk, @@ -85,9 +91,18 @@ impl AppContext { let top_bound = identity.public_keys().len() as u32 + 5; - let (public_key_result_map, public_key_hash_result_map) = wallet.identity_authentication_ecdsa_public_keys_data_map(self.network, identity_index, 0..top_bound)?; + let wallet_seed_hash; + let (public_key_result_map, public_key_hash_result_map) = { + let mut wallet = wallet_arc_ref.wallet.write().unwrap(); + wallet_seed_hash = wallet.seed_hash(); + wallet.identity_authentication_ecdsa_public_keys_data_map( + self.network, + identity_index, + 0..top_bound, + Some(self), + )? + }; - let wallet_seed_hash = wallet.seed_hash(); let private_keys = identity.public_keys().values().filter_map(|public_key| { let index: u32 = match public_key.key_type() { KeyType::ECDSA_SECP256K1 => { diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index 8e82897e1..e46fcc53f 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -13,7 +13,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::encrypted_key_storage::{KeyStorage, WalletDerivationPath}; use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::{IdentityType, PrivateKeyTarget, QualifiedIdentity}; -use crate::model::wallet::{Wallet, WalletSeedHash}; +use crate::model::wallet::{Wallet, WalletArcRef, WalletSeedHash}; use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath; use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::dashcore::{Address, PrivateKey, TxOut}; @@ -81,7 +81,10 @@ impl IdentityKeys { let qualified_identity_public_key = QualifiedIdentityPublicKey::from_identity_public_key_in_wallet( key, - Some(WalletDerivationPath { wallet_seed_hash, derivation_path: master_private_key_derivation_path.clone() }), + Some(WalletDerivationPath { + wallet_seed_hash, + derivation_path: master_private_key_derivation_path.clone(), + }), ); key_map.insert( (PrivateKeyTarget::PrivateKeyOnMainIdentity, 0), @@ -109,7 +112,10 @@ impl IdentityKeys { let qualified_identity_public_key = QualifiedIdentityPublicKey::from_identity_public_key_in_wallet( identity_public_key, - Some(WalletDerivationPath { wallet_seed_hash, derivation_path: derivation_path.clone() }), + Some(WalletDerivationPath { + wallet_seed_hash, + derivation_path: derivation_path.clone(), + }), ); ( (PrivateKeyTarget::PrivateKeyOnMainIdentity, id), @@ -221,7 +227,7 @@ pub struct RegisterDpnsNameInput { #[derive(Debug, Clone, PartialEq)] pub(crate) enum IdentityTask { LoadIdentity(IdentityInputToLoad), - SearchIdentityFromWallet(Wallet, IdentityIndex), + SearchIdentityFromWallet(WalletArcRef, IdentityIndex), RegisterIdentity(IdentityRegistrationInfo), AddKeyToIdentity(QualifiedIdentity, QualifiedIdentityPublicKey, [u8; 32]), WithdrawFromIdentity(QualifiedIdentity, Option
, Credits, Option), diff --git a/src/database/wallet.rs b/src/database/wallet.rs index 4d3b13157..16aaaf6dd 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -79,7 +79,7 @@ impl Database { /// Add a new address to a wallet with optional balance. /// If the address already exists, it does nothing. - pub fn add_address( + pub fn add_address_if_not_exists( &self, seed_hash: &[u8; 32], address: &Address, diff --git a/src/model/qualified_identity/encrypted_key_storage.rs b/src/model/qualified_identity/encrypted_key_storage.rs index 2d9e1e9f1..598fd4188 100644 --- a/src/model/qualified_identity/encrypted_key_storage.rs +++ b/src/model/qualified_identity/encrypted_key_storage.rs @@ -171,9 +171,11 @@ pub struct KeyStorage { } impl From> -for KeyStorage + for KeyStorage { - fn from(value: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, PrivateKeyData)>) -> Self { + fn from( + value: BTreeMap<(PrivateKeyTarget, KeyID), (QualifiedIdentityPublicKey, PrivateKeyData)>, + ) -> Self { Self { private_keys: value, } diff --git a/src/model/qualified_identity/qualified_identity_public_key.rs b/src/model/qualified_identity/qualified_identity_public_key.rs index 74da76d87..5596b1a55 100644 --- a/src/model/qualified_identity/qualified_identity_public_key.rs +++ b/src/model/qualified_identity/qualified_identity_public_key.rs @@ -1,10 +1,10 @@ +use crate::model::qualified_identity::encrypted_key_storage::WalletDerivationPath; use crate::model::wallet::Wallet; use bincode::{Decode, Encode}; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::identity::hash::IdentityPublicKeyHashMethodsV0; use dash_sdk::platform::IdentityPublicKey; use std::sync::{Arc, RwLock}; -use crate::model::qualified_identity::encrypted_key_storage::WalletDerivationPath; #[derive(Debug, Encode, Decode, Clone, PartialEq)] pub struct QualifiedIdentityPublicKey { @@ -44,11 +44,10 @@ impl QualifiedIdentityPublicKey { for locked_wallet in wallets { let wallet = locked_wallet.read().unwrap(); if let Some(derivation_path) = wallet.known_addresses.get(&address) { - in_wallet_at_derivation_path = - Some(WalletDerivationPath { - wallet_seed_hash: wallet.seed_hash(), - derivation_path: derivation_path.clone(), - }); + in_wallet_at_derivation_path = Some(WalletDerivationPath { + wallet_seed_hash: wallet.seed_hash(), + derivation_path: derivation_path.clone(), + }); } if in_wallet_at_derivation_path.is_some() { break; diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index 812b77725..03db4bede 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -70,6 +70,7 @@ use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::fee::Credits; use dash_sdk::dpp::prelude::AssetLockProof; use dash_sdk::platform::Identity; +use egui::epaint::tessellator::PathType; use zeroize::Zeroize; bitflags! { @@ -100,6 +101,25 @@ pub struct AddressInfo { pub path_reference: DerivationPathReference, } +#[derive(Debug, Clone)] +pub struct WalletArcRef { + pub wallet: Arc>, + pub seed_hash: WalletSeedHash, +} + +impl From>> for WalletArcRef { + fn from(wallet: Arc>) -> Self { + let seed_hash = { wallet.read().unwrap().seed_hash() }; + Self { wallet, seed_hash } + } +} + +impl PartialEq for WalletArcRef { + fn eq(&self, other: &Self) -> bool { + self.seed_hash == other.seed_hash + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Wallet { pub wallet_seed: WalletSeed, @@ -397,7 +417,7 @@ impl Wallet { .map_err(|e| e.to_string())?; app_context .db - .add_address( + .add_address_if_not_exists( &self.seed_hash(), &address, &derivation_path, @@ -448,11 +468,12 @@ impl Wallet { } pub fn identity_authentication_ecdsa_public_keys_data_map( - &self, + &mut self, network: Network, identity_index: u32, key_index_range: Range, - ) -> Result<(BTreeMap, u32>, BTreeMap<[u8;20], u32>), String> { + register_addresses: Option<&AppContext>, + ) -> Result<(BTreeMap, u32>, BTreeMap<[u8; 20], u32>), String> { let mut public_key_result_map = BTreeMap::new(); let mut public_key_hash_result_map = BTreeMap::new(); for key_index in key_index_range { @@ -466,18 +487,32 @@ impl Wallet { .derive_pub_ecdsa_for_master_seed(self.seed_bytes()?, network) .map_err(|e| e.to_string())?; - public_key_result_map.insert(extended_public_key.public_key.serialize().to_vec(), key_index); - public_key_hash_result_map.insert(extended_public_key.to_pub().pubkey_hash().to_byte_array(), key_index); + let public_key = extended_public_key.to_pub(); + public_key_result_map.insert( + extended_public_key.public_key.serialize().to_vec(), + key_index, + ); + public_key_hash_result_map.insert(public_key.pubkey_hash().to_byte_array(), key_index); + if let Some(app_context) = register_addresses { + self.register_address_from_public_key( + &public_key, + &derivation_path, + DerivationPathType::SINGLE_USER_AUTHENTICATION, + DerivationPathReference::BlockchainIdentities, + app_context, + )?; + } } Ok((public_key_result_map, public_key_hash_result_map)) } pub fn identity_authentication_ecdsa_private_key( - &self, + &mut self, network: Network, identity_index: u32, key_index: u32, + register_addresses: Option<&AppContext>, ) -> Result<(PrivateKey, DerivationPath), String> { let derivation_path = DerivationPath::identity_authentication_path( network, @@ -488,21 +523,87 @@ impl Wallet { let extended_public_key = derivation_path .derive_priv_ecdsa_for_master_seed(self.seed_bytes()?, network) .expect("derivation should not be able to fail"); - Ok((extended_public_key.to_priv(), derivation_path)) + + let private_key = extended_public_key.to_priv(); + if let Some(app_context) = register_addresses { + self.register_address_from_private_key( + &private_key, + &derivation_path, + DerivationPathType::SINGLE_USER_AUTHENTICATION, + DerivationPathReference::BlockchainIdentities, + app_context, + )?; + } + + Ok((private_key, derivation_path)) } - pub fn identity_registration_ecdsa_public_key( - &self, - network: Network, - index: u32, - ) -> PublicKey { - let derivation_path = DerivationPath::identity_registration_path(network, index); + fn register_address_from_private_key( + &mut self, + private_key: &PrivateKey, + derivation_path: &DerivationPath, + path_type: DerivationPathType, + path_reference: DerivationPathReference, + app_context: &AppContext, + ) -> Result<(), String> { let secp = Secp256k1::new(); - let extended_public_key = self - .master_bip44_ecdsa_extended_public_key - .derive_pub(&secp, &derivation_path) - .expect("derivation should not be able to fail"); - extended_public_key.to_pub() + let address = Address::p2pkh(&private_key.public_key(&secp), app_context.network); + self.register_address( + address, + derivation_path, + path_type, + path_reference, + app_context, + ) + } + + fn register_address_from_public_key( + &mut self, + public_key: &PublicKey, + derivation_path: &DerivationPath, + path_type: DerivationPathType, + path_reference: DerivationPathReference, + app_context: &AppContext, + ) -> Result<(), String> { + let address = Address::p2pkh(public_key, app_context.network); + self.register_address( + address, + derivation_path, + path_type, + path_reference, + app_context, + ) + } + fn register_address( + &mut self, + address: Address, + derivation_path: &DerivationPath, + path_type: DerivationPathType, + path_reference: DerivationPathReference, + app_context: &AppContext, + ) -> Result<(), String> { + app_context + .db + .add_address_if_not_exists( + &self.seed_hash(), + &address, + derivation_path, + DerivationPathReference::BlockchainIdentityCreditRegistrationFunding, + DerivationPathType::CREDIT_FUNDING, + None, + ) + .map_err(|e| e.to_string())?; + self.known_addresses + .insert(address.clone(), derivation_path.clone()); + self.watched_addresses.insert( + derivation_path.clone(), + AddressInfo { + address, + path_type, + path_reference, + }, + ); + Ok(()) } pub fn identity_registration_ecdsa_private_key( @@ -518,30 +619,13 @@ impl Wallet { let private_key = extended_private_key.to_priv(); if let Some(app_context) = register_addresses { - let secp = Secp256k1::new(); - let address = Address::p2pkh(&private_key.public_key(&secp), network); - app_context - .db - .add_address( - &self.seed_hash(), - &address, - &derivation_path, - DerivationPathReference::BlockchainIdentityCreditRegistrationFunding, - DerivationPathType::CREDIT_FUNDING, - None, - ) - .map_err(|e| e.to_string())?; - self.known_addresses - .insert(address.clone(), derivation_path.clone()); - self.watched_addresses.insert( - derivation_path.clone(), - AddressInfo { - address: address.clone(), - path_type: DerivationPathType::CREDIT_FUNDING, - path_reference: - DerivationPathReference::BlockchainIdentityCreditRegistrationFunding, - }, - ); + self.register_address_from_private_key( + &private_key, + &derivation_path, + DerivationPathType::CREDIT_FUNDING, + DerivationPathReference::BlockchainIdentityCreditRegistrationFunding, + app_context, + )?; } Ok(private_key) } diff --git a/src/ui/components/wallet_unlock.rs b/src/ui/components/wallet_unlock.rs index a964f4375..8410a7c4e 100644 --- a/src/ui/components/wallet_unlock.rs +++ b/src/ui/components/wallet_unlock.rs @@ -1,8 +1,8 @@ -use std::sync::{Arc, RwLock}; +use crate::model::wallet::Wallet; use eframe::epaint::Color32; use egui::Ui; +use std::sync::{Arc, RwLock}; use zeroize::Zeroize; -use crate::model::wallet::Wallet; pub trait ScreenWithWalletUnlock { fn selected_wallet_ref(&self) -> &Option>>; @@ -15,7 +15,7 @@ pub trait ScreenWithWalletUnlock { fn error_message(&self) -> Option<&String>; fn should_ask_for_password(&mut self) -> bool { - if let Some(wallet_guard) = self.selected_wallet_ref().clone() { + if let Some(wallet_guard) = self.selected_wallet_ref().clone() { let mut wallet = wallet_guard.write().unwrap(); if !wallet.uses_password { if let Err(e) = wallet.wallet_seed.open_no_password() { @@ -67,8 +67,7 @@ pub trait ScreenWithWalletUnlock { // Checkbox to toggle password visibility ui.checkbox(&mut local_show_password, "Show Password"); - if password_input.lost_focus() - && ui.input(|i| i.key_pressed(egui::Key::Enter)) + if password_input.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { // Use the password from wallet_password_mut let wallet_password_ref = &*wallet_password_mut; @@ -113,4 +112,4 @@ pub trait ScreenWithWalletUnlock { } false } -} \ No newline at end of file +} diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index ef2884cb8..1f8154850 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -5,6 +5,8 @@ use crate::context::AppContext; use crate::model::qualified_identity::IdentityType; use crate::model::wallet::Wallet; use crate::ui::components::top_panel::add_top_panel; +use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::identities::add_new_identity_screen::AddNewIdentityScreen; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dashcore_rpc::dashcore::Network; use dash_sdk::dpp::identity::TimestampMillis; @@ -17,8 +19,6 @@ use std::fs; use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; -use crate::ui::identities::add_new_identity_screen::AddNewIdentityScreen; #[derive(Debug, Clone, Deserialize)] struct MasternodeInfo { @@ -240,7 +240,7 @@ impl AddExistingIdentityScreen { }; let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); - + if needed_unlock && !just_unlocked { return action; } @@ -262,18 +262,14 @@ impl AddExistingIdentityScreen { if let Ok(identity_index) = self.identity_index_input.trim().parse::() { action = AppAction::BackendTask(BackendTask::IdentityTask( IdentityTask::SearchIdentityFromWallet( - self.selected_wallet - .as_ref() - .unwrap() - .read() - .unwrap() - .clone(), + self.selected_wallet.as_ref().unwrap().clone().into(), identity_index, ), )); } else { // Handle invalid index input (optional) - self.add_identity_status = AddIdentityStatus::ErrorMessage("Invalid identity index".to_string()); + self.add_identity_status = + AddIdentityStatus::ErrorMessage("Invalid identity index".to_string()); } } action @@ -398,7 +394,6 @@ impl AddExistingIdentityScreen { } } - impl ScreenWithWalletUnlock for AddExistingIdentityScreen { fn selected_wallet_ref(&self) -> &Option>> { &self.selected_wallet diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 92a2225d7..9d90cf1a0 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -11,6 +11,7 @@ use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::top_panel::add_top_panel; +use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::{MessageType, ScreenLike}; use arboard::Clipboard; use dash_sdk::dashcore_rpc::dashcore::Address; @@ -30,7 +31,6 @@ use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{fmt, thread}; use zeroize::Zeroize; -use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; #[derive(Debug, Clone, Deserialize)] struct KeyInfo { @@ -378,7 +378,7 @@ impl AddNewIdentityScreen { // Automatically select the only available wallet self.selected_wallet = Some(wallet.clone()); - let wallet = wallet.read().unwrap(); + let mut wallet = wallet.write().unwrap(); if wallet.is_open() { self.identity_id_number = @@ -390,6 +390,7 @@ impl AddNewIdentityScreen { self.app_context.network, 0, 0, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), ); @@ -401,6 +402,7 @@ impl AddNewIdentityScreen { self.app_context.network, 0, 1, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), KeyType::ECDSA_HASH160, @@ -413,6 +415,7 @@ impl AddNewIdentityScreen { self.app_context.network, 0, 2, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), KeyType::ECDSA_HASH160, @@ -716,7 +719,7 @@ impl AddNewIdentityScreen { } fn update_identity_key(&mut self) { if let Some(wallet_guard) = self.selected_wallet.as_ref() { - let wallet = wallet_guard.read().unwrap(); + let mut wallet = wallet_guard.write().unwrap(); let identity_index = self.identity_id_number; // Update the master private key and keys input from the wallet @@ -726,6 +729,7 @@ impl AddNewIdentityScreen { self.app_context.network, identity_index, 0, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), ); @@ -743,6 +747,7 @@ impl AddNewIdentityScreen { self.app_context.network, identity_index, key_index as u32 + 1, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), *key_type, @@ -756,7 +761,7 @@ impl AddNewIdentityScreen { fn add_identity_key(&mut self) { if let Some(wallet_guard) = self.selected_wallet.as_ref() { - let wallet = wallet_guard.read().unwrap(); + let mut wallet = wallet_guard.write().unwrap(); let new_key_index = self.identity_keys.keys_input.len() as u32 + 1; // Add a new key with default parameters @@ -766,6 +771,7 @@ impl AddNewIdentityScreen { self.app_context.network, self.identity_id_number, new_key_index, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), KeyType::ECDSA_HASH160, // Default key type @@ -882,7 +888,7 @@ impl ScreenLike for AddNewIdentityScreen { if needed_unlock { if just_unlocked { let wallet_guard = self.selected_wallet.as_ref().unwrap(); - let wallet = wallet_guard.read().unwrap(); + let mut wallet = wallet_guard.write().unwrap(); self.identity_id_number = wallet.identities.keys().copied().max().unwrap_or_default(); @@ -893,6 +899,7 @@ impl ScreenLike for AddNewIdentityScreen { self.app_context.network, 0, 0, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), ); @@ -904,6 +911,7 @@ impl ScreenLike for AddNewIdentityScreen { self.app_context.network, 0, 1, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), KeyType::ECDSA_HASH160, @@ -916,6 +924,7 @@ impl ScreenLike for AddNewIdentityScreen { self.app_context.network, 0, 2, + Some(&self.app_context), ) .expect("expected to have decrypted wallet"), KeyType::ECDSA_HASH160, @@ -930,10 +939,22 @@ impl ScreenLike for AddNewIdentityScreen { // Display the heading with an info icon that shows a tooltip on hover ui.horizontal(|ui| { - ui.heading(format!( - "{}. Choose an identity index. Leave this 0 if this is your first identity for this wallet.", - step_number - )); + + let wallet_guard = self.selected_wallet.as_ref().unwrap(); + let wallet = wallet_guard.read().unwrap(); + if wallet.identities.is_empty() { + ui.heading(format!( + "{}. Choose an identity index. Leave this 0 if this is your first identity for this wallet.", + step_number + )); + } else { + ui.heading(format!( + "{}. Choose an identity index. Leaving this {} is recommended.", + step_number, + wallet.identities.keys().cloned().max().unwrap_or_default() + )); + } + // Create a label with click sense and tooltip let info_icon = egui::Label::new("ℹ").sense(egui::Sense::click()); diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index b71d4d1ca..2b9fc2392 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -2,7 +2,9 @@ use crate::app::{AppAction, DesiredAppAction}; use crate::backend_task::identity::IdentityTask; use crate::backend_task::BackendTask; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; +use crate::model::qualified_identity::encrypted_key_storage::{ + PrivateKeyData, WalletDerivationPath, +}; use crate::model::qualified_identity::PrivateKeyTarget::{ PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; @@ -141,9 +143,9 @@ impl IdentitiesScreen { .as_ref() { None => "".to_string(), - Some(wallet_derivation_path) => { - self.find_wallet(&wallet_derivation_path.wallet_seed_hash).unwrap_or_default() - } + Some(wallet_derivation_path) => self + .find_wallet(&wallet_derivation_path.wallet_seed_hash) + .unwrap_or_default(), } } }; diff --git a/src/ui/key_info_screen.rs b/src/ui/key_info_screen.rs index fa388ce31..01be67e1e 100644 --- a/src/ui/key_info_screen.rs +++ b/src/ui/key_info_screen.rs @@ -1,6 +1,8 @@ use crate::app::AppAction; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; +use crate::model::qualified_identity::encrypted_key_storage::{ + PrivateKeyData, WalletDerivationPath, +}; use crate::model::qualified_identity::QualifiedIdentity; use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; @@ -82,12 +84,16 @@ impl ScreenLike for KeyInfoScreen { } ui.end_row(); - if let Some((_, Some(wallet_derivation_path))) = self.private_key_data.as_ref() { + if let Some((_, Some(wallet_derivation_path))) = self.private_key_data.as_ref() + { // Disabled ui.label(RichText::new("In local Wallet").strong()); ui.label( - RichText::new(format!("At derivation path {}", wallet_derivation_path.derivation_path)) - .strong(), + RichText::new(format!( + "At derivation path {}", + wallet_derivation_path.derivation_path + )) + .strong(), ); ui.end_row(); } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ac4472434..1df7ab822 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,7 +1,9 @@ use crate::app::AppAction; use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::{PrivateKeyData, WalletDerivationPath}; +use crate::model::qualified_identity::encrypted_key_storage::{ + PrivateKeyData, WalletDerivationPath, +}; use crate::model::qualified_identity::QualifiedIdentity; use crate::ui::add_key_screen::AddKeyScreen; use crate::ui::document_query_screen::DocumentQueryScreen; diff --git a/src/ui/wallet/wallets_screen.rs b/src/ui/wallet/wallets_screen/mod.rs similarity index 87% rename from src/ui/wallet/wallets_screen.rs rename to src/ui/wallet/wallets_screen/mod.rs index 312d5ee5f..66e4309ec 100644 --- a/src/ui/wallet/wallets_screen.rs +++ b/src/ui/wallet/wallets_screen/mod.rs @@ -10,6 +10,7 @@ use dash_sdk::dashcore_rpc::dashcore::{Address, Network}; use dash_sdk::dpp::dashcore::bip32::{ChildNumber, DerivationPath}; use eframe::egui::{self, ComboBox, Context, Ui}; use egui_extras::{Column, TableBuilder}; +use std::collections::HashSet; use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; @@ -36,6 +37,7 @@ pub struct WalletsBalancesScreen { error_message: Option<(String, MessageType)>, sort_column: SortColumn, sort_order: SortOrder, + selected_filters: HashSet, } pub trait DerivationPathHelpers { @@ -113,12 +115,15 @@ struct AddressData { impl WalletsBalancesScreen { pub fn new(app_context: &Arc) -> Self { let selected_wallet = app_context.wallets.read().unwrap().first().cloned(); + let mut selected_filters = HashSet::new(); + selected_filters.insert("Funds".to_string()); // "Funds" selected by default Self { selected_wallet, app_context: app_context.clone(), error_message: None, sort_column: SortColumn::Index, sort_order: SortOrder::Ascending, + selected_filters, } } @@ -168,6 +173,41 @@ impl WalletsBalancesScreen { }); } + fn render_filter_selector(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + let filter_options = ["Funds", "Identity Creation", "System", "Asset Locks"]; + + for filter_option in &filter_options { + let is_selected = self.selected_filters.contains(*filter_option); + + // Create RichText with a larger font size + let text = egui::RichText::new(*filter_option).size(14.0); + + let button = egui::SelectableLabel::new(is_selected, text); + + // Set the desired button size + let button_size = egui::Vec2::new(100.0, 30.0); + + if ui.add_sized(button_size, button).clicked() { + let shift_held = ui.input(|i| i.modifiers.shift_only()); + + if shift_held { + // If Shift is held, toggle the filter + if is_selected { + self.selected_filters.remove(*filter_option); + } else { + self.selected_filters.insert((*filter_option).to_string()); + } + } else { + // Without Shift, replace the selection + self.selected_filters.clear(); + self.selected_filters.insert((*filter_option).to_string()); + } + } + } + }); + } + fn render_wallet_selection(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { if self.app_context.has_wallet.load(Ordering::Relaxed) { @@ -261,6 +301,21 @@ impl WalletsBalancesScreen { fn render_address_table(&mut self, ui: &mut Ui) -> AppAction { let action = AppAction::None; + + let mut included_address_types = HashSet::new(); + + for filter in &self.selected_filters { + match filter.as_str() { + "Funds" => { + included_address_types.insert("Funds".to_string()); + included_address_types.insert("Change".to_string()); + } + other => { + included_address_types.insert(other.to_string()); + } + } + } + // Move the data preparation into its own scope let mut address_data = { let wallet = self.selected_wallet.as_ref().unwrap().read().unwrap(); @@ -269,7 +324,7 @@ impl WalletsBalancesScreen { wallet .known_addresses .iter() - .map(|(address, derivation_path)| { + .filter_map(|(address, derivation_path)| { let utxo_info = wallet.utxos.get(address); let utxo_count = utxo_info.map(|outpoints| outpoints.len()).unwrap_or(0); @@ -300,18 +355,22 @@ impl WalletsBalancesScreen { "System".to_string() }; - AddressData { - address: address.clone(), - balance: wallet - .address_balances - .get(address) - .cloned() - .unwrap_or_default(), - utxo_count, - total_received, - address_type, - index, - derivation_path: derivation_path.clone(), + if included_address_types.contains(address_type.as_str()) { + Some(AddressData { + address: address.clone(), + balance: wallet + .address_balances + .get(address) + .cloned() + .unwrap_or_default(), + utxo_count, + total_received, + address_type, + index, + derivation_path: derivation_path.clone(), + }) + } else { + None } }) .collect::>() @@ -467,9 +526,11 @@ impl WalletsBalancesScreen { } fn render_bottom_options(&mut self, ui: &mut Ui) { - // Add the button to add a receiving address - if ui.button("Add Receiving Address").clicked() { - self.add_receiving_address(); + if self.selected_filters.contains("Funds") { + // Add the button to add a receiving address + if ui.button("Add Receiving Address").clicked() { + self.add_receiving_address(); + } } } @@ -585,12 +646,24 @@ impl ScreenLike for WalletsBalancesScreen { // Render the address table if self.selected_wallet.is_some() { - action |= self.render_address_table(ui); + self.render_filter_selector(ui); ui.add_space(20.0); - // Render the asset locks section - self.render_wallet_asset_locks(ui); + if !(self.selected_filters.contains("Asset Locks") + && self.selected_filters.len() == 1) + { + action |= self.render_address_table(ui); + } + + ui.add_space(20.0); + + if self.selected_filters.contains("Asset Locks") { + // Render the asset locks section + self.render_wallet_asset_locks(ui); + } + + ui.add_space(15.0); self.render_bottom_options(ui); } else { From 72f6a568d3af6c04378a4bc5580734de2fc5ac4c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 11 Nov 2024 11:32:45 +0100 Subject: [PATCH 11/11] more work --- src/context.rs | 1 + src/database/asset_lock_transaction.rs | 59 +++++++++--- src/database/initialization.rs | 1 + src/database/wallet.rs | 34 ++++++- .../by_wallet_qr_code.rs | 6 +- .../identities/add_new_identity_screen/mod.rs | 95 +++++++++++++++---- 6 files changed, 156 insertions(+), 40 deletions(-) diff --git a/src/context.rs b/src/context.rs index ed2677cdb..c93a81cc0 100644 --- a/src/context.rs +++ b/src/context.rs @@ -361,6 +361,7 @@ impl AppContext { amount, islock.as_ref(), &wallet.seed_hash(), + self.network, )?; let first = payload diff --git a/src/database/asset_lock_transaction.rs b/src/database/asset_lock_transaction.rs index ac01b8d51..7e6dec4bf 100644 --- a/src/database/asset_lock_transaction.rs +++ b/src/database/asset_lock_transaction.rs @@ -1,7 +1,7 @@ use crate::database::Database; use dash_sdk::dpp::dashcore::{ consensus::{deserialize, serialize}, - InstantLock, Transaction, + InstantLock, Network, Transaction, }; use rusqlite::params; @@ -10,9 +10,10 @@ impl Database { pub fn store_asset_lock_transaction( &self, tx: &Transaction, - amount: u64, // Include amount as a parameter + amount: u64, islock: Option<&InstantLock>, - wallet_seed_hash: &[u8; 32], // Include wallet_seed_hash as a parameter + wallet_seed_hash: &[u8; 32], + network: Network, ) -> rusqlite::Result<()> { let tx_bytes = serialize(tx); let txid = tx.txid().to_string(); @@ -26,17 +27,25 @@ impl Database { let conn = self.conn.lock().unwrap(); let sql = " - INSERT INTO asset_lock_transaction (tx_id, transaction_data, amount, instant_lock_data, wallet) - VALUES (?1, ?2, ?3, ?4, ?5) + INSERT INTO asset_lock_transaction (tx_id, transaction_data, amount, instant_lock_data, wallet, network) + VALUES (?1, ?2, ?3, ?4, ?5, ?6) ON CONFLICT(tx_id) DO UPDATE SET transaction_data = excluded.transaction_data, amount = excluded.amount, - instant_lock_data = COALESCE(excluded.instant_lock_data, asset_lock_transaction.instant_lock_data); + instant_lock_data = COALESCE(excluded.instant_lock_data, asset_lock_transaction.instant_lock_data), + network = excluded.network; "; conn.execute( sql, - params![&txid, &tx_bytes, amount, &islock_bytes, wallet_seed_hash], + params![ + &txid, + &tx_bytes, + amount, + &islock_bytes, + wallet_seed_hash, + network.to_string() + ], )?; Ok(()) @@ -46,11 +55,11 @@ impl Database { pub fn get_asset_lock_transaction( &self, txid: &str, - ) -> rusqlite::Result, [u8; 32])>> { + ) -> rusqlite::Result, [u8; 32], String)>> { let conn = self.conn.lock().unwrap(); let mut stmt = conn.prepare( - "SELECT transaction_data, amount, instant_lock_data, wallet FROM asset_lock_transaction WHERE tx_id = ?1", + "SELECT transaction_data, amount, instant_lock_data, wallet, network FROM asset_lock_transaction WHERE tx_id = ?1", )?; let mut rows = stmt.query(params![txid])?; @@ -60,6 +69,7 @@ impl Database { let amount: u64 = row.get(1)?; let islock_data: Option> = row.get(2)?; let wallet_seed: Vec = row.get(3)?; + let network: String = row.get(4)?; let tx: Transaction = deserialize(&tx_data).map_err(|_| rusqlite::Error::InvalidQuery)?; @@ -73,7 +83,7 @@ impl Database { .try_into() .map_err(|_| rusqlite::Error::InvalidQuery)?; - Ok(Some((tx, amount, islock, wallet_seed_hash))) + Ok(Some((tx, amount, islock, wallet_seed_hash, network))) } else { Ok(None) } @@ -140,9 +150,11 @@ impl Database { Ok(()) } + /// Retrieves all asset lock transactions. pub fn get_all_asset_lock_transactions( &self, + network: Network, ) -> rusqlite::Result< Vec<( Transaction, @@ -156,10 +168,10 @@ impl Database { let conn = self.conn.lock().unwrap(); let mut stmt = conn.prepare( - "SELECT transaction_data, amount, instant_lock_data, chain_locked_height, identity_id, wallet FROM asset_lock_transaction", + "SELECT transaction_data, amount, instant_lock_data, chain_locked_height, identity_id, wallet, network FROM asset_lock_transaction where network = ?", )?; - let mut rows = stmt.query(params![])?; + let mut rows = stmt.query(params![network.to_string()])?; let mut results = Vec::new(); @@ -200,11 +212,20 @@ impl Database { pub fn get_asset_lock_transactions_by_identity_id( &self, identity_id: &[u8], - ) -> rusqlite::Result, Option, [u8; 32])>> { + ) -> rusqlite::Result< + Vec<( + Transaction, + u64, + Option, + Option, + [u8; 32], + String, + )>, + > { let conn = self.conn.lock().unwrap(); let mut stmt = conn.prepare( - "SELECT transaction_data, amount, instant_lock_data, chain_locked_height, wallet FROM asset_lock_transaction WHERE identity_id = ?1", + "SELECT transaction_data, amount, instant_lock_data, chain_locked_height, wallet, network FROM asset_lock_transaction WHERE identity_id = ?1", )?; let mut rows = stmt.query(params![identity_id])?; @@ -217,6 +238,7 @@ impl Database { let islock_data: Option> = row.get(2)?; let chain_locked_height: Option = row.get(3)?; let wallet_seed: Vec = row.get(4)?; + let network: String = row.get(5)?; let tx: Transaction = deserialize(&tx_data).map_err(|_| rusqlite::Error::InvalidQuery)?; @@ -230,7 +252,14 @@ impl Database { .try_into() .map_err(|_| rusqlite::Error::InvalidQuery)?; - results.push((tx, amount, islock, chain_locked_height, wallet_seed_hash)); + results.push(( + tx, + amount, + islock, + chain_locked_height, + wallet_seed_hash, + network, + )); } Ok(results) diff --git a/src/database/initialization.rs b/src/database/initialization.rs index 73122bd90..9f4d617ee 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -85,6 +85,7 @@ impl Database { identity_id BLOB, identity_id_potentially_in_creation BLOB, wallet BLOB NOT NULL, + network TEXT NOT NULL, FOREIGN KEY (identity_id) REFERENCES identity(id) ON DELETE CASCADE, FOREIGN KEY (identity_id_potentially_in_creation) REFERENCES identity(id), FOREIGN KEY (wallet) REFERENCES wallet(seed_hash) ON DELETE CASCADE diff --git a/src/database/wallet.rs b/src/database/wallet.rs index 16aaaf6dd..c11da0bc7 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -1,4 +1,5 @@ use crate::database::Database; +use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::{ AddressInfo, ClosedWalletSeed, DerivationPathReference, DerivationPathType, Wallet, WalletSeed, }; @@ -335,10 +336,10 @@ impl Database { // Step 6: Retrieve asset lock transactions for each wallet and add them to the wallets. let mut asset_lock_stmt = conn.prepare( - "SELECT wallet, amount, transaction_data, instant_lock_data, chain_locked_height FROM asset_lock_transaction where identity_id IS NULL", + "SELECT wallet, amount, transaction_data, instant_lock_data, chain_locked_height FROM asset_lock_transaction where identity_id IS NULL AND network = ?", )?; - let asset_lock_rows = asset_lock_stmt.query_map([], |row| { + let asset_lock_rows = asset_lock_stmt.query_map([network.to_string()], |row| { let wallet_seed: Vec = row.get(0)?; let amount: Duffs = row.get(1)?; let tx_data: Vec = row.get(2)?; @@ -403,6 +404,35 @@ impl Database { } } + // Step 8: Retrieve identities for each wallet and add them to the wallets. + let mut identity_stmt = conn.prepare( + "SELECT data, wallet, wallet_index FROM identity WHERE network = ? AND wallet IS NOT NULL AND wallet_index IS NOT NULL", + )?; + + let identity_rows = identity_stmt.query_map([network_str.clone()], |row| { + let data: Vec = row.get(0)?; + let wallet_seed_hash: Vec = row.get(1)?; + let wallet_index: u32 = row.get(2)?; + + let wallet_seed_hash_array: [u8; 32] = wallet_seed_hash + .try_into() + .expect("Seed hash should be 32 bytes"); + + Ok((data, wallet_seed_hash_array, wallet_index)) + })?; + + // Process the identities and add them to the corresponding wallets. + for row in identity_rows { + let (identity_data, wallet_seed_hash_array, wallet_index) = row?; + + if let Some(wallet) = wallets_map.get_mut(&wallet_seed_hash_array) { + let identity: QualifiedIdentity = QualifiedIdentity::from_bytes(&identity_data); + + // Insert the identity into the wallet's identities HashMap with wallet_index as the key + wallet.identities.insert(wallet_index, identity.identity); + } + } + // Convert the BTreeMap into a Vec of Wallets. Ok(wallets_map.into_values().collect()) } diff --git a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs index 946912cda..581155ecd 100644 --- a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs @@ -146,7 +146,7 @@ impl AddNewIdentityScreen { match step { AddNewIdentityWalletFundedScreenStep::ChooseFundingMethod => {} AddNewIdentityWalletFundedScreenStep::WaitingOnFunds => { - ui.heading("Waiting for funds"); + ui.heading("=> Waiting for funds. <="); } AddNewIdentityWalletFundedScreenStep::FundsReceived => { let Some(selected_wallet) = &self.selected_wallet else { @@ -177,10 +177,10 @@ impl AddNewIdentityScreen { } AddNewIdentityWalletFundedScreenStep::ReadyToCreate => {} AddNewIdentityWalletFundedScreenStep::WaitingForAssetLock => { - ui.heading("Waiting for Core Chain to produce proof of transfer of funds."); + ui.heading("=> Waiting for Core Chain to produce proof of transfer of funds. <="); } AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance => { - ui.heading("Waiting for Platform acknowledgement"); + ui.heading("=> Waiting for Platform acknowledgement. <="); } AddNewIdentityWalletFundedScreenStep::Success => { ui.heading("...Success..."); diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 9d90cf1a0..9d3dbf731 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -14,6 +14,7 @@ use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::{MessageType, ScreenLike}; use arboard::Clipboard; +use dash_sdk::dashcore_rpc::dashcore::transaction::special_transaction::TransactionPayload; use dash_sdk::dashcore_rpc::dashcore::Address; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::balances::credits::Duffs; @@ -21,6 +22,7 @@ use dash_sdk::dpp::dashcore::{OutPoint, PrivateKey, Transaction, TxOut}; use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; use dash_sdk::dpp::prelude::AssetLockProof; use eframe::egui::Context; +use egui::ahash::HashSet; use egui::{Color32, ColorImage, ComboBox, Ui}; use image::Luma; use qrcode::QrCode; @@ -245,24 +247,58 @@ impl AddNewIdentityScreen { ui.horizontal(|ui| { ui.label("Identity Index:"); - // Render a ComboBox to select the identity index - ComboBox::from_id_salt("identity_index") - .selected_text(format!("{}", self.identity_id_number)) - .show_ui(ui, |ui| { - // Provide up to 30 entries for selection (0 to 29) - for i in 0..30 { - if ui - .selectable_value(&mut self.identity_id_number, i, format!("{}", i)) - .clicked() - { - self.identity_id_number = i; - index_changed = true; - } + // Check if we have access to the selected wallet + if let Some(wallet_guard) = self.selected_wallet.as_ref() { + let wallet = wallet_guard.read().unwrap(); + let used_indices: HashSet = wallet.identities.keys().cloned().collect(); + + // Modify the selected text to include "(used)" if the current index is used + let selected_text = { + let is_used = used_indices.contains(&self.identity_id_number); + if is_used { + format!("{} (used)", self.identity_id_number) + } else { + format!("{}", self.identity_id_number) } - }); + }; + + // Render a ComboBox to select the identity index + ComboBox::from_id_salt("identity_index") + .selected_text(selected_text) + .show_ui(ui, |ui| { + // Provide up to 30 entries for selection (0 to 29) + for i in 0..30 { + let is_used = used_indices.contains(&i); + let label = if is_used { + format!("{} (used)", i) + } else { + format!("{}", i) + }; + + let is_selected = self.identity_id_number == i; + + // Enable the option if it's not used or if it's the currently selected index + let enabled = !is_used || is_selected; + + // Use `add_enabled` to disable used indices + let response = ui.add_enabled( + enabled, + egui::SelectableLabel::new(is_selected, label), + ); + + // Only allow selection if the index is not used + if response.clicked() && !is_used { + self.identity_id_number = i; + index_changed = true; + } + } + }); + } else { + ui.label("No wallet selected"); + } }); - // If the index has changed, call update_identity_key + // If the index has changed, update the identity key if index_changed { self.update_identity_key(); } @@ -854,6 +890,25 @@ impl ScreenLike for AddNewIdentityScreen { } } } + } else if *step == AddNewIdentityWalletFundedScreenStep::WaitingForAssetLock { + if let BackendTaskSuccessResult::CoreItem(CoreItem::ReceivedAvailableUTXOTransaction( + tx, + outpoints_with_addresses, + )) = backend_task_success_result + { + if let Some(TransactionPayload::AssetLockPayloadType(asset_lock_payload)) = + tx.special_transaction_payload + { + if let Some(funding_address) = self.funding_address.as_ref() { + for (outpoint, tx_out, address) in outpoints_with_addresses { + if funding_address == &address { + *step = AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance; + self.funding_utxo = Some((outpoint, tx_out, address)) + } + } + } + } + } } } fn ui(&mut self, ctx: &Context) -> AppAction { @@ -891,13 +946,13 @@ impl ScreenLike for AddNewIdentityScreen { let mut wallet = wallet_guard.write().unwrap(); self.identity_id_number = - wallet.identities.keys().copied().max().unwrap_or_default(); + wallet.identities.keys().copied().max().map(|max| max + 1).unwrap_or_default(); self.identity_keys.master_private_key = Some( wallet .identity_authentication_ecdsa_private_key( self.app_context.network, - 0, + self.identity_id_number, 0, Some(&self.app_context), ) @@ -909,7 +964,7 @@ impl ScreenLike for AddNewIdentityScreen { wallet .identity_authentication_ecdsa_private_key( self.app_context.network, - 0, + self.identity_id_number, 1, Some(&self.app_context), ) @@ -922,7 +977,7 @@ impl ScreenLike for AddNewIdentityScreen { wallet .identity_authentication_ecdsa_private_key( self.app_context.network, - 0, + self.identity_id_number, 2, Some(&self.app_context), ) @@ -951,7 +1006,7 @@ impl ScreenLike for AddNewIdentityScreen { ui.heading(format!( "{}. Choose an identity index. Leaving this {} is recommended.", step_number, - wallet.identities.keys().cloned().max().unwrap_or_default() + wallet.identities.keys().cloned().max().map(|max| max + 1).unwrap_or_default() )); }