From 6b591947d0333ba35a4babb8626fab01a3d23c14 Mon Sep 17 00:00:00 2001 From: Robert Kornacki <11645932+robkorn@users.noreply.github.com> Date: Mon, 22 Jul 2019 15:04:45 -0400 Subject: [PATCH] Cleaned up more of the project and improved readme --- Cargo.toml | 3 +- readme.md => README.md | 11 ++- src/lib.rs | 219 +++++++++++++++++++++++------------------ src/proof_of_work.rs | 139 -------------------------- 4 files changed, 132 insertions(+), 240 deletions(-) rename readme.md => README.md (81%) delete mode 100644 src/proof_of_work.rs diff --git a/Cargo.toml b/Cargo.toml index af5e5a5..46b6a70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ description = """ SHA256 PoW on any serializable datatype. """ edition = "2018" -# documentation = "https://docs.rs/PoW" keywords = ["PoW", "sha256", "proof-of-work"] readme = "readme.md" license = "MIT OR Apache-2.0" @@ -14,5 +13,5 @@ repository = "https://github.com/robkorn/pow" categories = [] # pr me! [dependencies] sha2 = "0.8.0" -serde = { version = "1", features = ["derive"] } +serde = { version = "1.0.97", features = ["derive"] } bincode = "1.1.4" diff --git a/readme.md b/README.md similarity index 81% rename from readme.md rename to README.md index dcf7411..7120829 100644 --- a/readme.md +++ b/README.md @@ -1,8 +1,13 @@ -# PoW +# PoW-SHA256 -Sha256 based proof of work over a typed piece of data. +Rust crate which generates SHA256 Proofs of Work on serializable datatypes. Any type that implements `serde::Deserialize` can be used. -Any type that implementes serde::Deserialize can be tagged with a proof of work. +This is a fork of the [`Pow` library](https://github.com/bddap/pow) by bddap with some new additions. Primary of these being: + +- PoW datatype now saves the calculation result to be used for checking proof validity given input +- `is_valid_proof` method to do the above mentioned + +Other small changes have also been included of various importance but mostly just stylistic/ease of use improvements. # Examples diff --git a/src/lib.rs b/src/lib.rs index 0f1b598..e56a0ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,112 +1,139 @@ -// Sha256 based proof of work over a typed piece of data. +use serde::{Deserialize, Serialize}; +use sha2::{digest::FixedOutput, Digest, Sha256}; +use std::marker::PhantomData; -// Any type that implementes serde::Deserialize can be tagged with a proof of work. +const SALT: &str = "79ziepia7vhjgviiwjhnend3ofjqocsi2winc4ptqhmkvcajihywxcizewvckg9h6gs4j83v9"; -// # Examples +/// Proof of Work over concrete type T. T can be any type that implements serde::Serialize. +#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Debug)] +pub struct PoW { + nonce: u128, + result: u128, + _spook: PhantomData, +} -// Prove we did work targeting a phrase. +impl PoW { + /// Create Proof of Work over item of type T. + /// + /// Make sure difficulty is not too high. A 64 bit difficulty, for example, takes a long time + /// on a general purpose processor. + /// + /// Returns bincode::Error if serialization fails. + pub fn prove_work(t: &T, difficulty: u128) -> bincode::Result> { + bincode_cfg() + .serialize(t) + .map(|v| Self::prove_work_serialized(&v, difficulty)) + } -// ```rust -// use PoW::PoW; + /// Create Proof of Work on an already serialized item of type T. + /// The input is assumed to be serialized using network byte order. + /// + /// Make sure difficulty is not too high. A 64 bit difficulty, for example, takes a long time + /// on a general purpose processor. + pub fn prove_work_serialized(prefix: &[u8], difficulty: u128) -> PoW { + let prefix_sha = Sha256::new().chain(SALT).chain(prefix); + let mut n = 0; + let mut result = 0; + while result < difficulty { + n += 1; + result = score(prefix_sha.clone(), n); + } + PoW { + nonce: n, + result: result, + _spook: PhantomData, + } + } -// // very easy mode -// let difficulty = u128::max_value() - u128::max_value() / 2; + /// Calculate the PoW score with the provided input T. + pub fn calculate(&self, t: &T) -> bincode::Result { + bincode_cfg() + .serialize(t) + .map(|v| self.calculate_serialized(&v)) + } -// let phrase = b"Phrase to tag.".to_vec(); -// let pw = PoW::prove_work(&phrase, difficulty).unwrap(); -// assert!(pw.calculate(&phrase).unwrap() >= difficulty); -// ``` + /// Calculate the PoW score of an already serialized T and self. + /// The input is assumed to be serialized using network byte order. + pub fn calculate_serialized(&self, target: &[u8]) -> u128 { + score(Sha256::new().chain(SALT).chain(target), self.nonce) + } -// Prove more difficult work. This time targeting a time. + /// Verifies that the PoW is indeed generated out of the phrase provided. + pub fn is_valid_proof(&self, t: &T) -> bool { + match self.calculate(t) { + Ok(res) => if self.result == res {return true;}, + Err(_) => return false + } + return false; + } +} -// ```rust -// # fn get_unix_time_seconds() -> u64 { -// # use std::time::{Duration, SystemTime}; -// # SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() -// # } -// # use PoW::PoW; +fn score(prefix_sha: Sha256, nonce: u128) -> u128 { + first_bytes_as_u128( + prefix_sha + .chain(&nonce.to_be_bytes()) // to_be_bytes() converts to network endian + .fixed_result() + .as_slice(), + ) +} -// // more diffcult, takes around 100_000 hashes to generate proof -// let difficulty = u128::max_value() - u128::max_value() / 100_000; +/// # Panics +/// +/// panics if inp.len() < 16 +fn first_bytes_as_u128(inp: &[u8]) -> u128 { + bincode_cfg().deserialize(&inp).unwrap() +} -// let now: u64 = get_unix_time_seconds(); -// let pw = PoW::prove_work(&now, difficulty).unwrap(); -// assert!(pw.calculate(&now).unwrap() >= difficulty); -// ``` +fn bincode_cfg() -> bincode::Config { + let mut cfg = bincode::config(); + cfg.big_endian(); + cfg +} -// Define a blockchain block. +#[cfg(test)] +mod test { + use super::*; -// ```rust -// # use PoW::PoW; -// struct Block { -// prev: [u8; 32], // hash of last block -// payload: T, // generic data -// proof_of_work: PoW<([u8; 32], T)>, -// } -// ``` + const DIFFICULTY: u128 = 0xff000000000000000000000000000000; -// # Score scheme + #[test] + fn base_functionality() { + // Let's prove we did work targeting a phrase. + let phrase = b"Ex nihilo nihil fit.".to_vec(); + let pw = PoW::prove_work(&phrase, DIFFICULTY).unwrap(); + assert!(pw.calculate(&phrase).unwrap() >= DIFFICULTY); + assert!(pw.is_valid_proof(&phrase)); + } -// To score a proof of work for a given (target, PoW) pair: -// Sha256 is calculated over the concatenation SALT + target + PoW. -// The first 16 bytes of the hash are interpreted as a 128 bit unsigned integer. -// That integer is the score. -// A constant, SALT, is used as prefix to prevent PoW reuse from other systems such as proof -// of work blockchains. + #[test] + fn double_pow() { + let phrase = "Ex nihilo nihil fit.".to_owned(); + let pw = PoW::prove_work(&phrase, DIFFICULTY).unwrap(); + let pwpw: PoW> = PoW::prove_work(&pw, DIFFICULTY).unwrap(); + assert!(pw.calculate(&phrase).unwrap() >= DIFFICULTY); + assert!(pwpw.calculate(&pw).unwrap() >= DIFFICULTY); + assert!(pwpw.is_valid_proof(&pw)); + } -// In other words: + #[test] + fn is_not_valid_proof() { + let phrase = "Ex nihilo nihil fit.".to_owned(); + let phrase2 = "Omne quod movetur ab alio movetur.".to_owned(); + let pw = PoW::prove_work(&phrase, DIFFICULTY).unwrap(); + let pw2 = PoW::prove_work(&phrase2, DIFFICULTY).unwrap(); + assert!(!pw.is_valid_proof(&phrase2)); + assert!(!pw2.is_valid_proof(&phrase)); + } -// ```rust -// # use serde::Serialize; -// # use PoW::PoW; -// # use core::any::Any; -// # const SALT: &'static str = "not the actual salt used"; -// # fn serialize(_: &T) -> u8 { 0 } // not the actual serialize function -// # fn deserialize(_: &[u8]) -> u128 { 0 } // not the actual deserialize function -// # fn sha256(_: &u8) -> [u8; 32] { [0; 32] } // not the actual sha256 function -// fn score(target: &T, PoW_tag: &PoW) -> u128 { -// let bytes = serialize(&SALT) + serialize(target) + serialize(PoW_tag); -// let hash = sha256(&bytes); -// deserialize(&hash[..16]) -// } -// ``` - -// # Serialization encoding. - -// It shouldn't matter to users of this library, but the bincode crate is used for cheap -// deterministic serialization. All values are serialized using network byte order. - -// # Threshold scheme - -// Given a minimum score m. A PoW p satisfies the minimum score for target t iff score(t, p) >= m. - -// # Choosing a difficulty setting. - -// Difficulty settings are usually best adjusted dynamically a la bitcoin. - -// To manually select a difficulty, choose the average number of hashes required. - -// ```rust -// fn difficulty(average: u128) -> u128 { -// debug_assert_ne!(average, 0, "It is impossible to prove work in zero attempts."); -// let m = u128::max_value(); -// m - m / average -// } -// ``` - -// Conversely, to calculate probable number of hashes required to satisfy a given minimum -// difficulty. - -// ```rust -// fn average(difficulty: u128) -> u128 { -// let m = u128::max_value(); -// if difficulty == m { -// return m; -// } -// m / (m - difficulty) -// } -// ``` - -mod proof_of_work; - -pub use proof_of_work::PoW; + #[test] + fn serialization_test() { + let target: u8 = 1; + let pw = PoW::prove_work(&target, DIFFICULTY).unwrap(); + let message: (u8, PoW) = (target, pw); + let message_ser = bincode_cfg().serialize(&message).unwrap(); + let recieved_message: (u8, PoW) = bincode_cfg().deserialize(&message_ser).unwrap(); + assert_eq!(recieved_message, message); + assert!(message.1.calculate(&message.0).unwrap() >= DIFFICULTY); + assert!(message.1.is_valid_proof(&target)); + } +} diff --git a/src/proof_of_work.rs b/src/proof_of_work.rs deleted file mode 100644 index 183a6e8..0000000 --- a/src/proof_of_work.rs +++ /dev/null @@ -1,139 +0,0 @@ -use serde::{Deserialize, Serialize}; -use sha2::{digest::FixedOutput, Digest, Sha256}; -use std::marker::PhantomData; - -const SALT: &str = "79ziepia7vhjgviiwjhnend3ofjqocsi2winc4ptqhmkvcajihywxcizewvckg9h6gs4j83v9"; - -/// Proof of Work over concrete type T. T can be any type that implements serde::Serialize. -#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Debug)] -pub struct PoW { - nonce: u128, - result: u128, - _spook: PhantomData, -} - -// prove_work and calculate could theoretically be without allocations, by serializing to a Write -// implementaion that performs sha256 lazily. -// `impl io::Write for sha2::Sha256 { ... }` - -impl PoW { - /// Create Proof of Work over item of type T. - /// - /// Make sure difficulty is not too high. A 64 bit difficulty, for example, takes a long time - /// on a general purpose processor. - /// - /// Returns bincode::Error if serialization fails. - pub fn prove_work(t: &T, difficulty: u128) -> bincode::Result> { - bincode_cfg() - .serialize(t) - .map(|v| Self::prove_work_serialized(&v, difficulty)) - } - - /// Create Proof of Work on an already serialized item of type T. - /// The input is assumed to be serialized using network byte order. - /// - /// Make sure difficulty is not too high. A 64 bit difficulty, for example, takes a long time - /// on a general purpose processor. - pub fn prove_work_serialized(prefix: &[u8], difficulty: u128) -> PoW { - let prefix_sha = Sha256::new().chain(SALT).chain(prefix); - let mut n = 0; - let mut result = 0; - while result < difficulty { - n += 1; - result = score(prefix_sha.clone(), n); - } - PoW { - nonce: n, - result: result, - _spook: PhantomData, - } - } - - /// Calculate the PoW score of t and self. - pub fn calculate(&self, t: &T) -> bincode::Result { - bincode_cfg() - .serialize(t) - .map(|v| self.calculate_serialized(&v)) - } - - /// Calculate the PoW score of an already serialized T and self. - /// The input is assumed to be serialized using network byte order. - pub fn calculate_serialized(&self, target: &[u8]) -> u128 { - score(Sha256::new().chain(SALT).chain(target), self.nonce) - } - - pub fn is_valid_proof(&self, t: &T) -> bool { - if self.result == self.calculate(t).unwrap() { - return true; - } - return false; - } -} - -fn score(prefix_sha: Sha256, nonce: u128) -> u128 { - first_bytes_as_u128( - prefix_sha - .chain(&nonce.to_be_bytes()) // to_be_bytes() converts to network endian - .fixed_result() - .as_slice(), - ) -} - -/// # Panics -/// -/// panics if inp.len() < 16 -fn first_bytes_as_u128(inp: &[u8]) -> u128 { - bincode_cfg().deserialize(&inp).unwrap() -} - -fn bincode_cfg() -> bincode::Config { - let mut cfg = bincode::config(); - cfg.big_endian(); - cfg -} - -#[cfg(test)] -mod test { - use super::*; - - const DIFFICULTY: u128 = 0xff000000000000000000000000000000; - - #[test] - fn base_functionality() { - // Let's prove we did work targeting a phrase. - let phrase = b"Ex nihilo nihil fit.".to_vec(); - let pw = PoW::prove_work(&phrase, DIFFICULTY).unwrap(); - assert!(pw.calculate(&phrase).unwrap() >= DIFFICULTY); - assert!(pw.is_valid_proof(&phrase)); - } - - #[test] - fn double_pow() { - let phrase = "Ex nihilo nihil fit.".to_owned(); - let pw = PoW::prove_work(&phrase, DIFFICULTY).unwrap(); - let pwpw: PoW> = PoW::prove_work(&pw, DIFFICULTY).unwrap(); - assert!(pw.calculate(&phrase).unwrap() >= DIFFICULTY); - assert!(pwpw.calculate(&pw).unwrap() >= DIFFICULTY); - } - - #[test] - fn is_not_valid_proof() { - let phrase = "Ex nihilo nihil fit.".to_owned(); - let phrase2 = "Omne quod movetur ab alio movetur.".to_owned(); - let pw = PoW::prove_work(&phrase, DIFFICULTY).unwrap(); - let pw2 = PoW::prove_work(&phrase2, DIFFICULTY).unwrap(); - assert!(!pw.is_valid_proof(&phrase2)); - assert!(!pw2.is_valid_proof(&phrase)); - } - - #[test] - fn ser_de() { - let target: u8 = 1; - let pw = PoW::prove_work(&target, DIFFICULTY).unwrap(); - let message: (u8, PoW) = (target, pw); - let message_ser = bincode_cfg().serialize(&message).unwrap(); - let recieved_message: (u8, PoW) = bincode_cfg().deserialize(&message_ser).unwrap(); - assert_eq!(recieved_message, message); - assert!(message.1.calculate(&message.0).unwrap() >= DIFFICULTY); - } -}