//! MCaptch's SHA256 based Proof of Work library //! //! # Example: //! ```rust //! use pow_sha256::{ConfigBuilder, PoW}; //! //! fn main() { //! let config = ConfigBuilder::default() //! .salt("myrandomsaltisnotlongenoug".into()) //! .build() //! .unwrap(); //! //! let phrase = "ironmansucks"; //! //! const DIFFICULTY: u32 = 1000; //! //! let work = config.prove_work(&phrase, DIFFICULTY).unwrap(); //! assert!(config.is_valid_proof(&work, &phrase)); //! assert!(config.is_sufficient_difficulty(&work, DIFFICULTY)); //! } //! ``` use std::marker::PhantomData; use derive_builder::Builder; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; /// Proof of Work over concrete type T. T can be any type that implements serde::Serialize. #[derive(Serialize, Builder, Deserialize, PartialEq, Clone, Debug)] pub struct PoW { pub nonce: u64, pub result: String, #[builder(default = "PhantomData", setter(skip))] _spook: PhantomData, } /// Configuration for generting proof of work /// Please choose a long, unique value for salt /// Resistance to dictionary/rainbow attacks depend on uniqueness /// of the salt #[derive(Serialize, Deserialize, Builder, PartialEq, Clone, Debug)] pub struct Config { pub salt: String, } impl Config { /// 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(&self, t: &T, difficulty: u32) -> bincode::Result> where T: Serialize, { bincode::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(&self, prefix: &[u8], difficulty: u32) -> PoW where T: Serialize, { let prefix_sha = Sha256::new().chain(&self.salt).chain(prefix); let mut n = 0; let mut result = 0; let difficulty = get_difficulty(difficulty); while result < difficulty { n += 1; result = score(prefix_sha.clone(), n); } PoW { nonce: n, result: result.to_string(), _spook: PhantomData, } } /// Calculate the PoW score with the provided input T. pub fn calculate(&self, pow: &PoW, t: &T) -> bincode::Result where T: Serialize, { bincode::serialize(t).map(|v| self.calculate_serialized(pow, &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, pow: &PoW, target: &[u8]) -> u128 where T: Serialize, { score(Sha256::new().chain(&self.salt).chain(target), pow.nonce) } /// Verifies that the PoW is indeed generated out of the phrase provided. pub fn is_valid_proof(&self, pow: &PoW, t: &T) -> bool where T: Serialize, { match self.calculate(pow, t) { Ok(res) => { return if pow.result == res.to_string() { true } else { false } } Err(_) => return false, } } /// Checks if the PoW result is of sufficient difficulty pub fn is_sufficient_difficulty(&self, pow: &PoW, target_diff: u32) -> bool where T: Serialize, { match pow.result.parse::() { Ok(res) => return res >= get_difficulty(target_diff), Err(_) => return false, } } } fn score(prefix_sha: Sha256, nonce: u64) -> u128 { first_bytes_as_u128( prefix_sha .chain(&nonce.to_be_bytes()) // to_be_bytes() converts to network endian .finalize() .as_slice(), ) } /// # Panics /// /// panics if inp.len() < 16 fn first_bytes_as_u128(inp: &[u8]) -> u128 { bincode::deserialize(&inp).unwrap() } // utility function to get u128 difficulty factor from u32 // javacript isn't capable of represnting u128 so fn get_difficulty(difficulty_factor: u32) -> u128 { u128::max_value() - u128::max_value() / difficulty_factor as u128 } #[cfg(test)] mod test { use super::*; const DIFFICULTY: u32 = 1000; fn get_config() -> Config { ConfigBuilder::default() .salt( "79ziepia7vhjgviiwjhnend3ofjqocsi2winc4ptqhmkvcajihywxcizewvckg9h6gs4j83v9".into(), ) .build() .unwrap() } #[test] fn base_functionality() { // Let's prove we did work targeting a phrase. let phrase = b"Ex nihilo nihil fit.".to_vec(); let config = get_config(); let pw = config.prove_work(&phrase, DIFFICULTY).unwrap(); assert!(config.calculate(&pw, &phrase).unwrap() >= get_difficulty(DIFFICULTY)); assert!(config.is_valid_proof(&pw, &phrase)); assert!(config.is_sufficient_difficulty(&pw, DIFFICULTY)); } #[test] fn double_pow() { let phrase = "Ex nihilo nihil fit.".to_owned(); let config = get_config(); let pw = config.prove_work(&phrase, DIFFICULTY).unwrap(); let pwpw = config.prove_work(&pw, DIFFICULTY).unwrap(); assert!(config.calculate(&pw, &phrase).unwrap() >= get_difficulty(DIFFICULTY)); assert!(config.is_valid_proof(&pw, &phrase)); assert!(config.is_sufficient_difficulty(&pw, DIFFICULTY)); assert!(config.calculate(&pwpw, &pw).unwrap() >= get_difficulty(DIFFICULTY)); assert!(config.is_valid_proof(&pwpw, &pw)); assert!(config.is_sufficient_difficulty(&pwpw, 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 config = get_config(); let pw = config.prove_work(&phrase, DIFFICULTY).unwrap(); let pw2 = config.prove_work(&phrase2, DIFFICULTY).unwrap(); assert!(!config.is_valid_proof(&pw, &phrase2)); assert!(!config.is_valid_proof(&pw2, &phrase)); } #[test] fn serialization_test() { let target: u8 = 1; let config = get_config(); let pw = config.prove_work(&target, DIFFICULTY).unwrap(); let message: (u8, PoW) = (target, pw); let message_ser = bincode::serialize(&message).unwrap(); let recieved_message: (u8, PoW) = bincode::deserialize(&message_ser).unwrap(); assert_eq!(recieved_message, message); assert!(config.is_sufficient_difficulty(&message.1, DIFFICULTY)); assert!(config.is_valid_proof(&message.1, &target)); } }