Compare commits

..

7 Commits

Author SHA1 Message Date
b8d52cc3e4 Make the Discovery event into a struct instead of a tuple 2018-02-24 11:15:03 -07:00
7d9bab9508 Update rendered demo diagram 2018-02-24 11:09:00 -07:00
944181a30e Version bump 2018-02-24 11:06:08 -07:00
d8dd50505a Merge pull request #21 from garious/add-signatures
Add signatures
2018-02-24 10:47:25 -07:00
d78082f5e4 Test bad signature 2018-02-24 10:27:51 -07:00
08e501e57b 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.
2018-02-24 10:09:49 -07:00
29a607427d 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.
2018-02-24 05:25:19 -07:00
7 changed files with 133 additions and 27 deletions

View File

@ -1,7 +1,7 @@
[package]
name = "silk"
description = "A silky smooth implementation of the Loom architecture"
version = "0.2.2"
version = "0.2.3"
documentation = "https://docs.rs/silk"
homepage = "http://loomprotocol.com/"
repository = "https://github.com/loomprotocol/silk"
@ -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

@ -24,7 +24,7 @@ Create a *Historian* and send it *events* to generate an *event log*, where each
is tagged with the historian's latest *hash*. Then ensure the order of events was not tampered
with by verifying each entry's hash can be generated from the hash in the previous entry:
![historian](https://user-images.githubusercontent.com/55449/36499105-7c8db6a0-16fd-11e8-8b88-c6e0f52d7a50.png)
![historian](https://user-images.githubusercontent.com/55449/36633440-f76f7bb8-1952-11e8-8328-387861d3d464.png)
```rust
extern crate silk;
@ -37,7 +37,8 @@ use std::sync::mpsc::SendError;
fn create_log(hist: &Historian) -> Result<(), SendError<Event>> {
sleep(Duration::from_millis(15));
hist.sender.send(Event::UserDataKey(Sha256Hash::default()))?;
let data = Sha256Hash::default();
hist.sender.send(Event::Discovery { data })?;
sleep(Duration::from_millis(10));
Ok(())
}
@ -62,7 +63,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 { data: [37, ...] } }
Entry { num_hashes: 3, end_hash: [123, ...], event: Tick }
```

View File

@ -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)" ] ;

View File

@ -8,7 +8,8 @@ use std::sync::mpsc::SendError;
fn create_log(hist: &Historian) -> Result<(), SendError<Event>> {
sleep(Duration::from_millis(15));
hist.sender.send(Event::UserDataKey(Sha256Hash::default()))?;
let data = Sha256Hash::default();
hist.sender.send(Event::Discovery { data })?;
sleep(Duration::from_millis(10));
Ok(())
}

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::UserDataKey(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,
@ -139,7 +137,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 { data: zero }).unwrap();
sleep(Duration::new(0, 1_000_000));
hist.sender.send(Event::Tick).unwrap();
@ -173,7 +171,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 { data: zero }).unwrap();
sleep(Duration::from_millis(15));
drop(hist.sender);
assert_eq!(

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 {
@ -32,7 +35,14 @@ pub struct Entry {
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Event {
Tick,
UserDataKey(Sha256Hash),
Discovery {
data: Sha256Hash,
},
Claim {
key: PublicKey,
data: Sha256Hash,
sig: Signature,
},
}
impl Entry {
@ -47,12 +57,30 @@ 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 {
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 +89,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::UserDataKey(key) = *event {
return extend_and_hash(&end_hash, &key);
}
end_hash
hash_event(&end_hash, event)
}
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
@ -107,6 +146,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;
@ -169,9 +218,12 @@ 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 { data: zero },
Event::Discovery { data: one },
];
let mut entries: Vec<Entry> = events
.iter()
.map(|event| {
@ -180,16 +232,66 @@ mod tests {
entry
})
.collect();
assert!(verify_slice(&entries, &zero)); // inductive step
assert!(verify_slice(&entries, &zero));
// 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;
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));
}
#[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<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))]