From 96981ce2cb62d5542f93544e6e716ebffc56efb7 Mon Sep 17 00:00:00 2001 From: Derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Sun, 17 Apr 2022 00:06:56 +0100 Subject: [PATCH 01/12] Multiple account support Potentially unstable. Work in progress, not yet bug tested or polished. 1. Add accounts from mnemonic, seed or private key. 2. Accounts are saved under OS default application data directory and then /dagchat/accounts.dagchat. 3A. Account data is encrypted using a user passphrase. Encrypted account data stored in accounts.dagchat comprises three elements: - 16 byte salt - 12 byte iv - encrypted bytes of bincode serialised vector of Account objects 3B. Process for encrypting account data using passphrase: - Expand passphrase into 256 bit key using Argon2 with randomly generated 128 bit salt - Serialise UserData's Vec with bincode into Vec - Encrypt this Vec with 256 bit key from 1. using AES256gcm - Concatenate bytes of salt, nonce, and encrypted bytes in that order - Write to [OS App data]/dagchat/accounts.dagchat 4. Create new accounts from seed. 5. File structure rework. --- src/accounts.rs | 486 ++++++++++++++++++++++++++++++++++ src/dcutil.rs | 5 +- src/main.rs | 680 +++++++----------------------------------------- src/receive.rs | 160 ++++++++++++ src/send.rs | 228 ++++++++++++++++ 5 files changed, 964 insertions(+), 595 deletions(-) create mode 100644 src/accounts.rs create mode 100644 src/receive.rs create mode 100644 src/send.rs diff --git a/src/accounts.rs b/src/accounts.rs new file mode 100644 index 0000000..af88a37 --- /dev/null +++ b/src/accounts.rs @@ -0,0 +1,486 @@ +use super::*; +use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead}; +use aes_gcm::{Aes256Gcm, Nonce}; +use argon2::{self, Config}; + +const SALT_LENGTH: usize = 16; +const IV_LENGTH: usize = 12; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Account { + pub mnemonic: Option, + pub seed: Option<[u8; 32]>, + pub key_idx: u128, + pub private_key: [u8; 32], + pub public_key: [u8; 32], + pub address: String, + pub balance: u128, + pub receivables: Vec, +} + +impl Account { + fn from_mnemonic(mnemonic: Option, seed: Option<[u8; 32]>, prefix: &str) -> Account { + let (private_key, public_key) = Account::get_keypair(&seed.unwrap()); + Account { + mnemonic, + seed, + key_idx: 0, + private_key, + public_key, + address: dcutil::get_address(&public_key, prefix), + balance: 0, + receivables: vec![], + } + } + fn from_seed(seed: Option<[u8; 32]>, prefix: &str) -> Account { + let (private_key, public_key) = Account::get_keypair(&seed.unwrap()); + Account { + mnemonic: None, + seed, + key_idx: 0, + private_key, + public_key, + address: dcutil::get_address(&public_key, prefix), + balance: 0, + receivables: vec![], + } + } + + fn from_private_key(private_key: [u8; 32], prefix: &str) -> Account { + let public_key = Account::get_public_key(&private_key); + Account { + mnemonic: None, + seed: None, + key_idx: 0, + private_key, + public_key, + address: dcutil::get_address(&public_key, prefix), + balance: 0, + receivables: vec![], + } + } + + fn get_keypair(seed: &[u8; 32]) -> ([u8; 32], [u8; 32]) { + let private_key = dcutil::get_private_key(seed); + let public_key = Account::get_public_key(&private_key); + (private_key, public_key) + } + fn get_public_key(private_key: &[u8; 32]) -> [u8; 32] { + let dalek = ed25519_dalek::SecretKey::from_bytes(private_key).unwrap(); + let public_key = ed25519_dalek::PublicKey::from(&dalek); + public_key.to_bytes() + } +} + +pub fn check_setup(s: &mut Cursive) { + if let Some(mut data_dir) = dirs::data_dir() { + data_dir = data_dir.join("dagchat"); + if data_dir.exists() { + load_accounts(s, data_dir); + } else { + fs::create_dir(data_dir.clone()).unwrap_or_else(|e| { + let content = format!( + "Failed to create a data folder for dagchat at path: {:?}\nError: {}", + data_dir, e + ); + s.add_layer(Dialog::info(content)) + }); + if data_dir.exists() { + load_accounts(s, data_dir); + } else { + return; + } + } + } else { + s.add_layer(Dialog::info( + "Error locating the application data folder on your system.", + )); + } +} + +fn load_accounts(s: &mut Cursive, data_path: PathBuf) { + s.pop_layer(); + let accounts_file = data_path.join("accounts.dagchat"); + if accounts_file.exists() { + let encrypted_bytes = fs::read(&accounts_file).unwrap_or_else(|e| { + let content = format!( + "Failed to read accounts.dagchat file at path: {:?}\nError: {}", + accounts_file.display(), + e + ); + s.add_layer(Dialog::info(content)); + vec![] + }); + if encrypted_bytes.is_empty() { + show_accounts(s); + return; + } + s.add_layer(Dialog::around( + LinearLayout::vertical() + .child(TextView::new("Enter password:")) + .child(TextArea::new().with_name("password").max_width(80)) + .child(Button::new("Submit", move |s| { + let mut password = String::from(""); + s.call_on_name("password", |view: &mut TextArea| { + password = String::from(view.get_content()); + }); + + let bytes = decrypt_accounts(&encrypted_bytes, &password); + if bytes.is_none() { + s.add_layer(Dialog::info("Incorrect password.")); + return; + } + let data = &mut s.user_data::().unwrap(); + data.password = password; + data.accounts = bincode::deserialize(&bytes.unwrap()[..]).unwrap(); + show_accounts(s); + })), + )); + } else { + show_accounts(s); + } +} + +fn write_accounts(encrypted_bytes: Vec) -> bool { + let mut success = false; + if let Some(data_dir) = dirs::data_dir() { + let accounts_file = data_dir.join("dagchat/accounts.dagchat"); + if !accounts_file.exists() { + fs::File::create(&accounts_file).expect(&format!( + "Unable to create accounts.dagchat at path: {:?}", + &accounts_file + )); + } + if accounts_file.exists() { + success = true; + fs::write(&accounts_file, encrypted_bytes).unwrap_or_else(|e| { + success = false; + eprintln!( + "Failed to write to accounts.dagchat file at path: {:?}\nError: {}", + accounts_file, e + ); + }); + } + } + success +} + +fn save_accounts(s: &mut Cursive) { + let data = &s.user_data::().unwrap(); + let success: bool; + if data.accounts.is_empty() { + success = write_accounts(vec![]); + } else { + let encrypted_bytes = encrypt_accounts(&data.accounts, &data.password); + success = write_accounts(encrypted_bytes); + } + if !success { + s.add_layer(Dialog::info("Error saving accounts data.")); + } +} + +fn derive_key(password: &str, salt: &[u8]) -> Vec { + let config = Config::default(); + let hash = argon2::hash_raw(password.as_bytes(), salt, &config).unwrap(); + hash +} + +fn encrypt_accounts(accounts: &Vec, password: &str) -> Vec { + let mut csprng = rand::thread_rng(); + let mut salt = [0u8; SALT_LENGTH]; + csprng.fill_bytes(&mut salt); + + let key_bytes = derive_key(password, &salt); + let key = GenericArray::from_slice(&key_bytes); + let encoded: Vec = bincode::serialize(accounts).unwrap(); + let aead = Aes256Gcm::new(key); + + let mut nonce_bytes = [0u8; 12]; + csprng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + + let ciphertext = aead.encrypt(nonce, &encoded[..]).unwrap(); + let mut encrypted_bytes = Vec::with_capacity(12 + ciphertext.len()); + encrypted_bytes.extend(salt); + encrypted_bytes.extend(nonce); + encrypted_bytes.extend(ciphertext); + encrypted_bytes +} + +fn decrypt_accounts(encrypted_bytes: &[u8], password: &str) -> Option> { + let salt = &encrypted_bytes[..SALT_LENGTH]; + + let key_bytes = derive_key(password, salt); + let key = GenericArray::from_slice(&key_bytes); + + let aead = Aes256Gcm::new(&key); + let nonce = Nonce::from_slice(&encrypted_bytes[SALT_LENGTH..SALT_LENGTH + IV_LENGTH]); + let encrypted = &encrypted_bytes[SALT_LENGTH + IV_LENGTH..]; + let decrypted = aead.decrypt(nonce, encrypted); + if decrypted.is_err() { + return None; + } + Some(decrypted.unwrap()) +} + +pub fn show_accounts(s: &mut Cursive) { + s.pop_layer(); + let data: UserData = s.take_user_data().unwrap(); + //Scroll view on left, iter on accounts and show them all + //Have 'Guest' button + //Have add, remove account, change password buttons on right + //When importing seed/mnemonic, option to specify index + let buttons = LinearLayout::vertical() + .child(Button::new("Add account", |s| add_account(s))) + .child(Button::new("Remove account", |s| remove_account(s))); + + let select = SelectView::::new() + .on_submit(select_account) + .with_name("accounts") + .scrollable() + .max_height(10); + + s.add_layer( + Dialog::around( + LinearLayout::horizontal() + .child(select) + .child(DummyView) + .child(buttons), + ) + .title("Select account"), + ); + + let mut i = 1; + for account in &data.accounts { + let tag; + if account.mnemonic.is_some() { + tag = format!("{}. From mnemonic", i); + } else if account.seed.is_some() { + tag = format!("{}. From seed", i); + } else { + tag = format!("{}. From private key", i); + } + s.call_on_name("accounts", |view: &mut SelectView| { + view.add_item_str(&tag) + }); + i += 1; + } + + s.set_user_data(data); +} + +fn select_account(s: &mut Cursive, name: &str) { + let select = s.find_name::>("accounts").unwrap(); + match select.selected_id() { + None => s.add_layer(Dialog::info("No account selected.")), + Some(focus) => { + // If from mnemonic or seed, let user choose id. Else start. + let data = &mut s.user_data::().unwrap(); + data.acc_idx = focus; + receive::load_receivables(s); + } + } +} + +fn remove_account(s: &mut Cursive) { + let select = s.find_name::>("accounts").unwrap(); + match select.selected_id() { + None => s.add_layer(Dialog::info("No account selected.")), + Some(focus) => { + // Add backup button to copy mnemonic/seed to clip + s.add_layer(Dialog::around(TextView::new("Confirm account deletion. If you have not backed up this account, it will be lost forever.").max_width(80)) + .button("Confirm", move |s| { + let data = &mut s.user_data::().unwrap(); + data.accounts.remove(focus); + save_accounts(s); + s.pop_layer(); + show_accounts(s); + }) + .button("Back", |s| { + s.pop_layer(); + }) + ); + } + } +} + +fn add_account(s: &mut Cursive) { + s.pop_layer(); + let data = &s.user_data::().unwrap(); + let coin = &data.coin.name; + let colour = data.coin.colour; + let content = format!("Choose a way to import your {} wallet.", coin); + s.add_layer( + Dialog::text(StyledString::styled(content, colour)) + .title("Import account") + .h_align(HAlign::Center) + .button("Mnemonic", |s| from_mnemonic(s)) + .button("Seed", |s| from_seed_or_key(s, String::from("seed"))) + .button("New", |s| new_account(s)), + ); +} + +fn from_mnemonic(s: &mut Cursive) { + s.pop_layer(); + s.add_layer( + Dialog::new() + .title("Enter your 24 word mnemonic") + .padding_lrtb(1, 1, 1, 0) + .content(EditView::new().with_name("mnemonic").fixed_width(29)) + .h_align(HAlign::Center) + .button("Done", |s| { + let mnemonic = s + .call_on_name("mnemonic", |view: &mut EditView| view.get_content()) + .unwrap(); + let seed = validate_mnemonic(&mnemonic); + let content; + s.pop_layer(); + if !mnemonic.is_empty() && seed.is_some() { + let seed_bytes = seed.unwrap(); + let data = &s.user_data::().unwrap(); + let account = Account::from_mnemonic( + Some(String::from(&*mnemonic)), + Some(seed_bytes), + &data.coin.prefix, + ); + setup_account(s, account, |s| { + import_success(s, "Successfully imported account from mnemonic phrase.") + }); + } else { + content = "The mnemonic you entered was not valid."; + s.add_layer( + Dialog::around(TextView::new(content)).button("Back", |s| from_mnemonic(s)), + ); + return; + } + }) + .button("Paste", |s| { + s.call_on_name("mnemonic", |view: &mut EditView| { + let mut clipboard = Clipboard::new().unwrap(); + let clip = clipboard + .get_text() + .unwrap_or_else(|_| String::from("Failed to read clipboard.")); + view.set_content(clip); + }) + .unwrap(); + }) + .button("Back", |s| { + show_accounts(s); + }), + ); +} + +fn from_seed_or_key(s: &mut Cursive, seed_or_key: String) { + s.pop_layer(); + s.add_layer( + Dialog::new() + .title(format!("Enter your {}", seed_or_key)) + .padding_lrtb(1, 1, 1, 0) + .content(EditView::new().with_name("seedorkey").fixed_width(29)) + .h_align(HAlign::Center) + .button("Done", move |s| { + let raw_seedorkey = s + .call_on_name("seedorkey", |view: &mut EditView| view.get_content()) + .unwrap(); + let seedorkey = raw_seedorkey.trim(); + if seedorkey.len() != 64 { + s.add_layer(Dialog::info(format!( + "Error: {} was invalid - not 64 characters long.", + seed_or_key + ))); + return; + } + let bytes_opt = hex::decode(seedorkey); + if bytes_opt.is_err() { + s.add_layer(Dialog::info(format!( + "Error: {} was invalid - failed to decode hex.", + seed_or_key + ))); + return; + } + let bytes = bytes_opt.unwrap(); + let seedorkey_bytes: [u8; 32] = bytes.try_into().unwrap(); + let data = &s.user_data::().unwrap(); + let account: Account; + if seed_or_key == "seed" { + account = Account::from_seed(Some(seedorkey_bytes), &data.coin.prefix); + } else { + account = Account::from_private_key(seedorkey_bytes, &data.coin.prefix); + } + let content = format!("Successfully imported account from {}.", seed_or_key); + setup_account(s, account, move |s| import_success(s, &content)); + }) + .button("Paste", |s| { + s.call_on_name("seedorkey", |view: &mut EditView| { + let mut clipboard = Clipboard::new().unwrap(); + let clip = clipboard + .get_text() + .unwrap_or_else(|_| String::from("Failed to read clipboard.")); + view.set_content(clip); + }) + .unwrap(); + }) + .button("Back", |s| { + show_accounts(s); + }), + ); +} + +fn new_account(s: &mut Cursive) { + s.pop_layer(); + let data = &s.user_data::().unwrap(); + let mut csprng = rand::thread_rng(); + let mut seed_bytes = [0u8; 32]; + csprng.fill_bytes(&mut seed_bytes); + let account = Account::from_seed(Some(seed_bytes), &data.coin.prefix); + setup_account(s, account, move |s| new_success(s, hex::encode(seed_bytes))); +} + +fn setup_account(s: &mut Cursive, account: Account, on_success: F) +where + F: Fn(&mut Cursive), +{ + let data = &mut s.user_data::().unwrap(); + data.accounts.push(account); + + // First account added, setup password + if data.accounts.len() == 1 { + s.add_layer(Dialog::around( + LinearLayout::vertical() + .child(TextView::new("Create a password. If you forget this, you will not be able to restore your accounts so make sure you have backed up the mnemonic phrases, seeds or private keys of your accounts.").max_width(80)) + .child(TextArea::new().with_name("password").max_width(80)) + .child(Button::new("Submit", move |s| { + let mut password = String::from(""); + s.call_on_name("password", |view: &mut TextArea| { + password = String::from(view.get_content()); + }); + let data = &mut s.user_data::().unwrap(); + data.password = password; + save_accounts(s); + s.pop_layer(); + on_success(s); + })))); + } else { + save_accounts(s); + on_success(s); + } +} + +fn import_success(s: &mut Cursive, content: &str) { + s.add_layer( + Dialog::around(TextView::new(content).max_width(80)) + .button("Begin", |s| alpha_info(s)) + .button("Back", |s| show_accounts(s)), + ); +} + +fn new_success(s: &mut Cursive, seed: String) { + let mut content = StyledString::plain("Successfully generated new account with seed: "); + content.append(StyledString::styled(&seed, OFF_WHITE)); + s.add_layer( + Dialog::around(TextView::new(content).max_width(80)) + .button("Copy seed", move |s| copy_to_clip(s, seed.clone())) + .button("Begin", |s| alpha_info(s)) + .button("Back", |s| show_accounts(s)), + ); +} diff --git a/src/dcutil.rs b/src/dcutil.rs index 94538e9..b5a8620 100644 --- a/src/dcutil.rs +++ b/src/dcutil.rs @@ -133,7 +133,6 @@ pub struct BlocksResponse { data: HashMap, } - #[derive(Serialize, Deserialize, Debug)] pub struct BlocksInfoResponse { blocks: BlocksResponse, @@ -174,13 +173,12 @@ pub fn send_message( } let public_key = ecies_ed25519::PublicKey::from_bytes(&public_key_bytes).unwrap(); counter.tick(50); - //println!("Encrypting message for send: {}", message); + let mut csprng = rand::thread_rng(); let encrypted_bytes = ecies_ed25519::encrypt(&public_key, message.as_bytes(), &mut csprng).unwrap(); let blocks_needed = ((60 + message.len()) / 32) + 1; counter.tick(50); - //println!("Blocks needed: {}", blocks_needed); let mut block_data = [0u8; 32]; let mut first_block_hash = [0u8; 32]; @@ -217,7 +215,6 @@ pub fn send_message( } else { block_data.copy_from_slice(&encrypted_bytes[start..end]); } - //println!("Block data as addr: {:?}", get_address(&block_data, addr_prefix)); let block_hash = get_block_hash( private_key_bytes, &block_data, diff --git a/src/main.rs b/src/main.rs index 4e5bdc7..bded37d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use arboard::Clipboard; +use bincode; use cursive::align::HAlign; use cursive::theme::{BaseColor, BorderStyle, Color, PaletteColor, Theme}; use cursive::traits::*; @@ -8,62 +9,77 @@ use cursive::views::{ TextArea, TextView, }; use cursive::Cursive; +use dirs; use rand::RngCore; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; pub mod defaults; use defaults::SHOW_TO_DP; -// Dagchat util +// dagchat util mod dcutil; use dcutil::*; -pub struct Data { - pub account: Account, - pub prefix: String, - pub coin: String, - pub ticker: String, - pub multiplier: String, - pub node_url: String, - pub colour: Color, -} +// dagchat accounts util +mod accounts; + +// send and receive with dagchat +mod receive; +mod send; -pub struct Account { - pub entropy: [u8; 32], - pub private_key: [u8; 32], - pub public_key: [u8; 32], - pub address: String, - pub balance: u128, - pub receivables: Vec, +pub struct UserData { + pub password: String, + pub accounts: Vec, + pub acc_idx: usize, + pub coin: Coin, } -impl Default for Account { - fn default() -> Account { - Account { - entropy: [0u8; 32], - private_key: [0u8; 32], - public_key: [0u8; 32], - address: String::from(""), - balance: 0, - receivables: Vec::new(), - } - } +pub struct Coin { + prefix: String, + name: String, + ticker: String, + multiplier: String, + node_url: String, + colour: Color, } -impl Data { - pub fn new() -> Self { - Data { - account: Default::default(), +impl Coin { + fn nano() -> Coin { + Coin { prefix: String::from("nano_"), - coin: String::from("nano"), + name: String::from("nano"), ticker: String::from("ΣΎ"), multiplier: String::from("1000000000000000000000000000000"), node_url: String::from("https://app.natrium.io/api"), colour: L_BLUE, } } + fn banano() -> Coin { + Coin { + prefix: String::from("ban_"), + name: String::from("banano"), + ticker: String::from(" BAN"), + multiplier: String::from("100000000000000000000000000000"), + node_url: String::from("https://kaliumapi.appditto.com/api"), + colour: YELLOW, + } + } } -const VERSION: &str = "alpha v1.0.0"; +impl UserData { + pub fn new() -> Self { + UserData { + password: String::from(""), + accounts: vec![], + acc_idx: 0, + coin: Coin::nano(), + } + } +} + +const VERSION: &str = "beta v1.0.0"; const L_BLUE: Color = Color::Rgb(62, 138, 227); const M_BLUE: Color = Color::Rgb(0, 106, 255); @@ -81,7 +97,7 @@ fn main() { let mut siv = cursive::default(); siv.set_window_title(format!("dagchat {}", VERSION)); - let data = Data::new(); + let data = UserData::new(); siv.set_user_data(data); set_theme(&mut siv, "nano", false); @@ -107,17 +123,11 @@ fn main() { let vibrant = theme_group.selection(); set_theme(s, &*coin, *vibrant); if *coin == "banano" { - s.with_user_data(|data: &mut Data| { - data.prefix = String::from("ban_"); - data.coin = String::from("banano"); - data.ticker = String::from(" BAN"); - // Down to 10^29 from 10^30 - data.multiplier.pop(); - data.node_url = String::from("https://kaliumapi.appditto.com/api"); - data.colour = YELLOW; + s.with_user_data(|data: &mut UserData| { + data.coin = Coin::banano(); }); } - show_start(s) + accounts::check_setup(s); }); siv.add_layer( @@ -135,361 +145,43 @@ fn main() { siv.try_run_with(backend_init).ok().unwrap(); } -fn show_start(s: &mut Cursive) { - s.pop_layer(); - let data = &s.user_data::().unwrap(); - let coin = &data.coin; - let colour = data.colour; - let content = format!("Choose a way to import your {} wallet", coin); - s.add_layer( - Dialog::text(StyledString::styled(content, colour)) - .title("Import account") - .h_align(HAlign::Center) - .button("Mnemonic", |s| get_mnemonic(s)) - .button("Seed", |s| get_seed(s)) - .button("New", |s| new_account(s)), - ); -} - -fn show_send(s: &mut Cursive, with_message: bool) { - s.pop_layer(); - - let data = &s.user_data::().unwrap(); - let balance = data.account.balance; - let coin = data.coin.clone(); - let ticker = data.ticker.clone(); - let multiplier = data.multiplier.clone(); - - - if balance == 0 { - let address = data.account.address.clone(); - let no_balance_message; - if with_message { - no_balance_message = format!("To send a message with dagchat you need a balance of at least 1 raw - a tiny fraction of a coin. One faucet claim will last you a lifetime. Your address is: {}", address); - } else { - no_balance_message = format!("You don't have any {} in your wallet to send.", coin); - } - s.add_layer( - Dialog::around(TextView::new(no_balance_message)) - .h_align(HAlign::Center) - .button("Back", |s| show_inbox(s)) - .button("Copy Address", move |s| copy_to_clip(s, address.clone())) - .max_width(75), - ); - return; - } - - let sub_title_colour = get_subtitle_colour(s); - - let mut form_content = LinearLayout::vertical() - .child(TextView::new(StyledString::styled( - "Recipient Address", - sub_title_colour, - ))) - .child(TextArea::new().with_name("address").max_width(66)) - .child( - LinearLayout::horizontal() - .child(Button::new("Paste", |s| { - s.call_on_name("address", |view: &mut TextArea| { - let mut clipboard = Clipboard::new().unwrap(); - let clip = clipboard - .get_text() - .unwrap_or_else(|_| String::from("Failed to read clipboard.")); - view.set_content(clip); - }) - .unwrap(); - })) - .child(Button::new("Address book", |s| { - s.add_layer(Dialog::info("Coming soon...")); - })), - ) - .child(DummyView); - let title_content; - if with_message { - title_content = String::from("Send a message"); - form_content.add_child(TextView::new(StyledString::styled( - "Message Content", - sub_title_colour, - ))); - form_content.add_child(TextArea::new().with_name("message").max_width(80)); - form_content.add_child(DummyView); - form_content.add_child(TextView::new(StyledString::styled( - format!("Optional {}", ticker), - sub_title_colour, - ))); - form_content.add_child(TextArea::new().with_name("amount")); - } else { - title_content = format!("Send {}", coin); - form_content.add_child(TextView::new(StyledString::styled( - "Amount", - sub_title_colour, - ))); - form_content.add_child(TextArea::new().with_name("amount")); - } - form_content.add_child(DummyView); - form_content.add_child( - LinearLayout::horizontal() - .child(Button::new("Send", move |s| { - let mut address = String::from(""); - let mut message = String::from(""); - let mut amount = String::from(""); - s.call_on_name("address", |view: &mut TextArea| { - address = String::from(view.get_content()); - }) - .unwrap(); - if with_message { - s.call_on_name("message", |view: &mut TextArea| { - message = String::from(view.get_content()); - }) - .unwrap(); - if message.trim().is_empty() { - s.add_layer(Dialog::info( - "You must provide message content to send a message!", - )); - return; - } - } - s.call_on_name("amount", |view: &mut TextArea| { - amount = String::from(view.get_content()); - }) - .unwrap(); - if address.is_empty() { - let content; - if with_message { - content = - String::from("You must provide an address to send the message to!"); - } else { - content = format!("You must provide an address to send {} to!", coin); - } - s.add_layer(Dialog::info(content)); - return; - } - let valid = validate_address(&address); - if !valid { - s.add_layer(Dialog::info("The recipient's address is invalid.")); - return; - } - let mut raw: u128 = 0; - if with_message { - raw = 1; - } - if !amount.is_empty() { - let raw_opt = whole_to_raw(amount, &multiplier); - if raw_opt.is_none() { - let content; - if with_message { - content = "The optional amount was invalid."; - } else { - content = "The amount was invalid."; - } - s.add_layer(Dialog::info(content)); - return; - } - raw = raw_opt.unwrap(); - if raw > balance { - s.add_layer(Dialog::info( - "The amount you want to send is more than your account balance!", - )); - return; - } else { - // The user supplied the amount 0 - if raw == 0 { - s.add_layer(Dialog::info(format!( - "You must provide an amount of {} to send!", - coin - ))); - return; - } - } - } else { - if message.is_empty() { - // The user supplied no amount and it's not a message - s.add_layer(Dialog::info(format!( - "You must provide an amount of {} to send!", - coin - ))); - return; - } - } - process_send(s, raw, address, message); - })) - .child(Button::new("Cancel", |s| show_inbox(s))), - ); - s.add_layer( - Dialog::around(form_content) - .title(title_content) - .padding_lrtb(1, 1, 1, 0), - ); -} +// Functions below are used across main.rs, send.rs, +// receive.rs and accounts.rs fn go_back(s: &mut Cursive) { s.pop_layer(); } fn get_subtitle_colour(s: &mut Cursive) -> Color { - let data = &s.user_data::().unwrap(); + let data = &s.user_data::().unwrap(); let sub_title_colour; - if data.colour == YELLOW { + if data.coin.colour == YELLOW { sub_title_colour = OFF_WHITE; } else { - sub_title_colour = data.colour; + sub_title_colour = data.coin.colour; } sub_title_colour } -fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { - let ticks = 1000; - let cb = s.cb_sink().clone(); - let data = &mut s.user_data::().unwrap(); - let node_url = data.node_url.clone(); - let private_key_bytes = data.account.private_key; - let prefix = data.prefix.clone(); - s.pop_layer(); - s.add_layer(Dialog::around( - ProgressBar::new() - .range(0, ticks) - .with_task(move |counter| { - let with_message = !message.is_empty(); - if !with_message { - send( - &private_key_bytes, - address, - raw, - &node_url, - &prefix, - &counter, - ); - } else { - send_message( - &private_key_bytes, - address, - raw, - message, - &node_url, - &prefix, - &counter, - ); - } - cb.send(Box::new(move |s| { - let data = &mut s.user_data::().unwrap(); - data.account.balance -= raw; - show_sent(s, with_message); - })) - .unwrap(); - }) - .full_width(), - )); - s.set_autorefresh(true); -} - -fn process_receive(s: &mut Cursive, idx: usize) { - let data = &mut s.user_data::().unwrap(); - let private_key = data.account.private_key; - let send_block_hash = data.account.receivables[idx].hash.clone(); - let amount = data.account.receivables[idx].amount; - let address = data.account.address.clone(); - let prefix = data.prefix.clone(); - let node_url = data.node_url.clone(); - let ticks = 1000; - let cb = s.cb_sink().clone(); - s.add_layer(Dialog::around( - ProgressBar::new() - .range(0, ticks) - .with_task(move |counter| { - counter.tick(100); - receive_block( - &private_key, - &send_block_hash, - amount, - &address, - &node_url, - &prefix, - &counter, - ); - cb.send(Box::new(move |s| { - let mut select = s.find_name::>("select").unwrap(); - select.remove_item(idx); - let mut balance = s.find_name::("balance").unwrap(); - let data = &mut s.user_data::().unwrap(); - data.account.receivables.remove(idx); - data.account.balance += amount; - let bal = display_to_dp(data.account.balance, SHOW_TO_DP, &data.multiplier, &data.ticker); - let bal_text = format!("Balance: {}", bal); - balance.set_content(StyledString::styled(bal_text, data.colour)); - s.pop_layer(); - s.pop_layer(); - })) - .unwrap(); - }) - .full_width(), - )); - s.set_autorefresh(true); -} - fn alpha_info(s: &mut Cursive) { s.pop_layer(); let mut info = StyledString::plain("Information for alpha testers:\n"); info.append(StyledString::styled("This is the inbox. Messages sent to you with 1 raw have already been identified as messages, but messages sent with an arbitrary amount will not yet have been detected. Select 'Find messages' from the buttons on the right to scan your list of receivables and identify these.", OFF_WHITE)); s.add_layer( Dialog::around(TextView::new(info)) - .button("Go to inbox", |s| load_receivables(s)) + .button("Go to inbox", |s| receive::load_receivables(s)) .max_width(60), ); } -fn show_sent(s: &mut Cursive, with_message: bool) { - s.set_autorefresh(false); - s.pop_layer(); - let content; - if with_message { - content = "Message sent successfully!"; - } else { - content = "Sent successfully!"; - } - s.add_layer(Dialog::text(content).button("Back", |s| show_inbox(s))); -} - -fn load_receivables(s: &mut Cursive) { - let ticks = 1000; - - let cb = s.cb_sink().clone(); - - let data = &s.user_data::().unwrap(); - let node_url = data.node_url.clone(); - let target_address = data.account.address.clone(); - s.pop_layer(); - s.add_layer(Dialog::around( - ProgressBar::new() - .range(0, ticks) - .with_task(move |counter| { - let account_info = get_account_info(&target_address, &node_url); - let mut balance: u128 = 0; - if account_info.is_some() { - balance = get_balance(&account_info.unwrap()); - } - counter.tick(100); - let receivables = find_incoming(&target_address, &node_url, &counter); - cb.send(Box::new(move |s| { - let data = &mut s.user_data::().unwrap(); - data.account.receivables = receivables; - data.account.balance = balance; - show_inbox(s); - })) - .unwrap(); - }) - .full_width(), - )); - s.set_autorefresh(true); -} - fn show_change_rep(s: &mut Cursive) { s.pop_layer(); - let data = &mut s.user_data::().unwrap(); - let private_key = data.account.private_key; - let prefix = data.prefix.clone(); - let node_url = data.node_url.clone(); - let address = data.account.address.clone(); - let coin = data.coin.clone(); + let data = &mut s.user_data::().unwrap(); + let private_key = data.accounts[data.acc_idx].private_key; + let prefix = data.coin.prefix.clone(); + let node_url = data.coin.node_url.clone(); + let address = data.accounts[data.acc_idx].address.clone(); + let coin = data.coin.name.clone(); let sub_title_colour = get_subtitle_colour(s); s.add_layer( Dialog::around( @@ -545,7 +237,7 @@ fn show_change_rep(s: &mut Cursive) { show_inbox(s); s.add_layer(Dialog::info("Successfully changed representative!")); })) - .child(Button::new("Cancel", |s| show_inbox(s)))), + .child(Button::new("Back", |s| show_inbox(s)))), ) .title("Change representative"), ); @@ -572,32 +264,37 @@ fn copy_to_clip(s: &mut Cursive, string: String) { fn show_inbox(s: &mut Cursive) { s.set_autorefresh(false); s.pop_layer(); - let data: Data = s.take_user_data().unwrap(); - let address = data.account.address.clone(); - let send_label = format!("Send {}", data.coin); + let data: UserData = s.take_user_data().unwrap(); + let address = data.accounts[data.acc_idx].address.clone(); + let send_label = format!("Send {}", data.coin.name); let buttons = LinearLayout::vertical() - .child(Button::new("Refresh", |s| load_receivables(s))) + .child(Button::new("Refresh", |s| receive::load_receivables(s))) .child(DummyView) - .child(Button::new(send_label, |s| show_send(s, false))) - .child(Button::new("Send message", |s| show_send(s, true))) + .child(Button::new(send_label, |s| send::show_send(s, false))) + .child(Button::new("Send message", |s| send::show_send(s, true))) .child(DummyView) .child(Button::new("Copy address", move |s| { copy_to_clip(s, address.clone()) })) .child(Button::new("Change rep", |s| show_change_rep(s))) .child(DummyView) - .child(Button::new("Quit", |s| s.quit())); + .child(Button::new("Accounts", |s| accounts::show_accounts(s))); let select = SelectView::::new() - .on_submit(show_message_info) + .on_submit(receive::show_message_info) .with_name("select") .scrollable() .max_height(6); - let bal = display_to_dp(data.account.balance, SHOW_TO_DP, &data.multiplier, &data.ticker); + let bal = display_to_dp( + data.accounts[data.acc_idx].balance, + SHOW_TO_DP, + &data.coin.multiplier, + &data.coin.ticker, + ); let bal_text = format!("Balance: {}", bal); let bal_content = - TextView::new(StyledString::styled(bal_text, data.colour)).with_name("balance"); + TextView::new(StyledString::styled(bal_text, data.coin.colour)).with_name("balance"); s.add_layer( Dialog::around( LinearLayout::horizontal() @@ -619,12 +316,17 @@ fn show_inbox(s: &mut Cursive) { .title(format!("dagchat {}", VERSION)), ); - for receivable in &data.account.receivables { + for receivable in &data.accounts[data.acc_idx].receivables { let mut tag; if receivable.amount == 1 && receivable.message.is_some() { tag = String::from("Message"); } else { - tag = display_to_dp(receivable.amount, SHOW_TO_DP, &data.multiplier, &data.ticker); + tag = display_to_dp( + receivable.amount, + SHOW_TO_DP, + &data.coin.multiplier, + &data.coin.ticker, + ); if receivable.message.is_some() { tag = format!("{} + Msg", tag); } @@ -638,210 +340,6 @@ fn show_inbox(s: &mut Cursive) { s.set_user_data(data); } -fn show_message_info(s: &mut Cursive, _name: &str) { - let select = s.find_name::>("select").unwrap(); - match select.selected_id() { - None => s.add_layer(Dialog::info("No receivable selected.")), - Some(focus) => { - let data = &mut s.user_data::().unwrap(); - let receivable = &mut data.account.receivables[focus]; - let private_key = &data.account.private_key; - let node_url = &data.node_url; - let plaintext: String; - - let mut content = LinearLayout::vertical(); - let mut title = format!("{} Receivable", &data.ticker); - let mut receive_label = String::from(""); - if receivable.message.is_some() { - receive_label = String::from(" and mark read"); - title = String::from("Message"); - let mut message = receivable.message.as_mut().unwrap(); - if message.plaintext.is_empty() { - // Potential feature: Confirm option with message length in chars (estimated) - // removes ability for attacks such as extremely long messages although probably - // not an issue. Harder to send a long message than read. - let target = &message.head.contents.account; - let root_hash = &message.root_hash; - let blocks = message.blocks; - // Potential feature: Add loading screen + process_message() - // time taken to load a (long) message can be noticeable if node - // is under load. - plaintext = read_message(private_key, target, root_hash, blocks, node_url); - message.plaintext = plaintext.clone(); - } else { - plaintext = message.plaintext.clone(); - } - content.add_child( - TextView::new(plaintext) - .scrollable() - .max_width(80) - .max_height(6), - ); - content.add_child(DummyView); - } - let colour = data.colour; - if !(receivable.amount == 1 && receivable.message.is_some()) { - receive_label = format!("Receive{}", receive_label); - let amount = display_to_dp(receivable.amount, SHOW_TO_DP, &data.multiplier, &data.ticker); - content.add_child(TextView::new(StyledString::styled("Amount", colour))); - content.add_child(TextView::new(StyledString::styled(amount, OFF_WHITE))); - content.add_child(DummyView); - } else { - receive_label = String::from("Mark read"); - } - let sender = receivable.source.clone(); - content.add_child(TextView::new(StyledString::styled("From", colour))); - content - .add_child(TextView::new(StyledString::styled(&sender, OFF_WHITE)).fixed_width(65)); - - s.add_layer( - Dialog::around(content) - .button(receive_label, move |s| { - process_receive(s, focus); - }) - .button("Copy address", move |s| copy_to_clip(s, sender.clone())) - .button("Back", |s| go_back(s)) - .title(title), - ); - } - } -} - -fn get_mnemonic(s: &mut Cursive) { - s.pop_layer(); - s.add_layer( - Dialog::new() - .title("Enter your 24 word mnemonic") - .padding_lrtb(1, 1, 1, 0) - .content( - EditView::new() - .on_submit(set_mnemonic) - .with_name("mnemonic") - .fixed_width(29), - ) - .h_align(HAlign::Center) - .button("Done", |s| { - let mnemonic = s - .call_on_name("mnemonic", |view: &mut EditView| view.get_content()) - .unwrap(); - - set_mnemonic(s, &mnemonic); - }) - .button("Paste", |s| { - s.call_on_name("mnemonic", |view: &mut EditView| { - let mut clipboard = Clipboard::new().unwrap(); - let clip = clipboard - .get_text() - .unwrap_or_else(|_| String::from("Failed to read clipboard.")); - view.set_content(clip); - }) - .unwrap(); - }) - .button("Back", |s| { - show_start(s); - }), - ); -} - -fn get_seed(s: &mut Cursive) { - s.pop_layer(); - s.add_layer( - Dialog::new() - .title("Enter your hex seed") - .padding_lrtb(1, 1, 1, 0) - .content( - EditView::new() - .on_submit(set_mnemonic) - .with_name("seed") - .fixed_width(29), - ) - .h_align(HAlign::Center) - .button("Done", |s| { - let raw_seed = s - .call_on_name("seed", |view: &mut EditView| view.get_content()) - .unwrap(); - let seed = raw_seed.trim(); - if seed.len() != 64 { - s.add_layer(Dialog::info("Seed was invalid: not 64 characters long.")); - return; - } - let bytes_opt = hex::decode(seed); - if bytes_opt.is_err() { - s.add_layer(Dialog::info("Seed was invalid: failed to decode hex.")); - return; - } - let bytes = bytes_opt.unwrap(); - set_seed(s, bytes); - }) - .button("Paste", |s| { - s.call_on_name("seed", |view: &mut EditView| { - let mut clipboard = Clipboard::new().unwrap(); - let clip = clipboard - .get_text() - .unwrap_or_else(|_| String::from("Failed to read clipboard.")); - view.set_content(clip); - }) - .unwrap(); - }) - .button("Back", |s| { - show_start(s); - }), - ); -} - -fn set_seed(s: &mut Cursive, seed: Vec) { - let mut entropy_bytes = [0u8; 32]; - entropy_bytes.copy_from_slice(seed.as_slice()); - s.pop_layer(); - setup_account(s, entropy_bytes); - let content = "Successfully imported account."; - s.add_layer(Dialog::around(TextView::new(content)).button("Begin", |s| alpha_info(s))); -} - -fn new_account(s: &mut Cursive) { - let mut csprng = rand::thread_rng(); - let mut entropy_bytes = [0u8; 32]; - csprng.fill_bytes(&mut entropy_bytes); - let entropy_hex = hex::encode(entropy_bytes); - setup_account(s, entropy_bytes); - s.pop_layer(); - let mut content = StyledString::plain("Successfully generated new account with seed: "); - content.append(StyledString::styled(&entropy_hex, OFF_WHITE)); - s.add_layer( - Dialog::around(TextView::new(content)) - .button("Copy seed", move |s| copy_to_clip(s, entropy_hex.clone())) - .button("Begin", |s| alpha_info(s)), - ); -} - -fn setup_account(s: &mut Cursive, entropy_bytes: [u8; 32]) { - let data = &mut s.user_data::().unwrap(); - let private_key_bytes = get_private_key(&entropy_bytes); - let private_key = ed25519_dalek::SecretKey::from_bytes(&private_key_bytes).unwrap(); - let public_key = ed25519_dalek::PublicKey::from(&private_key); - let public_key_bytes = public_key.to_bytes(); - let address = get_address(&public_key_bytes, &data.prefix); - data.account.entropy = entropy_bytes; - data.account.private_key = private_key_bytes; - data.account.public_key = public_key_bytes; - data.account.address = address; -} - -fn set_mnemonic(s: &mut Cursive, mnemonic: &str) { - let entropy = validate_mnemonic(mnemonic); - let content; - s.pop_layer(); - if !mnemonic.is_empty() && entropy.is_some() { - let entropy_bytes = entropy.unwrap(); - setup_account(s, entropy_bytes); - content = "Successfully imported account."; - s.add_layer(Dialog::around(TextView::new(content)).button("Begin", |s| alpha_info(s))); - } else { - content = "The mnemonic you entered was not valid."; - s.add_layer(Dialog::around(TextView::new(content)).button("Back", |s| get_mnemonic(s))); - } -} - fn set_theme(s: &mut Cursive, style: &str, vibrant: bool) { let mut theme = s.current_theme().clone(); if style == "nano" { diff --git a/src/receive.rs b/src/receive.rs new file mode 100644 index 0000000..03829a9 --- /dev/null +++ b/src/receive.rs @@ -0,0 +1,160 @@ +use super::*; + +pub fn show_message_info(s: &mut Cursive, _name: &str) { + let select = s.find_name::>("select").unwrap(); + match select.selected_id() { + None => s.add_layer(Dialog::info("No receivable selected.")), + Some(focus) => { + let data = &mut s.user_data::().unwrap(); + let account = &mut data.accounts[data.acc_idx]; + let receivable = &mut account.receivables[focus]; + let private_key = &account.private_key; + let node_url = &data.coin.node_url; + let plaintext: String; + + let mut content = LinearLayout::vertical(); + let mut title = format!("{} Receivable", &data.coin.ticker); + let mut receive_label = String::from(""); + if receivable.message.is_some() { + receive_label = String::from(" and mark read"); + title = String::from("Message"); + let mut message = receivable.message.as_mut().unwrap(); + if message.plaintext.is_empty() { + // Potential feature: Confirm option with message length in chars (estimated) + // removes ability for attacks such as extremely long messages although probably + // not an issue. Harder to send a long message than read. + let target = &message.head.contents.account; + let root_hash = &message.root_hash; + let blocks = message.blocks; + // Potential feature: Add loading screen + process_message() + // time taken to load a (long) message can be noticeable if node + // is under load. + plaintext = read_message(private_key, target, root_hash, blocks, node_url); + message.plaintext = plaintext.clone(); + } else { + plaintext = message.plaintext.clone(); + } + content.add_child( + TextView::new(plaintext) + .scrollable() + .max_width(80) + .max_height(6), + ); + content.add_child(DummyView); + } + let colour = data.coin.colour; + if !(receivable.amount == 1 && receivable.message.is_some()) { + receive_label = format!("Receive{}", receive_label); + let amount = display_to_dp( + receivable.amount, + SHOW_TO_DP, + &data.coin.multiplier, + &data.coin.ticker, + ); + content.add_child(TextView::new(StyledString::styled("Amount", colour))); + content.add_child(TextView::new(StyledString::styled(amount, OFF_WHITE))); + content.add_child(DummyView); + } else { + receive_label = String::from("Mark read"); + } + let sender = receivable.source.clone(); + content.add_child(TextView::new(StyledString::styled("From", colour))); + content + .add_child(TextView::new(StyledString::styled(&sender, OFF_WHITE)).fixed_width(65)); + + s.add_layer( + Dialog::around(content) + .button(receive_label, move |s| { + process_receive(s, focus); + }) + .button("Copy address", move |s| copy_to_clip(s, sender.clone())) + .button("Back", |s| go_back(s)) + .title(title), + ); + } + } +} + +pub fn load_receivables(s: &mut Cursive) { + let ticks = 1000; + + let cb = s.cb_sink().clone(); + + let data = &s.user_data::().unwrap(); + let node_url = data.coin.node_url.clone(); + let target_address = data.accounts[data.acc_idx].address.clone(); + s.pop_layer(); + s.add_layer(Dialog::around( + ProgressBar::new() + .range(0, ticks) + .with_task(move |counter| { + let account_info = get_account_info(&target_address, &node_url); + let mut balance: u128 = 0; + if account_info.is_some() { + balance = get_balance(&account_info.unwrap()); + } + counter.tick(100); + let receivables = find_incoming(&target_address, &node_url, &counter); + cb.send(Box::new(move |s| { + let data = &mut s.user_data::().unwrap(); + let mut account = &mut data.accounts[data.acc_idx]; + account.receivables = receivables; + account.balance = balance; + show_inbox(s); + })) + .unwrap(); + }) + .full_width(), + )); + s.set_autorefresh(true); +} + +fn process_receive(s: &mut Cursive, idx: usize) { + let data = &s.user_data::().unwrap(); + let private_key = data.accounts[data.acc_idx].private_key; + let send_block_hash = data.accounts[data.acc_idx].receivables[idx].hash.clone(); + let amount = data.accounts[data.acc_idx].receivables[idx].amount; + let address = data.accounts[data.acc_idx].address.clone(); + let prefix = data.coin.prefix.clone(); + let node_url = data.coin.node_url.clone(); + let ticks = 1000; + let cb = s.cb_sink().clone(); + s.add_layer(Dialog::around( + ProgressBar::new() + .range(0, ticks) + .with_task(move |counter| { + counter.tick(100); + receive_block( + &private_key, + &send_block_hash, + amount, + &address, + &node_url, + &prefix, + &counter, + ); + cb.send(Box::new(move |s| { + let mut select = s.find_name::>("select").unwrap(); + select.remove_item(idx); + let mut balance = s.find_name::("balance").unwrap(); + let data = &mut s.user_data::().unwrap(); + let account = &mut data.accounts[data.acc_idx]; + account.receivables.remove(idx); + account.balance += amount; + let bal = display_to_dp( + account.balance, + SHOW_TO_DP, + &data.coin.multiplier, + &data.coin.ticker, + ); + let bal_text = format!("Balance: {}", bal); + balance.set_content(StyledString::styled(bal_text, data.coin.colour)); + s.pop_layer(); + s.pop_layer(); + })) + .unwrap(); + }) + .full_width(), + )); + s.set_autorefresh(true); +} diff --git a/src/send.rs b/src/send.rs new file mode 100644 index 0000000..87574f2 --- /dev/null +++ b/src/send.rs @@ -0,0 +1,228 @@ +use super::*; + +pub fn show_send(s: &mut Cursive, with_message: bool) { + s.pop_layer(); + + let data = &s.user_data::().unwrap(); + let balance = data.accounts[data.acc_idx].balance; + let coin = data.coin.name.clone(); + let ticker = data.coin.ticker.clone(); + let multiplier = data.coin.multiplier.clone(); + + if balance == 0 { + let address = data.accounts[data.acc_idx].address.clone(); + let no_balance_message; + if with_message { + no_balance_message = format!("To send a message with dagchat you need a balance of at least 1 raw - a tiny fraction of a coin. One faucet claim will last you a lifetime. Your address is: {}", address); + } else { + no_balance_message = format!("You don't have any {} in your wallet to send.", coin); + } + s.add_layer( + Dialog::around(TextView::new(no_balance_message)) + .h_align(HAlign::Center) + .button("Back", |s| show_inbox(s)) + .button("Copy Address", move |s| copy_to_clip(s, address.clone())) + .max_width(75), + ); + return; + } + + let sub_title_colour = get_subtitle_colour(s); + + let mut form_content = LinearLayout::vertical() + .child(TextView::new(StyledString::styled( + "Recipient Address", + sub_title_colour, + ))) + .child(TextArea::new().with_name("address").max_width(66)) + .child( + LinearLayout::horizontal() + .child(Button::new("Paste", |s| { + s.call_on_name("address", |view: &mut TextArea| { + let mut clipboard = Clipboard::new().unwrap(); + let clip = clipboard + .get_text() + .unwrap_or_else(|_| String::from("Failed to read clipboard.")); + view.set_content(clip); + }) + .unwrap(); + })) + .child(Button::new("Address book", |s| { + s.add_layer(Dialog::info("Coming soon...")); + })), + ) + .child(DummyView); + let title_content; + if with_message { + title_content = String::from("Send a message"); + form_content.add_child(TextView::new(StyledString::styled( + "Message Content", + sub_title_colour, + ))); + form_content.add_child(TextArea::new().with_name("message").max_width(80)); + form_content.add_child(DummyView); + form_content.add_child(TextView::new(StyledString::styled( + format!("Optional {}", ticker), + sub_title_colour, + ))); + form_content.add_child(TextArea::new().with_name("amount")); + } else { + title_content = format!("Send {}", coin); + form_content.add_child(TextView::new(StyledString::styled( + "Amount", + sub_title_colour, + ))); + form_content.add_child(TextArea::new().with_name("amount")); + } + form_content.add_child(DummyView); + form_content.add_child( + LinearLayout::horizontal() + .child(Button::new("Send", move |s| { + let mut address = String::from(""); + let mut message = String::from(""); + let mut amount = String::from(""); + s.call_on_name("address", |view: &mut TextArea| { + address = String::from(view.get_content()); + }) + .unwrap(); + if with_message { + s.call_on_name("message", |view: &mut TextArea| { + message = String::from(view.get_content()); + }) + .unwrap(); + if message.trim().is_empty() { + s.add_layer(Dialog::info( + "You must provide message content to send a message!", + )); + return; + } + } + s.call_on_name("amount", |view: &mut TextArea| { + amount = String::from(view.get_content()); + }) + .unwrap(); + if address.is_empty() { + let content; + if with_message { + content = + String::from("You must provide an address to send the message to!"); + } else { + content = format!("You must provide an address to send {} to!", coin); + } + s.add_layer(Dialog::info(content)); + return; + } + let valid = validate_address(&address); + if !valid { + s.add_layer(Dialog::info("The recipient's address is invalid.")); + return; + } + let mut raw: u128 = 0; + if with_message { + raw = 1; + } + if !amount.is_empty() { + let raw_opt = whole_to_raw(amount, &multiplier); + if raw_opt.is_none() { + let content; + if with_message { + content = "The optional amount was invalid."; + } else { + content = "The amount was invalid."; + } + s.add_layer(Dialog::info(content)); + return; + } + raw = raw_opt.unwrap(); + if raw > balance { + s.add_layer(Dialog::info( + "The amount you want to send is more than your account balance!", + )); + return; + } else { + // The user supplied the amount 0 + if raw == 0 { + s.add_layer(Dialog::info(format!( + "You must provide an amount of {} to send!", + coin + ))); + return; + } + } + } else { + if message.is_empty() { + // The user supplied no amount and it's not a message + s.add_layer(Dialog::info(format!( + "You must provide an amount of {} to send!", + coin + ))); + return; + } + } + process_send(s, raw, address, message); + })) + .child(Button::new("Back", |s| show_inbox(s))), + ); + s.add_layer( + Dialog::around(form_content) + .title(title_content) + .padding_lrtb(1, 1, 1, 0), + ); +} + +pub fn show_sent(s: &mut Cursive, with_message: bool) { + s.set_autorefresh(false); + s.pop_layer(); + let content; + if with_message { + content = "Message sent successfully!"; + } else { + content = "Sent successfully!"; + } + s.add_layer(Dialog::text(content).button("Back", |s| show_inbox(s))); +} + +fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { + let ticks = 1000; + let cb = s.cb_sink().clone(); + let data = &mut s.user_data::().unwrap(); + let node_url = data.coin.node_url.clone(); + let private_key_bytes = data.accounts[data.acc_idx].private_key; + let prefix = data.coin.prefix.clone(); + s.pop_layer(); + s.add_layer(Dialog::around( + ProgressBar::new() + .range(0, ticks) + .with_task(move |counter| { + let with_message = !message.is_empty(); + if !with_message { + send( + &private_key_bytes, + address, + raw, + &node_url, + &prefix, + &counter, + ); + } else { + send_message( + &private_key_bytes, + address, + raw, + message, + &node_url, + &prefix, + &counter, + ); + } + cb.send(Box::new(move |s| { + let data = &mut s.user_data::().unwrap(); + data.accounts[data.acc_idx].balance -= raw; + show_sent(s, with_message); + })) + .unwrap(); + }) + .full_width(), + )); + s.set_autorefresh(true); +} From 6efc135ae84c014b85e54a46f7ec9004c203fc59 Mon Sep 17 00:00:00 2001 From: Derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Sun, 17 Apr 2022 00:39:51 +0100 Subject: [PATCH 02/12] Cargo dependencies for multi-accounts --- Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9dfd2fd..5ddb3de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dagchat" -version = "1.1.0" +version = "1.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,6 +10,10 @@ ecies-ed25519 = "0.5.1" ed25519-dalek = { version = "1.0.1" , package = "ed25519-dalek-blake2-feeless"} cursive = { version = "0.17.0", default-features = false, features = ["crossterm-backend"] } cursive_buffered_backend = "0.6.0" +dirs = "4.0.0" +rust-argon2 = "1.0.0" +bincode = "1.3.3" +aes-gcm = "0.9.4" sha2 = "0.10.1" blake2 = "0.10.2" rand = "0.7.0" From e483a1e6396903cc57fc6f67eeea9d58eec6c264 Mon Sep 17 00:00:00 2001 From: Derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Sun, 17 Apr 2022 23:31:20 +0100 Subject: [PATCH 03/12] Multi-account features + UI Tweaks 1. Changed multiple account access UI elements 2. Added ability to backup account (By copying mnemonic, seed or private key) 3. Added ability to change account index for seed and mnemonic based accounts 4. Added more error handling related to accounts data reading/writing --- src/accounts.rs | 491 ++++++++++++++++++++++++++++++++---------------- src/dcutil.rs | 4 +- src/defaults.rs | 8 +- src/main.rs | 48 +++-- src/pow.rs | 144 +++++++------- src/send.rs | 2 +- 6 files changed, 441 insertions(+), 256 deletions(-) diff --git a/src/accounts.rs b/src/accounts.rs index af88a37..0d5d7c0 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -10,7 +10,7 @@ const IV_LENGTH: usize = 12; pub struct Account { pub mnemonic: Option, pub seed: Option<[u8; 32]>, - pub key_idx: u128, + pub key_idx: u32, pub private_key: [u8; 32], pub public_key: [u8; 32], pub address: String, @@ -61,7 +61,7 @@ impl Account { } fn get_keypair(seed: &[u8; 32]) -> ([u8; 32], [u8; 32]) { - let private_key = dcutil::get_private_key(seed); + let private_key = dcutil::get_private_key(seed, 0); let public_key = Account::get_public_key(&private_key); (private_key, public_key) } @@ -105,8 +105,7 @@ fn load_accounts(s: &mut Cursive, data_path: PathBuf) { let encrypted_bytes = fs::read(&accounts_file).unwrap_or_else(|e| { let content = format!( "Failed to read accounts.dagchat file at path: {:?}\nError: {}", - accounts_file.display(), - e + accounts_file, e ); s.add_layer(Dialog::info(content)); vec![] @@ -115,57 +114,81 @@ fn load_accounts(s: &mut Cursive, data_path: PathBuf) { show_accounts(s); return; } - s.add_layer(Dialog::around( - LinearLayout::vertical() - .child(TextView::new("Enter password:")) - .child(TextArea::new().with_name("password").max_width(80)) - .child(Button::new("Submit", move |s| { - let mut password = String::from(""); - s.call_on_name("password", |view: &mut TextArea| { - password = String::from(view.get_content()); - }); - - let bytes = decrypt_accounts(&encrypted_bytes, &password); - if bytes.is_none() { - s.add_layer(Dialog::info("Incorrect password.")); - return; - } - let data = &mut s.user_data::().unwrap(); - data.password = password; - data.accounts = bincode::deserialize(&bytes.unwrap()[..]).unwrap(); - show_accounts(s); - })), - )); + let data = &mut s.user_data::().unwrap(); + data.encrypted_accounts = encrypted_bytes; + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child( + EditView::new() + .secret() + .on_submit(move |s, password| { + load_with_password(s, password); + }) + .with_name("password"), + ) + .child(DummyView) + .child(Button::new("Submit", move |s| { + let password = s + .call_on_name("password", |view: &mut EditView| view.get_content()) + .unwrap(); + load_with_password(s, &password); + })), + ) + .title("Enter dagchat password") + .max_width(80), + ); } else { show_accounts(s); } } +fn load_with_password(s: &mut Cursive, password: &str) { + let data = &mut s.user_data::().unwrap(); + let bytes = decrypt_accounts(&data.encrypted_accounts, &password); + if bytes.is_none() { + s.add_layer(Dialog::info("Password was incorrect.")); + return; + } + data.password = password.to_string(); + + let accounts_res = bincode::deserialize(&bytes.unwrap()[..]); + if accounts_res.is_err() { + show_accounts(s); + s.add_layer(Dialog::info(StyledString::styled("Error parsing accounts.dagchat file. File was either corrupted or edited outside of dagchat.", Color::Dark(BaseColor::Red)))); + } else { + data.accounts = accounts_res.unwrap(); + show_accounts(s); + } +} + fn write_accounts(encrypted_bytes: Vec) -> bool { let mut success = false; if let Some(data_dir) = dirs::data_dir() { let accounts_file = data_dir.join("dagchat/accounts.dagchat"); - if !accounts_file.exists() { - fs::File::create(&accounts_file).expect(&format!( - "Unable to create accounts.dagchat at path: {:?}", - &accounts_file - )); - } - if accounts_file.exists() { - success = true; - fs::write(&accounts_file, encrypted_bytes).unwrap_or_else(|e| { - success = false; - eprintln!( - "Failed to write to accounts.dagchat file at path: {:?}\nError: {}", - accounts_file, e - ); - }); - } + // Commented out code assumed unnecessary. + //if !accounts_file.exists() { + // fs::File::create(&accounts_file).expect(&format!( + // "Unable to create accounts.dagchat at path: {:?}", + // &accounts_file + // )); + //} + //if accounts_file.exists() { + success = true; + fs::write(&accounts_file, encrypted_bytes).unwrap_or_else(|e| { + success = false; + eprintln!( + "Failed to write to accounts.dagchat file at path: {:?}\nError: {}", + accounts_file, e + ); + }); + //} } success } -fn save_accounts(s: &mut Cursive) { +fn save_accounts(s: &mut Cursive) -> bool { let data = &s.user_data::().unwrap(); let success: bool; if data.accounts.is_empty() { @@ -174,9 +197,7 @@ fn save_accounts(s: &mut Cursive) { let encrypted_bytes = encrypt_accounts(&data.accounts, &data.password); success = write_accounts(encrypted_bytes); } - if !success { - s.add_layer(Dialog::info("Error saving accounts data.")); - } + success } fn derive_key(password: &str, salt: &[u8]) -> Vec { @@ -226,28 +247,34 @@ fn decrypt_accounts(encrypted_bytes: &[u8], password: &str) -> Option> { pub fn show_accounts(s: &mut Cursive) { s.pop_layer(); let data: UserData = s.take_user_data().unwrap(); - //Scroll view on left, iter on accounts and show them all - //Have 'Guest' button - //Have add, remove account, change password buttons on right - //When importing seed/mnemonic, option to specify index + // Need to add change password button let buttons = LinearLayout::vertical() - .child(Button::new("Add account", |s| add_account(s))) - .child(Button::new("Remove account", |s| remove_account(s))); + .child(Button::new("Import", |s| add_account(s))) + .child(Button::new("Create", |s| new_account(s))) + .child(DummyView) + .child(Button::new("Back", |s| { + s.pop_layer(); + show_title(s); + })) + .child(DummyView); let select = SelectView::::new() .on_submit(select_account) .with_name("accounts") .scrollable() - .max_height(10); + .max_height(5); s.add_layer( Dialog::around( - LinearLayout::horizontal() - .child(select) - .child(DummyView) - .child(buttons), + LinearLayout::vertical().child(DummyView).child( + LinearLayout::horizontal() + .child(Dialog::around(select).padding_lrtb(1, 1, 0, 0).title("Accounts")) + .child(DummyView) + .child(DummyView) + .child(buttons), + ) ) - .title("Select account"), + .title("Select an Account"), ); let mut i = 1; @@ -269,90 +296,177 @@ pub fn show_accounts(s: &mut Cursive) { s.set_user_data(data); } -fn select_account(s: &mut Cursive, name: &str) { +fn select_account(s: &mut Cursive, _: &str) { let select = s.find_name::>("accounts").unwrap(); match select.selected_id() { None => s.add_layer(Dialog::info("No account selected.")), Some(focus) => { - // If from mnemonic or seed, let user choose id. Else start. let data = &mut s.user_data::().unwrap(); data.acc_idx = focus; - receive::load_receivables(s); + + // Seemlessly use accounts between networks + if !data.accounts[focus].address.starts_with(&data.coin.prefix) { + let public_key = &data.accounts[focus].public_key; + data.accounts[focus].address = get_address(public_key, &data.coin.prefix); + } + + let account = &data.accounts[focus]; + let origin; + let mut extra = format!(" (at index {})", account.key_idx); + + let mut outer = Dialog::new() + .h_align(HAlign::Center) + .button("Begin", |s| { s.pop_layer(); + receive::load_receivables(s)}) + .button("Back", |s| { + s.pop_layer(); + s.pop_layer(); + show_accounts(s)}); + if account.mnemonic.is_some() { + let mnemonic = account.mnemonic.as_ref().unwrap().clone(); + origin = "loaded from mnemonic phrase"; + add_idx_button(&mut outer); + outer.add_button("Copy mnemonic",move |s| { + copy_to_clip(s, mnemonic.clone()); + }); + } else if account.seed.is_some() { + origin = "loaded from seed"; + let seed = hex::encode(account.seed.unwrap()); + add_idx_button(&mut outer); + outer.add_button("Copy seed", move |s| { + copy_to_clip(s, seed.clone()); + }); + } else { + origin = "loaded from private key"; + let key = hex::encode(account.private_key); + outer.add_button("Copy private key", move |s| { + copy_to_clip(s, key.clone()); + }); + extra = String::from(""); + } + outer.add_button("Remove", move |s| remove_account(s, focus)); + let content = LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(StyledString::styled(format!("Account address{}",extra), OFF_WHITE))) + .child(TextView::new(StyledString::styled(&account.address, data.coin.colour))) + .child(DummyView) + .child(TextView::new(StyledString::styled("Account type", OFF_WHITE))) + .child(TextView::new(StyledString::styled(origin, data.coin.colour))); + s.add_layer(outer.content(content) + .title(format!("Account {}", focus+1))); } } } -fn remove_account(s: &mut Cursive) { - let select = s.find_name::>("accounts").unwrap(); - match select.selected_id() { - None => s.add_layer(Dialog::info("No account selected.")), - Some(focus) => { - // Add backup button to copy mnemonic/seed to clip - s.add_layer(Dialog::around(TextView::new("Confirm account deletion. If you have not backed up this account, it will be lost forever.").max_width(80)) - .button("Confirm", move |s| { - let data = &mut s.user_data::().unwrap(); - data.accounts.remove(focus); - save_accounts(s); - s.pop_layer(); - show_accounts(s); - }) - .button("Back", |s| { - s.pop_layer(); - }) - ); - } +fn add_idx_button(dialog: &mut Dialog) { + dialog.add_button("Index", move |s| { + s.add_layer(Dialog::around(LinearLayout::vertical() + .child(DummyView) + .child(TextView::new("Account index (0 - 4,294,967,295)")) + .child(EditView::new().on_submit(process_idx).with_name("index"))) + .h_align(HAlign::Center) + .button("Submit", move |s| { + let idx = s + .call_on_name("index", |view: &mut EditView| view.get_content()) + .unwrap(); + process_idx(s, &idx); + }) + .button("Back", |s| { + s.pop_layer(); + }) + .title("Change account index")) + }); +} + +fn process_idx(s: &mut Cursive, idx: &str) { + let index_res: Result = idx.parse(); + if index_res.is_err() { + s.add_layer(Dialog::info("Error: index was not an integer within the valid range.")); + return; + } else { + let index: u32 = index_res.unwrap(); + let data = &mut s.user_data::().unwrap(); + let mut account = &mut data.accounts[data.acc_idx]; + account.key_idx = index; + let seed = account.seed.unwrap(); + account.private_key = get_private_key(&seed, index); + account.public_key = Account::get_public_key(&account.private_key); + account.address = get_address(&account.public_key, &data.coin.prefix); + s.pop_layer(); + s.pop_layer(); + select_account(s, ""); } } +fn remove_account(s: &mut Cursive, idx: usize) { + let warning = StyledString::styled( + "If you have not backed up this account, it will be lost forever.", + Color::Light(BaseColor::Red), + ); + s.add_layer(Dialog::around(LinearLayout::vertical().child(DummyView).child(TextView::new(warning)).child(DummyView)) + .h_align(HAlign::Center) + .button("Back", |s| { + s.pop_layer(); + }) + .button("Confirm", move |s| { + let data = &mut s.user_data::().unwrap(); + data.accounts.remove(idx); + let success = save_accounts(s); + s.pop_layer(); + s.pop_layer(); + s.pop_layer(); + show_accounts(s); + if !success { + s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not be removed upon relaunching dagchat.", Color::Light(BaseColor::Red)))); + } + }) + .title("Confirm account deletion") + ); +} + + fn add_account(s: &mut Cursive) { s.pop_layer(); let data = &s.user_data::().unwrap(); let coin = &data.coin.name; let colour = data.coin.colour; let content = format!("Choose a way to import your {} wallet.", coin); - s.add_layer( - Dialog::text(StyledString::styled(content, colour)) - .title("Import account") - .h_align(HAlign::Center) - .button("Mnemonic", |s| from_mnemonic(s)) - .button("Seed", |s| from_seed_or_key(s, String::from("seed"))) - .button("New", |s| new_account(s)), - ); + + let content = LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(StyledString::styled(content, colour))) + .child(DummyView) + + .child(Button::new("Mnemonic", |s| show_from_mnemonic(s))) + .child(Button::new("Hex Seed", |s| { + from_seedorkey(s, String::from("seed")) + })) + .child(Button::new("Private Key", |s| { + from_seedorkey(s, String::from("private key")) + })) + .child(DummyView) + .child(Button::new("Back", |s| show_accounts(s))); + s.add_layer(Dialog::around(content).title("Import account")); } -fn from_mnemonic(s: &mut Cursive) { +fn show_from_mnemonic(s: &mut Cursive) { s.pop_layer(); s.add_layer( Dialog::new() .title("Enter your 24 word mnemonic") .padding_lrtb(1, 1, 1, 0) - .content(EditView::new().with_name("mnemonic").fixed_width(29)) + .content( + EditView::new() + .on_submit(process_from_mnemonic) + .with_name("mnemonic") + .fixed_width(29), + ) .h_align(HAlign::Center) .button("Done", |s| { let mnemonic = s .call_on_name("mnemonic", |view: &mut EditView| view.get_content()) .unwrap(); - let seed = validate_mnemonic(&mnemonic); - let content; - s.pop_layer(); - if !mnemonic.is_empty() && seed.is_some() { - let seed_bytes = seed.unwrap(); - let data = &s.user_data::().unwrap(); - let account = Account::from_mnemonic( - Some(String::from(&*mnemonic)), - Some(seed_bytes), - &data.coin.prefix, - ); - setup_account(s, account, |s| { - import_success(s, "Successfully imported account from mnemonic phrase.") - }); - } else { - content = "The mnemonic you entered was not valid."; - s.add_layer( - Dialog::around(TextView::new(content)).button("Back", |s| from_mnemonic(s)), - ); - return; - } + process_from_mnemonic(s, &mnemonic); }) .button("Paste", |s| { s.call_on_name("mnemonic", |view: &mut EditView| { @@ -370,45 +484,50 @@ fn from_mnemonic(s: &mut Cursive) { ); } -fn from_seed_or_key(s: &mut Cursive, seed_or_key: String) { +fn process_from_mnemonic(s: &mut Cursive, mnemonic: &str) { + let seed = validate_mnemonic(&mnemonic); + let content; + s.pop_layer(); + if !mnemonic.is_empty() && seed.is_some() { + let seed_bytes = seed.unwrap(); + let data = &s.user_data::().unwrap(); + let account = Account::from_mnemonic( + Some(mnemonic.to_string()), + Some(seed_bytes), + &data.coin.prefix, + ); + setup_account(s, account, |s| { + import_success(s, "Successfully imported account from mnemonic phrase.") + }); + } else { + content = "The mnemonic you entered was not valid."; + s.add_layer( + Dialog::around(TextView::new(content)).button("Back", |s| show_from_mnemonic(s)), + ); + return; + } +} +fn from_seedorkey(s: &mut Cursive, seed_or_key: String) { s.pop_layer(); + let on_submit_seed_or_key = seed_or_key.clone(); s.add_layer( Dialog::new() .title(format!("Enter your {}", seed_or_key)) .padding_lrtb(1, 1, 1, 0) - .content(EditView::new().with_name("seedorkey").fixed_width(29)) + .content( + EditView::new() + .on_submit(move |s, sork_raw| { + process_from_seedorkey(s, sork_raw.to_string(), &on_submit_seed_or_key); + }) + .with_name("seedorkey") + .fixed_width(29), + ) .h_align(HAlign::Center) .button("Done", move |s| { - let raw_seedorkey = s + let sork_raw = s .call_on_name("seedorkey", |view: &mut EditView| view.get_content()) .unwrap(); - let seedorkey = raw_seedorkey.trim(); - if seedorkey.len() != 64 { - s.add_layer(Dialog::info(format!( - "Error: {} was invalid - not 64 characters long.", - seed_or_key - ))); - return; - } - let bytes_opt = hex::decode(seedorkey); - if bytes_opt.is_err() { - s.add_layer(Dialog::info(format!( - "Error: {} was invalid - failed to decode hex.", - seed_or_key - ))); - return; - } - let bytes = bytes_opt.unwrap(); - let seedorkey_bytes: [u8; 32] = bytes.try_into().unwrap(); - let data = &s.user_data::().unwrap(); - let account: Account; - if seed_or_key == "seed" { - account = Account::from_seed(Some(seedorkey_bytes), &data.coin.prefix); - } else { - account = Account::from_private_key(seedorkey_bytes, &data.coin.prefix); - } - let content = format!("Successfully imported account from {}.", seed_or_key); - setup_account(s, account, move |s| import_success(s, &content)); + process_from_seedorkey(s, sork_raw.to_string(), &seed_or_key); }) .button("Paste", |s| { s.call_on_name("seedorkey", |view: &mut EditView| { @@ -426,6 +545,36 @@ fn from_seed_or_key(s: &mut Cursive, seed_or_key: String) { ); } +fn process_from_seedorkey(s: &mut Cursive, sork_raw: String, seed_or_key: &String) { + let sork_val = sork_raw.trim(); + if sork_val.len() != 64 { + s.add_layer(Dialog::info(format!( + "Error: {} was invalid - not 64 characters long.", + seed_or_key + ))); + return; + } + let bytes_opt = hex::decode(sork_val); + if bytes_opt.is_err() { + s.add_layer(Dialog::info(format!( + "Error: {} was invalid - failed to decode hex.", + seed_or_key + ))); + return; + } + let bytes = bytes_opt.unwrap(); + let sork_bytes: [u8; 32] = bytes.try_into().unwrap(); + let data = &s.user_data::().unwrap(); + let account: Account; + if seed_or_key == "seed" { + account = Account::from_seed(Some(sork_bytes), &data.coin.prefix); + } else { + account = Account::from_private_key(sork_bytes, &data.coin.prefix); + } + let content = format!("Successfully imported account from {}.", seed_or_key); + setup_account(s, account, move |s| import_success(s, &content)); +} + fn new_account(s: &mut Cursive) { s.pop_layer(); let data = &s.user_data::().unwrap(); @@ -440,47 +589,69 @@ fn setup_account(s: &mut Cursive, account: Account, on_success: F) where F: Fn(&mut Cursive), { + let warning = StyledString::styled( + "Without this password, dagchat can not restore your accounts since they are saved in an encrypted format. Always backup or write down your mnemonics, seeds or keys elsewhere in case you forget your password.", Color::Light(BaseColor::Red)); + let data = &mut s.user_data::().unwrap(); data.accounts.push(account); - + data.acc_idx = data.accounts.len(); // First account added, setup password + // Need to add password confirmation here if data.accounts.len() == 1 { - s.add_layer(Dialog::around( - LinearLayout::vertical() - .child(TextView::new("Create a password. If you forget this, you will not be able to restore your accounts so make sure you have backed up the mnemonic phrases, seeds or private keys of your accounts.").max_width(80)) - .child(TextArea::new().with_name("password").max_width(80)) - .child(Button::new("Submit", move |s| { - let mut password = String::from(""); - s.call_on_name("password", |view: &mut TextArea| { - password = String::from(view.get_content()); - }); - let data = &mut s.user_data::().unwrap(); - data.password = password; - save_accounts(s); - s.pop_layer(); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(EditView::new().secret().with_name("password")) + .child(DummyView) + .child(TextView::new(warning)), + ) + .h_align(HAlign::Center) + .button("Submit", move |s| { + let password = s + .call_on_name("password", |view: &mut EditView| view.get_content()) + .unwrap(); + let data = &mut s.user_data::().unwrap(); + data.password = password.to_string(); + let success = save_accounts(s); + s.pop_layer(); + if success { on_success(s); - })))); + } else { + show_accounts(s); + s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not remain upon relaunching dagchat.", Color::Light(BaseColor::Red)))); + } + }) + .title("Create a password for dagchat") + .max_width(80), + ); } else { - save_accounts(s); - on_success(s); + let success = save_accounts(s); + if success { + on_success(s); + } else { + show_accounts(s); + s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not remain upon relaunching dagchat.", Color::Light(BaseColor::Red)))); + } } } fn import_success(s: &mut Cursive, content: &str) { s.add_layer( Dialog::around(TextView::new(content).max_width(80)) - .button("Begin", |s| alpha_info(s)) + .button("Begin", |s| receive::load_receivables(s)) .button("Back", |s| show_accounts(s)), ); } fn new_success(s: &mut Cursive, seed: String) { - let mut content = StyledString::plain("Successfully generated new account with seed: "); + let data = &mut s.user_data::().unwrap(); + let mut content = StyledString::styled("Successfully generated new account with seed: ", data.coin.colour); content.append(StyledString::styled(&seed, OFF_WHITE)); s.add_layer( Dialog::around(TextView::new(content).max_width(80)) .button("Copy seed", move |s| copy_to_clip(s, seed.clone())) - .button("Begin", |s| alpha_info(s)) + .button("Begin", |s| receive::load_receivables(s)) .button("Back", |s| show_accounts(s)), ); } diff --git a/src/dcutil.rs b/src/dcutil.rs index b5a8620..6853f0b 100644 --- a/src/dcutil.rs +++ b/src/dcutil.rs @@ -673,11 +673,11 @@ pub fn validate_mnemonic(mnemonic: &str) -> Option<[u8; 32]> { Some(entropy) } -pub fn get_private_key(seed_bytes: &[u8; 32]) -> [u8; 32] { +pub fn get_private_key(seed_bytes: &[u8; 32], idx: u32) -> [u8; 32] { let mut hasher = Blake2bVar::new(32).unwrap(); let mut buf = [0u8; 32]; hasher.update(seed_bytes); - hasher.update(&[0u8; 4]); + hasher.update(&idx.to_be_bytes()); hasher.finalize_variable(&mut buf).unwrap(); buf } diff --git a/src/defaults.rs b/src/defaults.rs index c7689ca..468990c 100644 --- a/src/defaults.rs +++ b/src/defaults.rs @@ -1,3 +1,5 @@ -pub const DEFAULT_REP_BANANO: &str = "ban_3catgir1p6b1edo5trp7fdb8gsxx4y5ffshbphj73zzy5hu678rsry7srh8b"; -pub const DEFAULT_REP_NANO: &str = "nano_3zx7rus19yr5qi5zmkawnzo5ehxr7i73xqghhondhfrzftgstgk4gxbubwfq"; -pub const SHOW_TO_DP: usize = 6; \ No newline at end of file +pub const DEFAULT_REP_BANANO: &str = + "ban_3catgir1p6b1edo5trp7fdb8gsxx4y5ffshbphj73zzy5hu678rsry7srh8b"; +pub const DEFAULT_REP_NANO: &str = + "nano_3zx7rus19yr5qi5zmkawnzo5ehxr7i73xqghhondhfrzftgstgk4gxbubwfq"; +pub const SHOW_TO_DP: usize = 6; diff --git a/src/main.rs b/src/main.rs index bded37d..13e623f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,8 +34,10 @@ pub struct UserData { pub accounts: Vec, pub acc_idx: usize, pub coin: Coin, + pub encrypted_accounts: Vec, } +#[derive(Debug, Clone)] pub struct Coin { prefix: String, name: String, @@ -75,6 +77,7 @@ impl UserData { accounts: vec![], acc_idx: 0, coin: Coin::nano(), + encrypted_accounts: vec![], } } } @@ -99,8 +102,14 @@ fn main() { siv.set_window_title(format!("dagchat {}", VERSION)); let data = UserData::new(); siv.set_user_data(data); - set_theme(&mut siv, "nano", false); + show_title(&mut siv); + + siv.try_run_with(backend_init).ok().unwrap(); +} + +fn show_title(s: &mut Cursive) { + set_theme(s, "nano", false); let mut theme_group: RadioGroup = RadioGroup::new(); let mut coin_group: RadioGroup = RadioGroup::new(); @@ -108,8 +117,8 @@ fn main() { let radios = LinearLayout::horizontal() .child( LinearLayout::vertical() - .child(coin_group.button("nano".to_string(), "nano").selected()) - .child(coin_group.button("banano".to_string(), "banano")), + .child(coin_group.button(String::from("nano"), "nano").selected()) + .child(coin_group.button(String::from("banano"), "banano")), ) .child(DummyView) .child( @@ -130,7 +139,7 @@ fn main() { accounts::check_setup(s); }); - siv.add_layer( + s.add_layer( Dialog::new() .content( LinearLayout::vertical() @@ -142,7 +151,6 @@ fn main() { .title(format!("dagchat {}", VERSION)) .h_align(HAlign::Center), ); - siv.try_run_with(backend_init).ok().unwrap(); } // Functions below are used across main.rs, send.rs, @@ -163,9 +171,11 @@ fn get_subtitle_colour(s: &mut Cursive) -> Color { sub_title_colour } +/* fn alpha_info(s: &mut Cursive) { s.pop_layer(); - let mut info = StyledString::plain("Information for alpha testers:\n"); + let data = &s.user_data::().unwrap(); + let mut info = StyledString::styled("Information for alpha testers:\n", data.coin.colour); info.append(StyledString::styled("This is the inbox. Messages sent to you with 1 raw have already been identified as messages, but messages sent with an arbitrary amount will not yet have been detected. Select 'Find messages' from the buttons on the right to scan your list of receivables and identify these.", OFF_WHITE)); s.add_layer( Dialog::around(TextView::new(info)) @@ -173,15 +183,14 @@ fn alpha_info(s: &mut Cursive) { .max_width(60), ); } +*/ fn show_change_rep(s: &mut Cursive) { s.pop_layer(); - let data = &mut s.user_data::().unwrap(); + let data = &s.user_data::().unwrap(); let private_key = data.accounts[data.acc_idx].private_key; - let prefix = data.coin.prefix.clone(); - let node_url = data.coin.node_url.clone(); + let coin = data.coin.clone(); let address = data.accounts[data.acc_idx].address.clone(); - let coin = data.coin.name.clone(); let sub_title_colour = get_subtitle_colour(s); s.add_layer( Dialog::around( @@ -217,7 +226,7 @@ fn show_change_rep(s: &mut Cursive) { }); if rep_address.is_empty() { s.add_layer(Dialog::info( - "You must provide an address to change representative to!", + "You must provide an address to change representative to!" )); return; } @@ -226,13 +235,13 @@ fn show_change_rep(s: &mut Cursive) { s.add_layer(Dialog::info("The representative's address is invalid.")); return; } - let account_info_opt = get_account_info(&address, &node_url); + let account_info_opt = get_account_info(&address, &coin.node_url); if account_info_opt.is_none() { - s.add_layer(Dialog::info(format!("You can't change representatives until you open your account by receiving some {}.", coin))); + s.add_layer(Dialog::info(format!("You can't change representatives until you open your account by receiving some {}.", coin.name))); return; } let account_info = account_info_opt.unwrap(); - change_rep(&private_key, account_info, &rep_address, &node_url, &prefix); + change_rep(&private_key, account_info, &rep_address, &coin.node_url, &coin.prefix); s.pop_layer(); show_inbox(s); s.add_layer(Dialog::info("Successfully changed representative!")); @@ -245,13 +254,18 @@ fn show_change_rep(s: &mut Cursive) { fn copy_to_clip(s: &mut Cursive, string: String) { let mut clipboard = Clipboard::new().unwrap(); + let data = &s.user_data::().unwrap(); let copied = clipboard.set_text(string.clone()); if copied.is_err() { - s.add_layer(Dialog::info("Error copying to clipboard.")); + s.add_layer(Dialog::info(StyledString::styled( + "Error copying to clipboard.", + Color::Light(BaseColor::Red), + ))); } else { let mut content = StyledString::styled(format!("{}\n", string), OFF_WHITE); - content.append(StyledString::plain( + content.append(StyledString::styled( "was successfully copied to your clipboard.", + data.coin.colour, )); s.add_layer( Dialog::around(TextView::new(content)) @@ -278,7 +292,7 @@ fn show_inbox(s: &mut Cursive) { })) .child(Button::new("Change rep", |s| show_change_rep(s))) .child(DummyView) - .child(Button::new("Accounts", |s| accounts::show_accounts(s))); + .child(Button::new("Back", |s| accounts::show_accounts(s))); let select = SelectView::::new() .on_submit(receive::show_message_info) diff --git a/src/pow.rs b/src/pow.rs index db3ecc3..d9e1c39 100644 --- a/src/pow.rs +++ b/src/pow.rs @@ -1,93 +1,91 @@ -use std::sync::Arc; +use blake2::digest::{Update, VariableOutput}; +use blake2::Blake2bVar; +use rand::RngCore; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread; -use rand::RngCore; -use blake2::Blake2bVar; -use blake2::digest::{Update, VariableOutput}; type Terminated = Arc; pub fn generate_work(input_hash: &[u8; 32], coin: &str) -> String { - let cpus = num_cpus::get(); - let terminated = Arc::new(AtomicBool::new(false)); - let threshold: u64; + let cpus = num_cpus::get(); + let terminated = Arc::new(AtomicBool::new(false)); + let threshold: u64; - if coin == "nano" { - threshold = u64::from_str_radix("FFFFFFF800000000", 16).unwrap(); - } else if coin == "banano" { - threshold = u64::from_str_radix("FFFFFE0000000000", 16).unwrap(); - } else { - panic!("Unknown coin threshold"); - } - - let mut threads = vec![]; + if coin == "nano" { + threshold = u64::from_str_radix("FFFFFFF800000000", 16).unwrap(); + } else if coin == "banano" { + threshold = u64::from_str_radix("FFFFFE0000000000", 16).unwrap(); + } else { + panic!("Unknown coin threshold"); + } - for _ in 0..cpus { - let mut input_copy = [0u8; 32]; - input_copy.clone_from_slice(input_hash); - let threshold_copy = threshold.clone(); - let terminator = terminated.clone(); - let thread_handle = thread::spawn(move || { - let (success, work) = compute_work(terminator, &input_copy, threshold_copy); - (success, work) - }); - threads.push(thread_handle); - } + let mut threads = vec![]; + + for _ in 0..cpus { + let mut input_copy = [0u8; 32]; + input_copy.clone_from_slice(input_hash); + let threshold_copy = threshold.clone(); + let terminator = terminated.clone(); + let thread_handle = thread::spawn(move || { + let (success, work) = compute_work(terminator, &input_copy, threshold_copy); + (success, work) + }); + threads.push(thread_handle); + } - let mut work_res: String = String::from(""); - for thread in threads.into_iter() { - let (success, work) = thread.join().unwrap(); - if success { - work_res = work; - } - } - work_res - + let mut work_res: String = String::from(""); + for thread in threads.into_iter() { + let (success, work) = thread.join().unwrap(); + if success { + work_res = work; + } + } + work_res } fn compute_work(terminated: Terminated, input_hash: &[u8; 32], threshold: u64) -> (bool, String) { - let mut work_n_hash = [0u8; 40]; - let hash = &mut work_n_hash[8..]; - let mut diff = [0u8; 32]; - for i in 0..32 { - hash[i] = input_hash[i]; - } - let work = &mut work_n_hash[0..8]; - rand::thread_rng().fill_bytes(work); + let mut work_n_hash = [0u8; 40]; + let hash = &mut work_n_hash[8..]; + let mut diff = [0u8; 32]; + for i in 0..32 { + hash[i] = input_hash[i]; + } + let work = &mut work_n_hash[0..8]; + rand::thread_rng().fill_bytes(work); + + loop { + let idx = (rand::random::() % 8) as usize; + let c = work_n_hash[idx]; + work_n_hash[idx] = if c == 0xff { 0 } else { c + 1 }; + new_diff(&work_n_hash, &mut diff); - loop { - let idx = (rand::random::() % 8) as usize; - let c = work_n_hash[idx]; - work_n_hash[idx] = if c == 0xff { 0 } else { c + 1 }; - new_diff(&work_n_hash, &mut diff); + if slice_to_u64(&diff[0..8]) > threshold { + println!("Found work {} > {}", slice_to_u64(&diff[0..8]), threshold); + terminated.store(true, Ordering::Relaxed); + break; + } - if slice_to_u64(&diff[0..8]) > threshold { - println!("Found work {} > {}", slice_to_u64(&diff[0..8]), threshold); - terminated.store(true, Ordering::Relaxed); - break; - } - - if terminated.load(Ordering::Relaxed) { - return (false, String::from("nope")); - } - + if terminated.load(Ordering::Relaxed) { + return (false, String::from("nope")); } + } - let work = &work_n_hash[0..8]; - let mut work_vec = Vec::from(work); - work_vec.reverse(); - let work_hex = hex::encode(work_vec); - (true, work_hex) + let work = &work_n_hash[0..8]; + let mut work_vec = Vec::from(work); + work_vec.reverse(); + let work_hex = hex::encode(work_vec); + (true, work_hex) } pub fn slice_to_u64(full: &[u8]) -> u64 { - let mut diff = [0u8; 8]; - diff.copy_from_slice(full); - u64::from_le_bytes(diff) - } - + let mut diff = [0u8; 8]; + diff.copy_from_slice(full); + u64::from_le_bytes(diff) +} + fn new_diff(work_and_hash: &[u8; 40], diff: &mut [u8; 32]) { - let mut hasher = Blake2bVar::new(32).unwrap(); - hasher.update(work_and_hash); - hasher.finalize_variable(diff).unwrap() -} \ No newline at end of file + let mut hasher = Blake2bVar::new(32).unwrap(); + hasher.update(work_and_hash); + hasher.finalize_variable(diff).unwrap() +} diff --git a/src/send.rs b/src/send.rs index 87574f2..63a45e6 100644 --- a/src/send.rs +++ b/src/send.rs @@ -13,7 +13,7 @@ pub fn show_send(s: &mut Cursive, with_message: bool) { let address = data.accounts[data.acc_idx].address.clone(); let no_balance_message; if with_message { - no_balance_message = format!("To send a message with dagchat you need a balance of at least 1 raw - a tiny fraction of a coin. One faucet claim will last you a lifetime. Your address is: {}", address); + no_balance_message = String::from("To send a message with dagchat you need a balance of at least 1 raw - a tiny fraction of a coin. One faucet claim will last you a lifetime."); } else { no_balance_message = format!("You don't have any {} in your wallet to send.", coin); } From 289acb4a14d7fd5d9c3c69b231ebafb912fe77ec Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:37:30 +0100 Subject: [PATCH 04/12] Create mnemonic, changed backup UI, fixed bug 1. Create a new account with a mnemonic by default. 2. Changed backup UI so it has its own tab. 3. Fixed bug which caused crash upon loading newly made account. --- Cargo.toml | 1 + src/accounts.rs | 230 +++++++++++++++++++++++++++++++++++------------- src/dcutil.rs | 19 ++++ 3 files changed, 189 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ddb3de..5ddbed0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ dirs = "4.0.0" rust-argon2 = "1.0.0" bincode = "1.3.3" aes-gcm = "0.9.4" +bitreader = "0.3.6" sha2 = "0.10.1" blake2 = "0.10.2" rand = "0.7.0" diff --git a/src/accounts.rs b/src/accounts.rs index 0d5d7c0..8d4444e 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -152,7 +152,7 @@ fn load_with_password(s: &mut Cursive, password: &str) { return; } data.password = password.to_string(); - + let accounts_res = bincode::deserialize(&bytes.unwrap()[..]); if accounts_res.is_err() { show_accounts(s); @@ -268,11 +268,15 @@ pub fn show_accounts(s: &mut Cursive) { Dialog::around( LinearLayout::vertical().child(DummyView).child( LinearLayout::horizontal() - .child(Dialog::around(select).padding_lrtb(1, 1, 0, 0).title("Accounts")) + .child( + Dialog::around(select) + .padding_lrtb(1, 1, 0, 0) + .title("Accounts"), + ) .child(DummyView) .child(DummyView) .child(buttons), - ) + ), ) .title("Select an Account"), ); @@ -315,73 +319,166 @@ fn select_account(s: &mut Cursive, _: &str) { let mut extra = format!(" (at index {})", account.key_idx); let mut outer = Dialog::new() - .h_align(HAlign::Center) - .button("Begin", |s| { s.pop_layer(); - receive::load_receivables(s)}) - .button("Back", |s| { - s.pop_layer(); - s.pop_layer(); - show_accounts(s)}); + .h_align(HAlign::Center) + .button("Load", |s| { + s.pop_layer(); + receive::load_receivables(s) + }) + .button("Back", |s| { + s.pop_layer(); + s.pop_layer(); + show_accounts(s) + }); if account.mnemonic.is_some() { - let mnemonic = account.mnemonic.as_ref().unwrap().clone(); - origin = "loaded from mnemonic phrase"; + origin = "mnemonic"; add_idx_button(&mut outer); - outer.add_button("Copy mnemonic",move |s| { - copy_to_clip(s, mnemonic.clone()); - }); } else if account.seed.is_some() { - origin = "loaded from seed"; - let seed = hex::encode(account.seed.unwrap()); + origin = "seed"; add_idx_button(&mut outer); - outer.add_button("Copy seed", move |s| { - copy_to_clip(s, seed.clone()); - }); } else { - origin = "loaded from private key"; - let key = hex::encode(account.private_key); - outer.add_button("Copy private key", move |s| { - copy_to_clip(s, key.clone()); - }); + origin = "private key"; extra = String::from(""); } + outer.add_button("Backup", move |s| backup_account(s, origin)); outer.add_button("Remove", move |s| remove_account(s, focus)); let content = LinearLayout::vertical() - .child(DummyView) - .child(TextView::new(StyledString::styled(format!("Account address{}",extra), OFF_WHITE))) - .child(TextView::new(StyledString::styled(&account.address, data.coin.colour))) - .child(DummyView) - .child(TextView::new(StyledString::styled("Account type", OFF_WHITE))) - .child(TextView::new(StyledString::styled(origin, data.coin.colour))); - s.add_layer(outer.content(content) - .title(format!("Account {}", focus+1))); + .child(DummyView) + .child(TextView::new(StyledString::styled( + format!("Account address{}", extra), + OFF_WHITE, + ))) + .child(TextView::new(StyledString::styled( + &account.address, + data.coin.colour, + ))) + .child(DummyView) + .child(TextView::new(StyledString::styled( + "Account type", + OFF_WHITE, + ))) + .child(TextView::new(StyledString::styled( + format!("loaded from {}", origin), + data.coin.colour, + ))); + s.add_layer( + outer + .content(content) + .title(format!("Account {}", focus + 1)), + ); } } } +fn backup_account(s: &mut Cursive, origin: &str) { + let data = &mut s.user_data::().unwrap(); + let account = &data.accounts[data.acc_idx]; + let mut content = Dialog::around(LinearLayout::vertical().child(DummyView).child( + TextView::new(StyledString::styled( + "Make sure you are in a safe location before viewing your mnemonic, seed or key.", + Color::Light(BaseColor::Red), + )), + )) + .h_align(HAlign::Center) + .title("Backup account"); + + if origin == "mnemonic" { + let mnemonic = account.mnemonic.as_ref().unwrap().clone(); + content.add_button("Mnemonic", move |s| { + let mnemonic = mnemonic.clone(); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(&mnemonic)), + ) + .h_align(HAlign::Center) + .button("Copy", move |s| { + s.pop_layer(); + s.pop_layer(); + copy_to_clip(s, mnemonic.clone()) + }) + .button("Back", |s| go_back(s)) + .title("Mnemonic") + .max_width(80), + ); + }); + } + if origin == "mnemonic" || origin == "seed" { + let seed = hex::encode(account.seed.unwrap()); + content.add_button("Hex seed", move |s| { + let seed = seed.clone(); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(&seed)), + ) + .h_align(HAlign::Center) + .button("Copy", move |s| { + s.pop_layer(); + s.pop_layer(); + copy_to_clip(s, seed.clone()) + }) + .button("Back", |s| go_back(s)) + .title("Seed"), + ); + }); + } else { + let private_key = hex::encode(account.private_key); + content.add_button("Private key", move |s| { + let private_key = private_key.clone(); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(&private_key)), + ) + .h_align(HAlign::Center) + .button("Copy", move |s| { + s.pop_layer(); + s.pop_layer(); + copy_to_clip(s, private_key.clone()) + }) + .button("Back", |s| go_back(s)) + .title("Private key"), + ); + }); + } + content.add_button("Back", |s| go_back(s)); + + s.add_layer(content.max_width(80)); +} + fn add_idx_button(dialog: &mut Dialog) { dialog.add_button("Index", move |s| { - s.add_layer(Dialog::around(LinearLayout::vertical() - .child(DummyView) - .child(TextView::new("Account index (0 - 4,294,967,295)")) - .child(EditView::new().on_submit(process_idx).with_name("index"))) - .h_align(HAlign::Center) - .button("Submit", move |s| { - let idx = s - .call_on_name("index", |view: &mut EditView| view.get_content()) - .unwrap(); - process_idx(s, &idx); - }) - .button("Back", |s| { - s.pop_layer(); - }) - .title("Change account index")) + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new("Account index (0 - 4,294,967,295)")) + .child(EditView::new().on_submit(process_idx).with_name("index")), + ) + .h_align(HAlign::Center) + .button("Submit", move |s| { + let idx = s + .call_on_name("index", |view: &mut EditView| view.get_content()) + .unwrap(); + process_idx(s, &idx); + }) + .button("Back", |s| { + s.pop_layer(); + }) + .title("Change account index"), + ) }); } fn process_idx(s: &mut Cursive, idx: &str) { let index_res: Result = idx.parse(); if index_res.is_err() { - s.add_layer(Dialog::info("Error: index was not an integer within the valid range.")); + s.add_layer(Dialog::info( + "Error: index was not an integer within the valid range.", + )); return; } else { let index: u32 = index_res.unwrap(); @@ -407,7 +504,7 @@ fn remove_account(s: &mut Cursive, idx: usize) { .h_align(HAlign::Center) .button("Back", |s| { s.pop_layer(); - }) + }) .button("Confirm", move |s| { let data = &mut s.user_data::().unwrap(); data.accounts.remove(idx); @@ -423,7 +520,6 @@ fn remove_account(s: &mut Cursive, idx: usize) { .title("Confirm account deletion") ); } - fn add_account(s: &mut Cursive) { s.pop_layer(); @@ -436,7 +532,6 @@ fn add_account(s: &mut Cursive) { .child(DummyView) .child(TextView::new(StyledString::styled(content, colour))) .child(DummyView) - .child(Button::new("Mnemonic", |s| show_from_mnemonic(s))) .child(Button::new("Hex Seed", |s| { from_seedorkey(s, String::from("seed")) @@ -581,8 +676,12 @@ fn new_account(s: &mut Cursive) { let mut csprng = rand::thread_rng(); let mut seed_bytes = [0u8; 32]; csprng.fill_bytes(&mut seed_bytes); - let account = Account::from_seed(Some(seed_bytes), &data.coin.prefix); - setup_account(s, account, move |s| new_success(s, hex::encode(seed_bytes))); + let mnemonic = seed_to_mnemonic(&seed_bytes); + let account = + Account::from_mnemonic(Some(mnemonic.clone()), Some(seed_bytes), &data.coin.prefix); + setup_account(s, account, move |s| { + new_success(s, mnemonic.clone(), hex::encode(seed_bytes)) + }); } fn setup_account(s: &mut Cursive, account: Account, on_success: F) @@ -594,7 +693,7 @@ where let data = &mut s.user_data::().unwrap(); data.accounts.push(account); - data.acc_idx = data.accounts.len(); + data.acc_idx = data.accounts.len() - 1; // First account added, setup password // Need to add password confirmation here if data.accounts.len() == 1 { @@ -631,7 +730,10 @@ where on_success(s); } else { show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not remain upon relaunching dagchat.", Color::Light(BaseColor::Red)))); + s.add_layer(Dialog::info(StyledString::styled( + "Error saving accounts data. Account may not remain upon relaunching dagchat.", + Color::Light(BaseColor::Red), + ))); } } } @@ -639,19 +741,25 @@ where fn import_success(s: &mut Cursive, content: &str) { s.add_layer( Dialog::around(TextView::new(content).max_width(80)) - .button("Begin", |s| receive::load_receivables(s)) + .button("Load", |s| receive::load_receivables(s)) .button("Back", |s| show_accounts(s)), ); } -fn new_success(s: &mut Cursive, seed: String) { +fn new_success(s: &mut Cursive, mnemonic: String, seed: String) { let data = &mut s.user_data::().unwrap(); - let mut content = StyledString::styled("Successfully generated new account with seed: ", data.coin.colour); + let mut content = StyledString::styled("\nMnemonic\n", data.coin.colour); + content.append(StyledString::styled(&mnemonic, OFF_WHITE)); + content.append(StyledString::styled("\n\nSeed\n", data.coin.colour)); content.append(StyledString::styled(&seed, OFF_WHITE)); s.add_layer( Dialog::around(TextView::new(content).max_width(80)) + .h_align(HAlign::Center) + .button("Load", |s| receive::load_receivables(s)) + .button("Back", |s| show_accounts(s)) + .button("Copy mnemonic", move |s| copy_to_clip(s, mnemonic.clone())) .button("Copy seed", move |s| copy_to_clip(s, seed.clone())) - .button("Begin", |s| receive::load_receivables(s)) - .button("Back", |s| show_accounts(s)), + .title("Successfully generated new account") + .max_width(80), ); } diff --git a/src/dcutil.rs b/src/dcutil.rs index 6853f0b..0fe44f2 100644 --- a/src/dcutil.rs +++ b/src/dcutil.rs @@ -1,4 +1,5 @@ use bigdecimal::BigDecimal; +use bitreader::BitReader; use blake2::digest::{Update, VariableOutput}; use blake2::Blake2bVar; use data_encoding::Encoding; @@ -746,6 +747,24 @@ pub fn validate_address(addr: &str) -> bool { } } +pub fn seed_to_mnemonic(seed_bytes: &[u8]) -> String { + let mut hasher = Sha256::new(); + Digest::update(&mut hasher, seed_bytes); + let check = hasher.finalize(); + + let mut combined = Vec::from(seed_bytes); + combined.extend(&check); + + let mut reader = BitReader::new(&combined); + + let mut words: Vec<&str> = Vec::new(); + for _ in 0..24 { + let n = reader.read_u16(11); + words.push(WORD_LIST[n.unwrap() as usize].as_ref()); + } + words.join(" ") +} + fn compute_address_checksum(pub_key_bytes: &[u8]) -> [u8; 5] { let mut hasher = Blake2bVar::new(5).unwrap(); let mut buf = [0u8; 5]; From 14ba42d17a34eb7f31882f829f1a7bc88e6c674c Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Mon, 18 Apr 2022 18:20:31 +0100 Subject: [PATCH 05/12] Password confirmation and info --- src/accounts.rs | 98 +++++++++++++++++++++++++++++++------------------ src/main.rs | 1 + 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/accounts.rs b/src/accounts.rs index 8d4444e..a4a55ee 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -375,7 +375,7 @@ fn backup_account(s: &mut Cursive, origin: &str) { let mut content = Dialog::around(LinearLayout::vertical().child(DummyView).child( TextView::new(StyledString::styled( "Make sure you are in a safe location before viewing your mnemonic, seed or key.", - Color::Light(BaseColor::Red), + RED, )), )) .h_align(HAlign::Center) @@ -498,7 +498,7 @@ fn process_idx(s: &mut Cursive, idx: &str) { fn remove_account(s: &mut Cursive, idx: usize) { let warning = StyledString::styled( "If you have not backed up this account, it will be lost forever.", - Color::Light(BaseColor::Red), + RED, ); s.add_layer(Dialog::around(LinearLayout::vertical().child(DummyView).child(TextView::new(warning)).child(DummyView)) .h_align(HAlign::Center) @@ -514,7 +514,7 @@ fn remove_account(s: &mut Cursive, idx: usize) { s.pop_layer(); show_accounts(s); if !success { - s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not be removed upon relaunching dagchat.", Color::Light(BaseColor::Red)))); + s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not be removed upon relaunching dagchat.", RED))); } }) .title("Confirm account deletion") @@ -688,42 +688,11 @@ fn setup_account(s: &mut Cursive, account: Account, on_success: F) where F: Fn(&mut Cursive), { - let warning = StyledString::styled( - "Without this password, dagchat can not restore your accounts since they are saved in an encrypted format. Always backup or write down your mnemonics, seeds or keys elsewhere in case you forget your password.", Color::Light(BaseColor::Red)); - let data = &mut s.user_data::().unwrap(); data.accounts.push(account); data.acc_idx = data.accounts.len() - 1; - // First account added, setup password - // Need to add password confirmation here if data.accounts.len() == 1 { - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child(EditView::new().secret().with_name("password")) - .child(DummyView) - .child(TextView::new(warning)), - ) - .h_align(HAlign::Center) - .button("Submit", move |s| { - let password = s - .call_on_name("password", |view: &mut EditView| view.get_content()) - .unwrap(); - let data = &mut s.user_data::().unwrap(); - data.password = password.to_string(); - let success = save_accounts(s); - s.pop_layer(); - if success { - on_success(s); - } else { - show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not remain upon relaunching dagchat.", Color::Light(BaseColor::Red)))); - } - }) - .title("Create a password for dagchat") - .max_width(80), - ); + set_password(s, on_success); } else { let success = save_accounts(s); if success { @@ -732,12 +701,69 @@ where show_accounts(s); s.add_layer(Dialog::info(StyledString::styled( "Error saving accounts data. Account may not remain upon relaunching dagchat.", - Color::Light(BaseColor::Red), + RED, ))); } } } +fn set_password(s: &mut Cursive, on_success: F) +where + F: Fn(&mut Cursive), +{ + let warning = StyledString::styled( + "Always backup or write down your mnemonics, seeds or keys elsewhere in case you forget your password.", RED); + + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new("Enter password")) + .child(EditView::new().secret().with_name("password")) + .child(DummyView) + .child(TextView::new("Confirm password")) + .child(EditView::new().secret().with_name("confirm")) + .child(DummyView) + .child(TextView::new(warning)), + ) + .h_align(HAlign::Center) + .button("Submit", move |s| { + let password = s + .call_on_name("password", |view: &mut EditView| view.get_content()) + .unwrap(); + let confirmed = s + .call_on_name("confirm", |view: &mut EditView| view.get_content()) + .unwrap(); + if password.is_empty() { + s.add_layer(Dialog::info("Password can't be blank.")); + return; + } + if password != confirmed { + s.add_layer(Dialog::info("Passwords did not match.")); + return; + } + let data = &mut s.user_data::().unwrap(); + data.password = password.to_string(); + let success = save_accounts(s); + s.pop_layer(); + if success { + on_success(s); + } else { + show_accounts(s); + s.add_layer(Dialog::info(StyledString::styled( + "Error saving accounts data. Account may not remain upon relaunching dagchat.", + RED, + ))); + } + }) + .button("Info", |s| { + let content = "\nThe password you are setting up for dagchat is used to encrypt your accounts, messages (If 'Encrypt and save' messages setting is selected) and address book when they are saved on your device. It should be strong and contain a range of characters (UPPERCASE, lowercase, numb3rs and symbo!s). Without this password, dagchat will not be able to decrypt any of your saved accounts, messages or address book."; + s.add_layer(Dialog::info(content).title("What is this password?").max_width(80)); + }) + .title("Create a password for dagchat") + .max_width(80), + ); +} fn import_success(s: &mut Cursive, content: &str) { s.add_layer( Dialog::around(TextView::new(content).max_width(80)) diff --git a/src/main.rs b/src/main.rs index 13e623f..f31147d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,6 +90,7 @@ const D_BLUE: Color = Color::Rgb(12, 37, 125); const YELLOW: Color = Color::Light(BaseColor::Yellow); const OFF_WHITE: Color = Color::Rgb(245, 245, 247); +const RED: Color = Color::Light(BaseColor::Red); fn main() { let backend_init = || -> std::io::Result> { From 0319f22b3b170892a467aa29ad3bc80128684f5f Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Tue, 19 Apr 2022 22:13:04 +0100 Subject: [PATCH 06/12] Save message [WARNING: Contains sensitive debug] THIS COMMIT OUTPUTS SENSITIVE CONTENT TO STDERR. Features listed below are not bug-tested. 1. Started adding code to save messages upon receive and send 2. Messages are encrypted with the user's password 3. Each address that a user sends or receives messages with has its own file 4. A HashMap of address and randomly generated key are kept in accounts.dagchat and are used to identify the message file (random_key.dagchat) 5. Added untested function to change encryption of each message store file when a user's password is changed (and set) --- src/accounts.rs | 259 +++++++++++++++++++++--------------------------- src/dcutil.rs | 61 +++++++++++- src/defaults.rs | 6 ++ src/main.rs | 74 +++++++++----- src/messages.rs | 133 +++++++++++++++++++++++++ src/receive.rs | 42 +++++++- src/send.rs | 40 +++++++- 7 files changed, 437 insertions(+), 178 deletions(-) create mode 100644 src/messages.rs diff --git a/src/accounts.rs b/src/accounts.rs index a4a55ee..bdae276 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -1,10 +1,12 @@ use super::*; -use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead}; -use aes_gcm::{Aes256Gcm, Nonce}; -use argon2::{self, Config}; -const SALT_LENGTH: usize = 16; -const IV_LENGTH: usize = 12; +use rand::RngCore; + +#[derive(Serialize, Deserialize, Debug)] +pub struct AccountsAndLookup { + accounts_bytes: Vec, + lookup_bytes: Vec, +} #[derive(Serialize, Deserialize, Debug)] pub struct Account { @@ -72,40 +74,14 @@ impl Account { } } -pub fn check_setup(s: &mut Cursive) { - if let Some(mut data_dir) = dirs::data_dir() { - data_dir = data_dir.join("dagchat"); - if data_dir.exists() { - load_accounts(s, data_dir); - } else { - fs::create_dir(data_dir.clone()).unwrap_or_else(|e| { - let content = format!( - "Failed to create a data folder for dagchat at path: {:?}\nError: {}", - data_dir, e - ); - s.add_layer(Dialog::info(content)) - }); - if data_dir.exists() { - load_accounts(s, data_dir); - } else { - return; - } - } - } else { - s.add_layer(Dialog::info( - "Error locating the application data folder on your system.", - )); - } -} - -fn load_accounts(s: &mut Cursive, data_path: PathBuf) { +pub fn load_accounts(s: &mut Cursive, data_path: PathBuf) { s.pop_layer(); - let accounts_file = data_path.join("accounts.dagchat"); + let accounts_file = data_path.join(ACCOUNTS_PATH); if accounts_file.exists() { let encrypted_bytes = fs::read(&accounts_file).unwrap_or_else(|e| { let content = format!( - "Failed to read accounts.dagchat file at path: {:?}\nError: {}", - accounts_file, e + "Failed to read {} file at path: {:?}\nError: {}", + ACCOUNTS_PATH, accounts_file, e ); s.add_layer(Dialog::info(content)); vec![] @@ -115,7 +91,7 @@ fn load_accounts(s: &mut Cursive, data_path: PathBuf) { return; } let data = &mut s.user_data::().unwrap(); - data.encrypted_accounts = encrypted_bytes; + data.encrypted_bytes = encrypted_bytes; s.add_layer( Dialog::around( LinearLayout::vertical() @@ -146,102 +122,64 @@ fn load_accounts(s: &mut Cursive, data_path: PathBuf) { fn load_with_password(s: &mut Cursive, password: &str) { let data = &mut s.user_data::().unwrap(); - let bytes = decrypt_accounts(&data.encrypted_accounts, &password); - if bytes.is_none() { + let bytes = decrypt_bytes(&data.encrypted_bytes, &password); + if bytes.is_err() { s.add_layer(Dialog::info("Password was incorrect.")); return; } data.password = password.to_string(); - let accounts_res = bincode::deserialize(&bytes.unwrap()[..]); - if accounts_res.is_err() { + let accounts_and_lookup_res = bincode::deserialize(&bytes.unwrap()[..]); + if accounts_and_lookup_res.is_err() { show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled("Error parsing accounts.dagchat file. File was either corrupted or edited outside of dagchat.", Color::Dark(BaseColor::Red)))); + s.add_layer(Dialog::info(StyledString::styled( + format!( + "Error parsing {} file. File was either corrupted or edited outside of dagchat.", + ACCOUNTS_PATH + ), + RED, + ))); } else { - data.accounts = accounts_res.unwrap(); + let accounts_and_lookup: AccountsAndLookup = accounts_and_lookup_res.unwrap(); + data.accounts = bincode::deserialize(&accounts_and_lookup.accounts_bytes).unwrap(); + data.lookup = bincode::deserialize(&accounts_and_lookup.lookup_bytes).unwrap(); show_accounts(s); } } -fn write_accounts(encrypted_bytes: Vec) -> bool { - let mut success = false; +fn write_accounts(encrypted_bytes: Vec) -> Result<(), String> { if let Some(data_dir) = dirs::data_dir() { - let accounts_file = data_dir.join("dagchat/accounts.dagchat"); - // Commented out code assumed unnecessary. - //if !accounts_file.exists() { - // fs::File::create(&accounts_file).expect(&format!( - // "Unable to create accounts.dagchat at path: {:?}", - // &accounts_file - // )); - //} - //if accounts_file.exists() { - success = true; - fs::write(&accounts_file, encrypted_bytes).unwrap_or_else(|e| { - success = false; - eprintln!( - "Failed to write to accounts.dagchat file at path: {:?}\nError: {}", - accounts_file, e - ); - }); - //} + let accounts_file = data_dir.join(DATA_DIR_PATH).join(ACCOUNTS_PATH); + let write_res = fs::write(&accounts_file, encrypted_bytes); + if write_res.is_err() { + return Err(format!( + "Failed to write to {} file at path: {:?}\nError: {:?}", + ACCOUNTS_PATH, + accounts_file, + write_res.err() + )); + } } - success + Ok(()) } -fn save_accounts(s: &mut Cursive) -> bool { +pub fn save_accounts(s: &mut Cursive) -> Result<(), String> { let data = &s.user_data::().unwrap(); - let success: bool; - if data.accounts.is_empty() { - success = write_accounts(vec![]); - } else { - let encrypted_bytes = encrypt_accounts(&data.accounts, &data.password); - success = write_accounts(encrypted_bytes); - } - success -} - -fn derive_key(password: &str, salt: &[u8]) -> Vec { - let config = Config::default(); - let hash = argon2::hash_raw(password.as_bytes(), salt, &config).unwrap(); - hash -} - -fn encrypt_accounts(accounts: &Vec, password: &str) -> Vec { - let mut csprng = rand::thread_rng(); - let mut salt = [0u8; SALT_LENGTH]; - csprng.fill_bytes(&mut salt); - - let key_bytes = derive_key(password, &salt); - let key = GenericArray::from_slice(&key_bytes); - let encoded: Vec = bincode::serialize(accounts).unwrap(); - let aead = Aes256Gcm::new(key); - - let mut nonce_bytes = [0u8; 12]; - csprng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - - let ciphertext = aead.encrypt(nonce, &encoded[..]).unwrap(); - let mut encrypted_bytes = Vec::with_capacity(12 + ciphertext.len()); - encrypted_bytes.extend(salt); - encrypted_bytes.extend(nonce); - encrypted_bytes.extend(ciphertext); - encrypted_bytes -} - -fn decrypt_accounts(encrypted_bytes: &[u8], password: &str) -> Option> { - let salt = &encrypted_bytes[..SALT_LENGTH]; - - let key_bytes = derive_key(password, salt); - let key = GenericArray::from_slice(&key_bytes); - - let aead = Aes256Gcm::new(&key); - let nonce = Nonce::from_slice(&encrypted_bytes[SALT_LENGTH..SALT_LENGTH + IV_LENGTH]); - let encrypted = &encrypted_bytes[SALT_LENGTH + IV_LENGTH..]; - let decrypted = aead.decrypt(nonce, encrypted); - if decrypted.is_err() { - return None; + if data.accounts.is_empty() && data.lookup.is_empty() { + write_accounts(vec![])?; + return Ok(()); } - Some(decrypted.unwrap()) + let accounts_bytes = bincode::serialize(&data.accounts).unwrap(); + let lookup_bytes = bincode::serialize(&data.lookup).unwrap(); + let accounts_and_lookup = AccountsAndLookup { + accounts_bytes, + lookup_bytes, + }; + let encoded: Vec = bincode::serialize(&accounts_and_lookup).unwrap(); + let encrypted_bytes = encrypt_bytes(&encoded, &data.password); + write_accounts(encrypted_bytes)?; + eprintln!("Saved accounts with password: {}", data.password); + Ok(()) } pub fn show_accounts(s: &mut Cursive) { @@ -320,9 +258,9 @@ fn select_account(s: &mut Cursive, _: &str) { let mut outer = Dialog::new() .h_align(HAlign::Center) - .button("Load", |s| { + .button("Load", move |s| { s.pop_layer(); - receive::load_receivables(s) + load_current_account(s); }) .button("Back", |s| { s.pop_layer(); @@ -369,6 +307,13 @@ fn select_account(s: &mut Cursive, _: &str) { } } +fn load_current_account(s: &mut Cursive) { + let messages = messages::load_messages(s); + let data = &mut s.user_data::().unwrap(); + eprintln!("Loaded messages: {:?}", messages); + data.acc_messages = messages; + receive::load_receivables(s); +} fn backup_account(s: &mut Cursive, origin: &str) { let data = &mut s.user_data::().unwrap(); let account = &data.accounts[data.acc_idx]; @@ -500,24 +445,43 @@ fn remove_account(s: &mut Cursive, idx: usize) { "If you have not backed up this account, it will be lost forever.", RED, ); - s.add_layer(Dialog::around(LinearLayout::vertical().child(DummyView).child(TextView::new(warning)).child(DummyView)) - .h_align(HAlign::Center) - .button("Back", |s| { - s.pop_layer(); - }) - .button("Confirm", move |s| { + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(warning)) + .child(DummyView), + ) + .h_align(HAlign::Center) + .button("Back", |s| { + s.pop_layer(); + }) + .button("Confirm", move |s| { let data = &mut s.user_data::().unwrap(); + let account = &data.accounts[idx]; + if data.lookup.contains_key(&account.address) { + let data_dir = dirs::data_dir().unwrap(); + let messages_dir = data_dir.join(DATA_DIR_PATH).join(MESSAGES_DIR_PATH); + let filename = format!("{}.dagchat", data.lookup.get(&account.address).unwrap()); + let messages_file = messages_dir.join(filename); + if !messages_file.exists() { + data.lookup.remove(&account.address); + } + } data.accounts.remove(idx); - let success = save_accounts(s); + let save_res = save_accounts(s); s.pop_layer(); s.pop_layer(); s.pop_layer(); show_accounts(s); - if !success { - s.add_layer(Dialog::info(StyledString::styled("Error saving accounts data. Account may not be removed upon relaunching dagchat.", RED))); + if save_res.is_err() { + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Error saving accounts data"), + ); } }) - .title("Confirm account deletion") + .title("Confirm account deletion"), ); } @@ -680,7 +644,7 @@ fn new_account(s: &mut Cursive) { let account = Account::from_mnemonic(Some(mnemonic.clone()), Some(seed_bytes), &data.coin.prefix); setup_account(s, account, move |s| { - new_success(s, mnemonic.clone(), hex::encode(seed_bytes)) + create_success(s, mnemonic.clone(), hex::encode(seed_bytes)) }); } @@ -691,18 +655,20 @@ where let data = &mut s.user_data::().unwrap(); data.accounts.push(account); data.acc_idx = data.accounts.len() - 1; - if data.accounts.len() == 1 { + + let data = &mut s.user_data::().unwrap(); + if data.accounts.len() == 1 && data.password.is_empty() { set_password(s, on_success); } else { - let success = save_accounts(s); - if success { + let save_res = save_accounts(s); + if save_res.is_ok() { on_success(s); } else { show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled( - "Error saving accounts data. Account may not remain upon relaunching dagchat.", - RED, - ))); + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Error saving accounts data"), + ); } } } @@ -742,18 +708,23 @@ where s.add_layer(Dialog::info("Passwords did not match.")); return; } + let msg_save_res = messages::change_message_passwords(s, &password); let data = &mut s.user_data::().unwrap(); data.password = password.to_string(); - let success = save_accounts(s); + let acc_save_res = save_accounts(s); s.pop_layer(); - if success { + if acc_save_res.is_ok() && msg_save_res.is_ok() { on_success(s); - } else { + } else if acc_save_res.is_err() { show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled( - "Error saving accounts data. Account may not remain upon relaunching dagchat.", + s.add_layer(Dialog::info(StyledString::styled(acc_save_res.err().unwrap(), RED, - ))); + )).title("Fatal error saving accounts")); + } else if msg_save_res.is_err() { + show_accounts(s); + s.add_layer(Dialog::info(StyledString::styled(msg_save_res.err().unwrap(), + RED, + )).title("Fatal error saving messages")); } }) .button("Info", |s| { @@ -767,12 +738,12 @@ where fn import_success(s: &mut Cursive, content: &str) { s.add_layer( Dialog::around(TextView::new(content).max_width(80)) - .button("Load", |s| receive::load_receivables(s)) + .button("Load", |s| load_current_account(s)) .button("Back", |s| show_accounts(s)), ); } -fn new_success(s: &mut Cursive, mnemonic: String, seed: String) { +fn create_success(s: &mut Cursive, mnemonic: String, seed: String) { let data = &mut s.user_data::().unwrap(); let mut content = StyledString::styled("\nMnemonic\n", data.coin.colour); content.append(StyledString::styled(&mnemonic, OFF_WHITE)); @@ -781,7 +752,7 @@ fn new_success(s: &mut Cursive, mnemonic: String, seed: String) { s.add_layer( Dialog::around(TextView::new(content).max_width(80)) .h_align(HAlign::Center) - .button("Load", |s| receive::load_receivables(s)) + .button("Load", |s| load_current_account(s)) .button("Back", |s| show_accounts(s)) .button("Copy mnemonic", move |s| copy_to_clip(s, mnemonic.clone())) .button("Copy seed", move |s| copy_to_clip(s, seed.clone())) diff --git a/src/dcutil.rs b/src/dcutil.rs index 0fe44f2..a26a4a9 100644 --- a/src/dcutil.rs +++ b/src/dcutil.rs @@ -1,3 +1,6 @@ +use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead}; +use aes_gcm::{Aes256Gcm, Nonce}; +use argon2::{self, Config}; use bigdecimal::BigDecimal; use bitreader::BitReader; use blake2::digest::{Update, VariableOutput}; @@ -6,6 +9,7 @@ use data_encoding::Encoding; use data_encoding_macro::new_encoding; use ecies_ed25519; use ed25519_dalek; +use rand::RngCore; use serde; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -13,8 +17,9 @@ use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::str; use std::str::FromStr; + // Will do something dynamic with reps in future -use crate::defaults; +use crate::defaults::*; // Used to update progress bar in cursive app // Each counter has 1000 ticks @@ -165,7 +170,7 @@ pub fn send_message( node_url: &str, addr_prefix: &str, counter: &Counter, -) { +) -> String { let public_key_bytes = to_public_key(&target_address); let mut message = message.clone(); let pad = (message.len() + 28) % 32; @@ -258,6 +263,7 @@ pub fn send_message( &addr_prefix, ); publish_block(block, sub.clone(), node_url); + hex::encode(last_block_hash) } pub fn change_rep( @@ -307,9 +313,9 @@ pub fn receive_block( if account_info_opt.is_none() { // OPEN BLOCK if addr_prefix == "nano_" { - representative = to_public_key(defaults::DEFAULT_REP_NANO); + representative = to_public_key(DEFAULT_REP_NANO); } else if addr_prefix == "ban_" { - representative = to_public_key(defaults::DEFAULT_REP_BANANO); + representative = to_public_key(DEFAULT_REP_BANANO); } else { panic!("Unknown network... no default rep to open account."); } @@ -674,6 +680,53 @@ pub fn validate_mnemonic(mnemonic: &str) -> Option<[u8; 32]> { Some(entropy) } +pub fn derive_key(password: &str, salt: &[u8]) -> Vec { + let config = Config::default(); + let hash = argon2::hash_raw(password.as_bytes(), salt, &config).unwrap(); + hash +} + +pub fn decrypt_bytes(encrypted_bytes: &Vec, password: &str) -> Result, String> { + let salt = &encrypted_bytes[..SALT_LENGTH]; + + let key_bytes = derive_key(password, salt); + let key = GenericArray::from_slice(&key_bytes); + + let aead = Aes256Gcm::new(&key); + let nonce = Nonce::from_slice(&encrypted_bytes[SALT_LENGTH..SALT_LENGTH + IV_LENGTH]); + let encrypted = &encrypted_bytes[SALT_LENGTH + IV_LENGTH..]; + let decrypted = aead.decrypt(nonce, encrypted); + match decrypted { + Ok(decrypted) => { + return Ok(decrypted); + } + Err(e) => { + return Err(format!("Failed to decrypt bytes. Error: {}", e)); + } + } +} + +pub fn encrypt_bytes(bytes: &Vec, password: &str) -> Vec { + let mut csprng = rand::thread_rng(); + let mut salt = [0u8; SALT_LENGTH]; + csprng.fill_bytes(&mut salt); + let key_bytes = derive_key(password, &salt); + let key = GenericArray::from_slice(&key_bytes); + + let aead = Aes256Gcm::new(key); + + let mut nonce_bytes = [0u8; IV_LENGTH]; + csprng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + + let ciphertext = aead.encrypt(nonce, &bytes[..]).unwrap(); + let mut encrypted_bytes = Vec::with_capacity(SALT_LENGTH + IV_LENGTH + ciphertext.len()); + encrypted_bytes.extend(salt); + encrypted_bytes.extend(nonce); + encrypted_bytes.extend(ciphertext); + encrypted_bytes +} + pub fn get_private_key(seed_bytes: &[u8; 32], idx: u32) -> [u8; 32] { let mut hasher = Blake2bVar::new(32).unwrap(); let mut buf = [0u8; 32]; diff --git a/src/defaults.rs b/src/defaults.rs index 468990c..72cd809 100644 --- a/src/defaults.rs +++ b/src/defaults.rs @@ -3,3 +3,9 @@ pub const DEFAULT_REP_BANANO: &str = pub const DEFAULT_REP_NANO: &str = "nano_3zx7rus19yr5qi5zmkawnzo5ehxr7i73xqghhondhfrzftgstgk4gxbubwfq"; pub const SHOW_TO_DP: usize = 6; +pub const DATA_DIR_PATH: &str = "dagchat-beta"; +pub const MESSAGES_DIR_PATH: &str = "messages"; +pub const ACCOUNTS_PATH: &str = "accounts.dagchat"; +pub const ADDRESS_BOOK_PATH: &str = "addressbook.dagchat"; +pub const SALT_LENGTH: usize = 16; +pub const IV_LENGTH: usize = 12; diff --git a/src/main.rs b/src/main.rs index f31147d..171c919 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,20 +10,22 @@ use cursive::views::{ }; use cursive::Cursive; use dirs; -use rand::RngCore; + use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; pub mod defaults; -use defaults::SHOW_TO_DP; - +use defaults::{ACCOUNTS_PATH, DATA_DIR_PATH, MESSAGES_DIR_PATH, SHOW_TO_DP}; // dagchat util mod dcutil; use dcutil::*; -// dagchat accounts util +// dagchat accounts and messages util mod accounts; +mod messages; +use messages::SavedMessage; // send and receive with dagchat mod receive; @@ -33,8 +35,10 @@ pub struct UserData { pub password: String, pub accounts: Vec, pub acc_idx: usize, + pub acc_messages: Result, String>, + pub lookup: HashMap, pub coin: Coin, - pub encrypted_accounts: Vec, + pub encrypted_bytes: Vec, } #[derive(Debug, Clone)] @@ -76,8 +80,10 @@ impl UserData { password: String::from(""), accounts: vec![], acc_idx: 0, + lookup: HashMap::new(), + acc_messages: Ok(vec![]), coin: Coin::nano(), - encrypted_accounts: vec![], + encrypted_bytes: vec![], } } } @@ -101,8 +107,6 @@ fn main() { let mut siv = cursive::default(); siv.set_window_title(format!("dagchat {}", VERSION)); - let data = UserData::new(); - siv.set_user_data(data); show_title(&mut siv); @@ -110,6 +114,9 @@ fn main() { } fn show_title(s: &mut Cursive) { + let data = UserData::new(); + s.set_user_data(data); + set_theme(s, "nano", false); let mut theme_group: RadioGroup = RadioGroup::new(); @@ -137,7 +144,7 @@ fn show_title(s: &mut Cursive) { data.coin = Coin::banano(); }); } - accounts::check_setup(s); + check_setup(s); }); s.add_layer( @@ -154,9 +161,6 @@ fn show_title(s: &mut Cursive) { ); } -// Functions below are used across main.rs, send.rs, -// receive.rs and accounts.rs - fn go_back(s: &mut Cursive) { s.pop_layer(); } @@ -172,19 +176,41 @@ fn get_subtitle_colour(s: &mut Cursive) -> Color { sub_title_colour } -/* -fn alpha_info(s: &mut Cursive) { - s.pop_layer(); - let data = &s.user_data::().unwrap(); - let mut info = StyledString::styled("Information for alpha testers:\n", data.coin.colour); - info.append(StyledString::styled("This is the inbox. Messages sent to you with 1 raw have already been identified as messages, but messages sent with an arbitrary amount will not yet have been detected. Select 'Find messages' from the buttons on the right to scan your list of receivables and identify these.", OFF_WHITE)); - s.add_layer( - Dialog::around(TextView::new(info)) - .button("Go to inbox", |s| receive::load_receivables(s)) - .max_width(60), - ); +fn check_setup(s: &mut Cursive) { + if let Some(data_dir) = dirs::data_dir() { + let dagchat_dir = data_dir.join(DATA_DIR_PATH); + let messages_dir = dagchat_dir.join(MESSAGES_DIR_PATH); + if !dagchat_dir.exists() { + fs::create_dir(&dagchat_dir).unwrap_or_else(|e| { + let content = format!( + "Failed to create a data folder for dagchat at path: {:?}\nError: {}", + dagchat_dir, e + ); + s.add_layer(Dialog::info(content)) + }); + if !dagchat_dir.exists() { + return; + } + } + if !messages_dir.exists() { + fs::create_dir(&messages_dir).unwrap_or_else(|e| { + let content = format!( + "Failed to create a messages folder for dagchat at path: {:?}\nError: {}", + messages_dir, e + ); + s.add_layer(Dialog::info(content)) + }); + if !messages_dir.exists() { + return; + } + } + accounts::load_accounts(s, dagchat_dir); + } else { + s.add_layer(Dialog::info( + "Error locating the application data folder on your system.", + )); + } } -*/ fn show_change_rep(s: &mut Cursive) { s.pop_layer(); diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..feec7b0 --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,133 @@ +use super::*; +use rand::RngCore; + +#[derive(Serialize, Deserialize, Debug)] +pub struct SavedMessage { + // If false, was incoming + pub outgoing: bool, + pub address: String, + pub timestamp: u64, + pub amount: String, + pub plaintext: String, + pub hash: String, +} + +pub fn create_key(s: &mut Cursive) -> String { + let data = &mut s.user_data::().unwrap(); + let address = data.accounts[data.acc_idx].address.clone(); + let mut csprng = rand::thread_rng(); + let mut random_id = [0u8; 32]; + csprng.fill_bytes(&mut random_id); + eprintln!("{} : {}", address, hex::encode(random_id)); + data.lookup.insert(address, hex::encode(random_id)); + hex::encode(random_id) +} + +pub fn load_messages(s: &mut Cursive) -> Result, String> { + let data = &mut s.user_data::().unwrap(); + let mut messages: Vec = vec![]; + let lookup_key = match data.lookup.get(&data.accounts[data.acc_idx].address) { + Some(id) => id.to_owned(), + None => create_key(s), + }; + let data = &mut s.user_data::().unwrap(); + let data_dir = dirs::data_dir().unwrap(); + let filename = format!("{}.dagchat", lookup_key); + let messages_file = data_dir + .join(DATA_DIR_PATH) + .join(MESSAGES_DIR_PATH) + .join(filename); + if messages_file.exists() { + let mut error = String::from(""); + let encrypted_bytes = fs::read(&messages_file).unwrap_or_else(|e| { + error = format!( + "Failed to read messages file at path: {:?}\nError: {}", + messages_file, e + ); + vec![] + }); + if !error.is_empty() { + return Err(error); + } + if encrypted_bytes.is_empty() { + return Ok(vec![]); + } + let bytes = dcutil::decrypt_bytes(&encrypted_bytes, &data.password); + let messages_opt = bincode::deserialize(&bytes.unwrap()[..]); + if messages_opt.is_err() { + let error = format!( + "Failed to decode messages from file at path: {:?}", + messages_file + ); + return Err(error); + } + messages = messages_opt.unwrap(); + } + + Ok(messages) +} + +pub fn save_messages(s: &mut Cursive) -> Result<(), String> { + let data = &mut s.user_data::().unwrap(); + let data_dir = dirs::data_dir().unwrap(); + let messages_dir = data_dir.join(DATA_DIR_PATH).join(MESSAGES_DIR_PATH); + let address = &data.accounts[data.acc_idx].address; + let lookup_key = match data.lookup.get(address) { + Some(id) => id.to_owned(), + None => create_key(s), + }; + let data = &mut s.user_data::().unwrap(); + let messages_file = messages_dir.join(format!("{}.dagchat", lookup_key)); + let messages_bytes = bincode::serialize(data.acc_messages.as_ref().unwrap()).unwrap(); + let encrypted_bytes = encrypt_bytes(&messages_bytes, &data.password); + let write_res = fs::write(&messages_file, encrypted_bytes); + if write_res.is_err() { + return Err(format!( + "Failed to write to messages file at path: {:?}\nError: {:?}", + messages_file, + write_res.err() + )); + } + eprintln!("Saved messages with password: {}", data.password); + Ok(()) +} + +pub fn change_message_passwords(s: &mut Cursive, new_password: &str) -> Result<(), String> { + let data = &mut s.user_data::().unwrap(); + let data_dir = dirs::data_dir().unwrap(); + let messages_dir = data_dir.join(DATA_DIR_PATH).join(MESSAGES_DIR_PATH); + + for (a, lookup_key) in data.lookup.iter() { + let filename = format!("{}.dagchat", lookup_key); + let messages_file = messages_dir.join(filename); + if messages_file.exists() { + eprintln!("Changing file password for {}", a); + let mut error = String::from(""); + let encrypted_bytes = fs::read(&messages_file).unwrap_or_else(|e| { + error = format!( + "Failed to read messages file at path: {:?}\nError: {}", + messages_file, e + ); + vec![] + }); + if encrypted_bytes.is_empty() { + continue; + } + let decrypted_bytes = decrypt_bytes(&encrypted_bytes, &data.password)?; + let reencrypted_bytes = encrypt_bytes(&decrypted_bytes, new_password); + let write_res = fs::write(&messages_file, reencrypted_bytes); + if write_res.is_err() { + return Err(format!( + "Failed to write to messages file at path: {:?}\nError: {:?}", + messages_file, + write_res.err() + )); + } + } + } + eprintln!( + "Saved and changed all new messages with password: {}", + new_password + ); + Ok(()) +} diff --git a/src/receive.rs b/src/receive.rs index 03829a9..e47d1a6 100644 --- a/src/receive.rs +++ b/src/receive.rs @@ -1,5 +1,7 @@ use super::*; +use std::time::SystemTime; + pub fn show_message_info(s: &mut Cursive, _name: &str) { let select = s.find_name::>("select").unwrap(); match select.selected_id() { @@ -112,8 +114,10 @@ pub fn load_receivables(s: &mut Cursive) { fn process_receive(s: &mut Cursive, idx: usize) { let data = &s.user_data::().unwrap(); let private_key = data.accounts[data.acc_idx].private_key; - let send_block_hash = data.accounts[data.acc_idx].receivables[idx].hash.clone(); - let amount = data.accounts[data.acc_idx].receivables[idx].amount; + let receivable = &data.accounts[data.acc_idx].receivables[idx]; + let send_block_hash = receivable.hash.clone(); + + let amount = receivable.amount; let address = data.accounts[data.acc_idx].address.clone(); let prefix = data.coin.prefix.clone(); let node_url = data.coin.node_url.clone(); @@ -138,6 +142,34 @@ fn process_receive(s: &mut Cursive, idx: usize) { select.remove_item(idx); let mut balance = s.find_name::("balance").unwrap(); let data = &mut s.user_data::().unwrap(); + let receivable = &data.accounts[data.acc_idx].receivables[idx]; + let send_block_hash = receivable.hash.clone(); + let amount = receivable.amount; + let has_message = { receivable.message.is_some() }; + + let mut save_res = Ok(()); + if has_message { + data.acc_messages.as_mut().unwrap().push(SavedMessage { + outgoing: false, + address: receivable.source.clone(), + timestamp: match SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + { + Ok(n) => n.as_secs() as u64, + Err(_) => 0u64, + }, + amount: display_to_dp( + amount, + SHOW_TO_DP, + &data.coin.multiplier, + &data.coin.ticker, + ), + hash: send_block_hash.clone(), + plaintext: receivable.message.as_ref().unwrap().plaintext.clone(), + }); + save_res = messages::save_messages(s); + } + let data = &mut s.user_data::().unwrap(); let account = &mut data.accounts[data.acc_idx]; account.receivables.remove(idx); account.balance += amount; @@ -151,6 +183,12 @@ fn process_receive(s: &mut Cursive, idx: usize) { balance.set_content(StyledString::styled(bal_text, data.coin.colour)); s.pop_layer(); s.pop_layer(); + if save_res.is_err() { + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Failed to save messages"), + ); + } })) .unwrap(); }) diff --git a/src/send.rs b/src/send.rs index 63a45e6..4c4adae 100644 --- a/src/send.rs +++ b/src/send.rs @@ -1,5 +1,7 @@ use super::*; +use std::time::SystemTime; + pub fn show_send(s: &mut Cursive, with_message: bool) { s.pop_layer(); @@ -195,30 +197,60 @@ fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { .range(0, ticks) .with_task(move |counter| { let with_message = !message.is_empty(); + let mut hash = String::from(""); if !with_message { send( &private_key_bytes, - address, + address.clone(), raw, &node_url, &prefix, &counter, ); } else { - send_message( + hash = send_message( &private_key_bytes, - address, + address.clone(), raw, - message, + message.clone(), &node_url, &prefix, &counter, ); } cb.send(Box::new(move |s| { + let mut save_res = Ok(()); + let data = &mut s.user_data::().unwrap(); + if with_message { + data.acc_messages.as_mut().unwrap().push(SavedMessage { + outgoing: false, + address: address.clone(), + timestamp: match SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + { + Ok(n) => n.as_secs() as u64, + Err(_) => 0u64, + }, + amount: display_to_dp( + raw, + SHOW_TO_DP, + &data.coin.multiplier, + &data.coin.ticker, + ), + hash: hash, + plaintext: message.clone(), + }); + save_res = messages::save_messages(s); + } let data = &mut s.user_data::().unwrap(); data.accounts[data.acc_idx].balance -= raw; show_sent(s, with_message); + if save_res.is_err() { + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Failed to save messages"), + ); + } })) .unwrap(); }) From fc75755bb51fdda03a79ba1e57949cee00707790 Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Sat, 23 Apr 2022 14:02:21 +0100 Subject: [PATCH 07/12] Added message viewing --- Cargo.toml | 1 + src/accounts.rs | 4 +-- src/main.rs | 1 + src/messages.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++----- src/receive.rs | 2 +- src/send.rs | 6 ++--- 6 files changed, 69 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ddbed0..fdd0343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ cursive_buffered_backend = "0.6.0" dirs = "4.0.0" rust-argon2 = "1.0.0" bincode = "1.3.3" +chrono = "0.4.19" aes-gcm = "0.9.4" bitreader = "0.3.6" sha2 = "0.10.1" diff --git a/src/accounts.rs b/src/accounts.rs index bdae276..acb2067 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -178,7 +178,7 @@ pub fn save_accounts(s: &mut Cursive) -> Result<(), String> { let encoded: Vec = bincode::serialize(&accounts_and_lookup).unwrap(); let encrypted_bytes = encrypt_bytes(&encoded, &data.password); write_accounts(encrypted_bytes)?; - eprintln!("Saved accounts with password: {}", data.password); + //eprintln!("Saved accounts with password: {}", data.password); Ok(()) } @@ -310,7 +310,7 @@ fn select_account(s: &mut Cursive, _: &str) { fn load_current_account(s: &mut Cursive) { let messages = messages::load_messages(s); let data = &mut s.user_data::().unwrap(); - eprintln!("Loaded messages: {:?}", messages); + //eprintln!("Loaded messages: {:?}", messages); data.acc_messages = messages; receive::load_receivables(s); } diff --git a/src/main.rs b/src/main.rs index 171c919..859dbd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -310,6 +310,7 @@ fn show_inbox(s: &mut Cursive) { let send_label = format!("Send {}", data.coin.name); let buttons = LinearLayout::vertical() .child(Button::new("Refresh", |s| receive::load_receivables(s))) + .child(Button::new("Messages", |s| messages::view_messages(s))) .child(DummyView) .child(Button::new(send_label, |s| send::show_send(s, false))) .child(Button::new("Send message", |s| send::show_send(s, true))) diff --git a/src/messages.rs b/src/messages.rs index feec7b0..10a12eb 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,4 +1,6 @@ use super::*; +use chrono::prelude::DateTime; +use chrono::{Local, NaiveDateTime, Utc}; use rand::RngCore; #[derive(Serialize, Deserialize, Debug)] @@ -18,11 +20,63 @@ pub fn create_key(s: &mut Cursive) -> String { let mut csprng = rand::thread_rng(); let mut random_id = [0u8; 32]; csprng.fill_bytes(&mut random_id); - eprintln!("{} : {}", address, hex::encode(random_id)); + //eprintln!("{} : {}", address, hex::encode(random_id)); data.lookup.insert(address, hex::encode(random_id)); hex::encode(random_id) } +pub fn view_messages(s: &mut Cursive) { + let data = &mut s.user_data::().unwrap(); + let messages = &data.acc_messages; + if messages.is_err() { + let err_msg = messages.as_ref().err().unwrap().clone(); + s.add_layer(Dialog::info(err_msg)); + return; + } + + let mut output = StyledString::new(); + for message in messages.as_ref().unwrap().iter().rev() { + let datetime: DateTime = DateTime::from(DateTime::::from_utc( + NaiveDateTime::from_timestamp(message.timestamp as i64, 0), + Utc, + )); + + let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S").to_string(); + let a: &str; + let b: &str; + if message.outgoing { + a = "Sent"; + b = "To:"; + } else { + a = "Received"; + b = "From:"; + } + let mut message_info = StyledString::styled(format!("{} at: ", a), OFF_WHITE); + message_info.append(StyledString::styled(timestamp_str, data.coin.colour)); + message_info.append(StyledString::styled(format!("\n{} ", b), OFF_WHITE)); + message_info.append(StyledString::styled(&message.address, data.coin.colour)); + if !message.plaintext.is_empty() { + message_info.append(StyledString::styled("\nMessage: ", OFF_WHITE)); + message_info.append(StyledString::styled(&message.plaintext, data.coin.colour)); + } + message_info.append(StyledString::styled("\nAmount: ", OFF_WHITE)); + message_info.append(StyledString::styled( + format!("{}\n\n", message.amount), + data.coin.colour, + )); + output.append(message_info); + } + s.add_layer( + Dialog::around( + TextView::new(output) + .scrollable() + .max_width(73) + .max_height(10), + ) + .button("Back", |s| go_back(s)) + .title("Message history"), + ); +} pub fn load_messages(s: &mut Cursive) -> Result, String> { let data = &mut s.user_data::().unwrap(); let mut messages: Vec = vec![]; @@ -88,7 +142,7 @@ pub fn save_messages(s: &mut Cursive) -> Result<(), String> { write_res.err() )); } - eprintln!("Saved messages with password: {}", data.password); + //eprintln!("Saved messages with password: {}", data.password); Ok(()) } @@ -101,7 +155,7 @@ pub fn change_message_passwords(s: &mut Cursive, new_password: &str) -> Result<( let filename = format!("{}.dagchat", lookup_key); let messages_file = messages_dir.join(filename); if messages_file.exists() { - eprintln!("Changing file password for {}", a); + //eprintln!("Changing file password for {}", a); let mut error = String::from(""); let encrypted_bytes = fs::read(&messages_file).unwrap_or_else(|e| { error = format!( @@ -125,9 +179,9 @@ pub fn change_message_passwords(s: &mut Cursive, new_password: &str) -> Result<( } } } - eprintln!( - "Saved and changed all new messages with password: {}", - new_password - ); + //eprintln!( + // "Saved and changed all new messages with password: {}", + // new_password + //); Ok(()) } diff --git a/src/receive.rs b/src/receive.rs index e47d1a6..42e70e4 100644 --- a/src/receive.rs +++ b/src/receive.rs @@ -155,7 +155,7 @@ fn process_receive(s: &mut Cursive, idx: usize) { timestamp: match SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) { - Ok(n) => n.as_secs() as u64, + Ok(n) => n.as_secs(), Err(_) => 0u64, }, amount: display_to_dp( diff --git a/src/send.rs b/src/send.rs index 4c4adae..3402e91 100644 --- a/src/send.rs +++ b/src/send.rs @@ -223,12 +223,12 @@ fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { let data = &mut s.user_data::().unwrap(); if with_message { data.acc_messages.as_mut().unwrap().push(SavedMessage { - outgoing: false, + outgoing: true, address: address.clone(), timestamp: match SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) { - Ok(n) => n.as_secs() as u64, + Ok(n) => n.as_secs(), Err(_) => 0u64, }, amount: display_to_dp( @@ -237,7 +237,7 @@ fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { &data.coin.multiplier, &data.coin.ticker, ), - hash: hash, + hash, plaintext: message.clone(), }); save_res = messages::save_messages(s); From df165c611232d457d6b5aef182efb8ab95cc43c7 Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Sat, 23 Apr 2022 17:58:24 +0100 Subject: [PATCH 08/12] Add messages history filter and bug fixes 1. Added filter options for message history - search button coming soon 2. Fixed bug parsing node response with message detecting refactor 3. Fixed bug causing messages not to appear to be saved - explicitly save accounts upon creating new accounts lookup entry --- .gitignore | 2 + Cargo.lock | 2466 +++++++++++++++++++++++++++++++++++++++++++++++ src/dcutil.rs | 11 + src/main.rs | 5 +- src/messages.rs | 132 ++- src/receive.rs | 2 +- src/send.rs | 2 +- 7 files changed, 2607 insertions(+), 13 deletions(-) create mode 100644 .gitignore create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55a9e37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +stderr.txt diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e6d4f36 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2466 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher 0.2.5", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" +dependencies = [ + "aead 0.3.2", + "aes 0.6.0", + "cipher 0.2.5", + "ctr 0.6.0", + "ghash 0.3.1", + "subtle", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.8.0", + "ghash 0.4.4", + "subtle", +] + +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher 0.2.5", + "opaque-debug", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher 0.2.5", + "opaque-debug", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.6", + "once_cell", + "version_check", +] + +[[package]] +name = "arboard" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6045ca509e4abacde2b884ac4618a51d0c017b5d85a3ee84a7226eb33b3154a9" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc", + "objc-foundation", + "objc_id", + "once_cell", + "parking_lot 0.12.0", + "thiserror", + "winapi", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitreader" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d84ea71c85d1fe98fe67a9b9988b1695bc24c0b0d3bfb18d4c510f44b4b09941" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "bytemuck" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "clipboard-win" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "cpuid-bool" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "crossterm" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio 0.7.14", + "parking_lot 0.11.2", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio 0.8.2", + "parking_lot 0.12.0", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +dependencies = [ + "cipher 0.2.5", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "cursive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca536d245342f6c005e7547ab640e444a3dc2fc0319a92124c8c1cbff025e775" +dependencies = [ + "ahash", + "cfg-if", + "crossbeam-channel", + "crossterm 0.22.1", + "cursive_core", + "lazy_static", + "libc", + "log", + "signal-hook", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "cursive_buffered_backend" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7725a87b0a5240427917d645d279669fa8bb523cd920d2c442da7ef8b769d" +dependencies = [ + "cursive_core", + "enumset", + "log", + "smallvec", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "cursive_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27fbda42833e46148ff28db338f6189a4407e4a38ba1f4105e2f623089e66a0" +dependencies = [ + "ahash", + "crossbeam-channel", + "enum-map", + "enumset", + "lazy_static", + "log", + "num", + "owning_ref", + "time 0.3.9", + "unicode-segmentation", + "unicode-width", + "xi-unicode", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "dagchat" +version = "1.0.0" +dependencies = [ + "aes-gcm 0.9.4", + "arboard", + "bigdecimal", + "bincode", + "bitreader", + "blake2 0.10.4", + "chrono", + "crossterm 0.23.2", + "cursive", + "cursive_buffered_backend", + "data-encoding", + "data-encoding-macro", + "dirs", + "ecies-ed25519", + "ed25519-dalek-blake2-feeless", + "hex 0.3.2", + "num_cpus", + "rand", + "reqwest", + "rust-argon2", + "serde", + "serde_json", + "sha2 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "data-encoding-macro" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" +dependencies = [ + "data-encoding", + "syn", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "ecies-ed25519" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a882353828f086290bedd0b598e18863a4d2135f8e632c4325ed89a4e5535db" +dependencies = [ + "aes-gcm 0.8.0", + "curve25519-dalek", + "digest 0.9.0", + "hex 0.4.3", + "hkdf", + "rand", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek-blake2-feeless" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7669f21c19267763527d701e801c02b89df11aacbf6e4ce96e3314587bd8d8d" +dependencies = [ + "blake2 0.9.2", + "curve25519-dalek", + "ed25519", + "rand", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-map" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348b2a57c82f98b9dbd8098b1abb2416f221823d3e50cbe24eaebdd16896826" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a63b7a0ddec6f38dcec5e36257750b7a8fcaf4227e12ceb306e341d63634da05" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumset" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" +dependencies = [ + "opaque-debug", + "polyval 0.4.5", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval 0.5.3", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" +dependencies = [ + "digest 0.9.0", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac 0.10.1", + "digest 0.9.0", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6330e8a36bd8c859f3fa6d9382911fbb7147ec39807f63b923933a247240b9ba" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational 0.3.2", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "mio" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.4.0", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.2", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "polyval" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" +dependencies = [ + "cpuid-bool", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.6", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rust-argon2" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "signal-hook" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio 0.7.14", + "mio 0.8.2", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "str-buf" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "itoa", + "libc", + "num_threads", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio 0.8.2", + "num_cpus", + "pin-project-lite", + "socket2", + "winapi", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "x11rb" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e99be55648b3ae2a52342f9a870c0e138709a3493261ce9b469afe6e4df6d8a" +dependencies = [ + "gethostname", + "nix", + "winapi", + "winapi-wsapoll", +] + +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/src/dcutil.rs b/src/dcutil.rs index a26a4a9..ebe5caa 100644 --- a/src/dcutil.rs +++ b/src/dcutil.rs @@ -492,7 +492,18 @@ pub fn get_blocks_info(hashes: Vec, node_url: &str) -> BlocksInfoRespons include_not_found: true, }; let body = serde_json::to_string(&request).unwrap(); + //eprintln!("Body: {}", body); let response = post_node(body, node_url); + + // Somewhat hacky way to catch this error. Would be better off implementing + // some proper serde deserialisation with untagged flag. + if response.contains("\"blocks\": \"\"") { + return BlocksInfoResponse { + blocks: BlocksResponse { + data: HashMap::new(), + }, + }; + } let blocks_info_response: BlocksInfoResponse = serde_json::from_str(&response).unwrap(); blocks_info_response } diff --git a/src/main.rs b/src/main.rs index 859dbd4..78c5e58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -310,7 +310,10 @@ fn show_inbox(s: &mut Cursive) { let send_label = format!("Send {}", data.coin.name); let buttons = LinearLayout::vertical() .child(Button::new("Refresh", |s| receive::load_receivables(s))) - .child(Button::new("Messages", |s| messages::view_messages(s))) + .child(Button::new("Messages", |s| { + let filter: messages::Filter = Default::default(); + messages::view_messages(s, filter); + })) .child(DummyView) .child(Button::new(send_label, |s| send::show_send(s, false))) .child(Button::new("Send message", |s| send::show_send(s, true))) diff --git a/src/messages.rs b/src/messages.rs index 10a12eb..13bd7a9 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -14,7 +14,26 @@ pub struct SavedMessage { pub hash: String, } -pub fn create_key(s: &mut Cursive) -> String { +#[derive(Debug, Clone)] +pub struct Filter { + pub incoming: bool, + pub outgoing: bool, + pub gt_1_raw: bool, + pub eq_1_raw: bool, +} + +impl Default for Filter { + fn default() -> Filter { + Filter { + incoming: true, + outgoing: true, + gt_1_raw: true, + eq_1_raw: true, + } + } +} + +pub fn create_key(s: &mut Cursive) -> Result { let data = &mut s.user_data::().unwrap(); let address = data.accounts[data.acc_idx].address.clone(); let mut csprng = rand::thread_rng(); @@ -22,20 +41,101 @@ pub fn create_key(s: &mut Cursive) -> String { csprng.fill_bytes(&mut random_id); //eprintln!("{} : {}", address, hex::encode(random_id)); data.lookup.insert(address, hex::encode(random_id)); - hex::encode(random_id) + accounts::save_accounts(s)?; + Ok(hex::encode(random_id)) } -pub fn view_messages(s: &mut Cursive) { +pub fn edit_filter(s: &mut Cursive, filter: Filter) { + let mut message_dir: RadioGroup = RadioGroup::new(); + let mut message_amount: RadioGroup = RadioGroup::new(); + + let data = &mut s.user_data::().unwrap(); + + let content = LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(StyledString::styled( + "Message type", + OFF_WHITE, + ))) + .child( + LinearLayout::horizontal() + .child(message_dir.button(0, "Both").selected()) + .child(DummyView) + .child(message_dir.button(1, "Sent")) + .child(DummyView) + .child(message_dir.button(2, "Received")), + ) + .child(DummyView) + .child(TextView::new(StyledString::styled( + "Message amount", + OFF_WHITE, + ))) + .child( + LinearLayout::horizontal() + .child(message_amount.button(0, "Both").selected()) + .child(DummyView) + .child(message_amount.button(1, "1 RAW")) + .child(DummyView) + .child(message_amount.button(2, format!("Custom {}", data.coin.ticker.trim()))), + ); + s.add_layer( + Dialog::around(content) + .h_align(HAlign::Center) + .button("Apply", move |s| { + let mut filter = filter.clone(); + let dir = message_dir.selection(); + let amount = message_amount.selection(); + if *dir == 0 { + filter.outgoing = true; + filter.outgoing = true; + } else if *dir == 1 { + filter.outgoing = true; + filter.incoming = false; + } else if *dir == 2 { + filter.outgoing = false; + filter.incoming = true; + } + if *amount == 0 { + filter.eq_1_raw = true; + filter.gt_1_raw = true; + } else if *amount == 1 { + filter.eq_1_raw = true; + filter.gt_1_raw = false; + } else if *amount == 2 { + filter.eq_1_raw = false; + filter.gt_1_raw = true; + } + s.pop_layer(); + s.pop_layer(); + view_messages(s, filter); + }) + .title("Filter setup"), + ); +} + +pub fn view_messages(s: &mut Cursive, filter: Filter) { let data = &mut s.user_data::().unwrap(); let messages = &data.acc_messages; if messages.is_err() { let err_msg = messages.as_ref().err().unwrap().clone(); s.add_layer(Dialog::info(err_msg)); return; + } else if messages.as_ref().unwrap().is_empty() { + s.add_layer(Dialog::info( + "You haven't sent or received any messages yet with dagchat!", + )); + return; } let mut output = StyledString::new(); for message in messages.as_ref().unwrap().iter().rev() { + if (message.outgoing && !filter.outgoing) + || (!message.outgoing && !filter.incoming) + || (message.amount == "1 RAW" && !filter.eq_1_raw) + || (message.amount != "1 RAW" && !filter.gt_1_raw) + { + continue; + } let datetime: DateTime = DateTime::from(DateTime::::from_utc( NaiveDateTime::from_timestamp(message.timestamp as i64, 0), Utc, @@ -68,21 +168,33 @@ pub fn view_messages(s: &mut Cursive) { } s.add_layer( Dialog::around( - TextView::new(output) - .scrollable() - .max_width(73) - .max_height(10), + LinearLayout::vertical() + .child( + LinearLayout::horizontal() + .child(Button::new("Filter", move |s| { + edit_filter(s, filter.clone()) + })) + .child(DummyView) + .child(Button::new("Back", |s| go_back(s))), + ) + .child(DummyView) + .child( + TextView::new(output) + .scrollable() + .max_width(73) + .max_height(10), + ), ) - .button("Back", |s| go_back(s)) .title("Message history"), ); } + pub fn load_messages(s: &mut Cursive) -> Result, String> { let data = &mut s.user_data::().unwrap(); let mut messages: Vec = vec![]; let lookup_key = match data.lookup.get(&data.accounts[data.acc_idx].address) { Some(id) => id.to_owned(), - None => create_key(s), + None => create_key(s)?, }; let data = &mut s.user_data::().unwrap(); let data_dir = dirs::data_dir().unwrap(); @@ -128,7 +240,7 @@ pub fn save_messages(s: &mut Cursive) -> Result<(), String> { let address = &data.accounts[data.acc_idx].address; let lookup_key = match data.lookup.get(address) { Some(id) => id.to_owned(), - None => create_key(s), + None => create_key(s)?, }; let data = &mut s.user_data::().unwrap(); let messages_file = messages_dir.join(format!("{}.dagchat", lookup_key)); diff --git a/src/receive.rs b/src/receive.rs index 42e70e4..eb3005a 100644 --- a/src/receive.rs +++ b/src/receive.rs @@ -15,7 +15,7 @@ pub fn show_message_info(s: &mut Cursive, _name: &str) { let plaintext: String; let mut content = LinearLayout::vertical(); - let mut title = format!("{} Receivable", &data.coin.ticker); + let mut title = format!("{} Receivable", &data.coin.ticker.trim()); let mut receive_label = String::from(""); if receivable.message.is_some() { receive_label = String::from(" and mark read"); diff --git a/src/send.rs b/src/send.rs index 3402e91..b869d20 100644 --- a/src/send.rs +++ b/src/send.rs @@ -64,7 +64,7 @@ pub fn show_send(s: &mut Cursive, with_message: bool) { form_content.add_child(TextArea::new().with_name("message").max_width(80)); form_content.add_child(DummyView); form_content.add_child(TextView::new(StyledString::styled( - format!("Optional {}", ticker), + format!("Optional {}", ticker.trim()), sub_title_colour, ))); form_content.add_child(TextArea::new().with_name("amount")); From 2c4a3974dd6621423e2241a1233e6d467942ae85 Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:52:52 +0100 Subject: [PATCH 09/12] Refactor wallet system 1. Overhaul of wallet system. 2. Fixed various bugs. --- src/accounts.rs | 762 ----------------------------------- src/dcutil.rs | 18 +- src/defaults.rs | 2 +- src/main.rs | 41 +- src/messages.rs | 149 +++++-- src/receive.rs | 26 +- src/send.rs | 16 +- src/wallets.rs | 1027 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1204 insertions(+), 837 deletions(-) delete mode 100644 src/accounts.rs create mode 100644 src/wallets.rs diff --git a/src/accounts.rs b/src/accounts.rs deleted file mode 100644 index acb2067..0000000 --- a/src/accounts.rs +++ /dev/null @@ -1,762 +0,0 @@ -use super::*; - -use rand::RngCore; - -#[derive(Serialize, Deserialize, Debug)] -pub struct AccountsAndLookup { - accounts_bytes: Vec, - lookup_bytes: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Account { - pub mnemonic: Option, - pub seed: Option<[u8; 32]>, - pub key_idx: u32, - pub private_key: [u8; 32], - pub public_key: [u8; 32], - pub address: String, - pub balance: u128, - pub receivables: Vec, -} - -impl Account { - fn from_mnemonic(mnemonic: Option, seed: Option<[u8; 32]>, prefix: &str) -> Account { - let (private_key, public_key) = Account::get_keypair(&seed.unwrap()); - Account { - mnemonic, - seed, - key_idx: 0, - private_key, - public_key, - address: dcutil::get_address(&public_key, prefix), - balance: 0, - receivables: vec![], - } - } - fn from_seed(seed: Option<[u8; 32]>, prefix: &str) -> Account { - let (private_key, public_key) = Account::get_keypair(&seed.unwrap()); - Account { - mnemonic: None, - seed, - key_idx: 0, - private_key, - public_key, - address: dcutil::get_address(&public_key, prefix), - balance: 0, - receivables: vec![], - } - } - - fn from_private_key(private_key: [u8; 32], prefix: &str) -> Account { - let public_key = Account::get_public_key(&private_key); - Account { - mnemonic: None, - seed: None, - key_idx: 0, - private_key, - public_key, - address: dcutil::get_address(&public_key, prefix), - balance: 0, - receivables: vec![], - } - } - - fn get_keypair(seed: &[u8; 32]) -> ([u8; 32], [u8; 32]) { - let private_key = dcutil::get_private_key(seed, 0); - let public_key = Account::get_public_key(&private_key); - (private_key, public_key) - } - fn get_public_key(private_key: &[u8; 32]) -> [u8; 32] { - let dalek = ed25519_dalek::SecretKey::from_bytes(private_key).unwrap(); - let public_key = ed25519_dalek::PublicKey::from(&dalek); - public_key.to_bytes() - } -} - -pub fn load_accounts(s: &mut Cursive, data_path: PathBuf) { - s.pop_layer(); - let accounts_file = data_path.join(ACCOUNTS_PATH); - if accounts_file.exists() { - let encrypted_bytes = fs::read(&accounts_file).unwrap_or_else(|e| { - let content = format!( - "Failed to read {} file at path: {:?}\nError: {}", - ACCOUNTS_PATH, accounts_file, e - ); - s.add_layer(Dialog::info(content)); - vec![] - }); - if encrypted_bytes.is_empty() { - show_accounts(s); - return; - } - let data = &mut s.user_data::().unwrap(); - data.encrypted_bytes = encrypted_bytes; - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child( - EditView::new() - .secret() - .on_submit(move |s, password| { - load_with_password(s, password); - }) - .with_name("password"), - ) - .child(DummyView) - .child(Button::new("Submit", move |s| { - let password = s - .call_on_name("password", |view: &mut EditView| view.get_content()) - .unwrap(); - load_with_password(s, &password); - })), - ) - .title("Enter dagchat password") - .max_width(80), - ); - } else { - show_accounts(s); - } -} - -fn load_with_password(s: &mut Cursive, password: &str) { - let data = &mut s.user_data::().unwrap(); - let bytes = decrypt_bytes(&data.encrypted_bytes, &password); - if bytes.is_err() { - s.add_layer(Dialog::info("Password was incorrect.")); - return; - } - data.password = password.to_string(); - - let accounts_and_lookup_res = bincode::deserialize(&bytes.unwrap()[..]); - if accounts_and_lookup_res.is_err() { - show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled( - format!( - "Error parsing {} file. File was either corrupted or edited outside of dagchat.", - ACCOUNTS_PATH - ), - RED, - ))); - } else { - let accounts_and_lookup: AccountsAndLookup = accounts_and_lookup_res.unwrap(); - data.accounts = bincode::deserialize(&accounts_and_lookup.accounts_bytes).unwrap(); - data.lookup = bincode::deserialize(&accounts_and_lookup.lookup_bytes).unwrap(); - show_accounts(s); - } -} - -fn write_accounts(encrypted_bytes: Vec) -> Result<(), String> { - if let Some(data_dir) = dirs::data_dir() { - let accounts_file = data_dir.join(DATA_DIR_PATH).join(ACCOUNTS_PATH); - let write_res = fs::write(&accounts_file, encrypted_bytes); - if write_res.is_err() { - return Err(format!( - "Failed to write to {} file at path: {:?}\nError: {:?}", - ACCOUNTS_PATH, - accounts_file, - write_res.err() - )); - } - } - Ok(()) -} - -pub fn save_accounts(s: &mut Cursive) -> Result<(), String> { - let data = &s.user_data::().unwrap(); - if data.accounts.is_empty() && data.lookup.is_empty() { - write_accounts(vec![])?; - return Ok(()); - } - let accounts_bytes = bincode::serialize(&data.accounts).unwrap(); - let lookup_bytes = bincode::serialize(&data.lookup).unwrap(); - let accounts_and_lookup = AccountsAndLookup { - accounts_bytes, - lookup_bytes, - }; - let encoded: Vec = bincode::serialize(&accounts_and_lookup).unwrap(); - let encrypted_bytes = encrypt_bytes(&encoded, &data.password); - write_accounts(encrypted_bytes)?; - //eprintln!("Saved accounts with password: {}", data.password); - Ok(()) -} - -pub fn show_accounts(s: &mut Cursive) { - s.pop_layer(); - let data: UserData = s.take_user_data().unwrap(); - // Need to add change password button - let buttons = LinearLayout::vertical() - .child(Button::new("Import", |s| add_account(s))) - .child(Button::new("Create", |s| new_account(s))) - .child(DummyView) - .child(Button::new("Back", |s| { - s.pop_layer(); - show_title(s); - })) - .child(DummyView); - - let select = SelectView::::new() - .on_submit(select_account) - .with_name("accounts") - .scrollable() - .max_height(5); - - s.add_layer( - Dialog::around( - LinearLayout::vertical().child(DummyView).child( - LinearLayout::horizontal() - .child( - Dialog::around(select) - .padding_lrtb(1, 1, 0, 0) - .title("Accounts"), - ) - .child(DummyView) - .child(DummyView) - .child(buttons), - ), - ) - .title("Select an Account"), - ); - - let mut i = 1; - for account in &data.accounts { - let tag; - if account.mnemonic.is_some() { - tag = format!("{}. From mnemonic", i); - } else if account.seed.is_some() { - tag = format!("{}. From seed", i); - } else { - tag = format!("{}. From private key", i); - } - s.call_on_name("accounts", |view: &mut SelectView| { - view.add_item_str(&tag) - }); - i += 1; - } - - s.set_user_data(data); -} - -fn select_account(s: &mut Cursive, _: &str) { - let select = s.find_name::>("accounts").unwrap(); - match select.selected_id() { - None => s.add_layer(Dialog::info("No account selected.")), - Some(focus) => { - let data = &mut s.user_data::().unwrap(); - data.acc_idx = focus; - - // Seemlessly use accounts between networks - if !data.accounts[focus].address.starts_with(&data.coin.prefix) { - let public_key = &data.accounts[focus].public_key; - data.accounts[focus].address = get_address(public_key, &data.coin.prefix); - } - - let account = &data.accounts[focus]; - let origin; - let mut extra = format!(" (at index {})", account.key_idx); - - let mut outer = Dialog::new() - .h_align(HAlign::Center) - .button("Load", move |s| { - s.pop_layer(); - load_current_account(s); - }) - .button("Back", |s| { - s.pop_layer(); - s.pop_layer(); - show_accounts(s) - }); - if account.mnemonic.is_some() { - origin = "mnemonic"; - add_idx_button(&mut outer); - } else if account.seed.is_some() { - origin = "seed"; - add_idx_button(&mut outer); - } else { - origin = "private key"; - extra = String::from(""); - } - outer.add_button("Backup", move |s| backup_account(s, origin)); - outer.add_button("Remove", move |s| remove_account(s, focus)); - let content = LinearLayout::vertical() - .child(DummyView) - .child(TextView::new(StyledString::styled( - format!("Account address{}", extra), - OFF_WHITE, - ))) - .child(TextView::new(StyledString::styled( - &account.address, - data.coin.colour, - ))) - .child(DummyView) - .child(TextView::new(StyledString::styled( - "Account type", - OFF_WHITE, - ))) - .child(TextView::new(StyledString::styled( - format!("loaded from {}", origin), - data.coin.colour, - ))); - s.add_layer( - outer - .content(content) - .title(format!("Account {}", focus + 1)), - ); - } - } -} - -fn load_current_account(s: &mut Cursive) { - let messages = messages::load_messages(s); - let data = &mut s.user_data::().unwrap(); - //eprintln!("Loaded messages: {:?}", messages); - data.acc_messages = messages; - receive::load_receivables(s); -} -fn backup_account(s: &mut Cursive, origin: &str) { - let data = &mut s.user_data::().unwrap(); - let account = &data.accounts[data.acc_idx]; - let mut content = Dialog::around(LinearLayout::vertical().child(DummyView).child( - TextView::new(StyledString::styled( - "Make sure you are in a safe location before viewing your mnemonic, seed or key.", - RED, - )), - )) - .h_align(HAlign::Center) - .title("Backup account"); - - if origin == "mnemonic" { - let mnemonic = account.mnemonic.as_ref().unwrap().clone(); - content.add_button("Mnemonic", move |s| { - let mnemonic = mnemonic.clone(); - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child(TextView::new(&mnemonic)), - ) - .h_align(HAlign::Center) - .button("Copy", move |s| { - s.pop_layer(); - s.pop_layer(); - copy_to_clip(s, mnemonic.clone()) - }) - .button("Back", |s| go_back(s)) - .title("Mnemonic") - .max_width(80), - ); - }); - } - if origin == "mnemonic" || origin == "seed" { - let seed = hex::encode(account.seed.unwrap()); - content.add_button("Hex seed", move |s| { - let seed = seed.clone(); - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child(TextView::new(&seed)), - ) - .h_align(HAlign::Center) - .button("Copy", move |s| { - s.pop_layer(); - s.pop_layer(); - copy_to_clip(s, seed.clone()) - }) - .button("Back", |s| go_back(s)) - .title("Seed"), - ); - }); - } else { - let private_key = hex::encode(account.private_key); - content.add_button("Private key", move |s| { - let private_key = private_key.clone(); - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child(TextView::new(&private_key)), - ) - .h_align(HAlign::Center) - .button("Copy", move |s| { - s.pop_layer(); - s.pop_layer(); - copy_to_clip(s, private_key.clone()) - }) - .button("Back", |s| go_back(s)) - .title("Private key"), - ); - }); - } - content.add_button("Back", |s| go_back(s)); - - s.add_layer(content.max_width(80)); -} - -fn add_idx_button(dialog: &mut Dialog) { - dialog.add_button("Index", move |s| { - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child(TextView::new("Account index (0 - 4,294,967,295)")) - .child(EditView::new().on_submit(process_idx).with_name("index")), - ) - .h_align(HAlign::Center) - .button("Submit", move |s| { - let idx = s - .call_on_name("index", |view: &mut EditView| view.get_content()) - .unwrap(); - process_idx(s, &idx); - }) - .button("Back", |s| { - s.pop_layer(); - }) - .title("Change account index"), - ) - }); -} - -fn process_idx(s: &mut Cursive, idx: &str) { - let index_res: Result = idx.parse(); - if index_res.is_err() { - s.add_layer(Dialog::info( - "Error: index was not an integer within the valid range.", - )); - return; - } else { - let index: u32 = index_res.unwrap(); - let data = &mut s.user_data::().unwrap(); - let mut account = &mut data.accounts[data.acc_idx]; - account.key_idx = index; - let seed = account.seed.unwrap(); - account.private_key = get_private_key(&seed, index); - account.public_key = Account::get_public_key(&account.private_key); - account.address = get_address(&account.public_key, &data.coin.prefix); - s.pop_layer(); - s.pop_layer(); - select_account(s, ""); - } -} - -fn remove_account(s: &mut Cursive, idx: usize) { - let warning = StyledString::styled( - "If you have not backed up this account, it will be lost forever.", - RED, - ); - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child(TextView::new(warning)) - .child(DummyView), - ) - .h_align(HAlign::Center) - .button("Back", |s| { - s.pop_layer(); - }) - .button("Confirm", move |s| { - let data = &mut s.user_data::().unwrap(); - let account = &data.accounts[idx]; - if data.lookup.contains_key(&account.address) { - let data_dir = dirs::data_dir().unwrap(); - let messages_dir = data_dir.join(DATA_DIR_PATH).join(MESSAGES_DIR_PATH); - let filename = format!("{}.dagchat", data.lookup.get(&account.address).unwrap()); - let messages_file = messages_dir.join(filename); - if !messages_file.exists() { - data.lookup.remove(&account.address); - } - } - data.accounts.remove(idx); - let save_res = save_accounts(s); - s.pop_layer(); - s.pop_layer(); - s.pop_layer(); - show_accounts(s); - if save_res.is_err() { - s.add_layer( - Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) - .title("Error saving accounts data"), - ); - } - }) - .title("Confirm account deletion"), - ); -} - -fn add_account(s: &mut Cursive) { - s.pop_layer(); - let data = &s.user_data::().unwrap(); - let coin = &data.coin.name; - let colour = data.coin.colour; - let content = format!("Choose a way to import your {} wallet.", coin); - - let content = LinearLayout::vertical() - .child(DummyView) - .child(TextView::new(StyledString::styled(content, colour))) - .child(DummyView) - .child(Button::new("Mnemonic", |s| show_from_mnemonic(s))) - .child(Button::new("Hex Seed", |s| { - from_seedorkey(s, String::from("seed")) - })) - .child(Button::new("Private Key", |s| { - from_seedorkey(s, String::from("private key")) - })) - .child(DummyView) - .child(Button::new("Back", |s| show_accounts(s))); - s.add_layer(Dialog::around(content).title("Import account")); -} - -fn show_from_mnemonic(s: &mut Cursive) { - s.pop_layer(); - s.add_layer( - Dialog::new() - .title("Enter your 24 word mnemonic") - .padding_lrtb(1, 1, 1, 0) - .content( - EditView::new() - .on_submit(process_from_mnemonic) - .with_name("mnemonic") - .fixed_width(29), - ) - .h_align(HAlign::Center) - .button("Done", |s| { - let mnemonic = s - .call_on_name("mnemonic", |view: &mut EditView| view.get_content()) - .unwrap(); - process_from_mnemonic(s, &mnemonic); - }) - .button("Paste", |s| { - s.call_on_name("mnemonic", |view: &mut EditView| { - let mut clipboard = Clipboard::new().unwrap(); - let clip = clipboard - .get_text() - .unwrap_or_else(|_| String::from("Failed to read clipboard.")); - view.set_content(clip); - }) - .unwrap(); - }) - .button("Back", |s| { - show_accounts(s); - }), - ); -} - -fn process_from_mnemonic(s: &mut Cursive, mnemonic: &str) { - let seed = validate_mnemonic(&mnemonic); - let content; - s.pop_layer(); - if !mnemonic.is_empty() && seed.is_some() { - let seed_bytes = seed.unwrap(); - let data = &s.user_data::().unwrap(); - let account = Account::from_mnemonic( - Some(mnemonic.to_string()), - Some(seed_bytes), - &data.coin.prefix, - ); - setup_account(s, account, |s| { - import_success(s, "Successfully imported account from mnemonic phrase.") - }); - } else { - content = "The mnemonic you entered was not valid."; - s.add_layer( - Dialog::around(TextView::new(content)).button("Back", |s| show_from_mnemonic(s)), - ); - return; - } -} -fn from_seedorkey(s: &mut Cursive, seed_or_key: String) { - s.pop_layer(); - let on_submit_seed_or_key = seed_or_key.clone(); - s.add_layer( - Dialog::new() - .title(format!("Enter your {}", seed_or_key)) - .padding_lrtb(1, 1, 1, 0) - .content( - EditView::new() - .on_submit(move |s, sork_raw| { - process_from_seedorkey(s, sork_raw.to_string(), &on_submit_seed_or_key); - }) - .with_name("seedorkey") - .fixed_width(29), - ) - .h_align(HAlign::Center) - .button("Done", move |s| { - let sork_raw = s - .call_on_name("seedorkey", |view: &mut EditView| view.get_content()) - .unwrap(); - process_from_seedorkey(s, sork_raw.to_string(), &seed_or_key); - }) - .button("Paste", |s| { - s.call_on_name("seedorkey", |view: &mut EditView| { - let mut clipboard = Clipboard::new().unwrap(); - let clip = clipboard - .get_text() - .unwrap_or_else(|_| String::from("Failed to read clipboard.")); - view.set_content(clip); - }) - .unwrap(); - }) - .button("Back", |s| { - show_accounts(s); - }), - ); -} - -fn process_from_seedorkey(s: &mut Cursive, sork_raw: String, seed_or_key: &String) { - let sork_val = sork_raw.trim(); - if sork_val.len() != 64 { - s.add_layer(Dialog::info(format!( - "Error: {} was invalid - not 64 characters long.", - seed_or_key - ))); - return; - } - let bytes_opt = hex::decode(sork_val); - if bytes_opt.is_err() { - s.add_layer(Dialog::info(format!( - "Error: {} was invalid - failed to decode hex.", - seed_or_key - ))); - return; - } - let bytes = bytes_opt.unwrap(); - let sork_bytes: [u8; 32] = bytes.try_into().unwrap(); - let data = &s.user_data::().unwrap(); - let account: Account; - if seed_or_key == "seed" { - account = Account::from_seed(Some(sork_bytes), &data.coin.prefix); - } else { - account = Account::from_private_key(sork_bytes, &data.coin.prefix); - } - let content = format!("Successfully imported account from {}.", seed_or_key); - setup_account(s, account, move |s| import_success(s, &content)); -} - -fn new_account(s: &mut Cursive) { - s.pop_layer(); - let data = &s.user_data::().unwrap(); - let mut csprng = rand::thread_rng(); - let mut seed_bytes = [0u8; 32]; - csprng.fill_bytes(&mut seed_bytes); - let mnemonic = seed_to_mnemonic(&seed_bytes); - let account = - Account::from_mnemonic(Some(mnemonic.clone()), Some(seed_bytes), &data.coin.prefix); - setup_account(s, account, move |s| { - create_success(s, mnemonic.clone(), hex::encode(seed_bytes)) - }); -} - -fn setup_account(s: &mut Cursive, account: Account, on_success: F) -where - F: Fn(&mut Cursive), -{ - let data = &mut s.user_data::().unwrap(); - data.accounts.push(account); - data.acc_idx = data.accounts.len() - 1; - - let data = &mut s.user_data::().unwrap(); - if data.accounts.len() == 1 && data.password.is_empty() { - set_password(s, on_success); - } else { - let save_res = save_accounts(s); - if save_res.is_ok() { - on_success(s); - } else { - show_accounts(s); - s.add_layer( - Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) - .title("Error saving accounts data"), - ); - } - } -} - -fn set_password(s: &mut Cursive, on_success: F) -where - F: Fn(&mut Cursive), -{ - let warning = StyledString::styled( - "Always backup or write down your mnemonics, seeds or keys elsewhere in case you forget your password.", RED); - - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child(DummyView) - .child(TextView::new("Enter password")) - .child(EditView::new().secret().with_name("password")) - .child(DummyView) - .child(TextView::new("Confirm password")) - .child(EditView::new().secret().with_name("confirm")) - .child(DummyView) - .child(TextView::new(warning)), - ) - .h_align(HAlign::Center) - .button("Submit", move |s| { - let password = s - .call_on_name("password", |view: &mut EditView| view.get_content()) - .unwrap(); - let confirmed = s - .call_on_name("confirm", |view: &mut EditView| view.get_content()) - .unwrap(); - if password.is_empty() { - s.add_layer(Dialog::info("Password can't be blank.")); - return; - } - if password != confirmed { - s.add_layer(Dialog::info("Passwords did not match.")); - return; - } - let msg_save_res = messages::change_message_passwords(s, &password); - let data = &mut s.user_data::().unwrap(); - data.password = password.to_string(); - let acc_save_res = save_accounts(s); - s.pop_layer(); - if acc_save_res.is_ok() && msg_save_res.is_ok() { - on_success(s); - } else if acc_save_res.is_err() { - show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled(acc_save_res.err().unwrap(), - RED, - )).title("Fatal error saving accounts")); - } else if msg_save_res.is_err() { - show_accounts(s); - s.add_layer(Dialog::info(StyledString::styled(msg_save_res.err().unwrap(), - RED, - )).title("Fatal error saving messages")); - } - }) - .button("Info", |s| { - let content = "\nThe password you are setting up for dagchat is used to encrypt your accounts, messages (If 'Encrypt and save' messages setting is selected) and address book when they are saved on your device. It should be strong and contain a range of characters (UPPERCASE, lowercase, numb3rs and symbo!s). Without this password, dagchat will not be able to decrypt any of your saved accounts, messages or address book."; - s.add_layer(Dialog::info(content).title("What is this password?").max_width(80)); - }) - .title("Create a password for dagchat") - .max_width(80), - ); -} -fn import_success(s: &mut Cursive, content: &str) { - s.add_layer( - Dialog::around(TextView::new(content).max_width(80)) - .button("Load", |s| load_current_account(s)) - .button("Back", |s| show_accounts(s)), - ); -} - -fn create_success(s: &mut Cursive, mnemonic: String, seed: String) { - let data = &mut s.user_data::().unwrap(); - let mut content = StyledString::styled("\nMnemonic\n", data.coin.colour); - content.append(StyledString::styled(&mnemonic, OFF_WHITE)); - content.append(StyledString::styled("\n\nSeed\n", data.coin.colour)); - content.append(StyledString::styled(&seed, OFF_WHITE)); - s.add_layer( - Dialog::around(TextView::new(content).max_width(80)) - .h_align(HAlign::Center) - .button("Load", |s| load_current_account(s)) - .button("Back", |s| show_accounts(s)) - .button("Copy mnemonic", move |s| copy_to_clip(s, mnemonic.clone())) - .button("Copy seed", move |s| copy_to_clip(s, seed.clone())) - .title("Successfully generated new account") - .max_width(80), - ); -} diff --git a/src/dcutil.rs b/src/dcutil.rs index ebe5caa..56b3ad6 100644 --- a/src/dcutil.rs +++ b/src/dcutil.rs @@ -193,7 +193,7 @@ pub fn send_message( let sender_pub = ed25519_dalek::PublicKey::from( &ed25519_dalek::SecretKey::from_bytes(private_key_bytes).unwrap(), ); - let sender_address = get_address(sender_pub.as_bytes(), addr_prefix); + let sender_address = get_address(sender_pub.as_bytes(), Some(addr_prefix)); // Set up the previous block hash and balance to start publishing blocks // Also note the representative from before sending, in order to change back afterwards @@ -360,7 +360,7 @@ pub fn send( let sender_pub = ed25519_dalek::PublicKey::from( &ed25519_dalek::SecretKey::from_bytes(private_key_bytes).unwrap(), ); - let sender_address = get_address(sender_pub.as_bytes(), addr_prefix); + let sender_address = get_address(sender_pub.as_bytes(), Some(addr_prefix)); // Safe because account must be opened to have got this far let account_info = get_account_info(&sender_address, node_url).unwrap(); @@ -647,9 +647,9 @@ pub fn get_signed_block( //let work = generate_work(&previous, "banano"); let block = Block { type_name: String::from("state"), - account: get_address(public.as_bytes(), addr_prefix), + account: get_address(public.as_bytes(), Some(addr_prefix)), previous: hex::encode(previous), - representative: get_address(rep, addr_prefix), + representative: get_address(rep, Some(addr_prefix)), balance: balance.to_string(), link: hex::encode(link), signature: hex::encode(&signed_bytes), @@ -747,14 +747,18 @@ pub fn get_private_key(seed_bytes: &[u8; 32], idx: u32) -> [u8; 32] { buf } -pub fn get_address(pub_key_bytes: &[u8], prefix: &str) -> String { +pub fn get_address(pub_key_bytes: &[u8], prefix: Option<&str>) -> String { let mut pub_key_vec = pub_key_bytes.to_vec(); let mut h = [0u8; 3].to_vec(); h.append(&mut pub_key_vec); let checksum = ADDR_ENCODING.encode(&compute_address_checksum(pub_key_bytes)); let address = { let encoded_addr = ADDR_ENCODING.encode(&h); - let mut addr = String::from(prefix); + + let mut addr = String::from(""); + if prefix.is_some() { + addr = String::from(prefix.unwrap()); + } addr.push_str(encoded_addr.get(4..).unwrap()); addr.push_str(&checksum); addr @@ -793,7 +797,7 @@ pub fn validate_address(addr: &str) -> bool { let pub_key_vec = ADDR_ENCODING.decode(encoded_addr.as_bytes()); - // Lazily catch decoding error - return false + // Catch decoding error - return false let mut pub_key_vec = match pub_key_vec { Ok(pub_key_vec) => pub_key_vec, Err(_) => return false, diff --git a/src/defaults.rs b/src/defaults.rs index 72cd809..66edbfc 100644 --- a/src/defaults.rs +++ b/src/defaults.rs @@ -5,7 +5,7 @@ pub const DEFAULT_REP_NANO: &str = pub const SHOW_TO_DP: usize = 6; pub const DATA_DIR_PATH: &str = "dagchat-beta"; pub const MESSAGES_DIR_PATH: &str = "messages"; -pub const ACCOUNTS_PATH: &str = "accounts.dagchat"; +pub const WALLETS_PATH: &str = "accounts.dagchat"; pub const ADDRESS_BOOK_PATH: &str = "addressbook.dagchat"; pub const SALT_LENGTH: usize = 16; pub const IV_LENGTH: usize = 12; diff --git a/src/main.rs b/src/main.rs index 78c5e58..cf87d12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,8 @@ use cursive::theme::{BaseColor, BorderStyle, Color, PaletteColor, Theme}; use cursive::traits::*; use cursive::utils::markup::StyledString; use cursive::views::{ - Button, Dialog, DummyView, EditView, LinearLayout, ProgressBar, RadioGroup, SelectView, - TextArea, TextView, + Button, Dialog, DummyView, EditView, LinearLayout, OnEventView, ProgressBar, RadioGroup, + SelectView, TextArea, TextView, }; use cursive::Cursive; use dirs; @@ -17,14 +17,14 @@ use std::fs; use std::path::PathBuf; pub mod defaults; -use defaults::{ACCOUNTS_PATH, DATA_DIR_PATH, MESSAGES_DIR_PATH, SHOW_TO_DP}; +use defaults::{DATA_DIR_PATH, MESSAGES_DIR_PATH, SHOW_TO_DP, WALLETS_PATH}; // dagchat util mod dcutil; use dcutil::*; -// dagchat accounts and messages util -mod accounts; +// dagchat wallets and messages util mod messages; +mod wallets; use messages::SavedMessage; // send and receive with dagchat @@ -33,9 +33,8 @@ mod send; pub struct UserData { pub password: String, - pub accounts: Vec, - pub acc_idx: usize, - pub acc_messages: Result, String>, + pub wallets: Vec, + pub wallet_idx: usize, pub lookup: HashMap, pub coin: Coin, pub encrypted_bytes: Vec, @@ -78,10 +77,9 @@ impl UserData { pub fn new() -> Self { UserData { password: String::from(""), - accounts: vec![], - acc_idx: 0, + wallets: vec![], + wallet_idx: 0, lookup: HashMap::new(), - acc_messages: Ok(vec![]), coin: Coin::nano(), encrypted_bytes: vec![], } @@ -204,7 +202,7 @@ fn check_setup(s: &mut Cursive) { return; } } - accounts::load_accounts(s, dagchat_dir); + wallets::load_wallets(s, dagchat_dir); } else { s.add_layer(Dialog::info( "Error locating the application data folder on your system.", @@ -215,9 +213,11 @@ fn check_setup(s: &mut Cursive) { fn show_change_rep(s: &mut Cursive) { s.pop_layer(); let data = &s.user_data::().unwrap(); - let private_key = data.accounts[data.acc_idx].private_key; + let wallet = &data.wallets[data.wallet_idx]; + let account = &wallet.accounts[wallet.acc_idx]; + let private_key = account.private_key; let coin = data.coin.clone(); - let address = data.accounts[data.acc_idx].address.clone(); + let address = account.address.clone(); let sub_title_colour = get_subtitle_colour(s); s.add_layer( Dialog::around( @@ -306,13 +306,14 @@ fn show_inbox(s: &mut Cursive) { s.set_autorefresh(false); s.pop_layer(); let data: UserData = s.take_user_data().unwrap(); - let address = data.accounts[data.acc_idx].address.clone(); + let wallet = &data.wallets[data.wallet_idx]; + let address = wallet.accounts[wallet.acc_idx].address.clone(); let send_label = format!("Send {}", data.coin.name); let buttons = LinearLayout::vertical() .child(Button::new("Refresh", |s| receive::load_receivables(s))) .child(Button::new("Messages", |s| { let filter: messages::Filter = Default::default(); - messages::view_messages(s, filter); + messages::show_messages(s, filter); })) .child(DummyView) .child(Button::new(send_label, |s| send::show_send(s, false))) @@ -323,16 +324,16 @@ fn show_inbox(s: &mut Cursive) { })) .child(Button::new("Change rep", |s| show_change_rep(s))) .child(DummyView) - .child(Button::new("Back", |s| accounts::show_accounts(s))); + .child(Button::new("Back", |s| wallets::show_accounts(s))); let select = SelectView::::new() .on_submit(receive::show_message_info) .with_name("select") .scrollable() - .max_height(6); + .fixed_height(5); let bal = display_to_dp( - data.accounts[data.acc_idx].balance, + wallet.accounts[wallet.acc_idx].balance, SHOW_TO_DP, &data.coin.multiplier, &data.coin.ticker, @@ -361,7 +362,7 @@ fn show_inbox(s: &mut Cursive) { .title(format!("dagchat {}", VERSION)), ); - for receivable in &data.accounts[data.acc_idx].receivables { + for receivable in &wallet.accounts[wallet.acc_idx].receivables { let mut tag; if receivable.amount == 1 && receivable.message.is_some() { tag = String::from("Message"); diff --git a/src/messages.rs b/src/messages.rs index 13bd7a9..5c606fd 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -20,6 +20,7 @@ pub struct Filter { pub outgoing: bool, pub gt_1_raw: bool, pub eq_1_raw: bool, + pub search_term: Option, } impl Default for Filter { @@ -29,23 +30,64 @@ impl Default for Filter { outgoing: true, gt_1_raw: true, eq_1_raw: true, + search_term: None, } } } pub fn create_key(s: &mut Cursive) -> Result { let data = &mut s.user_data::().unwrap(); - let address = data.accounts[data.acc_idx].address.clone(); + let wallet = &data.wallets[data.wallet_idx]; + let address = wallet.accounts[wallet.acc_idx].address.clone(); let mut csprng = rand::thread_rng(); let mut random_id = [0u8; 32]; csprng.fill_bytes(&mut random_id); //eprintln!("{} : {}", address, hex::encode(random_id)); data.lookup.insert(address, hex::encode(random_id)); - accounts::save_accounts(s)?; + wallets::save_wallets(s)?; Ok(hex::encode(random_id)) } -pub fn edit_filter(s: &mut Cursive, filter: Filter) { +pub fn show_search(s: &mut Cursive, filter: Filter) { + let content = LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(StyledString::styled( + "Search term or address", + OFF_WHITE, + ))) + .child(TextArea::new().with_name("search").max_width(66)) + .child(LinearLayout::horizontal().child(Button::new("Paste", |s| { + s.call_on_name("search", |view: &mut TextArea| { + let mut clipboard = Clipboard::new().unwrap(); + let clip = clipboard + .get_text() + .unwrap_or_else(|_| String::from("Failed to read clipboard.")); + view.set_content(clip); + }) + .unwrap(); + }))); + s.add_layer( + Dialog::around(content) + .h_align(HAlign::Center) + .button("Search", move |s| { + let mut filter = filter.clone(); + s.call_on_name("search", |view: &mut TextArea| { + if view.get_content().trim().is_empty() { + filter.search_term = None; + } else { + filter.search_term = Some(String::from(view.get_content())); + } + }); + s.pop_layer(); + s.pop_layer(); + show_messages(s, filter); + }) + .button("Back", |s| go_back(s)) + .title("Search"), + ) +} + +pub fn show_filter(s: &mut Cursive, filter: Filter) { let mut message_dir: RadioGroup = RadioGroup::new(); let mut message_amount: RadioGroup = RadioGroup::new(); @@ -107,27 +149,33 @@ pub fn edit_filter(s: &mut Cursive, filter: Filter) { } s.pop_layer(); s.pop_layer(); - view_messages(s, filter); + show_messages(s, filter); }) .title("Filter setup"), ); } -pub fn view_messages(s: &mut Cursive, filter: Filter) { +pub fn show_messages(s: &mut Cursive, mut filter: Filter) { let data = &mut s.user_data::().unwrap(); - let messages = &data.acc_messages; + let wallet = &data.wallets[data.wallet_idx]; + let messages = &wallet.accounts[wallet.acc_idx].messages; if messages.is_err() { let err_msg = messages.as_ref().err().unwrap().clone(); s.add_layer(Dialog::info(err_msg)); return; } else if messages.as_ref().unwrap().is_empty() { s.add_layer(Dialog::info( - "You haven't sent or received any messages yet with dagchat!", + "You haven't sent or received any messages yet with dagchat on this account!", )); return; } let mut output = StyledString::new(); + let mut search_term = String::from(""); + if filter.search_term.is_some() { + search_term = filter.search_term.unwrap(); + }; + for message in messages.as_ref().unwrap().iter().rev() { if (message.outgoing && !filter.outgoing) || (!message.outgoing && !filter.incoming) @@ -136,6 +184,7 @@ pub fn view_messages(s: &mut Cursive, filter: Filter) { { continue; } + let datetime: DateTime = DateTime::from(DateTime::::from_utc( NaiveDateTime::from_timestamp(message.timestamp as i64, 0), Utc, @@ -164,35 +213,68 @@ pub fn view_messages(s: &mut Cursive, filter: Filter) { format!("{}\n\n", message.amount), data.coin.colour, )); - output.append(message_info); + + if !search_term.as_str().is_empty() { + if message_info.source().contains(&search_term) { + output.append(message_info); + } + } else { + output.append(message_info); + } } - s.add_layer( - Dialog::around( - LinearLayout::vertical() - .child( - LinearLayout::horizontal() - .child(Button::new("Filter", move |s| { - edit_filter(s, filter.clone()) - })) - .child(DummyView) - .child(Button::new("Back", |s| go_back(s))), - ) + if search_term.is_empty() { + filter.search_term = None; + } else { + filter.search_term = Some(search_term); + } + + // Annoying reallocations due to having multiple closures + // requiring filter. Need to look into how to solve although + // minimal perfomance hit. + let search_filter = filter.clone(); + let filter_copy = filter.clone(); + + let mut content = LinearLayout::vertical() + .child( + LinearLayout::horizontal() + .child(Button::new("Search", move |s| { + show_search(s, search_filter.clone()) + })) + .child(DummyView) + .child(Button::new("Filter", move |s| { + show_filter(s, filter_copy.clone()) + })) .child(DummyView) - .child( - TextView::new(output) - .scrollable() - .max_width(73) - .max_height(10), - ), + .child(Button::new("Back", |s| go_back(s))), ) - .title("Message history"), + .child(DummyView); + if filter.search_term.is_some() { + content.add_child(TextView::new(StyledString::styled( + format!("Contains: {}", filter.search_term.unwrap()), + OFF_WHITE, + ))) + } + if output.is_empty() { + content.add_child(DummyView); + content.add_child(TextView::new(StyledString::styled( + "No messages found.", + data.coin.colour, + ))); + } + content.add_child( + TextView::new(output) + .scrollable() + .max_width(77) + .max_height(12), ); + s.add_layer(Dialog::around(content).title("Message history")); } pub fn load_messages(s: &mut Cursive) -> Result, String> { let data = &mut s.user_data::().unwrap(); + let wallet = &data.wallets[data.wallet_idx]; let mut messages: Vec = vec![]; - let lookup_key = match data.lookup.get(&data.accounts[data.acc_idx].address) { + let lookup_key = match data.lookup.get(&wallet.accounts[wallet.acc_idx].address) { Some(id) => id.to_owned(), None => create_key(s)?, }; @@ -235,16 +317,19 @@ pub fn load_messages(s: &mut Cursive) -> Result, String> { pub fn save_messages(s: &mut Cursive) -> Result<(), String> { let data = &mut s.user_data::().unwrap(); + let wallet = &data.wallets[data.wallet_idx]; let data_dir = dirs::data_dir().unwrap(); let messages_dir = data_dir.join(DATA_DIR_PATH).join(MESSAGES_DIR_PATH); - let address = &data.accounts[data.acc_idx].address; + let address = &wallet.accounts[wallet.acc_idx].address; let lookup_key = match data.lookup.get(address) { Some(id) => id.to_owned(), None => create_key(s)?, }; - let data = &mut s.user_data::().unwrap(); + let data = &s.user_data::().unwrap(); + let wallet = &data.wallets[data.wallet_idx]; let messages_file = messages_dir.join(format!("{}.dagchat", lookup_key)); - let messages_bytes = bincode::serialize(data.acc_messages.as_ref().unwrap()).unwrap(); + let messages_bytes = + bincode::serialize(wallet.accounts[wallet.acc_idx].messages.as_ref().unwrap()).unwrap(); let encrypted_bytes = encrypt_bytes(&messages_bytes, &data.password); let write_res = fs::write(&messages_file, encrypted_bytes); if write_res.is_err() { @@ -263,11 +348,11 @@ pub fn change_message_passwords(s: &mut Cursive, new_password: &str) -> Result<( let data_dir = dirs::data_dir().unwrap(); let messages_dir = data_dir.join(DATA_DIR_PATH).join(MESSAGES_DIR_PATH); - for (a, lookup_key) in data.lookup.iter() { + for (_a, lookup_key) in data.lookup.iter() { let filename = format!("{}.dagchat", lookup_key); let messages_file = messages_dir.join(filename); if messages_file.exists() { - //eprintln!("Changing file password for {}", a); + //eprintln!("Changing file password for {}", _a); let mut error = String::from(""); let encrypted_bytes = fs::read(&messages_file).unwrap_or_else(|e| { error = format!( diff --git a/src/receive.rs b/src/receive.rs index eb3005a..0c4ef2e 100644 --- a/src/receive.rs +++ b/src/receive.rs @@ -8,7 +8,8 @@ pub fn show_message_info(s: &mut Cursive, _name: &str) { None => s.add_layer(Dialog::info("No receivable selected.")), Some(focus) => { let data = &mut s.user_data::().unwrap(); - let account = &mut data.accounts[data.acc_idx]; + let wallet = &mut data.wallets[data.wallet_idx]; + let account = &mut wallet.accounts[wallet.acc_idx]; let receivable = &mut account.receivables[focus]; let private_key = &account.private_key; let node_url = &data.coin.node_url; @@ -84,7 +85,8 @@ pub fn load_receivables(s: &mut Cursive) { let data = &s.user_data::().unwrap(); let node_url = data.coin.node_url.clone(); - let target_address = data.accounts[data.acc_idx].address.clone(); + let wallet = &data.wallets[data.wallet_idx]; + let target_address = wallet.accounts[wallet.acc_idx].address.clone(); s.pop_layer(); s.add_layer(Dialog::around( ProgressBar::new() @@ -99,7 +101,8 @@ pub fn load_receivables(s: &mut Cursive) { let receivables = find_incoming(&target_address, &node_url, &counter); cb.send(Box::new(move |s| { let data = &mut s.user_data::().unwrap(); - let mut account = &mut data.accounts[data.acc_idx]; + let wallet = &mut data.wallets[data.wallet_idx]; + let account = &mut wallet.accounts[wallet.acc_idx]; account.receivables = receivables; account.balance = balance; show_inbox(s); @@ -113,12 +116,14 @@ pub fn load_receivables(s: &mut Cursive) { fn process_receive(s: &mut Cursive, idx: usize) { let data = &s.user_data::().unwrap(); - let private_key = data.accounts[data.acc_idx].private_key; - let receivable = &data.accounts[data.acc_idx].receivables[idx]; + let wallet = &data.wallets[data.wallet_idx]; + let account = &wallet.accounts[wallet.acc_idx]; + let private_key = account.private_key; + let receivable = &account.receivables[idx]; let send_block_hash = receivable.hash.clone(); let amount = receivable.amount; - let address = data.accounts[data.acc_idx].address.clone(); + let address = account.address.clone(); let prefix = data.coin.prefix.clone(); let node_url = data.coin.node_url.clone(); let ticks = 1000; @@ -142,14 +147,16 @@ fn process_receive(s: &mut Cursive, idx: usize) { select.remove_item(idx); let mut balance = s.find_name::("balance").unwrap(); let data = &mut s.user_data::().unwrap(); - let receivable = &data.accounts[data.acc_idx].receivables[idx]; + let wallet = &mut data.wallets[data.wallet_idx]; + let account = &mut wallet.accounts[wallet.acc_idx]; + let receivable = &account.receivables[idx]; let send_block_hash = receivable.hash.clone(); let amount = receivable.amount; let has_message = { receivable.message.is_some() }; let mut save_res = Ok(()); if has_message { - data.acc_messages.as_mut().unwrap().push(SavedMessage { + account.messages.as_mut().unwrap().push(SavedMessage { outgoing: false, address: receivable.source.clone(), timestamp: match SystemTime::now() @@ -170,7 +177,8 @@ fn process_receive(s: &mut Cursive, idx: usize) { save_res = messages::save_messages(s); } let data = &mut s.user_data::().unwrap(); - let account = &mut data.accounts[data.acc_idx]; + let wallet = &mut data.wallets[data.wallet_idx]; + let account = &mut wallet.accounts[wallet.acc_idx]; account.receivables.remove(idx); account.balance += amount; let bal = display_to_dp( diff --git a/src/send.rs b/src/send.rs index b869d20..10a323c 100644 --- a/src/send.rs +++ b/src/send.rs @@ -6,13 +6,15 @@ pub fn show_send(s: &mut Cursive, with_message: bool) { s.pop_layer(); let data = &s.user_data::().unwrap(); - let balance = data.accounts[data.acc_idx].balance; + let wallet = &data.wallets[data.wallet_idx]; + let account = &wallet.accounts[wallet.acc_idx]; + let balance = account.balance; let coin = data.coin.name.clone(); let ticker = data.coin.ticker.clone(); let multiplier = data.coin.multiplier.clone(); if balance == 0 { - let address = data.accounts[data.acc_idx].address.clone(); + let address = account.address.clone(); let no_balance_message; if with_message { no_balance_message = String::from("To send a message with dagchat you need a balance of at least 1 raw - a tiny fraction of a coin. One faucet claim will last you a lifetime."); @@ -189,7 +191,8 @@ fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { let cb = s.cb_sink().clone(); let data = &mut s.user_data::().unwrap(); let node_url = data.coin.node_url.clone(); - let private_key_bytes = data.accounts[data.acc_idx].private_key; + let wallet = &data.wallets[data.wallet_idx]; + let private_key_bytes = wallet.accounts[wallet.acc_idx].private_key; let prefix = data.coin.prefix.clone(); s.pop_layer(); s.add_layer(Dialog::around( @@ -221,8 +224,11 @@ fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { cb.send(Box::new(move |s| { let mut save_res = Ok(()); let data = &mut s.user_data::().unwrap(); + let wallet = &mut data.wallets[data.wallet_idx]; + let account = &mut wallet.accounts[wallet.acc_idx]; + account.balance -= raw; if with_message { - data.acc_messages.as_mut().unwrap().push(SavedMessage { + account.messages.as_mut().unwrap().push(SavedMessage { outgoing: true, address: address.clone(), timestamp: match SystemTime::now() @@ -242,8 +248,6 @@ fn process_send(s: &mut Cursive, raw: u128, address: String, message: String) { }); save_res = messages::save_messages(s); } - let data = &mut s.user_data::().unwrap(); - data.accounts[data.acc_idx].balance -= raw; show_sent(s, with_message); if save_res.is_err() { s.add_layer( diff --git a/src/wallets.rs b/src/wallets.rs new file mode 100644 index 0000000..10b1df0 --- /dev/null +++ b/src/wallets.rs @@ -0,0 +1,1027 @@ +use super::*; +use cursive::event::{Event, EventResult, EventTrigger, MouseEvent}; + +use rand::RngCore; + +#[derive(Serialize, Deserialize, Debug)] +pub struct WalletsAndLookup { + wallets_bytes: Vec, + lookup_bytes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Wallet { + pub name: String, + pub mnemonic: String, + pub seed: [u8; 32], + pub indexes: Vec, + #[serde(skip)] + pub accounts: Vec, + #[serde(skip)] + pub acc_idx: usize, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Account { + pub index: u32, + pub private_key: [u8; 32], + pub public_key: [u8; 32], + pub address: String, + pub balance: u128, + pub receivables: Vec, + pub messages: Result, String>, +} + +impl Account { + fn with_index(wallet: &Wallet, index: u32, prefix: &str) -> Account { + let (private_key, public_key) = Account::get_keypair(&wallet.seed, index); + Account { + index, + private_key, + public_key, + address: get_address(&public_key, Some(prefix)), + balance: 0, + receivables: vec![], + messages: Ok(vec![]), + } + } + + fn get_keypair(seed: &[u8; 32], index: u32) -> ([u8; 32], [u8; 32]) { + let private_key = dcutil::get_private_key(seed, index); + let public_key = Account::get_public_key(&private_key); + (private_key, public_key) + } + fn get_public_key(private_key: &[u8; 32]) -> [u8; 32] { + let dalek = ed25519_dalek::SecretKey::from_bytes(private_key).unwrap(); + let public_key = ed25519_dalek::PublicKey::from(&dalek); + public_key.to_bytes() + } +} + +impl Wallet { + fn new(mnemonic: String, seed: [u8; 32], name: String, prefix: &str) -> Wallet { + let mut wallet = Wallet { + name, + mnemonic, + seed, + indexes: vec![0], + accounts: vec![], + acc_idx: 0, + }; + wallet + .accounts + .push(Account::with_index(&wallet, 0, prefix)); + wallet + } + fn new_key(private_key: [u8; 32], name: String, prefix: &str) -> Wallet { + let public_key = Wallet::get_public_key(&private_key); + let mut wallet = Wallet { + name, + mnemonic: String::from(""), + seed: [0u8; 32], + indexes: vec![0], + accounts: vec![], + acc_idx: 0, + }; + wallet.accounts.push(Account { + index: 0, + private_key, + public_key, + address: get_address(&public_key, Some(prefix)), + balance: 0, + receivables: vec![], + messages: Ok(vec![]), + }); + wallet + } + + fn get_public_key(private_key: &[u8; 32]) -> [u8; 32] { + let dalek = ed25519_dalek::SecretKey::from_bytes(private_key).unwrap(); + let public_key = ed25519_dalek::PublicKey::from(&dalek); + public_key.to_bytes() + } +} + +fn add_account(s: &mut Cursive, index: Option, prefix: &str) { + let data = &mut s.user_data::().unwrap(); + let wallet = &mut data.wallets[data.wallet_idx]; + let mut i = 0; + let mut last = wallet.indexes[i]; + if let Some(index) = index { + if index < last { + wallet.indexes.insert(0, index); + wallet + .accounts + .insert(0, Account::with_index(wallet, index, prefix)); + return; + } + } else { + if last != 0 { + wallet.indexes.insert(0, 0); + wallet + .accounts + .insert(0, Account::with_index(wallet, 0, prefix)); + return; + } + } + for idx in wallet.indexes[1..].iter() { + if *idx != last + 1 { + break; + } + i += 1; + last = wallet.indexes[i] + } + if index.is_none() { + wallet.indexes.insert(i + 1, last + 1); + wallet + .accounts + .insert(i + 1, Account::with_index(wallet, last + 1, prefix)); + } else { + wallet.indexes.push(index.unwrap()); + wallet + .accounts + .push(Account::with_index(wallet, index.unwrap(), prefix)); + } +} + +pub fn load_wallets(s: &mut Cursive, data_path: PathBuf) { + s.pop_layer(); + let wallets_file = data_path.join(WALLETS_PATH); + if wallets_file.exists() { + let encrypted_bytes = fs::read(&wallets_file).unwrap_or_else(|e| { + let content = format!( + "Failed to read {} file at path: {:?}\nError: {}", + WALLETS_PATH, wallets_file, e + ); + s.add_layer(Dialog::info(content)); + vec![] + }); + if encrypted_bytes.is_empty() { + show_wallets(s); + return; + } + let data = &mut s.user_data::().unwrap(); + data.encrypted_bytes = encrypted_bytes; + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child( + EditView::new() + .secret() + .on_submit(move |s, password| { + load_with_password(s, password); + }) + .with_name("password"), + ) + .child(DummyView) + .child(Button::new("Submit", move |s| { + let password = s + .call_on_name("password", |view: &mut EditView| view.get_content()) + .unwrap(); + load_with_password(s, &password); + })), + ) + .title("Enter dagchat password") + .max_width(80), + ); + } else { + show_wallets(s); + } +} + +fn load_with_password(s: &mut Cursive, password: &str) { + let data = &mut s.user_data::().unwrap(); + let bytes = decrypt_bytes(&data.encrypted_bytes, &password); + if bytes.is_err() { + s.add_layer(Dialog::info("Password was incorrect.")); + return; + } + data.password = password.to_string(); + + let wallets_and_lookup_res = bincode::deserialize(&bytes.unwrap()[..]); + if wallets_and_lookup_res.is_err() { + show_wallets(s); + s.add_layer(Dialog::info(StyledString::styled( + format!( + "Error parsing {} file. File was either corrupted or edited outside of dagchat.", + WALLETS_PATH + ), + RED, + ))); + } else { + let wallets_and_lookup: WalletsAndLookup = wallets_and_lookup_res.unwrap(); + data.wallets = bincode::deserialize(&wallets_and_lookup.wallets_bytes).unwrap(); + data.lookup = bincode::deserialize(&wallets_and_lookup.lookup_bytes).unwrap(); + show_wallets(s); + } +} + +fn write_wallets(encrypted_bytes: Vec) -> Result<(), String> { + if let Some(data_dir) = dirs::data_dir() { + let wallets_file = data_dir.join(DATA_DIR_PATH).join(WALLETS_PATH); + let write_res = fs::write(&wallets_file, encrypted_bytes); + if write_res.is_err() { + return Err(format!( + "Failed to write to {} file at path: {:?}\nError: {:?}", + WALLETS_PATH, + wallets_file, + write_res.err() + )); + } + } + Ok(()) +} + +pub fn save_wallets(s: &mut Cursive) -> Result<(), String> { + let data = &s.user_data::().unwrap(); + if data.wallets.is_empty() && data.lookup.is_empty() { + write_wallets(vec![])?; + return Ok(()); + } + let wallets_bytes = bincode::serialize(&data.wallets).unwrap(); + let lookup_bytes = bincode::serialize(&data.lookup).unwrap(); + let wallets_and_lookup = WalletsAndLookup { + wallets_bytes, + lookup_bytes, + }; + let encoded: Vec = bincode::serialize(&wallets_and_lookup).unwrap(); + let encrypted_bytes = encrypt_bytes(&encoded, &data.password); + write_wallets(encrypted_bytes)?; + //eprintln!("Saved wallets with password: {}", data.password); + Ok(()) +} + +pub fn show_wallets(s: &mut Cursive) { + s.pop_layer(); + // Need to add change password button + let buttons = LinearLayout::vertical() + .child(Button::new("Import", |s| add_wallet(s))) + .child(Button::new("Create", |s| new_wallet_name(s))) + .child(DummyView) + .child(Button::new("Backup", |s| backup_wallet(s))) + .child(Button::new("Delete", |s| remove_wallet(s))) + .child(DummyView) + .child(Button::new("Back", |s| { + s.pop_layer(); + show_title(s); + })) + .child(DummyView); + + let mut select = SelectView::::new().on_submit(select_wallet); + + let mut i = 1; + let data = &s.user_data::().unwrap(); + for wallet in &data.wallets { + let tag = format!("{}. {}", i, wallet.name); + select.add_item_str(&tag); + i += 1; + } + let select = OnEventView::new(select).on_pre_event_inner(EventTrigger::mouse(), |s, e| { + if let &Event::Mouse { + event: MouseEvent::WheelUp, + .. + } = e + { + let cb = s.select_up(1); + Some(EventResult::Consumed(Some(cb))) + } else if let &Event::Mouse { + event: MouseEvent::WheelDown, + .. + } = e + { + let cb = s.select_down(1); + Some(EventResult::Consumed(Some(cb))) + } else { + None + } + }); + s.add_layer( + Dialog::around( + LinearLayout::vertical().child(DummyView).child( + LinearLayout::horizontal() + .child( + Dialog::around(select.with_name("wallets").scrollable().max_height(6)) + .padding_lrtb(1, 1, 0, 0) + .title("Wallets"), + ) + .child(DummyView) + .child(DummyView) + .child(buttons), + ), + ) + .title("Select a Wallet"), + ); +} + +fn select_account(s: &mut Cursive, _: &str) { + let eventview = s + .find_name::>>("accounts") + .unwrap(); + let select = eventview.get_inner(); + let focus_opt = select.selected_id(); + if focus_opt.is_none() { + s.add_layer(Dialog::info("No account selected.")); + return; + } + let focus = focus_opt.unwrap(); + let data = &mut s.user_data::().unwrap(); + let wallet = &mut data.wallets[data.wallet_idx]; + wallet.acc_idx = focus; + load_current_account(s); +} + +fn load_current_account(s: &mut Cursive) { + let messages = messages::load_messages(s); + let data = &mut s.user_data::().unwrap(); + //eprintln!("Loaded messages: {:?}", messages); + let wallet = &mut data.wallets[data.wallet_idx]; + wallet.accounts[wallet.acc_idx].messages = messages; + receive::load_receivables(s); +} + +fn select_wallet(s: &mut Cursive, _: &str) { + let eventview = s + .find_name::>>("wallets") + .unwrap(); + let select = eventview.get_inner(); + let focus_opt = select.selected_id(); + if focus_opt.is_none() { + s.add_layer(Dialog::info("No wallet selected.")); + return; + } + let focus = focus_opt.unwrap(); + let data = &mut s.user_data::().unwrap(); + data.wallet_idx = focus; + + // Generate accounts for saved indexes + let mut accounts: Vec = vec![]; + //eprintln!("Saved indexes: {:?}", data.wallets[focus].indexes); + for index in &data.wallets[focus].indexes { + accounts.push(Account::with_index( + &data.wallets[focus], + *index, + &data.coin.prefix, + )); + } + data.wallets[focus].accounts = accounts; + show_accounts(s); +} + +pub fn show_accounts(s: &mut Cursive) { + s.pop_layer(); + let data = &mut s.user_data::().unwrap(); + let wallet = &data.wallets[data.wallet_idx]; + let wallet_name = wallet.name.clone(); + let prefix = data.coin.prefix.clone(); + + let mut buttons = LinearLayout::horizontal().child(DummyView); + if !wallet.mnemonic.is_empty() { + buttons.add_child(Button::new("Show next", move |s| { + add_account(s, None, &prefix); + let save_res = save_wallets(s); + s.pop_layer(); + show_accounts(s); + if save_res.is_err() { + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Error saving wallets data"), + ); + } + })); + buttons.add_child(DummyView); + buttons.add_child(Button::new("Show index", |s| add_index(s))); + buttons.add_child(DummyView); + buttons.add_child(Button::new("Hide", |s| { + let data = &mut s.user_data::().unwrap(); + let wallet = &data.wallets[data.wallet_idx]; + if wallet.accounts.len() == 1 { + s.add_layer(Dialog::info("You can't hide your final account!")); + return; + } + remove_account(s); + let save_res = save_wallets(s); + if save_res.is_err() { + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Error saving wallets data"), + ); + } + })); + buttons.add_child(DummyView); + } + buttons.add_child(Button::new("Back", |s| { + s.pop_layer(); + show_wallets(s); + })); + + let mut select = SelectView::::new().on_submit(select_account); + + for account in &data.wallets[data.wallet_idx].accounts { + let tag = format!("{}: {}", account.index, account.address); + select.add_item_str(&tag) + } + + let select = OnEventView::new(select).on_pre_event_inner(EventTrigger::mouse(), |s, e| { + if let &Event::Mouse { + event: MouseEvent::WheelUp, + .. + } = e + { + let cb = s.select_up(1); + Some(EventResult::Consumed(Some(cb))) + } else if let &Event::Mouse { + event: MouseEvent::WheelDown, + .. + } = e + { + let cb = s.select_down(1); + Some(EventResult::Consumed(Some(cb))) + } else { + None + } + }); + s.add_layer( + Dialog::around( + LinearLayout::vertical().child(DummyView).child( + LinearLayout::horizontal().child( + LinearLayout::vertical() + .child(buttons) + .child(DummyView) + .child( + Dialog::around( + select + .with_name("accounts") + .scrollable() + .max_width(38) + .max_height(5), + ) + .padding_lrtb(1, 1, 0, 0) + .title("Accounts"), + ), + ), + ), + ) + .title(wallet_name), + ); +} + +fn add_index(s: &mut Cursive) { + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new("Account index (0 - 4,294,967,295)")) + .child(EditView::new().on_submit(process_idx).with_name("index")), + ) + .h_align(HAlign::Center) + .button("Submit", move |s| { + let idx = s + .call_on_name("index", |view: &mut EditView| view.get_content()) + .unwrap(); + process_idx(s, &idx); + }) + .button("Back", |s| { + s.pop_layer(); + }) + .title("Show account with index"), + ); +} + +fn process_idx(s: &mut Cursive, idx: &str) { + let data = &mut s.user_data::().unwrap(); + let prefix = &data.coin.prefix.clone(); + let wallet = &data.wallets[data.wallet_idx]; + let index_res: Result = idx.parse(); + if index_res.is_err() { + s.add_layer(Dialog::info( + "Error: index was not an integer within the valid range.", + )); + return; + } else if wallet.indexes.contains(index_res.as_ref().unwrap()) { + s.add_layer(Dialog::info("This account has already been added!")); + return; + } else { + add_account(s, Some(index_res.unwrap()), prefix); + let save_res = save_wallets(s); + s.pop_layer(); + s.pop_layer(); + show_accounts(s); + if save_res.is_err() { + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Error saving wallets data"), + ); + } + } +} + +fn backup_wallet(s: &mut Cursive) { + let eventview = s + .find_name::>>("wallets") + .unwrap(); + let select = eventview.get_inner(); + let selected_idx; + match select.selected_id() { + None => { + s.add_layer(Dialog::info("No wallet selected.")); + return; + } + Some(focus) => { + selected_idx = focus; + } + } + let data = &mut s.user_data::().unwrap(); + let wallet = &data.wallets[selected_idx]; + let mut content = Dialog::around(LinearLayout::vertical().child(DummyView).child( + TextView::new(StyledString::styled( + "Make sure you are in a safe location before viewing your mnemonic, seed or key.", + RED, + )), + )) + .h_align(HAlign::Center) + .title("Backup wallet"); + if &wallet.mnemonic != "" { + let mnemonic = wallet.mnemonic.clone(); + content.add_button("Mnemonic", move |s| { + let mnemonic = mnemonic.clone(); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(&mnemonic)), + ) + .h_align(HAlign::Center) + .button("Copy", move |s| { + s.pop_layer(); + s.pop_layer(); + copy_to_clip(s, mnemonic.clone()) + }) + .button("Back", |s| go_back(s)) + .title("Mnemonic") + .max_width(80), + ); + }); + let seed = hex::encode(wallet.seed); + content.add_button("Hex seed", move |s| { + let seed = seed.clone(); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(&seed)), + ) + .h_align(HAlign::Center) + .button("Copy", move |s| { + s.pop_layer(); + s.pop_layer(); + copy_to_clip(s, seed.clone()) + }) + .button("Back", |s| go_back(s)) + .title("Seed"), + ); + }); + } else { + let private_key = hex::encode(wallet.accounts[wallet.acc_idx].private_key); + content.add_button("Private key", move |s| { + let private_key = private_key.clone(); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(&private_key)), + ) + .h_align(HAlign::Center) + .button("Copy", move |s| { + s.pop_layer(); + s.pop_layer(); + copy_to_clip(s, private_key.clone()) + }) + .button("Back", |s| go_back(s)) + .title("Private key"), + ); + }); + } + content.add_button("Back", |s| go_back(s)); + + s.add_layer(content.max_width(80)); +} + +fn remove_account(s: &mut Cursive) { + let mut eventview = s + .find_name::>>("accounts") + .unwrap(); + let select = eventview.get_inner_mut(); + let focus_opt = select.selected_id(); + if focus_opt.is_none() { + s.add_layer(Dialog::info("No account selected.")); + return; + } + let focus = focus_opt.unwrap(); + let data = &mut s.user_data::().unwrap(); + let wallet = &mut data.wallets[data.wallet_idx]; + wallet.accounts.remove(focus); + wallet.indexes.remove(focus); + select.remove_item(focus); +} + +fn remove_wallet(s: &mut Cursive) { + let eventview = s + .find_name::>>("wallets") + .unwrap(); + let select = eventview.get_inner(); + let focus_opt = select.selected_id(); + if focus_opt.is_none() { + s.add_layer(Dialog::info("No wallet selected.")); + return; + } + let focus = focus_opt.unwrap(); + let warning = StyledString::styled( + "If you have not backed up this wallet, all of its accounts will be lost forever.", + RED, + ); + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(warning)) + .child(DummyView), + ) + .h_align(HAlign::Center) + .button("Back", |s| { + s.pop_layer(); + }) + .button("Backup", |s| backup_wallet(s)) + .button("Confirm", move |s| { + let data = &mut s.user_data::().unwrap(); + let wallet = &data.wallets[focus]; + + // Remove account addresses from lookup if they + // have no messages linked. + for account in &wallet.accounts { + let data_dir = dirs::data_dir().unwrap(); + let messages_dir = data_dir.join(DATA_DIR_PATH).join(MESSAGES_DIR_PATH); + if data.lookup.contains_key(&account.address) { + let filename = + format!("{}.dagchat", data.lookup.get(&account.address).unwrap()); + let messages_file = messages_dir.join(filename); + if !messages_file.exists() { + data.lookup.remove(&account.address); + } + } + } + data.wallets.remove(focus); + let save_res = save_wallets(s); + s.pop_layer(); + s.pop_layer(); + show_wallets(s); + if save_res.is_err() { + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Error saving wallets data"), + ); + } + }) + .title("Confirm wallet deletion"), + ); +} + +fn add_wallet(s: &mut Cursive) { + s.pop_layer(); + let data = &s.user_data::().unwrap(); + let coin = &data.coin.name; + let colour = data.coin.colour; + + let name_input = EditView::new() + .content(format!("Default {}", data.wallets.len() + 1)) + .with_name("name"); + let import_msg = format!("Choose a way to import your {} wallet.", coin); + + let content = LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(StyledString::styled("Wallet name", colour))) + .child(name_input) + .child(DummyView) + .child(TextView::new(StyledString::styled(import_msg, colour))); + s.add_layer( + Dialog::around(content) + .h_align(HAlign::Center) + .button("Mnemonic", |s| { + let name = get_name(s); + show_from_mnemonic(s, name); + }) + .button("Seed", |s| { + let name = get_name(s); + from_seedorkey(s, String::from("seed"), name); + }) + .button("Private Key", |s| { + let name = get_name(s); + from_seedorkey(s, String::from("private key"), name); + }) + .button("Back", |s| show_wallets(s)) + .title("Import wallet"), + ); +} + +fn get_name(s: &mut Cursive) -> String { + let name = s + .call_on_name("name", |view: &mut EditView| view.get_content()) + .unwrap(); + s.pop_layer(); + name.to_string() +} + +fn show_from_mnemonic(s: &mut Cursive, name: String) { + s.pop_layer(); + let on_submit_name = name.clone(); + s.add_layer( + Dialog::new() + .title("Enter your 24 word mnemonic") + .padding_lrtb(1, 1, 1, 0) + .content( + EditView::new() + .on_submit(move |s, mnemonic| { + process_from_mnemonic(s, mnemonic, on_submit_name.clone()) + }) + .with_name("mnemonic") + .fixed_width(29), + ) + .h_align(HAlign::Center) + .button("Done", move |s| { + let mnemonic = s + .call_on_name("mnemonic", |view: &mut EditView| view.get_content()) + .unwrap(); + process_from_mnemonic(s, &mnemonic, name.clone()); + }) + .button("Paste", |s| { + s.call_on_name("mnemonic", |view: &mut EditView| { + let mut clipboard = Clipboard::new().unwrap(); + let clip = clipboard + .get_text() + .unwrap_or_else(|_| String::from("Failed to read clipboard.")); + view.set_content(clip); + }) + .unwrap(); + }) + .button("Back", |s| { + show_wallets(s); + }), + ); +} + +fn process_from_mnemonic(s: &mut Cursive, mnemonic: &str, name: String) { + let seed = validate_mnemonic(&mnemonic); + let content; + s.pop_layer(); + if !mnemonic.is_empty() && seed.is_some() { + let seed_bytes = seed.unwrap(); + let data = &s.user_data::().unwrap(); + let wallet = Wallet::new(mnemonic.to_string(), seed_bytes, name, &data.coin.prefix); + setup_wallet(s, wallet, |s| { + import_success(s, "Successfully imported wallet from mnemonic phrase.") + }); + } else { + content = "The mnemonic you entered was not valid."; + s.add_layer( + Dialog::around(TextView::new(content)) + .button("Back", move |s| show_from_mnemonic(s, name.clone())), + ); + return; + } +} + +fn from_seedorkey(s: &mut Cursive, seed_or_key: String, name: String) { + s.pop_layer(); + let on_submit_seed_or_key = seed_or_key.clone(); + let on_submit_name = name.clone(); + s.add_layer( + Dialog::new() + .title(format!("Enter your {}", seed_or_key)) + .padding_lrtb(1, 1, 1, 0) + .content( + EditView::new() + .on_submit(move |s, sork_raw| { + process_from_seedorkey( + s, + sork_raw.to_string(), + &on_submit_seed_or_key, + on_submit_name.clone(), + ); + }) + .with_name("seedorkey") + .fixed_width(29), + ) + .h_align(HAlign::Center) + .button("Done", move |s| { + let sork_raw = s + .call_on_name("seedorkey", |view: &mut EditView| view.get_content()) + .unwrap(); + process_from_seedorkey(s, sork_raw.to_string(), &seed_or_key, name.clone()); + }) + .button("Paste", |s| { + s.call_on_name("seedorkey", |view: &mut EditView| { + let mut clipboard = Clipboard::new().unwrap(); + let clip = clipboard + .get_text() + .unwrap_or_else(|_| String::from("Failed to read clipboard.")); + view.set_content(clip); + }) + .unwrap(); + }) + .button("Back", |s| { + show_wallets(s); + }), + ); +} + +fn process_from_seedorkey(s: &mut Cursive, sork_raw: String, seed_or_key: &String, name: String) { + let sork_val = sork_raw.trim(); + if sork_val.len() != 64 { + s.add_layer(Dialog::info(format!( + "Error: {} was invalid - not 64 characters long.", + seed_or_key + ))); + return; + } + let bytes_opt = hex::decode(sork_val); + if bytes_opt.is_err() { + s.add_layer(Dialog::info(format!( + "Error: {} was invalid - failed to decode hex.", + seed_or_key + ))); + return; + } + let bytes = bytes_opt.unwrap(); + let sork_bytes: [u8; 32] = bytes.try_into().unwrap(); + let data = &s.user_data::().unwrap(); + let wallet: Wallet; + if seed_or_key == "seed" { + let mnemonic = seed_to_mnemonic(&sork_bytes); + wallet = Wallet::new(mnemonic, sork_bytes, name, &data.coin.prefix); + } else { + wallet = Wallet::new_key(sork_bytes, name, &data.coin.prefix); + } + let content = format!("Successfully imported wallet from {}.", seed_or_key); + setup_wallet(s, wallet, move |s| import_success(s, &content)); +} + +fn new_wallet_name(s: &mut Cursive) { + s.pop_layer(); + let data = &s.user_data::().unwrap(); + let colour = data.coin.colour; + let name_input = EditView::new() + .on_submit(new_wallet) + .content(format!("Default {}", data.wallets.len() + 1)) + .with_name("name"); + + let content = LinearLayout::vertical() + .child(DummyView) + .child(TextView::new(StyledString::styled("Wallet name", colour))) + .child(name_input); + + s.add_layer( + Dialog::around(content) + .button("Done", |s| { + let name = s + .call_on_name("name", |view: &mut EditView| view.get_content()) + .unwrap(); + new_wallet(s, &name); + }) + .title("Set up new wallet"), + ); +} + +fn new_wallet(s: &mut Cursive, name: &str) { + s.pop_layer(); + s.pop_layer(); + let data = &s.user_data::().unwrap(); + let mut csprng = rand::thread_rng(); + let mut seed_bytes = [0u8; 32]; + csprng.fill_bytes(&mut seed_bytes); + let mnemonic = seed_to_mnemonic(&seed_bytes); + let wallet = Wallet::new( + mnemonic.clone(), + seed_bytes, + name.to_string(), + &data.coin.prefix, + ); + setup_wallet(s, wallet, move |s| { + create_success(s, mnemonic.clone(), hex::encode(seed_bytes)) + }); +} +fn setup_wallet(s: &mut Cursive, wallet: Wallet, on_success: F) +where + F: Fn(&mut Cursive), +{ + let data = &mut s.user_data::().unwrap(); + data.wallets.push(wallet); + data.wallet_idx = data.wallets.len() - 1; + + let data = &mut s.user_data::().unwrap(); + if data.wallets.len() == 1 && data.password.is_empty() { + set_password(s, on_success); + } else { + let save_res = save_wallets(s); + if save_res.is_ok() { + on_success(s); + } else { + show_wallets(s); + s.add_layer( + Dialog::info(StyledString::styled(save_res.err().unwrap(), RED)) + .title("Error saving wallets data"), + ); + } + } +} + +fn set_password(s: &mut Cursive, on_success: F) +where + F: Fn(&mut Cursive), +{ + let warning = StyledString::styled( + "Always backup or write down your mnemonics, seeds or keys elsewhere in case you forget your password.", RED); + + s.add_layer( + Dialog::around( + LinearLayout::vertical() + .child(DummyView) + .child(TextView::new("Enter password")) + .child(EditView::new().secret().with_name("password")) + .child(DummyView) + .child(TextView::new("Confirm password")) + .child(EditView::new().secret().with_name("confirm")) + .child(DummyView) + .child(TextView::new(warning)), + ) + .h_align(HAlign::Center) + .button("Submit", move |s| { + let password = s + .call_on_name("password", |view: &mut EditView| view.get_content()) + .unwrap(); + let confirmed = s + .call_on_name("confirm", |view: &mut EditView| view.get_content()) + .unwrap(); + if password.is_empty() { + s.add_layer(Dialog::info("Password can't be blank.")); + return; + } + if password != confirmed { + s.add_layer(Dialog::info("Passwords did not match.")); + return; + } + let msg_save_res = messages::change_message_passwords(s, &password); + let data = &mut s.user_data::().unwrap(); + data.password = password.to_string(); + let acc_save_res = save_wallets(s); + s.pop_layer(); + if acc_save_res.is_ok() && msg_save_res.is_ok() { + on_success(s); + } else if acc_save_res.is_err() { + show_wallets(s); + s.add_layer(Dialog::info(StyledString::styled(acc_save_res.err().unwrap(), + RED, + )).title("Fatal error saving wallets")); + } else if msg_save_res.is_err() { + show_wallets(s); + s.add_layer(Dialog::info(StyledString::styled(msg_save_res.err().unwrap(), + RED, + )).title("Fatal error saving messages")); + } + }) + .button("Info", |s| { + let content = "\nThe password you are setting up for dagchat is used to encrypt your wallets, messages (If 'Encrypt and save' messages setting is selected) and address book when they are saved on your device. It should be strong and contain a range of characters (UPPERCASE, lowercase, numb3rs and symbo!s). Without this password, dagchat will not be able to decrypt any of your saved wallets, messages or address book."; + s.add_layer(Dialog::info(content).title("What is this password?").max_width(80)); + }) + .title("Create a password for dagchat") + .max_width(80), + ); +} +fn import_success(s: &mut Cursive, content: &str) { + s.add_layer( + Dialog::around(TextView::new(content).max_width(80)) + .button("Load", |s| load_current_account(s)) + .button("Back", |s| { + s.pop_layer(); + show_wallets(s); + }), + ); +} + +fn create_success(s: &mut Cursive, mnemonic: String, seed: String) { + s.pop_layer(); + let data = &mut s.user_data::().unwrap(); + let mut content = StyledString::styled("\nMnemonic\n", data.coin.colour); + content.append(StyledString::styled(&mnemonic, OFF_WHITE)); + content.append(StyledString::styled("\n\nSeed\n", data.coin.colour)); + content.append(StyledString::styled(&seed, OFF_WHITE)); + s.add_layer( + Dialog::around(TextView::new(content).max_width(80)) + .h_align(HAlign::Center) + .button("Load", |s| load_current_account(s)) + .button("Back", |s| show_wallets(s)) + .button("Copy mnemonic", move |s| copy_to_clip(s, mnemonic.clone())) + .button("Copy seed", move |s| copy_to_clip(s, seed.clone())) + .title("Successfully generated new wallet") + .max_width(80), + ); +} From b78ca6ceb3a82ebdcd60967bcde13eb46f3210d2 Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:11:07 +0100 Subject: [PATCH 10/12] Change clip crate 1. Moved from arboard crate to https://github.com/alacritty/copypasta to hopefully fix certain Linux OS issues. --- Cargo.lock | 431 ++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- src/main.rs | 10 +- src/messages.rs | 4 +- src/send.rs | 4 +- src/wallets.rs | 8 +- 6 files changed, 231 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6d4f36..c78da87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "aead" version = "0.3.2" @@ -114,26 +102,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "arboard" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6045ca509e4abacde2b884ac4618a51d0c017b5d85a3ee84a7226eb33b3154a9" -dependencies = [ - "clipboard-win", - "core-graphics", - "image", - "log", - "objc", - "objc-foundation", - "objc_id", - "once_cell", - "parking_lot 0.12.0", - "thiserror", - "winapi", - "x11rb", -] - [[package]] name = "arrayref" version = "0.3.6" @@ -254,12 +222,6 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" -[[package]] -name = "bytemuck" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" - [[package]] name = "byteorder" version = "1.4.3" @@ -317,27 +279,33 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "4.4.1" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" dependencies = [ - "error-code", - "str-buf", + "lazy-bytes-cast", "winapi", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "copypasta" +version = "0.7.1" +source = "git+https://github.com/alacritty/copypasta#ecafec9b6f944c38c99c4fc6cab4d9c65d21ecb6" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -354,31 +322,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" -dependencies = [ - "bitflags", - "core-foundation", - "foreign-types", - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.2" @@ -394,15 +337,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-channel" version = "0.5.4" @@ -583,12 +517,12 @@ name = "dagchat" version = "1.0.0" dependencies = [ "aes-gcm 0.9.4", - "arboard", "bigdecimal", "bincode", "bitreader", "blake2 0.10.4", "chrono", + "copypasta", "crossterm 0.23.2", "cursive", "cursive_buffered_backend", @@ -667,16 +601,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - [[package]] name = "digest" version = "0.9.0" @@ -717,6 +641,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "ecies-ed25519" version = "0.5.1" @@ -808,16 +747,6 @@ dependencies = [ "syn", ] -[[package]] -name = "error-code" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" -dependencies = [ - "libc", - "str-buf", -] - [[package]] name = "fastrand" version = "1.7.0" @@ -916,16 +845,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "getrandom" version = "0.1.16" @@ -1122,22 +1041,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-iter", - "num-rational 0.3.2", - "num-traits", - "png", - "tiff", -] - [[package]] name = "indexmap" version = "1.8.1" @@ -1169,12 +1072,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" - [[package]] name = "js-sys" version = "0.3.57" @@ -1184,6 +1081,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1196,6 +1099,16 @@ version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "lock_api" version = "0.4.7" @@ -1236,6 +1149,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -1252,23 +1174,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" @@ -1337,6 +1246,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntapi" version = "0.3.7" @@ -1355,7 +1274,7 @@ dependencies = [ "num-complex", "num-integer", "num-iter", - "num-rational 0.4.0", + "num-rational", "num-traits", ] @@ -1400,17 +1319,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.0" @@ -1605,18 +1513,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate", - "miniz_oxide 0.3.7", -] - [[package]] name = "polyval" version = "0.4.5" @@ -1655,6 +1551,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.18" @@ -1798,6 +1703,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1943,6 +1854,34 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "smithay-client-toolkit" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" +dependencies = [ + "bitflags", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + [[package]] name = "socket2" version = "0.4.4" @@ -1959,12 +1898,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "str-buf" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" - [[package]] name = "subtle" version = "2.4.1" @@ -2028,17 +1961,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.4", - "weezl", -] - [[package]] name = "time" version = "0.1.44" @@ -2327,6 +2249,79 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.57" @@ -2337,12 +2332,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "weezl" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4" - [[package]] name = "winapi" version = "0.3.9" @@ -2359,15 +2348,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-wsapoll" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2427,15 +2407,32 @@ dependencies = [ ] [[package]] -name = "x11rb" -version = "0.9.0" +name = "x11-clipboard" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e99be55648b3ae2a52342f9a870c0e138709a3493261ce9b469afe6e4df6d8a" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" dependencies = [ - "gethostname", - "nix", - "winapi", - "winapi-wsapoll", + "xcb", +] + +[[package]] +name = "xcb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +dependencies = [ + "libc", + "log", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", ] [[package]] @@ -2444,6 +2441,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + [[package]] name = "zeroize" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index fdd0343..e84eece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,5 +27,5 @@ serde_json = "1.0.74" serde = { version = "1.0.136", features = ["derive"] } reqwest = {version = "0.11.10", features = ["blocking"]} crossterm = "0.23.2" -arboard = "2.1.0" +copypasta = {git = "https://github.com/alacritty/copypasta"} bigdecimal = "0.3.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cf87d12..b625a35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -use arboard::Clipboard; use bincode; +use copypasta::{ClipboardContext, ClipboardProvider}; use cursive::align::HAlign; use cursive::theme::{BaseColor, BorderStyle, Color, PaletteColor, Theme}; use cursive::traits::*; @@ -232,9 +232,9 @@ fn show_change_rep(s: &mut Cursive) { LinearLayout::horizontal() .child(Button::new("Paste", |s| { s.call_on_name("address", |view: &mut TextArea| { - let mut clipboard = Clipboard::new().unwrap(); + let mut clipboard = ClipboardContext::new().unwrap(); let clip = clipboard - .get_text() + .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); view.set_content(clip); }) @@ -280,9 +280,9 @@ fn show_change_rep(s: &mut Cursive) { } fn copy_to_clip(s: &mut Cursive, string: String) { - let mut clipboard = Clipboard::new().unwrap(); + let mut clipboard = ClipboardContext::new().unwrap(); let data = &s.user_data::().unwrap(); - let copied = clipboard.set_text(string.clone()); + let copied = clipboard.set_contents(string.clone()); if copied.is_err() { s.add_layer(Dialog::info(StyledString::styled( "Error copying to clipboard.", diff --git a/src/messages.rs b/src/messages.rs index 5c606fd..f8170bf 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -58,9 +58,9 @@ pub fn show_search(s: &mut Cursive, filter: Filter) { .child(TextArea::new().with_name("search").max_width(66)) .child(LinearLayout::horizontal().child(Button::new("Paste", |s| { s.call_on_name("search", |view: &mut TextArea| { - let mut clipboard = Clipboard::new().unwrap(); + let mut clipboard = ClipboardContext::new().unwrap(); let clip = clipboard - .get_text() + .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); view.set_content(clip); }) diff --git a/src/send.rs b/src/send.rs index 10a323c..f634e84 100644 --- a/src/send.rs +++ b/src/send.rs @@ -43,9 +43,9 @@ pub fn show_send(s: &mut Cursive, with_message: bool) { LinearLayout::horizontal() .child(Button::new("Paste", |s| { s.call_on_name("address", |view: &mut TextArea| { - let mut clipboard = Clipboard::new().unwrap(); + let mut clipboard = ClipboardContext::new().unwrap(); let clip = clipboard - .get_text() + .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); view.set_content(clip); }) diff --git a/src/wallets.rs b/src/wallets.rs index 10b1df0..e558f47 100644 --- a/src/wallets.rs +++ b/src/wallets.rs @@ -755,9 +755,9 @@ fn show_from_mnemonic(s: &mut Cursive, name: String) { }) .button("Paste", |s| { s.call_on_name("mnemonic", |view: &mut EditView| { - let mut clipboard = Clipboard::new().unwrap(); + let mut clipboard = ClipboardContext::new().unwrap(); let clip = clipboard - .get_text() + .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); view.set_content(clip); }) @@ -820,9 +820,9 @@ fn from_seedorkey(s: &mut Cursive, seed_or_key: String, name: String) { }) .button("Paste", |s| { s.call_on_name("seedorkey", |view: &mut EditView| { - let mut clipboard = Clipboard::new().unwrap(); + let mut clipboard = ClipboardContext::new().unwrap(); let clip = clipboard - .get_text() + .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); view.set_content(clip); }) From 2f48670d9e25fbf9a8fd3670b0d63c3d3bd88959 Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:20:57 +0100 Subject: [PATCH 11/12] Fixed copy to clipboard 1. (Hopefully) Fixed copying to clipboard for Linux OSes --- Cargo.lock | 251 ++++-------------------------------------------- Cargo.toml | 2 +- src/main.rs | 6 +- src/messages.rs | 2 +- src/send.rs | 2 +- src/wallets.rs | 4 +- 6 files changed, 28 insertions(+), 239 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c78da87..d14cf94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,13 +277,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clipboard" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "x11-clipboard", +] + [[package]] name = "clipboard-win" -version = "3.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" dependencies = [ - "lazy-bytes-cast", "winapi", ] @@ -293,19 +305,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "copypasta" -version = "0.7.1" -source = "git+https://github.com/alacritty/copypasta#ecafec9b6f944c38c99c4fc6cab4d9c65d21ecb6" -dependencies = [ - "clipboard-win", - "objc", - "objc-foundation", - "objc_id", - "smithay-clipboard", - "x11-clipboard", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -522,7 +521,7 @@ dependencies = [ "bitreader", "blake2 0.10.4", "chrono", - "copypasta", + "clipboard", "crossterm 0.23.2", "cursive", "cursive_buffered_backend", @@ -641,21 +640,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dlib" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" -dependencies = [ - "libloading", -] - -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - [[package]] name = "ecies-ed25519" version = "0.5.1" @@ -1081,12 +1065,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy-bytes-cast" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" - [[package]] name = "lazy_static" version = "1.4.0" @@ -1099,16 +1077,6 @@ version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" -[[package]] -name = "libloading" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "lock_api" version = "0.4.7" @@ -1149,36 +1117,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" -[[package]] -name = "memmap2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "mio" version = "0.7.14" @@ -1233,29 +1177,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nix" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "memoffset", -] - -[[package]] -name = "nom" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "ntapi" version = "0.3.7" @@ -1551,15 +1472,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "quick-xml" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.18" @@ -1703,12 +1615,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scopeguard" version = "1.1.0" @@ -1854,34 +1760,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "smithay-client-toolkit" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" -dependencies = [ - "bitflags", - "dlib", - "lazy_static", - "log", - "memmap2", - "nix", - "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols", -] - -[[package]] -name = "smithay-clipboard" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55" -dependencies = [ - "smithay-client-toolkit", - "wayland-client", -] - [[package]] name = "socket2" version = "0.4.4" @@ -2249,79 +2127,6 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" -[[package]] -name = "wayland-client" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" -dependencies = [ - "bitflags", - "downcast-rs", - "libc", - "nix", - "scoped-tls", - "wayland-commons", - "wayland-scanner", - "wayland-sys", -] - -[[package]] -name = "wayland-commons" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" -dependencies = [ - "nix", - "once_cell", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-cursor" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" -dependencies = [ - "nix", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" -dependencies = [ - "bitflags", - "wayland-client", - "wayland-commons", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" -dependencies = [ - "proc-macro2", - "quote", - "xml-rs", -] - -[[package]] -name = "wayland-sys" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" -dependencies = [ - "dlib", - "lazy_static", - "pkg-config", -] - [[package]] name = "web-sys" version = "0.3.57" @@ -2408,31 +2213,21 @@ dependencies = [ [[package]] name = "x11-clipboard" -version = "0.5.3" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" +checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea" dependencies = [ "xcb", ] [[package]] name = "xcb" -version = "0.10.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de" dependencies = [ "libc", "log", - "quick-xml", -] - -[[package]] -name = "xcursor" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" -dependencies = [ - "nom", ] [[package]] @@ -2441,12 +2236,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "zeroize" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index e84eece..3cf3ef4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,5 +27,5 @@ serde_json = "1.0.74" serde = { version = "1.0.136", features = ["derive"] } reqwest = {version = "0.11.10", features = ["blocking"]} crossterm = "0.23.2" -copypasta = {git = "https://github.com/alacritty/copypasta"} +clipboard = "0.5.0" bigdecimal = "0.3.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b625a35..b9c0287 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use bincode; -use copypasta::{ClipboardContext, ClipboardProvider}; +use clipboard::{ClipboardContext, ClipboardProvider}; use cursive::align::HAlign; use cursive::theme::{BaseColor, BorderStyle, Color, PaletteColor, Theme}; use cursive::traits::*; @@ -232,7 +232,7 @@ fn show_change_rep(s: &mut Cursive) { LinearLayout::horizontal() .child(Button::new("Paste", |s| { s.call_on_name("address", |view: &mut TextArea| { - let mut clipboard = ClipboardContext::new().unwrap(); + let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap(); let clip = clipboard .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); @@ -280,7 +280,7 @@ fn show_change_rep(s: &mut Cursive) { } fn copy_to_clip(s: &mut Cursive, string: String) { - let mut clipboard = ClipboardContext::new().unwrap(); + let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap(); let data = &s.user_data::().unwrap(); let copied = clipboard.set_contents(string.clone()); if copied.is_err() { diff --git a/src/messages.rs b/src/messages.rs index f8170bf..b42dc71 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -58,7 +58,7 @@ pub fn show_search(s: &mut Cursive, filter: Filter) { .child(TextArea::new().with_name("search").max_width(66)) .child(LinearLayout::horizontal().child(Button::new("Paste", |s| { s.call_on_name("search", |view: &mut TextArea| { - let mut clipboard = ClipboardContext::new().unwrap(); + let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap(); let clip = clipboard .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); diff --git a/src/send.rs b/src/send.rs index f634e84..5cf0dcd 100644 --- a/src/send.rs +++ b/src/send.rs @@ -43,7 +43,7 @@ pub fn show_send(s: &mut Cursive, with_message: bool) { LinearLayout::horizontal() .child(Button::new("Paste", |s| { s.call_on_name("address", |view: &mut TextArea| { - let mut clipboard = ClipboardContext::new().unwrap(); + let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap(); let clip = clipboard .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); diff --git a/src/wallets.rs b/src/wallets.rs index e558f47..fb17b67 100644 --- a/src/wallets.rs +++ b/src/wallets.rs @@ -755,7 +755,7 @@ fn show_from_mnemonic(s: &mut Cursive, name: String) { }) .button("Paste", |s| { s.call_on_name("mnemonic", |view: &mut EditView| { - let mut clipboard = ClipboardContext::new().unwrap(); + let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap(); let clip = clipboard .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); @@ -820,7 +820,7 @@ fn from_seedorkey(s: &mut Cursive, seed_or_key: String, name: String) { }) .button("Paste", |s| { s.call_on_name("seedorkey", |view: &mut EditView| { - let mut clipboard = ClipboardContext::new().unwrap(); + let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap(); let clip = clipboard .get_contents() .unwrap_or_else(|_| String::from("Failed to read clipboard.")); From 6036fb1eee53bc016dc9fe77f493d081d2243e9b Mon Sep 17 00:00:00 2001 From: derfarctor <97409490+derfarctor@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:33:18 +0100 Subject: [PATCH 12/12] Updated images, info, and building advice --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fad688c..d2bb255 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,17 @@ An open source wallet with end-to-end encrypted, on chain messaging for nano and This application is not yet released. Any versions are test versions and come with no guarantees. Whilst messages are in theory encrypted end-to-end, the encrypted data is being put onto a public blockchain. The cryptography used in the dagchat protocol (and implementation of) has not been audited so it is advised not to include sensitive data within messages. # Features -- Import accounts using a 24 word mnemonic phrase or a 64 character seed. Or generate a new seed within the app. ![image](https://user-images.githubusercontent.com/97409490/162834155-f9680392-8e4f-41b9-8721-4b3b36fb5da1.png) -- Send on chain, end to end encrypted memos/messages using the dagchat protocol. ![image](https://user-images.githubusercontent.com/97409490/162834972-494bc8e5-7c5a-40f2-b14d-eb47313746c2.png) -- Receive your nano and banano, and read incoming messages all in the same place. ![image](https://user-images.githubusercontent.com/97409490/162835371-1f18feac-75c1-4eb6-8885-b957a34f1132.png) ![image](https://user-images.githubusercontent.com/97409490/162835416-22b6df85-ed37-4cb1-9ab1-db34c3b97725.png) -## Other images -![image](https://user-images.githubusercontent.com/97409490/162835489-e49ad8ba-a0b7-4ccb-9145-6935aaf8f73b.png) ![image](https://user-images.githubusercontent.com/97409490/162835520-b0763c63-6eae-4954-aeee-554488530e34.png) +- Import multiple wallets using a mnemonic phrase, hex seed, or even private key. Each wallet supports many accounts which can be shown procedurally or by specifying an index.
![image](https://user-images.githubusercontent.com/97409490/165167183-11114b67-71e3-4fcd-85a6-a2a4ff6a0f1e.png) ![image](https://user-images.githubusercontent.com/97409490/165167265-30516a86-5c99-448f-930a-b3ccd1d4bd08.png) +- Send on chain, end to end encrypted memos/messages using the dagchat protocol.
![image](https://user-images.githubusercontent.com/97409490/165167726-ec9a9fa9-ffa0-4c2f-8a63-eddc612abdbf.png) +- Receive your nano and banano, and read incoming messages all in the same place.
![image](https://user-images.githubusercontent.com/97409490/165168179-358d2fac-57b5-4ef9-b1ec-35db00f3fe2b.png) +- Messages are identified automatically by the wallet. +
![image](https://user-images.githubusercontent.com/97409490/165168312-18bc63d4-8912-4278-9f83-b2390400ba49.png) +- Messages when sent and received are automatically encrypted and saved to your computer. They can be read again in the message history tab. +
![image](https://user-images.githubusercontent.com/97409490/165168937-b41d7884-4dd5-4c60-a934-e57a07b82742.png) + +# Building from source +To build dagchat from source, you will need to have rust and cargo installed on your machine: https://www.rust-lang.org/tools/install +1. Clone the repository or download the zip and extract it. +2. If you are building for Linux (Windows and MacOS skip this step) you will need to install some other dependencies that are used for the rust-clipboard crate that manages copying and pasting in dagchat: `sudo apt-get install libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev`. +3. Enter the repository's directory (either the clone, or the extracted zip) and run `cargo build --release` to build an executable in release mode. This will appear in `/target/release/`. +4. The application should be built and ready to run.