diff --git a/Cargo.toml b/Cargo.toml index 5c317c51f..e7000c8b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ asm = ["sha2-asm"] [dependencies] rayon = "1.0.0" -itertools = "0.7.6" sha2 = "0.7.0" sha2-asm = {version="0.3", optional=true} digest = "0.7.2" diff --git a/README.md b/README.md index c8362de5f..4e86b0c2b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ use std::sync::mpsc::SendError; fn create_log(hist: &Historian) -> Result<(), SendError> { hist.sender.send(Event::Tick)?; thread::sleep(time::Duration::new(0, 100_000)); - hist.sender.send(Event::UserDataKey(0xdeadbeef))?; + hist.sender.send(Event::UserDataKey(Sha256Hash::default()))?; thread::sleep(time::Duration::new(0, 100_000)); hist.sender.send(Event::Tick)?; Ok(()) @@ -50,6 +50,9 @@ fn main() { for entry in &entries { println!("{:?}", entry); } + + // Proof-of-History: Verify the historian learned about the events + // in the same order they appear in the vector. assert!(verify_slice(&entries, &seed)); } ``` @@ -62,6 +65,19 @@ Entry { num_hashes: 6, end_hash: [67, ...], event: UserDataKey(3735928559) } Entry { num_hashes: 5, end_hash: [123, ...], event: Tick } ``` +Proof-of-History +--- + +Take note of the last line: + +```rust +assert!(verify_slice(&entries, &seed)); +``` + +[It's a proof!](https://en.wikipedia.org/wiki/Curry–Howard_correspondence) For each entry returned by the +historian, we can verify that `end_hash` is the result of applying a sha256 hash to the previous `end_hash` +exactly `num_hashes` times, and then hashing then event data on top of that. Because the event data is +included in the hash, the events cannot be reordered without regenerating all the hashes. # Developing diff --git a/src/bin/demo.rs b/src/bin/demo.rs index 962781e3f..ac7864c61 100644 --- a/src/bin/demo.rs +++ b/src/bin/demo.rs @@ -8,7 +8,7 @@ use std::sync::mpsc::SendError; fn create_log(hist: &Historian) -> Result<(), SendError> { hist.sender.send(Event::Tick)?; thread::sleep(time::Duration::new(0, 100_000)); - hist.sender.send(Event::UserDataKey(0xdeadbeef))?; + hist.sender.send(Event::UserDataKey(Sha256Hash::default()))?; thread::sleep(time::Duration::new(0, 100_000)); hist.sender.send(Event::Tick)?; Ok(()) diff --git a/src/historian.rs b/src/historian.rs index 8f410e4a8..f958e0b45 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -7,7 +7,7 @@ use std::thread::JoinHandle; use std::sync::mpsc::{Receiver, Sender}; -use log::{hash, Entry, Event, Sha256Hash}; +use log::{extend_and_hash, hash, Entry, Event, Sha256Hash}; pub struct Historian { pub sender: Sender, @@ -24,31 +24,33 @@ pub enum ExitReason { fn log_events( receiver: &Receiver, sender: &Sender, - num_hashes: u64, - end_hash: Sha256Hash, -) -> Result { + num_hashes: &mut u64, + end_hash: &mut Sha256Hash, +) -> Result<(), (Entry, ExitReason)> { use std::sync::mpsc::TryRecvError; - let mut num_hashes = num_hashes; loop { match receiver.try_recv() { Ok(event) => { + if let Event::UserDataKey(key) = event { + *end_hash = extend_and_hash(end_hash, &key); + } let entry = Entry { - end_hash, - num_hashes, + end_hash: *end_hash, + num_hashes: *num_hashes, event, }; if let Err(_) = sender.send(entry.clone()) { return Err((entry, ExitReason::SendDisconnected)); } - num_hashes = 0; + *num_hashes = 0; } Err(TryRecvError::Empty) => { - return Ok(num_hashes); + return Ok(()); } Err(TryRecvError::Disconnected) => { let entry = Entry { - end_hash, - num_hashes, + end_hash: *end_hash, + num_hashes: *num_hashes, event: Event::Tick, }; return Err((entry, ExitReason::RecvDisconnected)); @@ -69,9 +71,8 @@ pub fn create_logger( let mut end_hash = start_hash; let mut num_hashes = 0; loop { - match log_events(&receiver, &sender, num_hashes, end_hash) { - Ok(n) => num_hashes = n, - Err(err) => return err, + if let Err(err) = log_events(&receiver, &sender, &mut num_hashes, &mut end_hash) { + return err; } end_hash = hash(&end_hash); num_hashes += 1; @@ -108,7 +109,7 @@ mod tests { hist.sender.send(Event::Tick).unwrap(); sleep(Duration::new(0, 1_000_000)); - hist.sender.send(Event::UserDataKey(0xdeadbeef)).unwrap(); + hist.sender.send(Event::UserDataKey(zero)).unwrap(); sleep(Duration::new(0, 1_000_000)); hist.sender.send(Event::Tick).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 5087d40cf..8b2e50cb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,5 @@ pub mod log; pub mod historian; extern crate digest; -extern crate itertools; extern crate rayon; extern crate sha2; diff --git a/src/log.rs b/src/log.rs index a45bff956..edb8e9e0a 100644 --- a/src/log.rs +++ b/src/log.rs @@ -32,24 +32,24 @@ pub struct Entry { #[derive(Debug, PartialEq, Eq, Clone)] pub enum Event { Tick, - UserDataKey(u64), + UserDataKey(Sha256Hash), } impl Entry { /// Creates a Entry from the number of hashes 'num_hashes' since the previous event /// and that resulting 'end_hash'. pub fn new_tick(num_hashes: u64, end_hash: &Sha256Hash) -> Self { - let event = Event::Tick; Entry { num_hashes, end_hash: *end_hash, - event, + event: Event::Tick, } } /// Verifies self.end_hash is the result of hashing a 'start_hash' 'self.num_hashes' times. + /// If the event is a UserDataKey, then hash that as well. pub fn verify(self: &Self, start_hash: &Sha256Hash) -> bool { - self.end_hash == next_tick(start_hash, self.num_hashes).end_hash + self.end_hash == next_hash(start_hash, self.num_hashes, &self.event) } } @@ -60,13 +60,36 @@ pub fn hash(val: &[u8]) -> Sha256Hash { hasher.result() } -/// Creates the next Tick Entry 'num_hashes' after 'start_hash'. -pub fn next_tick(start_hash: &Sha256Hash, num_hashes: u64) -> Entry { +/// Return the hash of the given hash extended with the given value. +pub fn extend_and_hash(end_hash: &Sha256Hash, val: &[u8]) -> Sha256Hash { + let mut hash_data = end_hash.to_vec(); + hash_data.extend_from_slice(val); + hash(&hash_data) +} + +pub fn next_hash(start_hash: &Sha256Hash, num_hashes: u64, event: &Event) -> Sha256Hash { let mut end_hash = *start_hash; for _ in 0..num_hashes { end_hash = hash(&end_hash); } - Entry::new_tick(num_hashes, &end_hash) + if let Event::UserDataKey(key) = *event { + return extend_and_hash(&end_hash, &key); + } + end_hash +} + +/// Creates the next Tick Entry 'num_hashes' after 'start_hash'. +pub fn next_entry(start_hash: &Sha256Hash, num_hashes: u64, event: Event) -> Entry { + Entry { + num_hashes, + end_hash: next_hash(start_hash, num_hashes, &event), + event, + } +} + +/// Creates the next Tick Entry 'num_hashes' after 'start_hash'. +pub fn next_tick(start_hash: &Sha256Hash, num_hashes: u64) -> Entry { + next_entry(start_hash, num_hashes, Event::Tick) } /// Verifies the hashes and counts of a slice of events are all consistent. @@ -86,13 +109,16 @@ pub fn verify_slice_seq(events: &[Entry], start_hash: &Sha256Hash) -> bool { /// Create a vector of Ticks of length 'len' from 'start_hash' hash and 'num_hashes'. pub fn create_ticks(start_hash: &Sha256Hash, num_hashes: u64, len: usize) -> Vec { - use itertools::unfold; - let mut events = unfold(*start_hash, |state| { - let event = next_tick(state, num_hashes); - *state = event.end_hash; - return Some(event); - }); - events.by_ref().take(len).collect() + use std::iter; + let mut end_hash = *start_hash; + iter::repeat(Event::Tick) + .take(len) + .map(|event| { + let entry = next_entry(&end_hash, num_hashes, event); + end_hash = entry.end_hash; + entry + }) + .collect() } #[cfg(test)]