From bc6d2fbae3e123183113b000ec50cca7d42d3079 Mon Sep 17 00:00:00 2001 From: Revertron Date: Tue, 4 May 2021 16:47:03 +0200 Subject: [PATCH] Added second keypair for encryption of domain names. Changed keys file format to include second pair of keys, it will be in TOML now. Made many adjustments to block and transaction structures. Changed block serialization to binary format for hashing/mining. Removed old build dependencies. --- Cargo.toml | 5 +- src/blockchain/block.rs | 8 +- src/blockchain/chain.rs | 31 ++++--- src/blockchain/data/create_db.sql | 3 +- src/blockchain/transaction.rs | 58 ++++-------- src/commons/constants.rs | 2 +- src/context.rs | 14 --- src/crypto/chacha.rs | 67 -------------- src/crypto/crypto_box.rs | 88 ++++++++++++++++++ src/crypto/mod.rs | 4 +- src/{keys.rs => keystore.rs} | 148 +++++++++++++++++++++--------- src/lib.rs | 4 +- src/main.rs | 4 +- src/miner.rs | 2 +- src/p2p/network.rs | 9 +- src/web_ui.rs | 55 ++++++----- src/webview/index.html | 38 +++++--- src/webview/scripts.js | 52 +++++++---- src/webview/styles.css | 6 +- 19 files changed, 349 insertions(+), 249 deletions(-) delete mode 100644 src/crypto/chacha.rs create mode 100644 src/crypto/crypto_box.rs rename src/{keys.rs => keystore.rs} (59%) 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 @@