diff --git a/Cargo.toml b/Cargo.toml index 9b6651b..cdd2573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ toml = "0.5.8" digest = "0.9.0" sha2 = "0.9.3" ed25519-dalek = "1.0" -x25519-dalek = "1.1" +ecies-ed25519 = "0.5" chacha20poly1305 = "0.7.1" signature = "1.3.0" blakeout = "0.3.0" @@ -27,6 +27,7 @@ byteorder = "1.4.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.64" bincode = "1.3" +base64 = "0.13" num-bigint = "0.4" num-traits = "0.2.14" chrono = { version = "0.4", features = ["serde"] } @@ -50,8 +51,6 @@ thread-priority = "0.2.1" thread-priority = "0.2.1" [build-dependencies] -minreq = { version = "2.3.1", features = ["punycode", "https-rustls"] } -rust-crypto = "^0.2" # TODO change to sha2 winres = "0.1" [dev-dependencies] diff --git a/src/blockchain/block.rs b/src/blockchain/block.rs index a88353d..8f64901 100644 --- a/src/blockchain/block.rs +++ b/src/blockchain/block.rs @@ -16,16 +16,16 @@ pub struct Block { pub difficulty: u32, pub random: u32, pub nonce: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction: Option, - #[serde(default, skip_serializing_if = "Bytes::is_zero")] - pub prev_block_hash: Bytes, #[serde(default, skip_serializing_if = "Bytes::is_zero")] pub hash: Bytes, #[serde(default, skip_serializing_if = "Bytes::is_zero")] + pub prev_block_hash: Bytes, + #[serde(default, skip_serializing_if = "Bytes::is_zero")] pub pub_key: Bytes, #[serde(default, skip_serializing_if = "Bytes::is_zero")] pub signature: Bytes, + #[serde(skip_serializing_if = "Option::is_none")] + pub transaction: Option, } impl Block { diff --git a/src/blockchain/chain.rs b/src/blockchain/chain.rs index 9b326af..bcb97b0 100644 --- a/src/blockchain/chain.rs +++ b/src/blockchain/chain.rs @@ -17,7 +17,7 @@ use crate::blockchain::types::{BlockQuality, MineResult, Options, ZoneData}; use crate::blockchain::types::BlockQuality::*; use crate::blockchain::types::MineResult::*; use crate::commons::constants::*; -use crate::keys::check_public_key_strength; +use crate::keystore::check_public_key_strength; use crate::settings::Settings; const TEMP_DB_NAME: &str = ":memory:"; @@ -29,13 +29,13 @@ const SQL_GET_LAST_BLOCK: &str = "SELECT * FROM blocks ORDER BY id DESC LIMIT 1; const SQL_TRUNCATE_BLOCKS: &str = "DELETE FROM blocks WHERE id >= ?;"; const SQL_TRUNCATE_DOMAINS: &str = "DELETE FROM domains WHERE id >= ?;"; -const SQL_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, owner) VALUES (?, ?, ?, ?, ?, ?)"; +const SQL_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, signing, encryption) VALUES (?, ?, ?, ?, ?, ?, ?)"; const SQL_GET_BLOCK_BY_ID: &str = "SELECT * FROM blocks WHERE id=? LIMIT 1;"; const SQL_GET_LAST_FULL_BLOCK: &str = "SELECT * FROM blocks WHERE id < ? AND `transaction`<>'' ORDER BY id DESC LIMIT 1;"; const SQL_GET_LAST_FULL_BLOCK_FOR_KEY: &str = "SELECT * FROM blocks WHERE id < ? AND `transaction`<>'' AND pub_key = ? ORDER BY id DESC LIMIT 1;"; -const SQL_GET_DOMAIN_OWNER_BY_ID: &str = "SELECT owner FROM domains WHERE id < ? AND identity = ? LIMIT 1;"; +const SQL_GET_DOMAIN_OWNER_BY_ID: &str = "SELECT signing FROM domains WHERE id < ? AND identity = ? LIMIT 1;"; const SQL_GET_DOMAIN_BY_ID: &str = "SELECT * FROM domains WHERE identity = ? ORDER BY id DESC LIMIT 1;"; -const SQL_GET_DOMAINS_BY_KEY: &str = "SELECT * FROM domains WHERE owner = ?;"; +const SQL_GET_DOMAINS_BY_KEY: &str = "SELECT * FROM domains WHERE signing = ?;"; const SQL_GET_OPTIONS: &str = "SELECT * FROM options;"; @@ -237,8 +237,8 @@ impl Chain { let transaction = block.transaction.clone(); if self.add_block_to_table(block).is_ok() { if let Some(mut transaction) = transaction { - if transaction.owner.is_empty() { - transaction.owner = owner; + if transaction.signing.is_empty() { + transaction.signing = owner; } self.add_transaction_to_table(index, timestamp, &transaction).expect("Error adding transaction"); } @@ -374,7 +374,8 @@ impl Chain { statement.bind(3, &**t.identity)?; statement.bind(4, &**t.confirmation)?; statement.bind(5, t.data.as_ref() as &str)?; - statement.bind(6, &**t.owner)?; + statement.bind(6, &**t.signing)?; + statement.bind(7, &**t.encryption)?; statement.next() } @@ -534,7 +535,7 @@ impl Chain { return WrongZone; } if let Some(transaction) = self.get_domain_transaction(&name) { - if transaction.owner.ne(pub_key) { + if transaction.signing.ne(pub_key) { return NotOwned; } } @@ -568,10 +569,11 @@ impl Chain { } let identity = Bytes::from_bytes(&statement.read::>(2).unwrap()); let confirmation = Bytes::from_bytes(&statement.read::>(3).unwrap()); - let class = String::from("domain"); + let class = String::from(CLASS_DOMAIN); let data = statement.read::(4).unwrap(); - let pub_key = Bytes::from_bytes(&statement.read::>(5).unwrap()); - let transaction = Transaction { identity, confirmation, class, data, owner: pub_key }; + let signing = Bytes::from_bytes(&statement.read::>(5).unwrap()); + let encryption = Bytes::from_bytes(&statement.read::>(6).unwrap()); + let transaction = Transaction { identity, confirmation, class, data, signing, encryption }; debug!("Found transaction for domain {}: {:?}", domain, &transaction); if transaction.check_identity(domain) { return Some(transaction); @@ -604,11 +606,12 @@ impl Chain { let confirmation = Bytes::from_bytes(&statement.read::>(3).unwrap()); let class = String::from(CLASS_DOMAIN); let data = statement.read::(4).unwrap(); - let owner = Bytes::from_bytes(&statement.read::>(5).unwrap()); - let transaction = Transaction { identity: identity.clone(), confirmation: confirmation.clone(), class, data, owner }; + let signing = Bytes::from_bytes(&statement.read::>(5).unwrap()); + let encryption = Bytes::from_bytes(&statement.read::>(6).unwrap()); + let transaction = Transaction { identity: identity.clone(), confirmation: confirmation.clone(), class, data, signing, encryption }; debug!("Found transaction for domain {:?}", &transaction); if let Some(data) = transaction.get_domain_data() { - let decrypted = keystore.decrypt(data.domain.as_slice(), &confirmation.as_slice()[..12]); + let decrypted = keystore.decrypt(data.encrypted.as_slice()); let mut domain = String::from_utf8(decrypted.to_vec()).unwrap(); if domain.is_empty() { domain = String::from("unknown"); diff --git a/src/blockchain/data/create_db.sql b/src/blockchain/data/create_db.sql index 16a7f9d..0666b2a 100644 --- a/src/blockchain/data/create_db.sql +++ b/src/blockchain/data/create_db.sql @@ -20,7 +20,8 @@ CREATE TABLE domains ( 'identity' BINARY, 'confirmation' BINARY, 'data' TEXT, - 'owner' BINARY + 'signing' BINARY, + 'encryption' BINARY ); CREATE INDEX ids ON domains ('identity'); diff --git a/src/blockchain/transaction.rs b/src/blockchain/transaction.rs index 968c5f6..1897448 100644 --- a/src/blockchain/transaction.rs +++ b/src/blockchain/transaction.rs @@ -1,8 +1,7 @@ use std::fmt; use std::fmt::{Display, Formatter}; -use serde::{Deserialize, Serialize, Serializer}; -use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize}; use crate::blockchain::hash_utils::*; use crate::bytes::Bytes; @@ -12,7 +11,7 @@ use crate::{CLASS_ORIGIN, CLASS_DOMAIN}; extern crate serde; extern crate serde_json; -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Serialize, Deserialize, PartialEq)] pub struct Transaction { pub class: String, #[serde(default, skip_serializing_if = "Bytes::is_zero")] @@ -20,35 +19,27 @@ pub struct Transaction { #[serde(default, skip_serializing_if = "Bytes::is_zero")] pub confirmation: Bytes, #[serde(default, skip_serializing_if = "Bytes::is_zero")] - pub owner: Bytes, + pub signing: Bytes, + #[serde(default, skip_serializing_if = "Bytes::is_zero")] + pub encryption: Bytes, + #[serde(default, skip_serializing_if = "String::is_empty")] pub data: String, } impl Transaction { - pub fn from_str(identity: String, method: String, data: String, miner: Bytes, owner: Bytes) -> Self { + pub fn from_str(identity: String, method: String, data: String, signing: Bytes, encryption: Bytes) -> Self { let hash = hash_identity(&identity, None); - let key = if owner.is_empty() { - &miner - } else { - &owner - }; - let confirmation = hash_identity(&identity, Some(key)); - // If the miner doesn't change owner, we don't include owner at all - let owner = if owner.is_empty() || owner == miner { - miner - } else { - owner - }; - return Self::new(hash, confirmation, method, data, owner); + let confirmation = hash_identity(&identity, Some(&signing)); + return Self::new(hash, confirmation, method, data, signing, encryption); } - pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, owner: Bytes) -> Self { - Transaction { identity, confirmation, class: method, data, owner } + pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, signing: Bytes, encryption: Bytes) -> Self { + Transaction { identity, confirmation, class: method, data, signing, encryption } } - pub fn origin(hash: Bytes, owner: Bytes) -> Self { + pub fn origin(hash: Bytes, signing: Bytes, encryption: Bytes) -> Self { let data = serde_json::to_string(&Origin { zones: hash }).unwrap(); - Transaction { identity: Bytes::default(), confirmation: Bytes::default(), class: String::from(CLASS_ORIGIN), data, owner } + Transaction { identity: Bytes::default(), confirmation: Bytes::default(), class: String::from(CLASS_ORIGIN), data, signing, encryption } } pub fn from_json(json: &str) -> Option { @@ -70,7 +61,7 @@ impl Transaction { pub fn check_identity(&self, domain: &str) -> bool { let hash = hash_identity(&domain, None); - let confirmation = hash_identity(&domain, Some(&self.owner)); + let confirmation = hash_identity(&domain, Some(&self.signing)); self.identity.eq(&hash) && self.confirmation.eq(&confirmation) } @@ -107,24 +98,13 @@ impl fmt::Debug for Transaction { .field("class", &self.class) .field("identity", &self.identity) .field("confirmation", &self.confirmation) - .field("owner", &&self.owner) + .field("signing", &&self.signing) + .field("encryption", &&self.encryption) .field("data", &self.data) .finish() } } -impl Serialize for Transaction { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer { - let mut structure = serializer.serialize_struct("Transaction", 5).unwrap(); - structure.serialize_field("class", &self.class)?; - structure.serialize_field("identity", &self.identity)?; - structure.serialize_field("confirmation", &self.confirmation)?; - structure.serialize_field("owner", &self.owner)?; - structure.serialize_field("data", &self.data)?; - structure.end() - } -} - pub enum TransactionType { Unknown, Signing, @@ -134,7 +114,7 @@ pub enum TransactionType { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct DomainData { - pub domain: Bytes, + pub encrypted: Bytes, pub zone: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub info: String, @@ -145,8 +125,8 @@ pub struct DomainData { } impl DomainData { - pub fn new(domain: Bytes, zone: String, info: String, records: Vec, contacts: Vec) -> Self { - Self { domain, zone, info, records, contacts } + pub fn new(encrypted: Bytes, zone: String, info: String, records: Vec, contacts: Vec) -> Self { + Self { encrypted, zone, info, records, contacts } } } diff --git a/src/commons/constants.rs b/src/commons/constants.rs index 36e6a17..a7569f6 100644 --- a/src/commons/constants.rs +++ b/src/commons/constants.rs @@ -37,7 +37,7 @@ pub const LISTEN_PORT: u16 = 4244; pub const UI_REFRESH_DELAY_MS: u128 = 250; pub const LOG_REFRESH_DELAY_SEC: u64 = 60; -pub const POLL_TIMEOUT: Option = Some(Duration::from_millis(25000)); +pub const POLL_TIMEOUT: Option = Some(Duration::from_millis(250)); pub const MAX_PACKET_SIZE: usize = 1 * 1024 * 1024; // 1 Mb pub const MAX_READ_BLOCK_TIME: u128 = 500; pub const MAX_RECONNECTS: u32 = 5; diff --git a/src/context.rs b/src/context.rs index 68128e5..1fe4e99 100644 --- a/src/context.rs +++ b/src/context.rs @@ -26,20 +26,6 @@ impl Context { } } - /// Load keystore and return Context - pub fn load_keystore>(mut self, name: S, password: S) -> Context { - let filename = &name.into(); - match Keystore::from_file(filename, &password.into()) { - None => { - warn!("Error loading keystore '{}'!", filename); - }, - Some(keystore) => { - self.keystore = Some(keystore); - }, - } - self - } - pub fn get_keystore(&self) -> Option { self.keystore.clone() } diff --git a/src/crypto/chacha.rs b/src/crypto/chacha.rs deleted file mode 100644 index cff7674..0000000 --- a/src/crypto/chacha.rs +++ /dev/null @@ -1,67 +0,0 @@ -use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; -use chacha20poly1305::aead::{Aead, NewAead}; -use std::fmt::{Debug, Formatter}; -use std::fmt; -#[allow(unused_imports)] -use log::{debug, error, info, trace, warn}; - -/// A small wrap-up to use Chacha20 encryption for domain names. -#[derive(Clone)] -pub struct Chacha { - pub cipher: ChaCha20Poly1305 -} - -impl Chacha { - pub fn new(seed: &[u8]) -> Self { - let key = Key::from_slice(seed); - let cipher = ChaCha20Poly1305::new(key); - Chacha { cipher } - } - - pub fn encrypt(&self, data: &[u8], nonce: &[u8]) -> Vec { - let nonce = Nonce::from_slice(nonce); - match self.cipher.encrypt(nonce, data.as_ref()) { - Ok(bytes) => { bytes } - Err(_) => { - warn!("Error encrypting data!"); - Vec::new() - } - } - } - - pub fn decrypt(&self, data: &[u8], nonce: &[u8]) -> Vec { - let nonce = Nonce::from_slice(nonce); - match self.cipher.decrypt(nonce, data.as_ref()) { - Ok(bytes) => { bytes } - Err(_) => { - warn!("Error decrypting data!"); - Vec::new() - } - } - } -} - -impl Debug for Chacha { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - fmt.write_str("ChaCha20Poly1305") - } -} - -#[cfg(test)] -mod tests { - use crate::crypto::Chacha; - use crate::to_hex; - - #[test] - pub fn test_curved_chacha() { - let buf = b"178135D209C697625E3EC71DA5C760382E54936F824EE5083908DA66B14ECE18"; - let keys1 = Chacha::new(b"178135D209C697625E3EC71DA5C76038", ); - let bytes = keys1.encrypt(b"TEST", &buf[..12]); - println!("{}", to_hex(&bytes)); - - let keys2 = Chacha::new(b"178135D209C697625E3EC71DA5C76038"); - let bytes2 = keys2.decrypt(&bytes, &buf[..12]); - - assert_eq!(String::from_utf8(bytes2).unwrap(), "TEST"); - } -} \ No newline at end of file diff --git a/src/crypto/crypto_box.rs b/src/crypto/crypto_box.rs new file mode 100644 index 0000000..e20a192 --- /dev/null +++ b/src/crypto/crypto_box.rs @@ -0,0 +1,88 @@ +use ecies_ed25519::{SecretKey, PublicKey, Error, encrypt, decrypt}; +use rand_old::{CryptoRng, RngCore}; +use std::fmt::{Debug, Formatter}; +use crate::{to_hex, from_hex}; +use std::fmt; + +pub struct CryptoBox { + pub(crate) secret: SecretKey, + pub(crate) public: PublicKey, +} + +impl CryptoBox { + pub fn new(seed: &[u8]) -> Self { + let secret = SecretKey::from_bytes(seed).expect("Unable to parse secret key"); + let public = PublicKey::from_secret(&secret); + Self { secret, public } + } + + pub fn generate(csprng: &mut R) -> Self where R: CryptoRng + RngCore { + let (secret, public) = ecies_ed25519::generate_keypair(csprng); + Self { secret, public } + } + + pub fn from_strings(secret: &str, public: &str) -> Self { + let secret = SecretKey::from_bytes(&from_hex(secret).unwrap()).unwrap(); + let public = PublicKey::from_bytes(&from_hex(public).unwrap()).unwrap(); + Self { secret, public } + } + + pub fn hide(&self, msg: &[u8]) -> Result, Error> { + let mut random = rand_old::thread_rng(); + encrypt(&self.public, msg, &mut random) + } + + pub fn reveal(&self, msg: &[u8]) -> Result, Error> { + decrypt(&self.secret, msg) + } + + pub fn encrypt(public: &[u8], message: &[u8]) -> Result, Error> { + let public = PublicKey::from_bytes(public).unwrap(); + let mut random = rand_old::thread_rng(); + encrypt(&public, message, &mut random) + } + + pub fn decrypt(secret: &[u8], message: &[u8]) -> Result, Error> { + let secret = SecretKey::from_bytes(secret).unwrap(); + decrypt(&secret, &message) + } +} + +impl Debug for CryptoBox { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("CryptoBox") + .field("public", &to_hex(&self.public.to_bytes())) + .finish() + } +} + +impl Clone for CryptoBox { + fn clone(&self) -> Self { + let secret = SecretKey::from_bytes(&self.secret.as_bytes()[..]).expect("Unable clone secret key"); + let public = PublicKey::from_secret(&secret); + Self { secret, public } + } +} + +#[cfg(test)] +mod tests { + use rand::RngCore; + use crate::{to_hex, from_hex}; + use ed25519_dalek::Keypair; + use crate::crypto::CryptoBox; + use ecies_ed25519::{encrypt, decrypt, SecretKey, PublicKey}; + + const TEXT: &str = "Some very secret message"; + + #[test] + pub fn hide_reveal() { + let mut rng = rand::thread_rng(); + let mut buf = [0u8; 32]; + rng.fill_bytes(&mut buf); + let coder = CryptoBox::new(&buf); + let encrypted = coder.hide(TEXT.as_bytes()).unwrap(); + let decrypted = coder.reveal(&encrypted.as_slice()).unwrap(); + + assert_eq!(TEXT, &String::from_utf8(decrypted).unwrap()); + } +} \ No newline at end of file diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index f371de4..25839bb 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,3 +1,3 @@ -mod chacha; +mod crypto_box; -pub use chacha::Chacha; \ No newline at end of file +pub use crypto_box::CryptoBox; \ No newline at end of file diff --git a/src/keys.rs b/src/keystore.rs similarity index 59% rename from src/keys.rs rename to src/keystore.rs index 5aea0e7..a508e64 100644 --- a/src/keys.rs +++ b/src/keystore.rs @@ -11,82 +11,111 @@ use std::path::Path; use std::sync::{Arc, atomic, Mutex}; use std::sync::atomic::{AtomicBool, AtomicUsize}; +use serde::{Deserialize, Serialize}; use ed25519_dalek::Keypair; #[allow(unused_imports)] use log::{debug, error, info, trace, warn}; use crate::blockchain::hash_utils::*; -use crate::{Context, setup_miner_thread}; +use crate::{Context, setup_miner_thread, to_hex, from_hex}; use crate::event::Event; use crate::commons::KEYSTORE_DIFFICULTY; use crate::bytes::Bytes; +use crate::crypto::CryptoBox; use blakeout::Blakeout; use std::time::Instant; use std::cell::RefCell; use self::ed25519_dalek::{Signer, PublicKey, Verifier, SecretKey}; use self::ed25519_dalek::ed25519::signature::Signature; use rand_old::{CryptoRng, RngCore}; -use rand_old::rngs::OsRng; -use crate::crypto::Chacha; #[derive(Debug)] pub struct Keystore { keypair: Keypair, hash: RefCell, path: String, - chacha: Chacha + crypto_box: CryptoBox } impl Keystore { pub fn new() -> Self { - let mut csprng = OsRng::default(); + let mut csprng = rand_old::thread_rng(); let keypair = ed25519_dalek::Keypair::generate(&mut csprng); - let chacha = get_chacha(&keypair); - Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha } + let crypto_box = CryptoBox::generate(&mut csprng); + Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), crypto_box } } pub fn from_random(csprng: &mut R) -> Self where R: CryptoRng + RngCore { let keypair = ed25519_dalek::Keypair::generate(csprng); - let chacha = get_chacha(&keypair); - Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha } + let crypto_box = CryptoBox::generate(csprng); + Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), crypto_box } } pub fn from_bytes(seed: &[u8]) -> Self { let keypair = Keypair::from_bytes(seed).expect("Error creating keypair from bytes!"); - let chacha = get_chacha(&keypair); - Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha } + let mut csprng = rand_old::thread_rng(); + let crypto_box = CryptoBox::generate(&mut csprng); + Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), crypto_box } } pub fn from_random_bytes(key: &[u8]) -> Self { let secret = SecretKey::from_bytes(&key).unwrap(); let public = PublicKey::from(&secret); let keypair = Keypair { secret, public }; - let chacha = get_chacha(&keypair); - Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha } + let mut csprng = rand_old::thread_rng(); + let crypto_box = CryptoBox::generate(&mut csprng); + Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), crypto_box } } pub fn from_file(filename: &str, _password: &str) -> Option { let path = Path::new(filename); match fs::read(&path) { Ok(key) => { - if key.len() == 32 { - let mut keystore = Keystore::from_random_bytes(key.as_slice()); - keystore.path = path.to_str().unwrap().to_owned(); - let bytes = Bytes::from_bytes(&keystore.keypair.public.to_bytes()); - return if check_public_key_strength(&bytes, KEYSTORE_DIFFICULTY) { - Some(keystore) - } else { - None - }; + return match key.len() { + 32 => { + let mut keystore = Keystore::from_random_bytes(key.as_slice()); + keystore.path = path.to_str().unwrap().to_owned(); + let bytes = Bytes::from_bytes(&keystore.keypair.public.to_bytes()); + if check_public_key_strength(&bytes, KEYSTORE_DIFFICULTY) { + warn!("Loaded key from OLD format! Please, resave it!"); + Some(keystore) + } else { + None + } + } + 64 => { + let mut keystore = Self::from_bytes(key.as_slice()); + keystore.path = path.to_str().unwrap().to_owned(); + let bytes = Bytes::from_bytes(&keystore.keypair.public.to_bytes()); + if check_public_key_strength(&bytes, KEYSTORE_DIFFICULTY) { + warn!("Loaded key from OLD format! Please, resave it!"); + Some(keystore) + } else { + None + } + } + _ => { + match toml::from_slice::(key.as_slice()) { + Ok(keys) => { + let secret = SecretKey::from_bytes(&from_hex(&keys.signing.secret).unwrap()).unwrap(); + let public = PublicKey::from_bytes(&from_hex(&keys.signing.public).unwrap()).unwrap(); + let keypair = Keypair { secret, public }; + let crypto_box = CryptoBox::from_strings(&keys.encryption.secret, &keys.encryption.public); + let keystore = Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::from(filename), crypto_box }; + let bytes = Bytes::from_bytes(&keystore.keypair.public.to_bytes()); + if check_public_key_strength(&bytes, KEYSTORE_DIFFICULTY) { + Some(keystore) + } else { + None + } + } + Err(e) => { + error!("Error loading keystore from {}: {}", filename, e); + None + } + } + } } - let mut keystore = Self::from_bytes(key.as_slice()); - keystore.path = path.to_str().unwrap().to_owned(); - let bytes = Bytes::from_bytes(&keystore.keypair.public.to_bytes()); - return if check_public_key_strength(&bytes, KEYSTORE_DIFFICULTY) { - Some(keystore) - } else { - None - }; } Err(_) => { None @@ -98,8 +127,9 @@ impl Keystore { match File::create(Path::new(filename)) { Ok(mut f) => { //TODO implement key encryption - let bytes = self.keypair.to_bytes(); - f.write_all(&bytes).expect("Error saving keystore"); + let keys = self.get_keys(); + let data = toml::to_string(&keys).unwrap(); + f.write_all(data.trim().as_bytes()).expect("Error saving keystore"); self.path = filename.to_owned(); } Err(_) => { error!("Error saving key file!"); } @@ -114,6 +144,16 @@ impl Keystore { Bytes::from_bytes(&self.keypair.secret.to_bytes()) } + pub fn get_encryption_public(&self) -> Bytes { + Bytes::from_bytes(self.crypto_box.public.as_bytes()) + } + + pub fn get_keys(&self) -> Keys { + let signing = KeyPack::new(to_hex(&self.keypair.public.to_bytes()), to_hex(&self.keypair.secret.to_bytes())); + let encryption = KeyPack::new(to_hex(&self.crypto_box.public.to_bytes()), to_hex(&self.crypto_box.secret.to_bytes())); + Keys::new(false, signing, encryption) + } + pub fn get_path(&self) -> &str { &self.path } @@ -138,13 +178,13 @@ impl Keystore { } } - pub fn encrypt(&self, message: &[u8], nonce: &[u8]) -> Bytes { - let encrypted = self.chacha.encrypt(message, nonce); + pub fn encrypt(&self, message: &[u8]) -> Bytes { + let encrypted = self.crypto_box.hide(message).unwrap(); Bytes::from_bytes(&encrypted) } - pub fn decrypt(&self, message: &[u8], nonce: &[u8]) -> Bytes { - let decrypted = self.chacha.decrypt(message, nonce); + pub fn decrypt(&self, message: &[u8]) -> Bytes { + let decrypted = self.crypto_box.reveal(message).unwrap(); Bytes::from_bytes(&decrypted) } } @@ -152,7 +192,7 @@ impl Keystore { impl Clone for Keystore { fn clone(&self) -> Self { let keypair = Keypair::from_bytes(&self.keypair.to_bytes()).unwrap(); - Self { keypair, hash: RefCell::new(Bytes::default()), path: self.path.clone(), chacha: self.chacha.clone() } + Self { keypair, hash: RefCell::new(Bytes::default()), path: self.path.clone(), crypto_box: self.crypto_box.clone() } } } @@ -227,10 +267,12 @@ fn generate_key(difficulty: u32, mining: Arc) -> Option { let mut buf = [0u8; 32]; loop { rng.fill_bytes(&mut buf); - let keystore = Keystore::from_random_bytes(&buf); + let secret = SecretKey::from_bytes(&buf).unwrap(); + let public = PublicKey::from(&secret); digest.reset(); - digest.update(keystore.get_public().as_slice()); + digest.update(public.as_bytes()); if key_hash_difficulty(digest.result()) >= difficulty { + let keystore = Keystore::from_random_bytes(&buf); info!("Generated keypair with public key: {:?} and hash {:?}", &keystore.get_public(), &keystore.get_hash()); return Some(keystore); } @@ -247,11 +289,29 @@ fn generate_key(difficulty: u32, mining: Arc) -> Option { } } -fn get_chacha(keypair: &Keypair) -> Chacha { - let mut digest = Blakeout::new(); - digest.update(&keypair.to_bytes()); - let seed = digest.result(); - Chacha::new(seed) +#[derive(Serialize, Deserialize, Debug)] +pub struct KeyPack { + public: String, + secret: String +} + +impl KeyPack { + pub fn new(public: String, secret: String) -> Self { + Self { public, secret } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Keys { + encrypted: bool, + signing: KeyPack, + encryption: KeyPack +} + +impl Keys { + pub fn new(encrypted: bool, signing: KeyPack, encryption: KeyPack) -> Self { + Self { encrypted, signing, encryption } + } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index cc41e12..d8c9701 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,14 +7,14 @@ pub use crate::miner::Miner; pub use crate::p2p::Network; pub use crate::settings::Settings; pub use crate::bytes::Bytes; -pub use crate::keys::Keystore; +pub use crate::keystore::Keystore; pub use crate::simplebus::*; pub use crate::commons::*; pub mod blockchain; pub mod commons; pub mod simplebus; -pub mod keys; +pub mod keystore; pub mod miner; pub mod context; pub mod event; diff --git a/src/main.rs b/src/main.rs index 8300fdf..da59ac9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -208,8 +208,8 @@ fn create_genesis_if_needed(context: &Arc>, miner: &Arc MAX_IDLE_SECONDS { - warn!("Something is wrong with swarm connections, closing all."); - peers.close_all_peers(poll.registry()); + if peers.get_peers_count() > 0 { + warn!("Something is wrong with swarm connections, closing all."); + peers.close_all_peers(poll.registry()); + continue; + } else { + thread::sleep(POLL_TIMEOUT.unwrap()); + } } if ui_timer.elapsed().as_millis() > UI_REFRESH_DELAY_MS { diff --git a/src/web_ui.rs b/src/web_ui.rs index b33584a..4660cae 100644 --- a/src/web_ui.rs +++ b/src/web_ui.rs @@ -15,8 +15,7 @@ use serde::Deserialize; use web_view::Content; use alfis::{Block, Bytes, Context, Keystore, Transaction}; -use alfis::keys; -use alfis::blockchain::hash_utils::hash_identity; +use alfis::keystore; use alfis::blockchain::transaction::DomainData; use alfis::blockchain::types::MineResult; use alfis::commons::*; @@ -26,6 +25,7 @@ use alfis::miner::Miner; use Cmd::*; use self::web_view::{Handle, WebView}; +use alfis::crypto::CryptoBox; pub fn run_interface(context: Arc>, miner: Arc>) { let file_content = include_str!("webview/index.html"); @@ -49,12 +49,12 @@ pub fn run_interface(context: Arc>, miner: Arc>) { match serde_json::from_str(arg).unwrap() { Loaded => { action_loaded(&context, web_view); } LoadKey => { action_load_key(&context, web_view); } - CreateKey => { keys::create_key(Arc::clone(&context)); } + CreateKey => { keystore::create_key(Arc::clone(&context)); } SaveKey => { action_save_key(&context); } CheckRecord { data } => { action_check_record(web_view, data); } CheckDomain { name } => { action_check_domain(&context, web_view, name); } - MineDomain { name, data, owner } => { - action_create_domain(Arc::clone(&context), Arc::clone(&miner), web_view, name, data, owner); + MineDomain { name, data, signing, encryption } => { + action_create_domain(Arc::clone(&context), Arc::clone(&miner), web_view, name, data, signing, encryption); } TransferDomain { .. } => {} StopMining => { context.lock().unwrap().bus.post(Event::ActionStopMining); } @@ -126,10 +126,13 @@ fn action_save_key(context: &Arc>) { if context.lock().unwrap().get_keystore().is_none() { return; } - let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)"); + let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.toml"], "Key files (*.toml)"); match result { None => {} - Some(new_path) => { + Some(mut new_path) => { + if !new_path.ends_with(".toml") { + new_path.push_str(".toml"); + } let mut context = context.lock().unwrap(); let path = new_path.clone(); if let Some(mut keystore) = context.get_keystore() { @@ -144,7 +147,7 @@ fn action_save_key(context: &Arc>) { } fn action_load_key(context: &Arc>, web_view: &mut WebView<()>) { - let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key"], "*.key"))); + let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key", "*.toml"], "Key files"))); match result { None => {} Some(file_name) => { @@ -155,7 +158,7 @@ fn action_load_key(context: &Arc>, web_view: &mut WebView<()>) { event_fail(web_view, &format!("Error loading key from \\'{}\\'!", &file_name)); } Some(keystore) => { - info!("Loaded keystore with key: {:?}", &keystore.get_public()); + info!("Loaded keystore with key: {:?}", &keystore); let mut c = context.lock().unwrap(); let path = keystore.get_path().to_owned(); let public = keystore.get_public().to_string(); @@ -332,7 +335,7 @@ fn load_domains(context: &mut MutexGuard, handle: &Handle<()>) { }); } -fn action_create_domain(context: Arc>, miner: Arc>, web_view: &mut WebView<()>, name: String, data: String, owner: String) { +fn action_create_domain(context: Arc>, miner: Arc>, web_view: &mut WebView<()>, name: String, data: String, signing: String, encryption: String) { debug!("Creating domain with data: {}", &data); let c = Arc::clone(&context); let context = context.lock().unwrap(); @@ -358,11 +361,6 @@ fn action_create_domain(context: Arc>, miner: Arc>, return; } }; - let owner = if !owner.is_empty() { - Bytes::new(from_hex(&owner).unwrap()) - } else { - Bytes::default() - }; // Check if yggdrasil only quality of zone is not violated let zones = context.chain.get_zones(); for z in zones { @@ -378,10 +376,20 @@ fn action_create_domain(context: Arc>, miner: Arc>, } } } + let signing = if !signing.is_empty() { + Bytes::new(from_hex(&signing).unwrap()) + } else { + Bytes::default() + }; + let encryption = if !encryption.is_empty() { + Bytes::new(from_hex(&encryption).unwrap()) + } else { + Bytes::default() + }; match context.chain.can_mine_domain(context.chain.get_height(), &name, &pub_key) { MineResult::Fine => { std::mem::drop(context); - create_domain(c, miner, CLASS_DOMAIN, &name, data, DOMAIN_DIFFICULTY, &keystore, owner); + create_domain(c, miner, CLASS_DOMAIN, &name, data, DOMAIN_DIFFICULTY, &keystore, signing, encryption); let _ = web_view.eval("domainMiningStarted();"); event_info(web_view, &format!("Mining of domain \\'{}\\' has started", &name)); } @@ -487,13 +495,18 @@ fn format_event_now(kind: &str, message: &str) -> String { format!("addEvent('{}', '{}', '{}');", kind, time.format("%d.%m.%y %X"), message) } -fn create_domain(_context: Arc>, miner: Arc>, class: &str, name: &str, mut data: DomainData, difficulty: u32, keystore: &Keystore, owner: Bytes) { +fn create_domain(_context: Arc>, miner: Arc>, class: &str, name: &str, mut data: DomainData, difficulty: u32, keystore: &Keystore, signing: Bytes, encryption: Bytes) { let name = name.to_owned(); - let confirmation = hash_identity(&name, Some(&keystore.get_public())); - data.domain = keystore.encrypt(name.as_bytes(), &confirmation.as_slice()[..12]); + let encrypted = CryptoBox::encrypt(encryption.as_slice(), name.as_bytes()).expect("Error encrypting domain name!"); + data.encrypted = Bytes::from_bytes(&encrypted); let data = serde_json::to_string(&data).unwrap(); - let transaction = Transaction::from_str(name, class.to_owned(), data, keystore.get_public().clone(), owner); + let (signing, encryption) = if signing.is_empty() || encryption.is_empty() { + (keystore.get_public(), keystore.get_encryption_public()) + } else { + (signing, encryption) + }; + let transaction = Transaction::from_str(name, class.to_owned(), data, signing, encryption); let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), difficulty); miner.lock().unwrap().add_block(block, keystore.clone()); } @@ -507,7 +520,7 @@ pub enum Cmd { SaveKey, CheckRecord { data: String }, CheckDomain { name: String }, - MineDomain { name: String, data: String, owner: String }, + MineDomain { name: String, data: String, signing: String, encryption: String }, TransferDomain { name: String, owner: String }, StopMining, Open { link: String }, diff --git a/src/webview/index.html b/src/webview/index.html index 518c1b6..1556b53 100644 --- a/src/webview/index.html +++ b/src/webview/index.html @@ -63,7 +63,12 @@ -

To mine domains you need to mine a strong pair of keys.

+

To mine domains you need to mine a strong pair of signing keys and a pair of encryption keys.

+ +
+ + If you have old keys in 32 or 64 byte format you need to save them to a new TOML format, otherwise you may encounter grave consequences! +
@@ -188,9 +193,9 @@ @@ -221,18 +226,27 @@