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.
This commit is contained in:
Revertron
2021-05-04 16:47:03 +02:00
parent 52695e0988
commit bc6d2fbae3
19 changed files with 349 additions and 249 deletions
+2 -3
View File
@@ -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]
+4 -4
View File
@@ -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<Transaction>,
#[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<Transaction>,
}
impl Block {
+17 -14
View File
@@ -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::<Vec<u8>>(2).unwrap());
let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap());
let class = String::from("domain");
let class = String::from(CLASS_DOMAIN);
let data = statement.read::<String>(4).unwrap();
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
let transaction = Transaction { identity, confirmation, class, data, owner: pub_key };
let signing = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
let encryption = Bytes::from_bytes(&statement.read::<Vec<u8>>(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::<Vec<u8>>(3).unwrap());
let class = String::from(CLASS_DOMAIN);
let data = statement.read::<String>(4).unwrap();
let owner = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
let transaction = Transaction { identity: identity.clone(), confirmation: confirmation.clone(), class, data, owner };
let signing = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
let encryption = Bytes::from_bytes(&statement.read::<Vec<u8>>(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");
+2 -1
View File
@@ -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');
+19 -39
View File
@@ -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<Self> {
@@ -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<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::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<DnsRecord>, contacts: Vec<ContactsData>) -> Self {
Self { domain, zone, info, records, contacts }
pub fn new(encrypted: Bytes, zone: String, info: String, records: Vec<DnsRecord>, contacts: Vec<ContactsData>) -> Self {
Self { encrypted, zone, info, records, contacts }
}
}
+1 -1
View File
@@ -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<Duration> = Some(Duration::from_millis(25000));
pub const POLL_TIMEOUT: Option<Duration> = 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;
-14
View File
@@ -26,20 +26,6 @@ impl Context {
}
}
/// Load keystore and return Context
pub fn load_keystore<S: Into<String>>(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<Keystore> {
self.keystore.clone()
}
-67
View File
@@ -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<u8> {
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<u8> {
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");
}
}
+88
View File
@@ -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<R>(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<Vec<u8>, Error> {
let mut random = rand_old::thread_rng();
encrypt(&self.public, msg, &mut random)
}
pub fn reveal(&self, msg: &[u8]) -> Result<Vec<u8>, Error> {
decrypt(&self.secret, msg)
}
pub fn encrypt(public: &[u8], message: &[u8]) -> Result<Vec<u8>, 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<Vec<u8>, 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());
}
}
+2 -2
View File
@@ -1,3 +1,3 @@
mod chacha;
mod crypto_box;
pub use chacha::Chacha;
pub use crypto_box::CryptoBox;
+104 -44
View File
@@ -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<Bytes>,
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<R>(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<Self> {
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::<Keys>(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<AtomicBool>) -> Option<Keystore> {
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<AtomicBool>) -> Option<Keystore> {
}
}
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)]
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -208,8 +208,8 @@ fn create_genesis_if_needed(context: &Arc<Mutex<Context>>, miner: &Arc<Mutex<Min
if origin.is_empty() && last_block.is_none() {
if let Some(keystore) = &context.keystore {
// If blockchain is empty, we are going to mine a Genesis block
let transaction = Transaction::origin(Chain::get_zones_hash(), context.get_keystore().unwrap().get_public());
let block = Block::new(Some(transaction), context.get_keystore().unwrap().get_public(), Bytes::default(), ORIGIN_DIFFICULTY);
let transaction = Transaction::origin(Chain::get_zones_hash(), keystore.get_public(), keystore.get_encryption_public());
let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), ORIGIN_DIFFICULTY);
miner.lock().unwrap().add_block(block, keystore.clone());
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ use crate::{Block, Bytes, Context, Keystore, setup_miner_thread};
use crate::commons::*;
use crate::blockchain::types::BlockQuality;
use crate::blockchain::hash_utils::*;
use crate::keys::check_public_key_strength;
use crate::keystore::check_public_key_strength;
use crate::event::Event;
use blakeout::Blakeout;
use std::thread::sleep;
+7 -2
View File
@@ -137,8 +137,13 @@ impl Network {
if !events.is_empty() {
last_events_time = Instant::now();
} else if last_events_time.elapsed().as_secs() > 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 {
+34 -21
View File
@@ -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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
let file_content = include_str!("webview/index.html");
@@ -49,12 +49,12 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
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<Mutex<Context>>) {
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<Mutex<Context>>) {
}
fn action_load_key(context: &Arc<Mutex<Context>>, 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<Mutex<Context>>, 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<Context>, handle: &Handle<()>) {
});
}
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, web_view: &mut WebView<()>, name: String, data: String, owner: String) {
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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<Mutex<Context>>, miner: Arc<Mutex<Miner>>,
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<Mutex<Context>>, miner: Arc<Mutex<Miner>>,
}
}
}
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<Mutex<Context>>, miner: Arc<Mutex<Miner>>, class: &str, name: &str, mut data: DomainData, difficulty: u32, keystore: &Keystore, owner: Bytes) {
fn create_domain(_context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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 },
+26 -12
View File
@@ -63,7 +63,12 @@
<button class="button is-link" id="new_key_button" onclick="createKey();" title="Generate new keypair, suitable to mine domains">Mine new key</button>
</div>
</div>
<p class="help">To mine domains you need to mine a strong pair of keys.</p>
<p class="help">To mine domains you need to mine a strong pair of signing keys and a pair of encryption keys.</p>
<div class="notification is-warning mt-4">
<!--<button class="delete"></button>-->
If you have old keys in 32 or 64 byte format <strong>you need to save them to a new TOML format</strong>, otherwise you may encounter grave consequences!
</div>
</div>
<!-- Domain mining -->
@@ -188,9 +193,9 @@
</div>
<div class="dropdown-menu" id="advanced-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item" onclick="showOwnerDialog();" title="You can change domain owner. Leave empty to be yours only.">Change owner</a>
<a class="dropdown-item" onclick="showContactsDialog();" title="You can add contact information to your domain, if you wish">Set contacts</a>
<a class="dropdown-item">Set info</a>
<a class="dropdown-item" onclick="showOwnerDialog();" title="You can change domain owner. Leave empty to be yours only.">Change domain owner</a>
<a class="dropdown-item" onclick="showContactsDialog();" title="You can add contact information to your domain, if you wish.">Set owner contacts</a>
<a class="dropdown-item" onclick="showDomainInfoDialog();" title="Set some information about your domain.">Set domain info</a>
</div>
</div>
</div>
@@ -221,18 +226,27 @@
<div class="modal-content">
<div class="box">
<div class="field">
<label class="label">Public keys</label>
<label class="label">Signing public key</label>
<div class="control is-expanded has-icons-left">
<input class="input is-expanded" type="text" placeholder="Public key" id="owner_text">
<input class="input is-expanded" type="text" placeholder="Signing public key" id="owner_signing">
<span class="icon is-small is-left">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px;"><path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"></path></svg>
</span>
</div>
</div>
<p class="help mb-3">Domains can be owned by several people.
You need to add their public keys in this text field, separated by new line.
If you don't include your own key, then domain will be completely transferred to entered owner.
If you wish to own domain by yourself, just leave this space empty.</p>
<div class="field">
<label class="label">Encryption public key</label>
<div class="control is-expanded has-icons-left">
<input class="input is-expanded" type="text" placeholder="Encryption public key" id="owner_encryption">
<span class="icon is-small is-left">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px;"><path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"></path></svg>
</span>
</div>
</div>
<p class="help mb-3">
If you wish to transfer this domain to another owner, you need to set new owners public keys.
Signing public key to the first field. And encryption public key to the second field.
If you don't want to transfer just leave both fields empty.</p>
<div class="buttons is-grouped is-centered">
<button class="button is-link" id="owner_positive_button" onclick="ownerPositiveButton();">Ok</button>
<button class="button is-link is-light" id="owner_negative_button" onclick="ownerCancelButton();">Cancel</button>
@@ -342,12 +356,12 @@
</div>
</div>
<div class="notification is-warning is-hidden" id="notification_warning">
<div class="notification mini is-warning is-hidden" id="notification_warning">
<button class="delete" id="warning_close"></button>
<p id="warning_text"></p>
</div>
<div class="notification is-success is-hidden" id="notification_success">
<div class="notification mini is-success is-hidden" id="notification_success">
<button class="delete" id="success_close"></button>
<p id="success_text"></p>
</div>
+35 -17
View File
@@ -1,5 +1,6 @@
var recordsBuffer = [];
var owner = "";
var ownerSigning = "";
var ownerEncryption = "";
var availableZones = [];
var myDomains = [];
var currentZone;
@@ -235,13 +236,13 @@ function createDomain() {
var new_domain = document.getElementById("new_domain").value.toLowerCase();
var domain = new_domain + "." + currentZone.name;
var data = {};
data.domain = "";
data.encrypted = "";
data.zone = currentZone.name;
data.info = "";
data.records = recordsBuffer;
data.contacts = []; // TODO make a dialog to fill them
data = JSON.stringify(data);
external.invoke(JSON.stringify({cmd: 'mineDomain', name: domain, data: data, owner: owner}));
external.invoke(JSON.stringify({cmd: 'mineDomain', name: domain, data: data, signing: ownerSigning, encryption: ownerEncryption}));
}
function domainMiningStarted() {
@@ -318,11 +319,6 @@ function showOwnerDialog() {
dialog.className = "modal is-active";
}
function showContactsDialog() {
var dialog = document.getElementById("contacts_dialog");
dialog.className = "modal is-active";
}
function isValidOwner(text) {
if (text.length != 64) {
return false;
@@ -332,17 +328,20 @@ function isValidOwner(text) {
}
function ownerPositiveButton() {
var text = document.getElementById("owner_text").value;
if (text != "") {
if (isValidOwner(text)) {
owner = text;
var signing = document.getElementById("owner_signing").value;
var encryption = document.getElementById("owner_encryption").value;
if (signing != "" && encryption != "") {
if (isValidOwner(signing) && isValidOwner(encryption)) {
ownerSigning = signing;
ownerEncryption = encryption;
} else {
alert("Wrong owner '{}'!".replace("{}", value));
wrong = true;
return;
}
} else {
owner = "";
ownerSigning = "";
ownerEncryption = "";
}
var dialog = document.getElementById("owner_dialog");
dialog.className = "modal";
@@ -353,16 +352,35 @@ function ownerCancelButton() {
dialog.className = "modal";
}
function showContactsDialog() {
var dialog = document.getElementById("contacts_dialog");
dialog.className = "modal is-active";
}
function contactsPositiveButton() {
var dialog = document.getElementById("contacts_dialog");
dialog.className = "modal";
}
function contactsNegativeButton() {
var dialog = document.getElementById("contacts_dialog");
dialog.className = "modal";
}
function showDomainInfoDialog() {
}
function showWarning(text) {
var warning = document.getElementById("notification_warning");
var message = document.getElementById("warning_text");
message.innerHTML = text;
warning.className = "notification is-warning";
warning.className = "notification mini is-warning";
var button = document.getElementById("warning_close");
button.onclick = function() {
message.value = "";
warning.className = "notification is-warning is-hidden";
warning.className = "notification mini is-warning is-hidden";
}
setTimeout(button.onclick, 5000);
}
@@ -372,11 +390,11 @@ function showSuccess(text) {
var message = document.getElementById("success_text");
message.innerHTML = text;
warning.className = "notification is-success";
warning.className = "notification mini is-success";
var button = document.getElementById("success_close");
button.onclick = function() {
message.value = "";
warning.className = "notification is-success is-hidden";
warning.className = "notification mini is-success is-hidden";
}
}
+3 -3
View File
@@ -81,19 +81,19 @@ body {
min-height: 2rem;
}
.notification {
.notification.mini {
position: absolute;
z-index: 100;
width: 50%;
}
.notification.is-warning {
.notification.mini.is-warning {
position: absolute;
top: 10pt;
right: 10pt;
}
.notification.is-success {
.notification.mini.is-success {
position: absolute;
bottom: 30pt;
right: 10pt;