diff --git a/README.md b/README.md index c8362de5f6eef0..2c803fb5e2e2a5 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,21 @@ 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. So long as this +proof is verified and signed faster than a malicious historian could regenerate those hashes *and* be +verified, this history is, for all practical purposes, carved in stone. # Developing diff --git a/src/bin/demo.rs b/src/bin/demo.rs index 962781e3f1a81e..ac7864c6198833 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 8f410e4a80e0aa..f958e0b45a1e7c 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/log.rs b/src/log.rs index a45bff956f03b8..55dbff1f9c4430 100644 --- a/src/log.rs +++ b/src/log.rs @@ -32,24 +32,30 @@ 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 + let tick = next_tick(start_hash, self.num_hashes); + let end_hash = if let Event::UserDataKey(key) = self.event { + extend_and_hash(&tick.end_hash, &key) + } else { + tick.end_hash + }; + self.end_hash == end_hash } } @@ -60,6 +66,13 @@ pub fn hash(val: &[u8]) -> Sha256Hash { hasher.result() } +/// 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) +} + /// Creates the next Tick Entry 'num_hashes' after 'start_hash'. pub fn next_tick(start_hash: &Sha256Hash, num_hashes: u64) -> Entry { let mut end_hash = *start_hash;