diff --git a/Cargo.toml b/Cargo.toml index 45c8441..b47f2f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ rust-crypto = "^0.2" num_cpus = "1.13.0" byteorder = "1.3.2" web-view = { version = "0.7.2", features = [] } +tinyfiledialogs = "3.3.10" serde = { version = "1.0.102", features = ["derive"] } serde_json = "1.0.42" num-bigint = "0.2" diff --git a/src/context.rs b/src/context.rs index 8e2b21f..035cf01 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,6 +3,7 @@ use crate::event::Event; use serde::{Serialize, Deserialize}; use std::fs::File; use std::io::Read; +use std::sync::MutexGuard; pub struct Context { pub settings: Settings, diff --git a/src/event.rs b/src/event.rs index e500601..4ca0c2a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -4,6 +4,9 @@ pub enum Event { MinerStopped, KeyGeneratorStarted, KeyGeneratorStopped, + KeyCreated {path: String, public: String}, + KeyLoaded {path: String, public: String}, + KeySaved {path: String, public: String}, NewBlockReceived, BlockchainChanged, ActionStopMining, diff --git a/src/keys.rs b/src/keys.rs index 9094079..9e2304f 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -21,6 +21,8 @@ pub struct Keystore { private_key: Bytes, public_key: Bytes, #[serde(skip)] + path: String, + #[serde(skip)] seed: Vec } @@ -30,18 +32,21 @@ impl Keystore { let mut rng = thread_rng(); rng.fill(&mut buf); let (private, public) = keypair(&buf); - Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), seed: Vec::from(&buf[..])} + Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(&buf[..])} } pub fn from_bytes(seed: &[u8]) -> Self { let (private, public) = keypair(&seed); - Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), seed: Vec::from(seed)} + Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(seed)} } pub fn from_file(filename: &str, _password: &str) -> Option { - match fs::read(&Path::new(filename)) { + let path = Path::new(filename); + match fs::read(&path) { Ok(key) => { - Some(Self::from_bytes(key.as_slice())) + let mut keystore = Self::from_bytes(key.as_slice()); + keystore.path = path.to_str().unwrap().to_owned(); + Some(keystore) }, Err(_) => { None @@ -50,11 +55,12 @@ impl Keystore { } //TODO Implement error conditions - pub fn save(&self, filename: &str, _password: &str) { + pub fn save(&mut self, filename: &str, _password: &str) { match File::create(Path::new(filename)) { Ok(mut f) => { //TODO implement key encryption f.write_all(&self.seed).expect("Error saving keystore"); + self.path = filename.to_owned(); } Err(_) => { println!("Error saving key file!"); } } @@ -68,6 +74,10 @@ impl Keystore { self.private_key.clone() } + pub fn get_path(&self) -> &str { + &self.path + } + pub fn sign(&self, message: &[u8]) -> [u8; 64] { signature(message, self.private_key.data.as_slice()) } @@ -120,6 +130,10 @@ impl Bytes { self.data.as_slice() } + pub fn to_string(&self) -> String { + crate::utils::to_hex(&self.data) + } + pub fn zero32() -> Self { Bytes { data: [0u8; 32].to_vec() } } diff --git a/src/main.rs b/src/main.rs index 4eb7b2c..8b62f1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ #![windows_subsystem = "windows"] extern crate web_view; +extern crate tinyfiledialogs as tfd; use std::sync::{Arc, Mutex}; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, Ordering, AtomicUsize}; use std::thread; use rand::RngCore; @@ -27,7 +28,10 @@ fn main() { println!("ALFIS 0.1.0"); let settings = Settings::load(SETTINGS_FILENAME).expect("Error loading settings"); let keystore: Keystore = match Keystore::from_file(&settings.key_file, "") { - None => { generate_key(KEYSTORE_DIFFICULTY, Arc::new(AtomicBool::new(true))).expect("Could not load or generate keypair") } + None => { + println!("Generated temporary keystore. Please, generate full-privileged keys."); + Keystore::new() + } Some(keystore) => { keystore } }; let blockchain: Blockchain = Blockchain::new(&settings); @@ -81,36 +85,66 @@ fn run_interface(context: Arc>, miner: Arc>) { Loaded => { web_view.eval("showMiningIndicator(false);").expect("Error evaluating!"); let handle = web_view.handle(); + let context_copy = context.clone(); let mut c = context.lock().unwrap(); c.bus.register(move |_uuid, e| { println!("Got event from bus {:?}", &e); - let visible = match e { - Event::MinerStarted => { true } - Event::KeyGeneratorStarted => { true } - Event::MinerStopped => { false } - Event::KeyGeneratorStopped => { false } - _ => { false } + let eval = match e { + Event::KeyCreated { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } + Event::KeyLoaded { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } + Event::KeySaved { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } + Event::MinerStarted => { format!("showMiningIndicator({});", true) } + Event::KeyGeneratorStarted => { format!("showMiningIndicator({});", true) } + Event::MinerStopped => { format!("showMiningIndicator({});", false) } + Event::KeyGeneratorStopped => { format!("showMiningIndicator({});", false) } + _ => { String::new() } }; - handle.dispatch(move |web_view| { - web_view.eval(&format!("showMiningIndicator({});", visible)).expect("Error evaluating!"); - return WVResult::Ok(()); - }).expect("Error dispatching!"); + + if !eval.is_empty() { + println!("Evaluating {}", &eval); + handle.dispatch(move |web_view| { + web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!"); + return WVResult::Ok(()); + }).expect("Error dispatching!"); + } true }); } - LoadKey { name, pass } => { - match Keystore::from_file(&name, &pass) { - None => { - println!("Error loading keystore '{}'!", &name); - }, - Some(keystore) => { - let mut c = context.lock().unwrap(); - c.set_keystore(keystore); + LoadKey {} => { + let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key"], "*.key"))); + match result { + None => {} + Some(file_name) => { + match Keystore::from_file(&file_name, "") { + None => { + println!("Error loading keystore '{}'!", &file_name); + }, + Some(keystore) => { + println!("Loaded keystore with key: {:?}", &keystore.get_public()); + let mut c = context.lock().unwrap(); + c.bus.post(Event::KeyLoaded {path: keystore.get_path().to_owned(), public: keystore.get_public().to_string()}); + c.set_keystore(keystore); + } + } } } }, - CreateKey { name, pass } => { - create_key(context.clone(), &name, &pass); + CreateKey {} => { + create_key(context.clone()); + } + SaveKey {} => { + let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)"); + match result { + None => {} + Some(new_path) => { + let mut c = context.lock().unwrap(); + let path = new_path.clone(); + let public = c.keystore.get_public().to_string(); + c.keystore.save(&new_path, ""); + println!("Key file saved to {}", &path); + c.bus.post(Event::KeySaved {path, public }); + } + } } CheckDomain { name} => { let c = context.lock().unwrap(); @@ -170,28 +204,32 @@ fn create_transaction>(keystore: &Keystore, name: S, method: S, transaction } -fn create_key(context: Arc>, filename: &str, password: &str) { +fn create_key(context: Arc>) { let mining = Arc::new(AtomicBool::new(true)); + let miners_count = Arc::new(AtomicUsize::new(0)); { context.lock().unwrap().bus.post(Event::KeyGeneratorStarted); } for _ in 0..num_cpus::get() { let context = context.clone(); - let filename= filename.to_owned(); - let password= password.to_owned(); let mining = mining.clone(); + let miners_count = miners_count.clone(); thread::spawn(move || { + miners_count.fetch_add(1, Ordering::Relaxed); match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) { None => { println!("Keystore mining finished"); - context.lock().unwrap().bus.post(Event::KeyGeneratorStopped); } Some(keystore) => { + println!("Key mined successfully: {:?}", &keystore.get_public()); let mut c = context.lock().unwrap(); mining.store(false,Ordering::Relaxed); - keystore.save(&filename, &password); + c.bus.post(Event::KeyCreated {path: keystore.get_path().to_owned(), public: keystore.get_public().to_string()}); c.set_keystore(keystore); - c.bus.post(Event::KeyGeneratorStopped); } } + let miners = miners_count.fetch_sub(1, Ordering::Relaxed) - 1; + if miners == 0 { + context.lock().unwrap().bus.post(Event::KeyGeneratorStopped); + } }); } context.lock().unwrap().bus.register(move |_uuid, e| { @@ -222,8 +260,9 @@ fn generate_key(difficulty: usize, mining: Arc) -> Option #[serde(tag = "cmd", rename_all = "camelCase")] pub enum Cmd { Loaded, - LoadKey{name: String, pass: String}, - CreateKey{name: String, pass: String}, + LoadKey{}, + CreateKey{}, + SaveKey{}, CheckDomain{name: String}, CreateDomain{name: String, records: String, tags: String}, ChangeDomain{name: String, records: String, tags: String}, diff --git a/src/miner.rs b/src/miner.rs index 8e378ed..7814d33 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -10,6 +10,7 @@ use num_cpus; use crate::{Block, Bytes, Context, hash_is_good, Transaction}; use crate::event::Event; +use std::ops::DerefMut; pub struct Miner { context: Arc>, diff --git a/src/p2p/network.rs b/src/p2p/network.rs index 69eb62e..453822d 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -13,6 +13,7 @@ use mio::net::{TcpListener, TcpStream}; use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers}; use std::net::{SocketAddr, IpAddr, SocketAddrV4, Shutdown}; +use std::ops::DerefMut; const SERVER: Token = Token(0); const POLL_TIMEOUT: Option = Some(Duration::from_millis(3000)); diff --git a/src/webview/index.html b/src/webview/index.html index d288ba8..7367123 100644 --- a/src/webview/index.html +++ b/src/webview/index.html @@ -37,9 +37,7 @@