From fbb084f6361bb7cef0cb7c918d968e144ef21c1b Mon Sep 17 00:00:00 2001 From: ardocrat Date: Thu, 29 May 2025 00:38:45 +0300 Subject: [PATCH] wallet: do not scan outputs for new wallet --- Cargo.lock | 1 + Cargo.toml | 1 + src/gui/views/network/setup/stratum.rs | 4 +- src/node/stratum.rs | 2 +- src/settings/config.rs | 7 ++ src/settings/settings.rs | 10 ++- src/wallet/config.rs | 33 ++++++-- src/wallet/mod.rs | 3 +- src/wallet/seed.rs | 102 +++++++++++++++++++++++++ src/wallet/wallet.rs | 86 ++++++++++++--------- 10 files changed, 199 insertions(+), 50 deletions(-) create mode 100644 src/wallet/seed.rs diff --git a/Cargo.lock b/Cargo.lock index a1ebe3ce..2d3c901c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3551,6 +3551,7 @@ dependencies = [ "qrcodegen", "rand 0.9.1", "rfd", + "ring", "rkv", "rqrr", "rust-i18n", diff --git a/Cargo.toml b/Cargo.toml index 88b4d60f..0d3ef215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ ur = "0.4.1" gif = "0.13.1" rkv = { version = "0.19.0", features = ["lmdb"] } usvg = "0.45.1" +ring = "0.16.20" ## tor arti-client = { version = "0.29.0", features = ["pt-client", "static", "onion-service-service", "onion-service-client"] } diff --git a/src/gui/views/network/setup/stratum.rs b/src/gui/views/network/setup/stratum.rs index 47262a03..c02c1a3e 100644 --- a/src/gui/views/network/setup/stratum.rs +++ b/src/gui/views/network/setup/stratum.rs @@ -73,7 +73,7 @@ impl Default for StratumSetup { // Setup mining rewards wallet name and identifier. let mut wallet_id = NodeConfig::get_stratum_wallet_id(); let wallet_name = if let Some(id) = wallet_id { - WalletConfig::name_by_id(id) + WalletConfig::read_name_by_id(id) } else { None }; @@ -115,7 +115,7 @@ impl ModalContainer for StratumSetup { self.wallets_modal.ui(ui, modal, &mut self.wallets, cb, |wallet, _| { let id = wallet.get_config().id; NodeConfig::save_stratum_wallet_id(id); - self.wallet_name = WalletConfig::name_by_id(id); + self.wallet_name = WalletConfig::read_name_by_id(id); }) }, STRATUM_PORT_MODAL => self.port_modal(ui, modal, cb), diff --git a/src/node/stratum.rs b/src/node/stratum.rs index dcbff4ee..4406665f 100644 --- a/src/node/stratum.rs +++ b/src/node/stratum.rs @@ -588,7 +588,7 @@ impl Handler { let mut state = self.current_state.write(); let wallet_listener_url = if !config.burn_reward { if let Ok(id) = config.wallet_listener_url.parse::() { - if let Some(port) = WalletConfig::api_port_by_id(id) { + if let Some(port) = WalletConfig::read_api_port_by_id(id) { let url = format!("http://127.0.0.1:{}", port); Some(url) } else { diff --git a/src/settings/config.rs b/src/settings/config.rs index 0b6ef007..0fa68474 100644 --- a/src/settings/config.rs +++ b/src/settings/config.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use grin_core::global; use grin_core::global::ChainTypes; use serde_derive::{Deserialize, Serialize}; use crate::gui::views::Content; @@ -118,6 +119,12 @@ impl AppConfig { *w_conn_config = ConnectionsConfig::for_chain_type(chain_type); } } + if !global::GLOBAL_CHAIN_TYPE.is_init() { + global::init_global_chain_type(*chain_type); + } else { + global::set_global_chain_type(*chain_type); + global::set_local_chain_type(*chain_type); + } } /// Get current [`ChainTypes`] for node and wallets. diff --git a/src/settings/settings.rs b/src/settings/settings.rs index 35b4c29c..07e90907 100644 --- a/src/settings/settings.rs +++ b/src/settings/settings.rs @@ -21,7 +21,7 @@ use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use serde::de::DeserializeOwned; use serde::Serialize; use grin_config::ConfigError; - +use grin_core::global; use crate::node::NodeConfig; use crate::settings::AppConfig; use crate::tor::TorConfig; @@ -62,7 +62,15 @@ impl Settings { let tor_config_path = Settings::config_path(TorConfig::FILE_NAME, None); let tor_config = Self::init_config::(tor_config_path); + // Setup chain type. let chain_type = &app_config.chain_type; + if !global::GLOBAL_CHAIN_TYPE.is_init() { + global::init_global_chain_type(*chain_type); + } else { + global::set_global_chain_type(*chain_type); + global::set_local_chain_type(*chain_type); + } + Self { node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(chain_type))), conn_config: Arc::new(RwLock::new(ConnectionsConfig::for_chain_type(chain_type))), diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 4ac7ed73..caa00163 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -50,10 +50,16 @@ pub struct WalletConfig { /// Base wallets directory name. const BASE_DIR_NAME: &'static str = "wallets"; +/// Base wallets directory name. +const DB_DIR_NAME: &'static str = "db"; +/// Wallet data directory name. +const DATA_DIR_NAME: &'static str = "wallet_data"; /// Wallet configuration file name. const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml"; /// Slatepacks directory name. const SLATEPACKS_DIR_NAME: &'static str = "slatepacks"; +/// Seed file name. +const SEED_FILE: &str = "wallet.seed"; /// Default value of minimal amount of confirmations. const MIN_CONFIRMATIONS_DEFAULT: u64 = 10; @@ -98,7 +104,7 @@ impl WalletConfig { } /// Get wallet name by provided identifier. - pub fn name_by_id(id: i64) -> Option { + pub fn read_name_by_id(id: i64) -> Option { let mut wallet_dir = WalletConfig::get_base_path(AppConfig::chain_type()); wallet_dir.push(id.to_string()); if let Some(cfg) = Self::load(wallet_dir) { @@ -108,7 +114,7 @@ impl WalletConfig { } /// Get wallet API port by provided identifier. - pub fn api_port_by_id(id: i64) -> Option { + pub fn read_api_port_by_id(id: i64) -> Option { let mut wallet_dir = WalletConfig::get_base_path(AppConfig::chain_type()); wallet_dir.push(id.to_string()); if let Some(cfg) = Self::load(wallet_dir) { @@ -157,25 +163,38 @@ impl WalletConfig { config_path } - /// Get current wallet data path. - pub fn get_data_path(&self) -> String { + /// Get current wallet path. + pub fn get_wallet_path(&self) -> String { let chain_type = AppConfig::chain_type(); let mut data_path = Self::get_base_path(chain_type); data_path.push(self.id.to_string()); data_path.to_str().unwrap().to_string() } + /// Get wallet data path. + pub fn get_data_path(&self) -> String { + let mut data_path = PathBuf::from(self.get_wallet_path()); + data_path.push(DATA_DIR_NAME); + data_path.to_str().unwrap().to_string() + } + + /// Get wallet seed path. + pub fn seed_path(&self) -> String { + let mut path = PathBuf::from(self.get_data_path()); + path.push(SEED_FILE); + path.to_str().unwrap().to_string() + } + /// Get wallet database data path. pub fn get_db_path(&self) -> String { let mut path = PathBuf::from(self.get_data_path()); - path.push("wallet_data"); - path.push("db"); + path.push(DB_DIR_NAME); path.to_str().unwrap().to_string() } /// Get Slatepacks data path for current wallet. pub fn get_slatepack_path(&self, slate: &Slate) -> PathBuf { - let mut path = PathBuf::from(self.get_data_path()); + let mut path = PathBuf::from(self.get_wallet_path()); path.push(SLATEPACKS_DIR_NAME); if !path.exists() { let _ = fs::create_dir_all(path.clone()); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 8b20596a..d75d2e24 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -32,4 +32,5 @@ pub use list::*; mod utils; pub use utils::WalletUtils; -pub mod store; \ No newline at end of file +pub mod store; +mod seed; \ No newline at end of file diff --git a/src/wallet/seed.rs b/src/wallet/seed.rs new file mode 100644 index 00000000..0710780b --- /dev/null +++ b/src/wallet/seed.rs @@ -0,0 +1,102 @@ +// Copyright 2025 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::num::NonZeroU32; +use grin_util::{ToHex, ZeroingString}; +use grin_wallet_impls::Error; +use rand::{rng, Rng}; +use serde_derive::{Deserialize, Serialize}; +use serde_json; +use std::fs::File; +use std::io::Write; + +use ring::aead; +use ring::pbkdf2; + +#[derive(Clone, Debug, PartialEq)] +pub struct WalletSeed(Vec); + +impl WalletSeed { + pub fn from_bytes(bytes: &[u8]) -> WalletSeed { + WalletSeed(bytes.to_vec()) + } + + pub fn from_mnemonic(word_list: ZeroingString) -> Result { + let res = grin_keychain::mnemonic::to_entropy(&word_list); + match res { + Ok(s) => Ok(WalletSeed::from_bytes(&s)), + Err(_) => Err(Error::Mnemonic.into()), + } + } + + pub fn init_file( + seed_file_path: &str, + recovery_phrase: ZeroingString, + password: ZeroingString, + ) -> Result { + let seed = WalletSeed::from_mnemonic(recovery_phrase)?; + let enc_seed = EncryptedWalletSeed::from_seed(&seed, password)?; + let enc_seed_json = serde_json::to_string_pretty(&enc_seed).map_err(|_| Error::Format)?; + let mut file = File::create(seed_file_path).map_err(|_| Error::IO)?; + file.write_all(&enc_seed_json.as_bytes()) + .map_err(|_| Error::IO)?; + Ok(seed) + } +} + +/// Encrypted wallet seed, for storing on disk and decrypting with provided password. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct EncryptedWalletSeed { + encrypted_seed: String, + pub salt: String, + pub nonce: String, +} + +impl EncryptedWalletSeed { + /// Create a new encrypted seed from the given seed + password. + pub fn from_seed( + seed: &WalletSeed, + password: ZeroingString, + ) -> Result { + let salt: [u8; 8] = rng().random(); + let nonce: [u8; 12] = rng().random(); + let password = password.as_bytes(); + let mut key = [0; 32]; + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA512, + NonZeroU32::new(100).unwrap(), + &salt, + password, + &mut key, + ); + let content = seed.0.to_vec(); + let mut enc_bytes = content; + let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); + let sealing_key: aead::LessSafeKey = aead::LessSafeKey::new(unbound_key); + let aad = aead::Aad::from(&[]); + let res = sealing_key.seal_in_place_append_tag( + aead::Nonce::assume_unique_for_key(nonce), + aad, + &mut enc_bytes, + ); + if let Err(_) = res { + return Err(Error::Encryption); + } + Ok(EncryptedWalletSeed { + encrypted_seed: enc_bytes.to_hex(), + salt: salt.to_hex(), + nonce: nonce.to_hex(), + }) + } +} \ No newline at end of file diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index 49a3bf37..a5482e6e 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -12,42 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{fs, thread}; +use futures::channel::oneshot; +use parking_lot::RwLock; +use rand::Rng; +use serde_json::{json, Value}; use std::fs::File; use std::io::Write; use std::net::{SocketAddr, TcpListener}; use std::path::PathBuf; -use std::sync::{Arc, mpsc}; -use parking_lot::RwLock; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use std::sync::{mpsc, Arc}; use std::thread::Thread; use std::time::Duration; -use futures::channel::oneshot; -use serde_json::{json, Value}; +use std::{fs, thread}; use grin_api::{ApiServer, Router}; use grin_chain::SyncStatus; -use grin_core::global; use grin_keychain::{ExtKeychain, Identifier, Keychain}; -use grin_util::{Mutex, ToHex}; use grin_util::secp::SecretKey; use grin_util::types::ZeroingString; +use grin_util::{Mutex, ToHex}; use grin_wallet_api::Owner; use grin_wallet_controller::command::parse_slatepack; use grin_wallet_controller::controller; use grin_wallet_controller::controller::ForeignAPIHandlerV2; -use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; -use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlatepackAddress, SlateState, SlateVersion, StatusMessage, TxLogEntry, TxLogEntryType, VersionedSlate, WalletInst, WalletLCProvider}; +use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient, LMDBBackend}; use grin_wallet_libwallet::api_impl::owner::{cancel_tx, retrieve_summary_info, retrieve_txs}; +use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlateState, SlateVersion, SlatepackAddress, StatusMessage, TxLogEntry, TxLogEntryType, VersionedSlate, WalletBackend, WalletInitStatus, WalletInst, WalletLCProvider}; use grin_wallet_util::OnionV3Address; -use rand::Rng; -use crate::AppConfig; use crate::node::{Node, NodeConfig}; use crate::tor::Tor; -use crate::wallet::{ConnectionsConfig, Mnemonic, WalletConfig}; +use crate::wallet::seed::WalletSeed; use crate::wallet::store::TxHeightStore; -use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction}; +use crate::wallet::types::{ConnectionMethod, PhraseMode, WalletAccount, WalletData, WalletInstance, WalletTransaction}; +use crate::wallet::{ConnectionsConfig, Mnemonic, WalletConfig}; +use crate::AppConfig; /// Contains wallet instance, configuration and state, handles wallet commands. #[derive(Clone)] @@ -132,18 +132,32 @@ impl Wallet { mnemonic: &Mnemonic, conn_method: &ConnectionMethod ) -> Result { - let mut config = WalletConfig::create(name.clone(), conn_method); + let config = WalletConfig::create(name.clone(), conn_method); let w = Wallet::new(config.clone()); { - let instance = Self::create_wallet_instance(&mut config)?; - let mut w_lock = instance.lock(); - let p = w_lock.lc_provider()?; - p.create_wallet(None, - Some(ZeroingString::from(mnemonic.get_phrase())), - mnemonic.size().entropy_size(), - password.clone(), - false, - )?; + // create directory if it doesn't exist + fs::create_dir_all(config.get_data_path()) + .map_err(|_| Error::IO("Directory creation error".to_string()))?; + // Create seed file. + let _ = WalletSeed::init_file(config.seed_path().as_str(), + ZeroingString::from(mnemonic.get_phrase()), + password.clone()) + .map_err(|_| Error::IO("Seed file creation error".to_string()))?; + let node_client = Self::create_node_client(&config)?; + let mut wallet: LMDBBackend<'static, HTTPNodeClient, ExtKeychain> = + match LMDBBackend::new(config.get_data_path().as_str(), node_client) { + Err(_) => { + return Err(Error::Lifecycle("DB creation error".to_string()).into()); + } + Ok(d) => d, + }; + // Save init status of this wallet, to determine whether it needs a full UTXO scan + let mut batch = wallet.batch_no_mask()?; + match mnemonic.mode() { + PhraseMode::Generate => batch.save_init_status(WalletInitStatus::InitNoScanning)?, + PhraseMode::Import => batch.save_init_status(WalletInitStatus::InitNeedsScanning)?, + } + batch.commit()?; } Ok(w) } @@ -157,18 +171,8 @@ impl Wallet { None } - /// Create [`WalletInstance`] from provided [`WalletConfig`]. - fn create_wallet_instance(config: &mut WalletConfig) -> Result { - // Assume global chain type has already been initialized. - let chain_type = config.chain_type; - if !global::GLOBAL_CHAIN_TYPE.is_init() { - global::init_global_chain_type(chain_type); - } else { - global::set_global_chain_type(chain_type); - global::set_local_chain_type(chain_type); - } - - // Setup node client. + /// Create [`HTTPNodeClient`] from provided config. + fn create_node_client(config: &WalletConfig) -> Result { let integrated = || { let api_url = format!("http://{}", NodeConfig::get_api_address()); let api_secret = NodeConfig::get_api_secret(true); @@ -183,7 +187,13 @@ impl Wallet { } else { integrated() }; - let node_client = HTTPNodeClient::new(&node_api_url, node_secret)?; + Ok(HTTPNodeClient::new(&node_api_url, node_secret)?) + } + + /// Create [`WalletInstance`] from provided [`WalletConfig`]. + fn create_wallet_instance(config: &mut WalletConfig) -> Result { + // Setup node client. + let node_client = Self::create_node_client(config)?; // Create wallet instance. let wallet = Self::inst_wallet::< @@ -208,7 +218,7 @@ impl Wallet { let mut wallet = Box::new(DefaultWalletImpl::<'static, C>::new(node_client).unwrap()) as Box>; let lc = wallet.lc_provider()?; - lc.set_top_level_directory(config.get_data_path().as_str())?; + lc.set_top_level_directory(config.get_wallet_path().as_str())?; Ok(Arc::new(Mutex::new(wallet))) } @@ -1065,7 +1075,7 @@ impl Wallet { thread::sleep(Duration::from_millis(100)); } // Remove wallet files. - let _ = fs::remove_dir_all(wallet_delete.get_config().get_data_path()); + let _ = fs::remove_dir_all(wallet_delete.get_config().get_wallet_path()); // Mark wallet as deleted. wallet_delete.deleted.store(true, Ordering::Relaxed); // Start sync to close thread.