Skip to content

Commit

Permalink
feat: paper wallet cli (#6522)
Browse files Browse the repository at this point in the history
Description
---
Add paper wallet to console wallet
Add scrape wallet to wallet FFI

Motivation and Context
---
This will allow you to import a paper wallet to your own wallet

How Has This Been Tested?
---
Manual
  • Loading branch information
SWvheerden committed Sep 4, 2024
1 parent be9e32e commit 31a953c
Show file tree
Hide file tree
Showing 13 changed files with 621 additions and 59 deletions.
162 changes: 159 additions & 3 deletions applications/minotari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use std::{
io,
io::{LineWriter, Write},
path::{Path, PathBuf},
str::FromStr,
time::{Duration, Instant},
};

Expand Down Expand Up @@ -66,6 +67,7 @@ use tari_common_types::{
use tari_comms::{
connectivity::{ConnectivityEvent, ConnectivityRequester},
multiaddr::Multiaddr,
peer_manager::{Peer, PeerQuery},
types::CommsPublicKey,
};
use tari_comms_dht::{envelope::NodeDestination, DhtDiscoveryRequester};
Expand All @@ -89,9 +91,14 @@ use tari_core::{
},
};
use tari_crypto::ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey};
use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface};
use tari_key_manager::{
key_manager_service::{KeyId, KeyManagerInterface},
SeedWords,
};
use tari_p2p::{auto_update::AutoUpdateConfig, peer_seeds::SeedPeer, PeerSeedsConfig};
use tari_script::{script, CheckSigSchnorrSignature};
use tari_utilities::{hex::Hex, ByteArray};
use tari_shutdown::Shutdown;
use tari_utilities::{hex::Hex, ByteArray, SafePassword};
use tokio::{
sync::{broadcast, mpsc},
time::{sleep, timeout},
Expand Down Expand Up @@ -119,7 +126,10 @@ use crate::{
PreMineSpendStep4OutputsForLeader,
},
cli::{CliCommands, MakeItRainTransactionType},
utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY},
init::init_wallet,
recovery::{get_seed_from_seed_words, wallet_recovery},
utils::db::{get_custom_base_node_peer_from_db, CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY},
wallet_modes::PeerConfig,
};

pub const LOG_TARGET: &str = "wallet::automation::commands";
Expand Down Expand Up @@ -1816,6 +1826,152 @@ pub async fn command_runner(
println!("Spend key: {}", spend_key_hex);
}
},
ImportPaperWallet(args) => {
let temp_path = config
.db_file
.parent()
.ok_or(CommandError::General("No parent".to_string()))?
.join("temp");
println!("saving temp wallet in: {:?}", temp_path);
{
let seed_words = SeedWords::from_str(args.seed_words.as_str())
.map_err(|e| CommandError::General(e.to_string()))?;
let seed =
get_seed_from_seed_words(&seed_words).map_err(|e| CommandError::General(e.to_string()))?;
let wallet_type = WalletType::DerivedKeys;
let password = SafePassword::from("password".to_string());
let shutdown = Shutdown::new();
let shutdown_signal = shutdown.to_signal();
let mut new_config = config.clone();
new_config.set_base_path(temp_path.clone());

let peer_config = PeerSeedsConfig::default();
let mut new_wallet = init_wallet(
&new_config,
AutoUpdateConfig::default(),
peer_config,
password,
None,
Some(seed),
shutdown_signal,
true,
Some(wallet_type),
)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
// config

let query = PeerQuery::new().select_where(|p| p.is_seed());
let peer_seeds = wallet
.comms
.peer_manager()
.perform_query(query)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
// config
let base_node_peers = config
.base_node_service_peers
.iter()
.map(|s| SeedPeer::from_str(s))
.map(|r| r.map(Peer::from))
.collect::<Result<Vec<_>, _>>()
.map_err(|e| CommandError::General(e.to_string()))?;
let selected_base_node = match config.custom_base_node {
Some(ref custom) => SeedPeer::from_str(custom)
.map(|node| Some(Peer::from(node)))
.map_err(|e| CommandError::General(e.to_string()))?,
None => get_custom_base_node_peer_from_db(&wallet),
};

let peer_config = PeerConfig::new(selected_base_node, base_node_peers, peer_seeds);

let base_node = peer_config
.get_base_node_peer()
.map_err(|e| CommandError::General(e.to_string()))?;
new_wallet
.set_base_node_peer(
base_node.public_key.clone(),
Some(
base_node
.last_address_used()
.ok_or(CommandError::General("No address found".to_string()))?,
),
)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
wallet_recovery(&new_wallet, &peer_config, new_config.recovery_retry_limit)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
print!("Wallet recovery completed");
let mut oms = new_wallet.output_manager_service.clone();
oms.validate_txos().await?;
let mut event = oms.get_event_stream();
loop {
match event.recv().await {
Ok(event) => match *event {
OutputManagerEvent::TxoValidationSuccess(_) => {
println!("Validation succeeded");
break;
},
OutputManagerEvent::TxoValidationAlreadyBusy(_) => {
println!("Validation already busy");
},
_ => {
println!("Validation failed");
break;
},
},
Err(e) => {
eprintln!("Sync error! {}", e);
break;
},
}
}
println!("balance as of scanning height");
match oms.clone().get_balance().await {
Ok(balance) => {
println!("{}", balance);
},
Err(e) => eprintln!("GetBalance error! {}", e),
}
let mut tms = new_wallet.transaction_service.clone();
match tms
.scrape_wallet(
wallet
.get_wallet_one_sided_address()
.await
.map_err(|e| CommandError::General(e.to_string()))?,
config.fee_per_gram * uT,
)
.await
.map_err(CommandError::TransactionServiceError)
{
Ok(tx_id) => {
debug!(target: LOG_TARGET, "send-minotari concluded with tx_id {}", tx_id);
let duration = config.command_send_wait_timeout;
match timeout(duration, monitor_transactions(tms.clone(), vec![tx_id], wait_stage)).await {
Ok(txs) => {
debug!(
target: LOG_TARGET,
"monitor_transactions done to stage {:?} with tx_ids: {:?}", wait_stage, txs
);
println!("Done! All transactions monitored to {:?} stage.", wait_stage);
},
Err(_e) => {
println!(
"The configured timeout ({:#?}) was reached before all transactions reached \
the {:?} stage. See the logs for more info.",
duration, wait_stage
);
},
}
},
Err(e) => eprintln!("SendMinotari error! {}", e),
}
}
println!("removing temp wallet in: {:?}", temp_path);
fs::remove_dir_all(temp_path)?;
},
}
}

