commit 16f7b38446d3ae51ef32a14744bae49f7b85e7f5 Author: Andrew Dirksen Date: Thu May 9 14:49:50 2019 -0700 Initial idea using custom serializer trait. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f88dba --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e3234bd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pow" +version = "0.1.0" +authors = ["Andrew Dirksen "] +edition = "2018" +license = "MIT OR Apache-2.0" + +[dependencies] +byteorder = "1.3.1" +sha2 = "0.8.0" + +[dev-dependencies] +rand = "0.6.5" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cdf5505 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +mod network_byte_order; +mod proof_of_work; + +pub use proof_of_work::ProofOfWork; diff --git a/src/network_byte_order.rs b/src/network_byte_order.rs new file mode 100644 index 0000000..f7ac5c3 --- /dev/null +++ b/src/network_byte_order.rs @@ -0,0 +1,211 @@ +// "Ne" is short for Network Endian + +use byteorder::{ByteOrder, NetworkEndian}; +use std::convert::TryInto; +use std::marker::Sized; +use std::mem::size_of; + +/// Serialize to network endian encoded bytes. +pub trait ToNe: Sized { + /// Write self into dest and return rest, if self is too large to fit in dest, return None + fn put<'a>(&self, dest: &'a mut [u8]) -> Option<&'a mut [u8]>; + + /// Returns the size of self when serialized. + /// Panics will occur If the return value of this function is too small. + fn size(&self) -> usize; + + /// Serialize self to a Vec. + /// + /// # Panics + /// + /// This function will panic it the size reported by [`size`] is incorrect. + fn serialize_to_vec(&self) -> Vec { + let mut ret = vec![0u8; self.size()]; + let rest = self + .put(&mut ret) + .expect("object serialized was larger than reported"); + if !rest.is_empty() { + panic!("object serialized was smaller than reported"); + } + ret + } +} + +/// Serialize to/from network endian encoded bytes. +pub trait Ne: ToNe { + /// Parse bytes as network endian. Return parsed value and unparsed bytes. + /// If src is not long enough, return None. + fn pick(src: &[u8]) -> Option<(Self, &[u8])>; +} + +impl ToNe for u128 { + fn put<'a>(&self, dest: &'a mut [u8]) -> Option<&'a mut [u8]> { + let (mut head, rest) = safe_split_mut(dest, size_of::())?; + NetworkEndian::write_u128(&mut head, *self); + Some(rest) + } + + fn size(&self) -> usize { + size_of::() + } +} + +impl Ne for u128 { + fn pick(src: &[u8]) -> Option<(Self, &[u8])> { + let (head, rest) = take_sized::<[u8; 16]>(src)?; + Some((NetworkEndian::read_u128(&head), rest)) + } +} + +impl ToNe for &T { + fn put<'a>(&self, dest: &'a mut [u8]) -> Option<&'a mut [u8]> { + (*self).put(dest) + } + + fn size(&self) -> usize { + (*self).size() + } +} + +impl ToNe for (T, S) { + fn put<'a>(&self, dest: &'a mut [u8]) -> Option<&'a mut [u8]> { + let (t, s) = self; + let dest = t.put(dest)?; + let dest = s.put(dest)?; + Some(dest) + } + + fn size(&self) -> usize { + let (t, s) = self; + ToNe::size(t) + ToNe::size(s) + } +} + +impl Ne for (T, S) { + fn pick(src: &[u8]) -> Option<(Self, &[u8])> { + let (t, src) = T::pick(src)?; + let (s, src) = S::pick(src)?; + Some(((t, s), src)) + } +} + +impl ToNe for (A, B, C, D) { + fn put<'a>(&self, dest: &'a mut [u8]) -> Option<&'a mut [u8]> { + let (a, b, c, d) = self; + let dest = a.put(dest)?; + let dest = b.put(dest)?; + let dest = c.put(dest)?; + let dest = d.put(dest)?; + Some(dest) + } + + fn size(&self) -> usize { + let (a, b, c, d) = self; + ToNe::size(a) + ToNe::size(b) + ToNe::size(c) + ToNe::size(d) + } +} + +impl Ne for (A, B, C, D) { + fn pick(src: &[u8]) -> Option<(Self, &[u8])> { + let (a, src) = A::pick(src)?; + let (b, src) = B::pick(src)?; + let (c, src) = C::pick(src)?; + let (d, src) = D::pick(src)?; + Some(((a, b, c, d), src)) + } +} + +impl ToNe for Vec { + fn put<'a>(&self, dest: &'a mut [u8]) -> Option<&'a mut [u8]> { + put(self.as_ref(), dest) + } + + fn size(&self) -> usize { + self.len() + } +} + +impl Ne for Vec { + fn pick(src: &[u8]) -> Option<(Self, &[u8])> { + Some((src.to_vec(), &[])) + } +} + +/// Split src at n index or None if src.len() < n. +fn safe_split(src: &[u8], n: usize) -> Option<(&[u8], &[u8])> { + if src.len() >= n { + Some(src.split_at(n)) + } else { + None + } +} + +/// Split src at n index or None if src.len() < n. +fn safe_split_mut(src: &mut [u8], n: usize) -> Option<(&mut [u8], &mut [u8])> { + if src.len() >= n { + Some(src.split_at_mut(n)) + } else { + None + } +} + +/// Split src on at n index or None if src.len() < n. +fn take_sized<'a, T>(src: &'a [u8]) -> Option<(T, &'a [u8])> +where + &'a [u8]: TryInto, +{ + let (head, tail) = safe_split(src, size_of::())?; + let ret = head.try_into().ok(); + debug_assert!(ret.is_some()); + Some((ret?, tail)) +} + +/// Write src into dest, return unwriten bytes or None if dest is not long enough. +fn put<'a>(src: &[u8], dest: &'a mut [u8]) -> Option<(&'a mut [u8])> { + let (head, tail) = safe_split_mut(dest, src.len())?; + head.copy_from_slice(src); + Some(tail) +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::random; + use std::fmt::Debug; + + fn ser(t: T) { + t.serialize_to_vec(); + } + + fn ser_deser(t: T) { + let v = t.serialize_to_vec(); + let (t2, rest) = T::pick(&v).unwrap(); + assert_eq!(rest.len(), 0); + assert_eq!(t, t2); + } + + fn rand_vecu8() -> Vec { + (0..(random::() % 265)).map(|_| random()).collect() + } + + #[test] + fn sd_u128() { + ser_deser::(random()); + ser::<&u128>(&random()); + } + + #[test] + fn sd_2t() { + ser_deser::<(u128, u128)>(random()); + } + + #[test] + fn sd_4t() { + ser_deser::<(u128, u128, u128, u128)>(random()); + } + + #[test] + fn sd_vu8() { + ser_deser(rand_vecu8()); + } +} diff --git a/src/proof_of_work.rs b/src/proof_of_work.rs new file mode 100644 index 0000000..c1eee72 --- /dev/null +++ b/src/proof_of_work.rs @@ -0,0 +1,86 @@ +use crate::network_byte_order::Ne; +use sha2::{digest::FixedOutput, Digest, Sha256}; +use std::marker::PhantomData; + +pub struct ProofOfWork { + proof: u128, + _spook: PhantomData, +} + +impl ProofOfWork { + /// Prove work over T. + /// + /// 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(t: &T, difficulty: u32) -> ProofOfWork { + let v = t.serialize_to_vec(); + Self::prove_work_serialized(&v, difficulty) + } + + /// Prove 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: u32) -> ProofOfWork { + debug_assert!(difficulty <= 256); + let prefix_sha = Sha256::new().chain(prefix); + let mut n = 0; + while score(prefix_sha.clone(), n) < difficulty { + n += 1; + } + ProofOfWork { + proof: n, + _spook: PhantomData, + } + } + + /// Calculate the pow score of t and self. + pub fn score(&self, t: &T) -> u32 { + let v = t.serialize_to_vec(); + self.score_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 score_serialized(&self, target: &[u8]) -> u32 { + score(Sha256::new().chain(target), self.proof) + } +} + +fn score(prefix_sha: Sha256, proof: u128) -> u32 { + leading_zeros( + prefix_sha + .chain(&proof.to_be_bytes()) // to_be_bytes() converts to network endian + .fixed_result() + .as_slice(), + ) +} + +fn leading_zeros(inp: &[u8]) -> u32 { + let mut ret = 0; + for n in inp { + if n == &0 { + ret += 8; + } else { + ret += n.leading_zeros(); + break; + } + } + return ret; +} + +#[cfg(test)] +mod test { + use super::*; + + const DIFFICULTY: u32 = 10; + + #[test] + fn base_functionality() { + // Let's prove we did work targeting a phrase. + let phrase = b"Corver bandar palladianism retroform.".to_vec(); + let pw = ProofOfWork::prove_work(&phrase, DIFFICULTY); + assert!(pw.score(&phrase) >= DIFFICULTY); + } +}