From 08e501e57b510d5cfc20b90f80ac31f0994abd8f Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 24 Feb 2018 06:53:36 -0700 Subject: [PATCH] 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 31e6919a4..47e88ac8d 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 606997f95..8312272d9 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 3ccb87efc..276566052 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 23c48fb7e..668fe5589 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))]