Expand Down
7 changes: 7 additions & 0 deletions applications/minotari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ pub enum CliCommands {
CreateTlsCerts,
Sync(SyncArgs),
ExportViewKeyAndSpendKey(ExportViewKeyAndSpendKeyArgs),
ImportPaperWallet(ImportPaperWalletArgs),
}

#[derive(Debug, Args, Clone)]
Expand Down Expand Up @@ -319,6 +320,12 @@ pub struct ExportViewKeyAndSpendKeyArgs {
pub output_file: Option<PathBuf>,
}

#[derive(Debug, Args, Clone)]
pub struct ImportPaperWalletArgs {
#[clap(short, long)]
pub seed_words: String,
}

#[derive(Debug, Args, Clone)]
pub struct ImportTxArgs {
#[clap(short, long)]
Expand Down
70 changes: 22 additions & 48 deletions applications/minotari_console_wallet/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ use tari_key_manager::{
key_manager_service::{storage::database::KeyManagerBackend, KeyManagerInterface},
mnemonic::MnemonicLanguage,
};
use tari_p2p::{peer_seeds::SeedPeer, TransportType};
use tari_p2p::{auto_update::AutoUpdateConfig, peer_seeds::SeedPeer, PeerSeedsConfig, TransportType};
use tari_shutdown::ShutdownSignal;
use tari_utilities::{encoding::Base58, hex::Hex, ByteArray, SafePassword};
use zxcvbn::zxcvbn;
Expand Down Expand Up @@ -262,7 +262,9 @@ pub async fn change_password(
non_interactive_mode: bool,
) -> Result<(), ExitError> {
let mut wallet = init_wallet(
config,
&config.wallet,
config.auto_update.clone(),
config.peer_seeds.clone(),
existing.clone(),
None,
None,
Expand Down Expand Up @@ -290,13 +292,13 @@ pub async fn change_password(
/// 3. The detected local base node if any
/// 4. The service peers defined in config they exist
/// 5. The peer seeds defined in config
pub async fn get_base_node_peer_config(
config: &ApplicationConfig,
pub async fn set_peer_and_get_base_node_peer_config(
config: &WalletConfig,
wallet: &mut WalletSqlite,
non_interactive_mode: bool,
) -> Result<PeerConfig, ExitError> {
let mut use_custom_base_node_peer = false;
let mut selected_base_node = match config.wallet.custom_base_node {
let mut selected_base_node = match config.custom_base_node {
Some(ref custom) => SeedPeer::from_str(custom)
.map(|node| Some(Peer::from(node)))
.map_err(|err| ExitError::new(ExitCode::ConfigError, format!("Malformed custom base node: {}", err)))?,
Expand All @@ -311,8 +313,8 @@ pub async fn get_base_node_peer_config(
};

// If the user has not explicitly set a base node in the config, we try detect one
if !non_interactive_mode && config.wallet.custom_base_node.is_none() && !use_custom_base_node_peer {
if let Some(detected_node) = detect_local_base_node(config.wallet.network).await {
if !non_interactive_mode && config.custom_base_node.is_none() && !use_custom_base_node_peer {
if let Some(detected_node) = detect_local_base_node(config.network).await {
match selected_base_node {
Some(ref base_node) if base_node.public_key == detected_node.public_key => {
// Skip asking because it's already set
Expand Down Expand Up @@ -349,10 +351,8 @@ pub async fn get_base_node_peer_config(
format!("Could net get seed peers from peer manager: {}", err),
)
})?;

// config
let base_node_peers = config
.wallet
.base_node_service_peers
.iter()
.map(|s| SeedPeer::from_str(s))
Expand Down Expand Up @@ -394,7 +394,9 @@ pub(crate) fn wallet_mode(cli: &Cli, boot_mode: WalletBoot) -> WalletMode {
/// Set up the app environment and state for use by the UI
#[allow(clippy::too_many_lines)]
pub async fn init_wallet(
config: &ApplicationConfig,
config: &WalletConfig,
auto_update: AutoUpdateConfig,
peer_seeds: PeerSeedsConfig,
arg_password: SafePassword,
seed_words_file_name: Option<PathBuf>,
recovery_seed: Option<CipherSeed>,
Expand All @@ -404,47 +406,46 @@ pub async fn init_wallet(
) -> Result<WalletSqlite, ExitError> {
fs::create_dir_all(
config
.wallet
.db_file
.parent()
.expect("console_wallet_db_file cannot be set to a root directory"),
)
.map_err(|e| ExitError::new(ExitCode::WalletError, format!("Error creating Wallet folder. {}", e)))?;
fs::create_dir_all(&config.wallet.p2p.datastore_path)
fs::create_dir_all(&config.p2p.datastore_path)
.map_err(|e| ExitError::new(ExitCode::WalletError, format!("Error creating peer db folder. {}", e)))?;

debug!(target: LOG_TARGET, "Running Wallet database migrations");

let db_path = &config.wallet.db_file;
let db_path = &config.db_file;

// wallet should be encrypted from the beginning, so we must require a password to be provided by the user
let (wallet_backend, transaction_backend, output_manager_backend, contacts_backend, key_manager_backend) =
initialize_sqlite_database_backends(db_path, arg_password, config.wallet.db_connection_pool_size)?;
initialize_sqlite_database_backends(db_path, arg_password, config.db_connection_pool_size)?;

let wallet_db = WalletDatabase::new(wallet_backend);
let output_db = OutputManagerDatabase::new(output_manager_backend.clone());

debug!(target: LOG_TARGET, "Databases Initialized. Wallet is encrypted.",);

let node_addresses = if config.wallet.p2p.public_addresses.is_empty() {
let node_addresses = if config.p2p.public_addresses.is_empty() {
match wallet_db.get_node_address()? {
Some(addr) => MultiaddrList::from(vec![addr]),
None => MultiaddrList::default(),
}
} else {
config.wallet.p2p.public_addresses.clone()
config.p2p.public_addresses.clone()
};

let master_seed = read_or_create_master_seed(recovery_seed.clone(), &wallet_db)?;

let node_identity = setup_identity_from_db(&wallet_db, &master_seed, node_addresses.to_vec())?;

let mut wallet_config = config.wallet.clone();
if let TransportType::Tor = config.wallet.p2p.transport.transport_type {
let mut wallet_config = config.clone();
if let TransportType::Tor = config.p2p.transport.transport_type {
wallet_config.p2p.transport.tor.identity = wallet_db.get_tor_id()?;
}

let consensus_manager = ConsensusManager::builder(config.wallet.network)
let consensus_manager = ConsensusManager::builder(config.network)
.build()
.map_err(|e| ExitError::new(ExitCode::WalletError, format!("Error consensus manager. {}", e)))?;
let factories = CryptoFactories::default();
Expand All @@ -453,8 +454,8 @@ pub async fn init_wallet(
let user_agent = format!("tari/wallet/{}", consts::APP_VERSION_NUMBER);
let mut wallet = Wallet::start(
wallet_config,
config.peer_seeds.clone(),
config.auto_update.clone(),
peer_seeds,
auto_update,
node_identity,
consensus_manager,
factories,
Expand Down Expand Up @@ -572,33 +573,6 @@ pub async fn start_wallet(
base_node: &Peer,
wallet_mode: &WalletMode,
) -> Result<(), ExitError> {
// Verify ledger build if wallet type is Ledger
if let WalletType::Ledger(_) = *wallet.key_manager_service.get_wallet_type().await {
#[cfg(not(feature = "ledger"))]
{
return Err(ExitError::new(
ExitCode::WalletError,
format!("{}", LEDGER_NOT_SUPPORTED),
));
}

#[cfg(feature = "ledger")]
{
let key_id = TariKeyId::Managed {
branch: TransactionKeyManagerBranch::RandomKey.get_branch_key(),
index: 0,
};
match wallet.key_manager_service.get_public_key_at_key_id(&key_id).await {
Ok(public_key) => {},
Err(e) => {
if e.to_string().contains(LEDGER_NOT_SUPPORTED) {
return Err(ExitError::new(ExitCode::WalletError, format!(" {}", e)));
}
},
}
}
}

debug!(target: LOG_TARGET, "Setting base node peer");

let net_address = base_node
Expand Down
Loading

0 comments on commit 31a953c

Please sign in to comment.