From 29a607427d6af8ff57566b30c214c50e24072325 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 24 Feb 2018 05:18:59 -0700 Subject: [PATCH 1/3] Rename UserDataKey to Discovery From the perspective of the log, when some data's hash is added, that data is "discovered" by the historian. Another event might be a "claim" that some signed data belongs to the owner of a public key. --- README.md | 4 ++-- diagrams/historian.msc | 6 +++--- src/bin/demo.rs | 2 +- src/historian.rs | 6 +++--- src/log.rs | 14 +++++++------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 074643e857..9ca021d406 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ use std::sync::mpsc::SendError; fn create_log(hist: &Historian) -> Result<(), SendError> { sleep(Duration::from_millis(15)); - hist.sender.send(Event::UserDataKey(Sha256Hash::default()))?; + hist.sender.send(Event::Discovery(Sha256Hash::default()))?; sleep(Duration::from_millis(10)); Ok(()) } @@ -62,7 +62,7 @@ Running the program should produce a log similar to: ```rust Entry { num_hashes: 0, end_hash: [0, ...], event: Tick } -Entry { num_hashes: 2, end_hash: [67, ...], event: UserDataKey(3735928559) } +Entry { num_hashes: 2, end_hash: [67, ...], event: Discovery(3735928559) } Entry { num_hashes: 3, end_hash: [123, ...], event: Tick } ``` diff --git a/diagrams/historian.msc b/diagrams/historian.msc index ee67f49d12..b2cb4e3d52 100644 --- a/diagrams/historian.msc +++ b/diagrams/historian.msc @@ -4,10 +4,10 @@ msc { logger=>historian [ label = "e0 = Entry{hash: h0, n: 0, event: Tick}" ] ; logger=>logger [ label = "h1 = hash(h0)" ] ; logger=>logger [ label = "h2 = hash(h1)" ] ; - client=>historian [ label = "UserData(d0)" ] ; - historian=>logger [ label = "UserData(d0)" ] ; + client=>historian [ label = "Discovery(d0)" ] ; + historian=>logger [ label = "Discovery(d0)" ] ; logger=>logger [ label = "h3 = hash(h2 + d0)" ] ; - logger=>historian [ label = "e1 = Entry{hash: hash(h3), n: 2, event: UserData(d0)}" ] ; + logger=>historian [ label = "e1 = Entry{hash: hash(h3), n: 2, event: Discovery(d0)}" ] ; logger=>logger [ label = "h4 = hash(h3)" ] ; logger=>logger [ label = "h5 = hash(h4)" ] ; logger=>logger [ label = "h6 = hash(h5)" ] ; diff --git a/src/bin/demo.rs b/src/bin/demo.rs index e4930521af..bd5d49bc2f 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> { sleep(Duration::from_millis(15)); - hist.sender.send(Event::UserDataKey(Sha256Hash::default()))?; + hist.sender.send(Event::Discovery(Sha256Hash::default()))?; sleep(Duration::from_millis(10)); Ok(()) } diff --git a/src/historian.rs b/src/historian.rs index 37592dee83..606997f956 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -27,7 +27,7 @@ fn log_event( end_hash: &mut Sha256Hash, event: Event, ) -> Result<(), (Entry, ExitReason)> { - if let Event::UserDataKey(key) = event { + if let Event::Discovery(key) = event { *end_hash = extend_and_hash(end_hash, &key); } let entry = Entry { @@ -139,7 +139,7 @@ mod tests { hist.sender.send(Event::Tick).unwrap(); sleep(Duration::new(0, 1_000_000)); - hist.sender.send(Event::UserDataKey(zero)).unwrap(); + hist.sender.send(Event::Discovery(zero)).unwrap(); sleep(Duration::new(0, 1_000_000)); hist.sender.send(Event::Tick).unwrap(); @@ -173,7 +173,7 @@ mod tests { let zero = Sha256Hash::default(); let hist = Historian::new(&zero, Some(20)); sleep(Duration::from_millis(30)); - hist.sender.send(Event::UserDataKey(zero)).unwrap(); + hist.sender.send(Event::Discovery(zero)).unwrap(); sleep(Duration::from_millis(15)); drop(hist.sender); assert_eq!( diff --git a/src/log.rs b/src/log.rs index ce20285130..23c48fb7e6 100644 --- a/src/log.rs +++ b/src/log.rs @@ -32,7 +32,7 @@ pub struct Entry { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Event { Tick, - UserDataKey(Sha256Hash), + Discovery(Sha256Hash), } impl Entry { @@ -47,7 +47,7 @@ impl Entry { } /// 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. + /// If the event is not a Tick, then hash that as well. pub fn verify(self: &Self, start_hash: &Sha256Hash) -> bool { self.end_hash == next_hash(start_hash, self.num_hashes, &self.event) } @@ -72,8 +72,8 @@ pub fn next_hash(start_hash: &Sha256Hash, num_hashes: u64, event: &Event) -> Sha for _ in 0..num_hashes { end_hash = hash(&end_hash); } - if let Event::UserDataKey(key) = *event { - return extend_and_hash(&end_hash, &key); + if let Event::Discovery(data) = *event { + return extend_and_hash(&end_hash, &data); } end_hash } @@ -169,9 +169,9 @@ mod tests { let zero = Sha256Hash::default(); let one = hash(&zero); - // First, verify UserData events + // First, verify Discovery events let mut end_hash = zero; - let events = [Event::UserDataKey(zero), Event::UserDataKey(one)]; + let events = [Event::Discovery(zero), Event::Discovery(one)]; let mut entries: Vec = events .iter() .map(|event| { @@ -182,7 +182,7 @@ mod tests { .collect(); assert!(verify_slice(&entries, &zero)); // inductive step - // Next, swap only two UserData events and ensure verification fails. + // Next, swap two Discovery events and ensure verification fails. let event0 = entries[0].event.clone(); let event1 = entries[1].event.clone(); entries[0].event = event1; From 08e501e57b510d5cfc20b90f80ac31f0994abd8f Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 24 Feb 2018 06:53:36 -0700 Subject: [PATCH 2/3] Extend the event log with a Claim event to claim possession Unlike a Discovery event, a Claim event associates a public key with a hash. It's intended to to be used to claim ownership of some hashable data. For example, a graphic designer could claim copyright by hashing some image they created, signing it with their private key, and publishing the hash-signature pair via the historian. If someone else tries to claim it as their own, the designer can point to the historian's log as cryptographically secure evidence that the designer's copy existed before anyone else's. Note there's nothing here that verifies the first claim is the actual content owner, only that the first claim almost certainly happened before a second. --- Cargo.toml | 2 ++ src/historian.rs | 6 ++-- src/lib.rs | 2 ++ src/log.rs | 85 +++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31e6919a42..47e88ac8d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,5 @@ sha2-asm = {version="0.3", optional=true} generic-array = { version = "0.9.0", default-features = false, features = ["serde"] } serde = "1.0.27" serde_derive = "1.0.27" +ring = "0.12.1" +untrusted = "0.5.1" diff --git a/src/historian.rs b/src/historian.rs index 606997f956..8312272d9e 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -8,7 +8,7 @@ use std::thread::JoinHandle; use std::sync::mpsc::{Receiver, Sender}; use std::time::{Duration, SystemTime}; -use log::{extend_and_hash, hash, Entry, Event, Sha256Hash}; +use log::{hash, hash_event, Entry, Event, Sha256Hash}; pub struct Historian { pub sender: Sender, @@ -27,9 +27,7 @@ fn log_event( end_hash: &mut Sha256Hash, event: Event, ) -> Result<(), (Entry, ExitReason)> { - if let Event::Discovery(key) = event { - *end_hash = extend_and_hash(end_hash, &key); - } + *end_hash = hash_event(end_hash, &event); let entry = Entry { end_hash: *end_hash, num_hashes: *num_hashes, diff --git a/src/lib.rs b/src/lib.rs index 3ccb87efc5..2765660520 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,9 @@ pub mod log; pub mod historian; extern crate generic_array; extern crate rayon; +extern crate ring; extern crate serde; #[macro_use] extern crate serde_derive; extern crate sha2; +extern crate untrusted; diff --git a/src/log.rs b/src/log.rs index 23c48fb7e6..668fe55891 100644 --- a/src/log.rs +++ b/src/log.rs @@ -14,8 +14,11 @@ /// was generated by the fastest processor at the time the entry was logged. use generic_array::GenericArray; -use generic_array::typenum::U32; +use generic_array::typenum::{U32, U64}; +use ring::signature::Ed25519KeyPair; pub type Sha256Hash = GenericArray; +pub type PublicKey = GenericArray; +pub type Signature = GenericArray; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Entry { @@ -33,6 +36,11 @@ pub struct Entry { pub enum Event { Tick, Discovery(Sha256Hash), + Claim { + key: PublicKey, + data: Sha256Hash, + sig: Signature, + }, } impl Entry { @@ -49,10 +57,28 @@ impl Entry { /// Verifies self.end_hash is the result of hashing a 'start_hash' 'self.num_hashes' times. /// If the event is not a Tick, then hash that as well. pub fn verify(self: &Self, start_hash: &Sha256Hash) -> bool { + if let Event::Claim { key, data, sig } = self.event { + if !verify_signature(&key, &data, &sig) { + return false; + } + } self.end_hash == next_hash(start_hash, self.num_hashes, &self.event) } } +/// Return a Claim Event for the given hash and key-pair. +pub fn sign_hash(data: &Sha256Hash, key_pair: &Ed25519KeyPair) -> Event { + let sig = key_pair.sign(data); + let peer_public_key_bytes = key_pair.public_key_bytes(); + let sig_bytes = sig.as_ref(); + Event::Claim { + key: GenericArray::clone_from_slice(peer_public_key_bytes), + data: GenericArray::clone_from_slice(data), + sig: GenericArray::clone_from_slice(sig_bytes), + } +} + +/// Return a Sha256 hash for the given data. pub fn hash(val: &[u8]) -> Sha256Hash { use sha2::{Digest, Sha256}; let mut hasher = Sha256::default(); @@ -61,21 +87,32 @@ pub fn hash(val: &[u8]) -> Sha256Hash { } /// Return the hash of the given hash extended with the given value. -pub fn extend_and_hash(end_hash: &Sha256Hash, val: &[u8]) -> Sha256Hash { +pub fn extend_and_hash(end_hash: &Sha256Hash, ty: u8, val: &[u8]) -> Sha256Hash { let mut hash_data = end_hash.to_vec(); + hash_data.push(ty); hash_data.extend_from_slice(val); hash(&hash_data) } +pub fn hash_event(end_hash: &Sha256Hash, event: &Event) -> Sha256Hash { + match *event { + Event::Tick => *end_hash, + Event::Discovery(data) => extend_and_hash(end_hash, 1, &data), + Event::Claim { key, data, sig } => { + let mut event_data = data.to_vec(); + event_data.extend_from_slice(&sig); + event_data.extend_from_slice(&key); + extend_and_hash(end_hash, 2, &event_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); } - if let Event::Discovery(data) = *event { - return extend_and_hash(&end_hash, &data); - } - end_hash + hash_event(&end_hash, event) } /// Creates the next Tick Entry 'num_hashes' after 'start_hash'. @@ -107,6 +144,16 @@ pub fn verify_slice_seq(events: &[Entry], start_hash: &Sha256Hash) -> bool { event_pairs.all(|(x0, x1)| x1.verify(&x0.end_hash)) } +/// Verify a signed message with the given public key. +pub fn verify_signature(peer_public_key_bytes: &[u8], msg_bytes: &[u8], sig_bytes: &[u8]) -> bool { + use untrusted; + use ring::signature; + let peer_public_key = untrusted::Input::from(peer_public_key_bytes); + let msg = untrusted::Input::from(msg_bytes); + let sig = untrusted::Input::from(sig_bytes); + signature::verify(&signature::ED25519, peer_public_key, msg, sig).is_ok() +} + /// 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 std::iter; @@ -180,16 +227,38 @@ mod tests { entry }) .collect(); - assert!(verify_slice(&entries, &zero)); // inductive step + assert!(verify_slice(&entries, &zero)); // Next, swap two Discovery events and ensure verification fails. let event0 = entries[0].event.clone(); let event1 = entries[1].event.clone(); entries[0].event = event1; entries[1].event = event0; - assert!(!verify_slice(&entries, &zero)); // inductive step + assert!(!verify_slice(&entries, &zero)); } + #[test] + fn test_signature() { + use untrusted; + use ring::{rand, signature}; + let rng = rand::SystemRandom::new(); + let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); + let key_pair = + signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&pkcs8_bytes)).unwrap(); + const MESSAGE: &'static [u8] = b"hello, world"; + let event0 = sign_hash(&hash(MESSAGE), &key_pair); + let zero = Sha256Hash::default(); + let mut end_hash = zero; + let entries: Vec = [event0] + .iter() + .map(|event| { + let entry = next_entry(&end_hash, 0, event.clone()); + end_hash = entry.end_hash; + entry + }) + .collect(); + assert!(verify_slice(&entries, &zero)); + } } #[cfg(all(feature = "unstable", test))] From d78082f5e437f0fd8f45f2b115c9c724a237b722 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 24 Feb 2018 10:27:51 -0700 Subject: [PATCH 3/3] Test bad signature --- src/log.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/log.rs b/src/log.rs index 668fe55891..92be3cefcd 100644 --- a/src/log.rs +++ b/src/log.rs @@ -259,6 +259,34 @@ mod tests { .collect(); assert!(verify_slice(&entries, &zero)); } + + #[test] + fn test_bad_signature() { + use untrusted; + use ring::{rand, signature}; + let rng = rand::SystemRandom::new(); + let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); + let key_pair = + signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&pkcs8_bytes)).unwrap(); + const MESSAGE: &'static [u8] = b"hello, world"; + let mut event0 = sign_hash(&hash(MESSAGE), &key_pair); + if let Event::Claim { key, sig, .. } = event0 { + const GOODBYE: &'static [u8] = b"goodbye cruel world"; + let data = hash(GOODBYE); + event0 = Event::Claim { key, data, sig }; + } + let zero = Sha256Hash::default(); + let mut end_hash = zero; + let entries: Vec = [event0] + .iter() + .map(|event| { + let entry = next_entry(&end_hash, 0, event.clone()); + end_hash = entry.end_hash; + entry + }) + .collect(); + assert!(!verify_slice(&entries, &zero)); + } } #[cfg(all(feature = "unstable", test))]