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.
This commit is contained in:
Greg Fitzgerald 2018-02-24 06:53:36 -07:00
parent 29a607427d
commit 08e501e57b
4 changed files with 83 additions and 12 deletions

View File

@ -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"

View File

@ -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<Event>,
@ -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,

View File

@ -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;

View File

@ -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<u8, U32>;
pub type PublicKey = GenericArray<u8, U32>;
pub type Signature = GenericArray<u8, U64>;
#[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<Entry> {
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<Entry> = [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))]