commit f46367c95d0cf2a24053010ad6c9766fddcfeea8 Author: Revertron Date: Sun Dec 1 22:45:25 2019 +0100 First correct working serialization. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..09d337a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "Wyrd" +version = "0.1.0" +authors = ["Revertron "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-crypto = "^0.2" +num_cpus = "1.10.1" +byteorder = "1.3.2" +serde = { version = "1.0.102", features = ["derive"] } +serde_json = "1.0.42" +num-bigint = "0.2" +num-traits = "0.2" +bincode = "1.2.0" +groestl = "0.8.0" +base64 = "0.11.0" +chrono = "0.4.9" +rand = "0.7.2" + +[dev-dependencies] +serde_bytes = "0.11.2" +serde_derive = "1.0.27" \ No newline at end of file diff --git a/src/block.rs b/src/block.rs new file mode 100644 index 0000000..3e3ba3b --- /dev/null +++ b/src/block.rs @@ -0,0 +1,81 @@ +extern crate serde; +extern crate serde_json; +extern crate num_bigint; +extern crate num_traits; + +use super::*; +use rand::{thread_rng, Rng}; +use std::fmt::Debug; +use chrono::{Utc, DateTime}; +use serde::{Serialize, Deserialize}; +use num_bigint::BigUint; +use num_traits::One; +use crypto::sha2::Sha512; +use crypto::digest::Digest; + +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct Block { + pub index: u64, + pub timestamp: i64, + pub chain_id: u32, + pub version: u32, + pub difficulty: usize, + pub random: u32, + pub nonce: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub transaction: Option, + pub prev_block_hash: Hash, + #[serde(skip_serializing_if = "Hash::is_default")] + pub hash: Hash, +} + +impl Block { + pub fn new(index: u64, timestamp: i64, chain_id: u32, version: u32, prev_block_hash: Hash, transaction: Option) -> Self { + Block { + index, + timestamp, + chain_id, + version, + difficulty: 18, + random: 0, + nonce: 0, + transaction, + prev_block_hash, + hash: Hash::default(), + } + } + + pub fn mine(&mut self) { + self.random = rand::random(); + let data = serde_json::to_string(&self).unwrap(); + println!("Mining block:\n{}", data); + for nonce_attempt in 0..std::u64::MAX { + self.nonce = nonce_attempt; + self.timestamp = Utc::now().timestamp(); + let hash = Self::hash(serde_json::to_string(&self).unwrap().as_bytes()); + if hash_is_good(&hash.as_bytes(), self.difficulty) { + self.hash = hash; + return; + } + } + } + + pub fn hash(data: &[u8]) -> Hash { + let mut buf: [u8; 64] = [0; 64]; + let mut digest = Sha512::new(); + digest.input(data); + digest.result(&mut buf); + Hash::from_vec(&buf.to_vec()) + } + + pub fn is_genesis(&self) -> bool { + self.index == 0 && self.transaction.is_none() && self.prev_block_hash == Hash::default() + } +} + +fn hash_is_good(hash: &[u8], difficulty: usize) -> bool { + let target = BigUint::one() << ((hash.len() << 3) - difficulty); + let hash_int = BigUint::from_bytes_be(&hash); + + return hash_int < target; +} \ No newline at end of file diff --git a/src/blockchain.rs b/src/blockchain.rs new file mode 100644 index 0000000..5b73fef --- /dev/null +++ b/src/blockchain.rs @@ -0,0 +1,68 @@ +use crate::{Block, Transaction, Hash}; +use chrono::{Utc, DateTime}; + +pub struct Blockchain { + pub chain_id: u32, + pub version: u32, + pub blocks: Vec, +} + +impl Blockchain { + pub fn new(chain_id: u32, version: u32) -> Self { + let mut blockchain = Blockchain{chain_id, version, blocks: Vec::new()}; + let mut genesis = Self::genesis(chain_id, version); + genesis.mine(); + blockchain.add_block(genesis); + blockchain + } + + pub fn new_block(&self, transaction: Transaction) -> Block { + let prev_block = self.blocks.last().unwrap(); + let block = Block::new(prev_block.index + 1,Utc::now().timestamp(), self.chain_id, self.version, prev_block.hash.clone(), Some(transaction)); + block + } + + pub fn genesis(chain_id: u32, version: u32) -> Block { + Block::new(0, Utc::now().timestamp(), chain_id, version, Hash::default(), None) + } + + pub fn add_block(&mut self, block: Block) { + if self.check_block(&block, None) { + println!("Adding block:\n{:?}", &block); + self.blocks.push(block); + } else { + println!("Bad block found, ignoring:\n{:?}", &block); + } + } + + pub fn check(&self) -> bool { + let mut prev_block = None; + for block in self.blocks.iter() { + if !self.check_block(block, prev_block) { + println!("Block {:?} is bad", block); + return false; + } + prev_block = Some(block); + } + true + } + + fn check_block(&self, block: &Block, prev_block: Option<&Block>) -> bool { + if !Self::check_block_hash(block) { + return false; + } + if prev_block.is_none() { + return true; + } + + return block.prev_block_hash == prev_block.unwrap().hash; + } + + pub fn check_block_hash(block: &Block) -> bool { + // We need to clear Hash value to rehash it without it for check :( + let mut copy: Block = block.clone(); + copy.hash = Hash::default(); + let data = serde_json::to_string(©).unwrap(); + Block::hash(data.as_bytes()) == block.hash + } +} \ No newline at end of file diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..d95efc3 --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,121 @@ +use crate::utils; +use std::cmp::min; +use serde::{Serialize, Deserialize, Serializer, Deserializer}; +use std::fmt; +use serde::de::{Error as DeError, Visitor}; +use serde::ser::SerializeSeq; +use serde::export::Formatter; +use serde::export::fmt::Error; +use std::ops::Deref; + +/// A hash consisting of all zeroes, used as a constant +pub const ZERO_HASH: Hash = Hash{ bytes: [0u8; 64]}; + +/// A hash struct +#[derive(Copy, Clone)] +pub struct Hash { + bytes: [u8; 64] +} + +impl Hash { + /// Size of a hash in bytes. + const LEN: usize = 64; + + pub fn new(bytes: [u8; 64]) -> Self { + Hash{bytes} + } + + /// Builds a Hash from a byte vector. If the vector is too short, it will be + /// completed by zeroes. If it's too long, it will be truncated. + pub fn from_vec(v: &[u8]) -> Hash { + let mut h = [0; Hash::LEN]; + let copy_size = min(v.len(), Hash::LEN); + h[..copy_size].copy_from_slice(&v[..copy_size]); + Hash::new(h) + } + + /// Converts the hash to a byte vector + pub fn to_vec(&self) -> Vec { + self.bytes.to_vec() + } + + /// Returns a byte slice of the hash contents. + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + + /// Convert a hash to hex string format. + pub fn to_hex(&self) -> String { + utils::to_hex(self.to_vec().as_ref()) + } + + pub fn is_default(&self) -> bool { + utils::same_hash(&self.bytes, &Hash::default().bytes) + } +} + +impl Serialize for Hash { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + serializer.serialize_str(&crate::utils::to_hex(&self.bytes)) + } +} + +struct HashVisitor; + +impl<'de> Visitor<'de> for HashVisitor { + type Value = Hash; + + fn expecting(&self, formatter: &mut Formatter) -> Result<(), Error> { + formatter.write_str("64 bytes") + } + + fn visit_bytes(self, v: &[u8]) -> Result where E: DeError, { + if v.len() == Hash::LEN { + let mut h = [0; Hash::LEN]; + let copy_size = min(v.len(), Hash::LEN); + h[..copy_size].copy_from_slice(&v[..copy_size]); + Ok(Hash::new(h)) + } else { + Err(E::custom("Hash must be 64 bytes!")) + } + } +} + +impl<'dd> Deserialize<'dd> for Hash { + fn deserialize(deserializer: D) -> Result>::Error> where + D: Deserializer<'dd> { + deserializer.deserialize_bytes(HashVisitor) + } +} + +impl PartialEq for Hash { + fn eq(&self, other: &Self) -> bool { + utils::same_hash(&self.bytes, &other.bytes) + } + + fn ne(&self, other: &Self) -> bool { + !utils::same_hash(&self.bytes, &other.bytes) + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl fmt::Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hash_hex = self.to_hex(); + const NUM_SHOW: usize = 8; + + write!(f, "{}", &hash_hex[..NUM_SHOW]) + } +} + +impl Default for Hash { + fn default() -> Hash { + ZERO_HASH + } +} \ No newline at end of file diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..cde06bb --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,204 @@ +extern crate crypto; +extern crate serde; +use crypto::ed25519::{keypair, signature, verify}; +use rand::{thread_rng, Rng}; +use std::fs; +use std::fmt; +use std::path::Path; +use std::io::Error as IoError; +use serde::export::fmt::Error; +use serde::{Serialize, Deserialize, Serializer, Deserializer}; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct Signature { + private_key: KeyPrivate, + public_key: KeyPublic, +} + +impl Signature { + pub fn new() -> Self { + let mut buf = [0u8; 64]; + let mut rng = thread_rng(); + rng.fill(&mut buf); + let (private, public) = keypair(&buf); + Signature {private_key: KeyPrivate::new(&private), public_key: KeyPublic::new(&public)} + } + + pub fn from_bytes(seed: &[u8]) -> Self { + let (private, public) = keypair(&seed); + Signature {private_key: KeyPrivate::new(&private), public_key: KeyPublic::new(&public)} + } + + pub fn from_file(filename: &str, _password: &str) -> Option { + match fs::read(&Path::new(filename)) { + Ok(key) => { + Some(Self::from_bytes(key.as_slice())) + }, + Err(_) => { + None + }, + } + } + + pub fn get_public(&self) -> KeyPublic { + self.public_key.clone() + } + + pub fn get_private(&self) -> KeyPrivate { + self.private_key.clone() + } + + pub fn sign(&self, message: &[u8]) -> [u8; 64] { + signature(message, &self.private_key.data) + } + + pub fn check(&self, message: &[u8], public_key: &[u8], signature: &[u8]) -> bool { + verify(message, &self.public_key.data, signature) + } +} + +/*impl fmt::Debug for Signature { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("Signature") + .field("pub", &&self.public_key[..]) + .field("priv", &&self.private_key[..]) + .finish() + } +}*/ + +#[derive(Clone, Copy)] +pub struct KeyPublic { + data: [u8; 32] +} + +impl KeyPublic { + pub fn new(data: &[u8]) -> Self { + let mut buf = [0u8; 32]; + buf.copy_from_slice(data); + KeyPublic{ data: buf } + } + + pub fn length(&self) -> usize { + self.data.len() + } +} + +impl PartialEq for KeyPublic { + fn eq(&self, other: &Self) -> bool { + crate::utils::same_hash(&self.data, &other.data) + } + + fn ne(&self, other: &Self) -> bool { + !crate::utils::same_hash(&self.data, &other.data) + } +} + +impl fmt::Debug for KeyPublic { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str(&crate::utils::to_hex(&self.data)) + } +} + +impl Serialize for KeyPublic { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + serializer.serialize_str(&crate::utils::to_hex(&self.data)) + } +} + +#[derive(Clone, Copy)] +pub struct KeyPrivate { + data: [u8; 64] +} + +impl KeyPrivate { + pub fn new(data: &[u8]) -> Self { + let mut buf = [0u8; 64]; + buf.copy_from_slice(data); + KeyPrivate{ data: buf } + } + + pub fn length(&self) -> usize { + self.data.len() + } +} + +impl PartialEq for KeyPrivate { + fn eq(&self, other: &Self) -> bool { + crate::utils::same_hash(&self.data, &other.data) + } + + fn ne(&self, other: &Self) -> bool { + !crate::utils::same_hash(&self.data, &other.data) + } +} + +impl fmt::Debug for KeyPrivate { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str(&crate::utils::to_hex(&self.data)) + } +} + +impl Serialize for KeyPrivate { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + serializer.serialize_str(&crate::utils::to_hex(&self.data)) + } +} + +use serde::de::{Error as DeError, Visitor}; +use serde::export::Formatter; + +struct PublicVisitor; + +impl<'de> Visitor<'de> for PublicVisitor { + type Value = KeyPublic; + + fn expecting(&self, formatter: &mut Formatter) -> Result<(), Error> { + formatter.write_str("32 bytes") + } + + fn visit_bytes(self, v: &[u8]) -> Result where E: DeError, { + if v.len() == 32 { + let mut h = [0; 32]; + h[..32].copy_from_slice(&v[..32]); + Ok(KeyPublic::new(&h)) + } else { + Err(E::custom("KeyPublic must be 32 bytes!")) + } + } +} + +impl<'dd> Deserialize<'dd> for KeyPublic { + fn deserialize(deserializer: D) -> Result>::Error> where + D: Deserializer<'dd> { + deserializer.deserialize_bytes(PublicVisitor) + } +} + +struct PrivateVisitor; + +impl<'de> Visitor<'de> for PrivateVisitor { + type Value = KeyPrivate; + + fn expecting(&self, formatter: &mut Formatter) -> Result<(), Error> { + formatter.write_str("32 bytes") + } + + fn visit_bytes(self, v: &[u8]) -> Result where E: DeError, { + if v.len() == 64 { + let mut h = [0; 64]; + h[..64].copy_from_slice(&v[..64]); + Ok(KeyPrivate::new(&h)) + } else { + Err(E::custom("KeyPrivate must be 64 bytes!")) + } + } +} + +impl<'dd> Deserialize<'dd> for KeyPrivate { + fn deserialize(deserializer: D) -> Result>::Error> where + D: Deserializer<'dd> { + deserializer.deserialize_bytes(PrivateVisitor) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..391a526 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +mod block; +pub use crate::block::Block; +mod blockchain; +pub use crate::blockchain::Blockchain; +pub mod transaction; +pub use crate::transaction::Transaction; +pub mod utils; +pub use crate::utils::*; +pub mod hash; +pub use crate::hash::Hash; +pub mod keys; +pub use crate::keys::Signature; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bfd715e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,40 @@ +use Wyrd::{Blockchain, Block, Transaction, Signature, Hash}; +use Wyrd::transaction::Action; + +fn main() { + println!("Wyrd DNS 0.1.0"); + test_blockchain() +} + +fn test_blockchain() -> () { + let mut blockchain = Blockchain::new(42, 0); + println!("Blockchain with genesis block has been created"); + let signature = Signature::from_file("default.key", "").unwrap(); + + // Creating transaction + let action = Action::new_domain("test.zz".to_owned(), &signature, vec!["AAAA IN 301:2925::1".to_owned()], vec![], 365); + let mut transaction = Transaction::new(action, signature.get_public().clone()); + + // Signing it with private key from Signature + let sign_hash = signature.sign(&transaction.get_bytes()); + transaction.set_signature(Hash::new(sign_hash)); + + // Creating a block with that signed transaction + let mut block = blockchain.new_block(transaction); + + // Mining the nonce + block.mine(); + + // Our block is ready, we can print it and add to Blockchain + let s = serde_json::to_string(&block).unwrap(); + println!("Serialized block:\n{}", s); + blockchain.add_block(block); + println!("Second block added"); + + // Let's check if the blockchain is valid + if blockchain.check() { + println!("Blockchain is correct"); + } else { + println!("Blockchain is corrupted, aborting"); + } +} diff --git a/src/ser.rs b/src/ser.rs new file mode 100644 index 0000000..df0a72a --- /dev/null +++ b/src/ser.rs @@ -0,0 +1,9 @@ + +/// Signal to a serializable object how much of its data should be serialized +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum SerializationMode { + /// Serialize everything sufficiently to fully reconstruct the object + Full, + /// Serialize the data that defines the object + Hash, +} \ No newline at end of file diff --git a/src/transaction.rs b/src/transaction.rs new file mode 100644 index 0000000..4f067b5 --- /dev/null +++ b/src/transaction.rs @@ -0,0 +1,124 @@ +use super::*; +use crate::transaction::Action::{MoveDomain, RenewDomain, ChangeDomain, NewDomain}; +use crate::keys::*; +extern crate serde; +extern crate serde_json; + +use serde::{Serialize, Deserialize, Serializer}; +use serde::ser::SerializeStruct; +use std::fmt; +use crypto::util::fixed_time_eq; + +#[derive(Clone, Serialize, Deserialize, PartialEq)] +#[serde(tag = "type")] +pub enum Action { + NewDomain { name: String, owner: KeyPublic, #[serde(skip_serializing_if = "Vec::is_empty")] records: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] tags: Vec, days: u16 }, + ChangeDomain { name: String, records: Vec, tags: Vec }, + RenewDomain { name: String, days: u16 }, + MoveDomain { name: String, new_owner: KeyPublic }, +} + +impl Action { + pub fn new_domain(name: String, signature: &Signature, records: Vec, tags: Vec, days: u16) -> Self { + NewDomain {name, owner: signature.get_public(), records, tags, days} + } + + pub fn change_domain(name: String, records: Vec, tags: Vec) -> Self { + ChangeDomain {name, records, tags} + } + + pub fn renew_domain(name: String, days: u16) -> Self { + RenewDomain {name, days} + } + + pub fn move_domain(name: String, new_owner: [u8; 32]) -> Self { + MoveDomain {name, new_owner: KeyPublic::new(&new_owner)} + } + + pub fn get_bytes(&self) -> Vec { + // Let it panic if something is not okay + serde_json::to_vec(&self).unwrap() + } + + pub fn from_bytes(bytes: Vec) -> Self { + // Let it panic (for now) if something is not okay + serde_json::from_slice(bytes.as_slice()).unwrap() + } +} + +impl fmt::Debug for Action { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Action::NewDomain { name, owner, records, tags, days } => { + fmt.debug_struct("NewDomain") + .field("name", name) + .field("owner", &owner) + .field("records", records) + .field("tags", tags) + .field("days", days) + .finish() + }, + Action::ChangeDomain { name, records, tags } => { + fmt.debug_struct("ChangeDomain") + .field("name", name) + .field("records", records) + .field("tags", tags) + .finish() + }, + Action::RenewDomain { name, days } => { + fmt.debug_struct("RenewDomain") + .field("name", name) + .field("days", days) + .finish() + }, + Action::MoveDomain { name, new_owner } => { + fmt.debug_struct("MoveDomain") + .field("name", name) + .field("new_owner", new_owner) + .finish() + }, + } + } +} + +#[derive(Clone, Deserialize, PartialEq)] +pub struct Transaction { + pub action: Action, + pub pub_key: KeyPublic, + pub signature: Hash, +} + +impl Transaction { + pub fn new(action: Action, pub_key: KeyPublic) -> Self { + Transaction {action, pub_key, signature: Hash::new([0u8; 64])} + } + + pub fn set_signature(&mut self, hash: Hash) { + self.signature = hash; + } + + pub fn get_bytes(&self) -> Vec { + // Let it panic if something is not okay + serde_json::to_vec(&self).unwrap() + } +} + +impl fmt::Debug for Transaction { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("Transaction") + .field("pub", &&self.pub_key) + .field("sign", &&self.signature) + .finish() + } +} + +impl Serialize for Transaction { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + let mut structure = serializer.serialize_struct("Transaction", 3).unwrap(); + structure.serialize_field("action", &self.action); + structure.serialize_field("pub_key", &self.pub_key); + structure.serialize_field("signature", &self.signature); + structure.end() + } +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..d90db3f --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,19 @@ + +/// Convert bytes array to HEX format +pub fn to_hex(buf: &[u8]) -> String { + let mut result = String::new(); + for x in buf.iter() { + result.push_str(&format!("{:01$X}", x, 2)); + } + result +} + +/// There is no default PartialEq implementation for arrays > 32 in size +pub fn same_hash(left: &[u8], right: &[u8]) -> bool { + for (x, y) in left.iter().zip(right) { + if x != y { + return false; + } + } + true +} \ No newline at end of file