Merge pull request #30 from garious/simplify
Unify Claim and Transaction handling
This commit is contained in:
commit
8871bb2d8e
14
README.md
14
README.md
|
@ -37,15 +37,18 @@ with by verifying each entry's hash can be generated from the hash in the previo
|
|||
extern crate silk;
|
||||
|
||||
use silk::historian::Historian;
|
||||
use silk::log::{verify_slice, Entry, Event, Sha256Hash};
|
||||
use silk::log::{verify_slice, Entry, Sha256Hash};
|
||||
use silk::event::{generate_keypair, get_pubkey, sign_claim_data, Event};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::sync::mpsc::SendError;
|
||||
|
||||
fn create_log(hist: &Historian) -> Result<(), SendError<Event>> {
|
||||
fn create_log(hist: &Historian<Sha256Hash>) -> Result<(), SendError<Event<Sha256Hash>>> {
|
||||
sleep(Duration::from_millis(15));
|
||||
let data = Sha256Hash::default();
|
||||
hist.sender.send(Event::Claim { data })?;
|
||||
let keypair = generate_keypair();
|
||||
let event0 = Event::new_claim(get_pubkey(&keypair), data, sign_claim_data(&data, &keypair));
|
||||
hist.sender.send(event0)?;
|
||||
sleep(Duration::from_millis(10));
|
||||
Ok(())
|
||||
}
|
||||
|
@ -55,11 +58,10 @@ fn main() {
|
|||
let hist = Historian::new(&seed, Some(10));
|
||||
create_log(&hist).expect("send error");
|
||||
drop(hist.sender);
|
||||
let entries: Vec<Entry> = hist.receiver.iter().collect();
|
||||
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
|
||||
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));
|
||||
|
@ -70,7 +72,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: Claim { data: [37, ...] } }
|
||||
Entry { num_hashes: 2, end_hash: [67, ...], event: Transaction { data: [37, ...] } }
|
||||
Entry { num_hashes: 3, end_hash: [123, ...], event: Tick }
|
||||
```
|
||||
|
||||
|
|
|
@ -2,11 +2,22 @@
|
|||
//! event log to record transactions. Its users can deposit funds and
|
||||
//! transfer funds to other users.
|
||||
|
||||
use log::{Entry, Event, PublicKey, Sha256Hash, Signature};
|
||||
use log::{Entry, Sha256Hash};
|
||||
use event::{Event, PublicKey, Signature};
|
||||
use historian::Historian;
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use std::sync::mpsc::{RecvError, SendError};
|
||||
use std::sync::mpsc::SendError;
|
||||
use std::collections::HashMap;
|
||||
use std::result;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AccountingError {
|
||||
InsufficientFunds,
|
||||
InvalidEvent,
|
||||
SendError,
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, AccountingError>;
|
||||
|
||||
pub struct Accountant {
|
||||
pub historian: Historian<u64>,
|
||||
|
@ -24,71 +35,43 @@ impl Accountant {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn process_event(self: &mut Self, event: &Event<u64>) {
|
||||
match *event {
|
||||
Event::Claim { key, data, .. } => {
|
||||
if self.balances.contains_key(&key) {
|
||||
if let Some(x) = self.balances.get_mut(&key) {
|
||||
*x += data;
|
||||
}
|
||||
} else {
|
||||
self.balances.insert(key, data);
|
||||
}
|
||||
}
|
||||
Event::Transaction { from, to, data, .. } => {
|
||||
if let Some(x) = self.balances.get_mut(&from) {
|
||||
*x -= data;
|
||||
}
|
||||
if self.balances.contains_key(&to) {
|
||||
if let Some(x) = self.balances.get_mut(&to) {
|
||||
*x += data;
|
||||
}
|
||||
} else {
|
||||
self.balances.insert(to, data);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync(self: &mut Self) -> Vec<Entry<u64>> {
|
||||
let mut entries = vec![];
|
||||
while let Ok(entry) = self.historian.receiver.try_recv() {
|
||||
entries.push(entry);
|
||||
}
|
||||
// TODO: Does this cause the historian's channel to get blocked?
|
||||
//use log::verify_slice_u64;
|
||||
//println!("accountant: verifying {} entries...", entries.len());
|
||||
//assert!(verify_slice_u64(&entries, &self.end_hash));
|
||||
//println!("accountant: Done verifying {} entries.", entries.len());
|
||||
|
||||
if let Some(last_entry) = entries.last() {
|
||||
self.end_hash = last_entry.end_hash;
|
||||
}
|
||||
for e in &entries {
|
||||
self.process_event(&e.event);
|
||||
}
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
pub fn deposit_signed(
|
||||
self: &Self,
|
||||
key: PublicKey,
|
||||
data: u64,
|
||||
sig: Signature,
|
||||
) -> Result<(), SendError<Event<u64>>> {
|
||||
let event = Event::Claim { key, data, sig };
|
||||
self.historian.sender.send(event)
|
||||
pub fn deposit_signed(self: &mut Self, to: PublicKey, data: u64, sig: Signature) -> Result<()> {
|
||||
let event = Event::new_claim(to, data, sig);
|
||||
if !self.historian.verify_event(&event) {
|
||||
return Err(AccountingError::InvalidEvent);
|
||||
}
|
||||
if let Err(SendError(_)) = self.historian.sender.send(event) {
|
||||
return Err(AccountingError::SendError);
|
||||
}
|
||||
|
||||
if self.balances.contains_key(&to) {
|
||||
if let Some(x) = self.balances.get_mut(&to) {
|
||||
*x += data;
|
||||
}
|
||||
} else {
|
||||
self.balances.insert(to, data);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn deposit(
|
||||
self: &Self,
|
||||
n: u64,
|
||||
keypair: &Ed25519KeyPair,
|
||||
) -> Result<Signature, SendError<Event<u64>>> {
|
||||
use log::{get_pubkey, sign_serialized};
|
||||
pub fn deposit(self: &mut Self, n: u64, keypair: &Ed25519KeyPair) -> Result<Signature> {
|
||||
use event::{get_pubkey, sign_claim_data};
|
||||
let key = get_pubkey(keypair);
|
||||
let sig = sign_serialized(&n, keypair);
|
||||
let sig = sign_claim_data(&n, keypair);
|
||||
self.deposit_signed(key, n, sig).map(|_| sig)
|
||||
}
|
||||
|
||||
|
@ -98,19 +81,37 @@ impl Accountant {
|
|||
to: PublicKey,
|
||||
data: u64,
|
||||
sig: Signature,
|
||||
) -> Result<(), SendError<Event<u64>>> {
|
||||
if self.get_balance(&from).unwrap() < data {
|
||||
// TODO: Replace the SendError result with a custom one.
|
||||
println!("Error: Insufficient funds");
|
||||
return Ok(());
|
||||
) -> Result<()> {
|
||||
if self.get_balance(&from).unwrap_or(0) < data {
|
||||
return Err(AccountingError::InsufficientFunds);
|
||||
}
|
||||
|
||||
let event = Event::Transaction {
|
||||
from,
|
||||
from: Some(from),
|
||||
to,
|
||||
data,
|
||||
sig,
|
||||
};
|
||||
self.historian.sender.send(event)
|
||||
if !self.historian.verify_event(&event) {
|
||||
return Err(AccountingError::InvalidEvent);
|
||||
}
|
||||
if let Err(SendError(_)) = self.historian.sender.send(event) {
|
||||
return Err(AccountingError::SendError);
|
||||
}
|
||||
|
||||
if let Some(x) = self.balances.get_mut(&from) {
|
||||
*x -= data;
|
||||
}
|
||||
|
||||
if self.balances.contains_key(&to) {
|
||||
if let Some(x) = self.balances.get_mut(&to) {
|
||||
*x += data;
|
||||
}
|
||||
} else {
|
||||
self.balances.insert(to, data);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn transfer(
|
||||
|
@ -118,17 +119,15 @@ impl Accountant {
|
|||
n: u64,
|
||||
keypair: &Ed25519KeyPair,
|
||||
to: PublicKey,
|
||||
) -> Result<Signature, SendError<Event<u64>>> {
|
||||
use log::{get_pubkey, sign_transaction_data};
|
||||
|
||||
) -> Result<Signature> {
|
||||
use event::{get_pubkey, sign_transaction_data};
|
||||
let from = get_pubkey(keypair);
|
||||
let sig = sign_transaction_data(&n, keypair, &to);
|
||||
self.transfer_signed(from, to, n, sig).map(|_| sig)
|
||||
}
|
||||
|
||||
pub fn get_balance(self: &mut Self, pubkey: &PublicKey) -> Result<u64, RecvError> {
|
||||
self.sync();
|
||||
Ok(*self.balances.get(pubkey).unwrap_or(&0))
|
||||
pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option<u64> {
|
||||
self.balances.get(pubkey).map(|x| *x)
|
||||
}
|
||||
|
||||
pub fn wait_on_signature(self: &mut Self, wait_sig: &Signature) {
|
||||
|
@ -138,7 +137,6 @@ impl Accountant {
|
|||
let mut found = false;
|
||||
while !found {
|
||||
found = entries.iter().any(|e| match e.event {
|
||||
Event::Claim { sig, .. } => sig == *wait_sig,
|
||||
Event::Transaction { sig, .. } => sig == *wait_sig,
|
||||
_ => false,
|
||||
});
|
||||
|
@ -153,7 +151,7 @@ impl Accountant {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use log::{generate_keypair, get_pubkey};
|
||||
use event::{generate_keypair, get_pubkey};
|
||||
use historian::ExitReason;
|
||||
|
||||
#[test]
|
||||
|
@ -192,7 +190,10 @@ mod tests {
|
|||
acc.wait_on_signature(&sig);
|
||||
|
||||
let bob_pubkey = get_pubkey(&bob_keypair);
|
||||
acc.transfer(10_001, &alice_keypair, bob_pubkey).unwrap();
|
||||
assert_eq!(
|
||||
acc.transfer(10_001, &alice_keypair, bob_pubkey),
|
||||
Err(AccountingError::InsufficientFunds)
|
||||
);
|
||||
sleep(Duration::from_millis(30));
|
||||
|
||||
let alice_pubkey = get_pubkey(&alice_keypair);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::io;
|
||||
use accountant::Accountant;
|
||||
use log::{PublicKey, Signature};
|
||||
use event::{PublicKey, Signature};
|
||||
//use serde::Serialize;
|
||||
|
||||
pub struct AccountantSkel {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use std::net::UdpSocket;
|
||||
use std::io;
|
||||
use bincode::{deserialize, serialize};
|
||||
use log::{PublicKey, Signature};
|
||||
use event::{PublicKey, Signature};
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use accountant_skel::{Request, Response};
|
||||
|
||||
|
@ -34,9 +34,9 @@ impl AccountantStub {
|
|||
}
|
||||
|
||||
pub fn deposit(self: &mut Self, n: u64, keypair: &Ed25519KeyPair) -> io::Result<Signature> {
|
||||
use log::{get_pubkey, sign_serialized};
|
||||
use event::{get_pubkey, sign_claim_data};
|
||||
let key = get_pubkey(keypair);
|
||||
let sig = sign_serialized(&n, keypair);
|
||||
let sig = sign_claim_data(&n, keypair);
|
||||
self.deposit_signed(key, n, sig).map(|_| sig)
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ impl AccountantStub {
|
|||
keypair: &Ed25519KeyPair,
|
||||
to: PublicKey,
|
||||
) -> io::Result<Signature> {
|
||||
use log::{get_pubkey, sign_transaction_data};
|
||||
use event::{get_pubkey, sign_transaction_data};
|
||||
let from = get_pubkey(keypair);
|
||||
let sig = sign_transaction_data(&n, keypair, &to);
|
||||
self.transfer_signed(from, to, n, sig).map(|_| sig)
|
||||
|
@ -100,7 +100,8 @@ mod tests {
|
|||
use accountant_skel::AccountantSkel;
|
||||
use std::thread::{sleep, spawn};
|
||||
use std::time::Duration;
|
||||
use log::{generate_keypair, get_pubkey, Sha256Hash};
|
||||
use log::Sha256Hash;
|
||||
use event::{generate_keypair, get_pubkey};
|
||||
|
||||
#[test]
|
||||
fn test_accountant_stub() {
|
||||
|
|
|
@ -4,7 +4,7 @@ fn main() {
|
|||
use silk::accountant_stub::AccountantStub;
|
||||
use std::time::Instant;
|
||||
use std::net::UdpSocket;
|
||||
use silk::log::{generate_keypair, get_pubkey};
|
||||
use silk::event::{generate_keypair, get_pubkey};
|
||||
|
||||
let addr = "127.0.0.1:8000";
|
||||
let send_addr = "127.0.0.1:8001";
|
||||
|
@ -12,7 +12,7 @@ fn main() {
|
|||
let mut acc = AccountantStub::new(addr, socket);
|
||||
let alice_keypair = generate_keypair();
|
||||
let alice_pubkey = get_pubkey(&alice_keypair);
|
||||
let txs = 10_000;
|
||||
let txs = 2_000;
|
||||
println!("Depositing {} units in Alice's account...", txs);
|
||||
let sig = acc.deposit(txs, &alice_keypair).unwrap();
|
||||
acc.wait_on_signature(&sig).unwrap();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
extern crate silk;
|
||||
|
||||
use silk::historian::Historian;
|
||||
use silk::log::{generate_keypair, get_pubkey, sign_serialized, verify_slice, Entry, Event,
|
||||
Sha256Hash};
|
||||
use silk::log::{verify_slice, Entry, Sha256Hash};
|
||||
use silk::event::{generate_keypair, get_pubkey, sign_claim_data, Event};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::sync::mpsc::SendError;
|
||||
|
@ -11,11 +11,7 @@ fn create_log(hist: &Historian<Sha256Hash>) -> Result<(), SendError<Event<Sha256
|
|||
sleep(Duration::from_millis(15));
|
||||
let data = Sha256Hash::default();
|
||||
let keypair = generate_keypair();
|
||||
let event0 = Event::Claim {
|
||||
key: get_pubkey(&keypair),
|
||||
data,
|
||||
sig: sign_serialized(&data, &keypair),
|
||||
};
|
||||
let event0 = Event::new_claim(get_pubkey(&keypair), data, sign_claim_data(&data, &keypair));
|
||||
hist.sender.send(event0)?;
|
||||
sleep(Duration::from_millis(10));
|
||||
Ok(())
|
||||
|
@ -30,5 +26,7 @@ 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));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
//! The `log` crate provides the foundational data structures for Proof-of-History,
|
||||
//! an ordered log of events in time.
|
||||
|
||||
/// Each log entry contains three pieces of data. The 'num_hashes' field is the number
|
||||
/// of hashes performed since the previous entry. The 'end_hash' field is the result
|
||||
/// of hashing 'end_hash' from the previous entry 'num_hashes' times. The 'event'
|
||||
/// field points to an Event that took place shortly after 'end_hash' was generated.
|
||||
///
|
||||
/// If you divide 'num_hashes' by the amount of time it takes to generate a new hash, you
|
||||
/// get a duration estimate since the last event. Since processing power increases
|
||||
/// over time, one should expect the duration 'num_hashes' represents to decrease proportionally.
|
||||
/// Though processing power varies across nodes, the network gives priority to the
|
||||
/// fastest processor. Duration should therefore be estimated by assuming that the hash
|
||||
/// was generated by the fastest processor at the time the entry was logged.
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use generic_array::typenum::{U32, U64};
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use serde::Serialize;
|
||||
|
||||
pub type PublicKey = GenericArray<u8, U32>;
|
||||
pub type Signature = GenericArray<u8, U64>;
|
||||
|
||||
/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
|
||||
/// sole purpose of improving the performance of event log verification. A tick can
|
||||
/// be generated in 'num_hashes' hashes and verified in 'num_hashes' hashes. By logging
|
||||
/// a hash alongside the tick, each tick and be verified in parallel using the 'end_hash'
|
||||
/// of the preceding tick to seed its hashing.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Event<T> {
|
||||
Tick,
|
||||
Transaction {
|
||||
from: Option<PublicKey>,
|
||||
to: PublicKey,
|
||||
data: T,
|
||||
sig: Signature,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
pub fn new_claim(to: PublicKey, data: T, sig: Signature) -> Self {
|
||||
Event::Transaction {
|
||||
from: None,
|
||||
to,
|
||||
data,
|
||||
sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a new ED25519 keypair
|
||||
pub fn generate_keypair() -> Ed25519KeyPair {
|
||||
use ring::{rand, signature};
|
||||
use untrusted;
|
||||
let rng = rand::SystemRandom::new();
|
||||
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
|
||||
signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&pkcs8_bytes)).unwrap()
|
||||
}
|
||||
|
||||
/// Return the public key for the given keypair
|
||||
pub fn get_pubkey(keypair: &Ed25519KeyPair) -> PublicKey {
|
||||
GenericArray::clone_from_slice(keypair.public_key_bytes())
|
||||
}
|
||||
|
||||
/// Return a signature for the given data using the private key from the given keypair.
|
||||
fn sign_serialized<T: Serialize>(data: &T, keypair: &Ed25519KeyPair) -> Signature {
|
||||
use bincode::serialize;
|
||||
let serialized = serialize(data).unwrap();
|
||||
GenericArray::clone_from_slice(keypair.sign(&serialized).as_ref())
|
||||
}
|
||||
|
||||
/// Return a signature for the given transaction data using the private key from the given keypair.
|
||||
pub fn sign_transaction_data<T: Serialize>(
|
||||
data: &T,
|
||||
keypair: &Ed25519KeyPair,
|
||||
to: &PublicKey,
|
||||
) -> Signature {
|
||||
let from = &Some(get_pubkey(keypair));
|
||||
sign_serialized(&(from, to, data), keypair)
|
||||
}
|
||||
|
||||
/// Return a signature for the given data using the private key from the given keypair.
|
||||
pub fn sign_claim_data<T: Serialize>(data: &T, keypair: &Ed25519KeyPair) -> Signature {
|
||||
let to = get_pubkey(keypair);
|
||||
let from: Option<PublicKey> = None;
|
||||
sign_serialized(&(&from, &to, data), keypair)
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
pub fn get_signature<T>(event: &Event<T>) -> Option<Signature> {
|
||||
match *event {
|
||||
Event::Tick => None,
|
||||
Event::Transaction { sig, .. } => Some(sig),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_event<T: Serialize>(event: &Event<T>) -> bool {
|
||||
use bincode::serialize;
|
||||
if let Event::Transaction {
|
||||
from,
|
||||
to,
|
||||
ref data,
|
||||
sig,
|
||||
} = *event
|
||||
{
|
||||
let sign_data = serialize(&(&from, &to, &data)).unwrap();
|
||||
if !verify_signature(&from.unwrap_or(to), &sign_data, &sig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bincode::{deserialize, serialize};
|
||||
|
||||
#[test]
|
||||
fn test_serialize_claim() {
|
||||
let claim0 = Event::new_claim(Default::default(), 0u8, Default::default());
|
||||
let buf = serialize(&claim0).unwrap();
|
||||
let claim1: Event<u8> = deserialize(&buf).unwrap();
|
||||
assert_eq!(claim1, claim0);
|
||||
}
|
||||
}
|
|
@ -6,10 +6,11 @@
|
|||
//! The resulting stream of entries represents ordered events in time.
|
||||
|
||||
use std::thread::JoinHandle;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::mpsc::{Receiver, SyncSender};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use log::{get_signature, hash, hash_event, verify_event, Entry, Event, Sha256Hash, Signature};
|
||||
use log::{hash, hash_event, Entry, Sha256Hash};
|
||||
use event::{get_signature, verify_event, Event, Signature};
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
@ -17,6 +18,7 @@ pub struct Historian<T> {
|
|||
pub sender: SyncSender<Event<T>>,
|
||||
pub receiver: Receiver<Entry<T>>,
|
||||
pub thread_hdl: JoinHandle<(Entry<T>, ExitReason)>,
|
||||
pub signatures: HashSet<Signature>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -43,10 +45,25 @@ fn log_event<T: Serialize + Clone + Debug>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_event_and_reserve_signature<T: Serialize>(
|
||||
signatures: &mut HashSet<Signature>,
|
||||
event: &Event<T>,
|
||||
) -> bool {
|
||||
if !verify_event(&event) {
|
||||
return false;
|
||||
}
|
||||
if let Some(sig) = get_signature(&event) {
|
||||
if signatures.contains(&sig) {
|
||||
return false;
|
||||
}
|
||||
signatures.insert(sig);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn log_events<T: Serialize + Clone + Debug>(
|
||||
receiver: &Receiver<Event<T>>,
|
||||
sender: &SyncSender<Entry<T>>,
|
||||
signatures: &mut HashMap<Signature, bool>,
|
||||
num_hashes: &mut u64,
|
||||
end_hash: &mut Sha256Hash,
|
||||
epoch: SystemTime,
|
||||
|
@ -64,15 +81,7 @@ fn log_events<T: Serialize + Clone + Debug>(
|
|||
}
|
||||
match receiver.try_recv() {
|
||||
Ok(event) => {
|
||||
if verify_event(&event) {
|
||||
if let Some(sig) = get_signature(&event) {
|
||||
if signatures.contains_key(&sig) {
|
||||
continue;
|
||||
}
|
||||
signatures.insert(sig, true);
|
||||
}
|
||||
log_event(sender, num_hashes, end_hash, event)?;
|
||||
}
|
||||
log_event(sender, num_hashes, end_hash, event)?;
|
||||
}
|
||||
Err(TryRecvError::Empty) => {
|
||||
return Ok(());
|
||||
|
@ -102,13 +111,11 @@ pub fn create_logger<T: 'static + Serialize + Clone + Debug + Send>(
|
|||
let mut end_hash = start_hash;
|
||||
let mut num_hashes = 0;
|
||||
let mut num_ticks = 0;
|
||||
let mut signatures = HashMap::new();
|
||||
let epoch = SystemTime::now();
|
||||
loop {
|
||||
if let Err(err) = log_events(
|
||||
&receiver,
|
||||
&sender,
|
||||
&mut signatures,
|
||||
&mut num_hashes,
|
||||
&mut end_hash,
|
||||
epoch,
|
||||
|
@ -129,18 +136,24 @@ impl<T: 'static + Serialize + Clone + Debug + Send> Historian<T> {
|
|||
let (sender, event_receiver) = sync_channel(1000);
|
||||
let (entry_sender, receiver) = sync_channel(1000);
|
||||
let thread_hdl = create_logger(*start_hash, ms_per_tick, event_receiver, entry_sender);
|
||||
let signatures = HashSet::new();
|
||||
Historian {
|
||||
sender,
|
||||
receiver,
|
||||
thread_hdl,
|
||||
signatures,
|
||||
}
|
||||
}
|
||||
pub fn verify_event(self: &mut Self, event: &Event<T>) -> bool {
|
||||
return verify_event_and_reserve_signature(&mut self.signatures, event);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use log::*;
|
||||
use event::*;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -199,22 +212,24 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_event_attack() {
|
||||
let zero = Sha256Hash::default();
|
||||
let hist = Historian::new(&zero, None);
|
||||
fn test_bad_event_signature() {
|
||||
let keypair = generate_keypair();
|
||||
let event0 = Event::Claim {
|
||||
key: get_pubkey(&keypair),
|
||||
data: hash(b"goodbye cruel world"),
|
||||
sig: sign_serialized(&hash(b"hello, world"), &keypair),
|
||||
};
|
||||
hist.sender.send(event0).unwrap();
|
||||
drop(hist.sender);
|
||||
assert_eq!(
|
||||
hist.thread_hdl.join().unwrap().1,
|
||||
ExitReason::RecvDisconnected
|
||||
);
|
||||
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
|
||||
assert_eq!(entries.len(), 0);
|
||||
let sig = sign_claim_data(&hash(b"hello, world"), &keypair);
|
||||
let event0 = Event::new_claim(get_pubkey(&keypair), hash(b"goodbye cruel world"), sig);
|
||||
let mut sigs = HashSet::new();
|
||||
assert!(!verify_event_and_reserve_signature(&mut sigs, &event0));
|
||||
assert!(!sigs.contains(&sig));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_event_signature() {
|
||||
let keypair = generate_keypair();
|
||||
let to = get_pubkey(&keypair);
|
||||
let data = &hash(b"hello, world");
|
||||
let sig = sign_claim_data(data, &keypair);
|
||||
let event0 = Event::new_claim(to, data, sig);
|
||||
let mut sigs = HashSet::new();
|
||||
assert!(verify_event_and_reserve_signature(&mut sigs, &event0));
|
||||
assert!(!verify_event_and_reserve_signature(&mut sigs, &event0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![cfg_attr(feature = "unstable", feature(test))]
|
||||
pub mod log;
|
||||
pub mod event;
|
||||
pub mod historian;
|
||||
pub mod accountant;
|
||||
pub mod accountant_skel;
|
||||
|
|
137
src/log.rs
137
src/log.rs
|
@ -14,13 +14,11 @@
|
|||
/// was generated by the fastest processor at the time the entry was logged.
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use generic_array::typenum::{U32, U64};
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use generic_array::typenum::U32;
|
||||
use serde::Serialize;
|
||||
use event::*;
|
||||
|
||||
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<T> {
|
||||
|
@ -29,27 +27,6 @@ pub struct Entry<T> {
|
|||
pub event: Event<T>,
|
||||
}
|
||||
|
||||
/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
|
||||
/// sole purpose of improving the performance of event log verification. A tick can
|
||||
/// be generated in 'num_hashes' hashes and verified in 'num_hashes' hashes. By logging
|
||||
/// a hash alongside the tick, each tick and be verified in parallel using the 'end_hash'
|
||||
/// of the preceding tick to seed its hashing.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Event<T> {
|
||||
Tick,
|
||||
Claim {
|
||||
key: PublicKey,
|
||||
data: T,
|
||||
sig: Signature,
|
||||
},
|
||||
Transaction {
|
||||
from: PublicKey,
|
||||
to: PublicKey,
|
||||
data: T,
|
||||
sig: Signature,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> Entry<T> {
|
||||
/// Creates a Entry from the number of hashes 'num_hashes' since the previous event
|
||||
/// and that resulting 'end_hash'.
|
||||
|
@ -62,36 +39,6 @@ impl<T> Entry<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return a new ED25519 keypair
|
||||
pub fn generate_keypair() -> Ed25519KeyPair {
|
||||
use ring::{rand, signature};
|
||||
use untrusted;
|
||||
let rng = rand::SystemRandom::new();
|
||||
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
|
||||
signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&pkcs8_bytes)).unwrap()
|
||||
}
|
||||
|
||||
/// Return the public key for the given keypair
|
||||
pub fn get_pubkey(keypair: &Ed25519KeyPair) -> PublicKey {
|
||||
GenericArray::clone_from_slice(keypair.public_key_bytes())
|
||||
}
|
||||
|
||||
/// Return a signature for the given data using the private key from the given keypair.
|
||||
pub fn sign_serialized<T: Serialize>(data: &T, keypair: &Ed25519KeyPair) -> Signature {
|
||||
use bincode::serialize;
|
||||
let serialized = serialize(data).unwrap();
|
||||
GenericArray::clone_from_slice(keypair.sign(&serialized).as_ref())
|
||||
}
|
||||
|
||||
/// Return a signature for the given transaction data using the private key from the given keypair.
|
||||
pub fn sign_transaction_data<T: Serialize>(
|
||||
data: &T,
|
||||
keypair: &Ed25519KeyPair,
|
||||
to: &PublicKey,
|
||||
) -> Signature {
|
||||
sign_serialized(&(data, to), keypair)
|
||||
}
|
||||
|
||||
/// Return a Sha256 hash for the given data.
|
||||
pub fn hash(val: &[u8]) -> Sha256Hash {
|
||||
use sha2::{Digest, Sha256};
|
||||
|
@ -107,14 +54,6 @@ pub fn extend_and_hash(end_hash: &Sha256Hash, val: &[u8]) -> Sha256Hash {
|
|||
hash(&hash_data)
|
||||
}
|
||||
|
||||
pub fn get_signature<T>(event: &Event<T>) -> Option<Signature> {
|
||||
match *event {
|
||||
Event::Tick => None,
|
||||
Event::Claim { sig, .. } => Some(sig),
|
||||
Event::Transaction { sig, .. } => Some(sig),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_event<T>(end_hash: &Sha256Hash, event: &Event<T>) -> Sha256Hash {
|
||||
match get_signature(event) {
|
||||
None => *end_hash,
|
||||
|
@ -164,29 +103,6 @@ pub fn next_tick<T: Serialize>(start_hash: &Sha256Hash, num_hashes: u64) -> Entr
|
|||
next_entry(start_hash, num_hashes, Event::Tick)
|
||||
}
|
||||
|
||||
pub fn verify_event<T: Serialize>(event: &Event<T>) -> bool {
|
||||
use bincode::serialize;
|
||||
if let Event::Claim { key, ref data, sig } = *event {
|
||||
let mut claim_data = serialize(&data).unwrap();
|
||||
if !verify_signature(&key, &claim_data, &sig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Event::Transaction {
|
||||
from,
|
||||
to,
|
||||
ref data,
|
||||
sig,
|
||||
} = *event
|
||||
{
|
||||
let sign_data = serialize(&(&data, &to)).unwrap();
|
||||
if !verify_signature(&from, &sign_data, &sig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// 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_entry<T: Serialize>(entry: &Entry<T>, start_hash: &Sha256Hash) -> bool {
|
||||
|
@ -219,16 +135,6 @@ pub fn verify_slice_seq<T: Serialize>(events: &[Entry<T>], start_hash: &Sha256Ha
|
|||
event_pairs.all(|(x0, x1)| verify_entry(&x1, &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()
|
||||
}
|
||||
|
||||
pub fn create_entries<T: Serialize>(
|
||||
start_hash: &Sha256Hash,
|
||||
num_hashes: u64,
|
||||
|
@ -303,24 +209,15 @@ mod tests {
|
|||
let zero = Sha256Hash::default();
|
||||
let one = hash(&zero);
|
||||
|
||||
// First, verify Claim events
|
||||
// First, verify entries
|
||||
let keypair = generate_keypair();
|
||||
let event0 = Event::Claim {
|
||||
key: get_pubkey(&keypair),
|
||||
data: zero,
|
||||
sig: sign_serialized(&zero, &keypair),
|
||||
};
|
||||
|
||||
let event1 = Event::Claim {
|
||||
key: get_pubkey(&keypair),
|
||||
data: one,
|
||||
sig: sign_serialized(&one, &keypair),
|
||||
};
|
||||
let event0 = Event::new_claim(get_pubkey(&keypair), zero, sign_claim_data(&zero, &keypair));
|
||||
let event1 = Event::new_claim(get_pubkey(&keypair), one, sign_claim_data(&one, &keypair));
|
||||
let events = vec![event0, event1];
|
||||
let mut entries = create_entries(&zero, 0, events);
|
||||
assert!(verify_slice(&entries, &zero));
|
||||
|
||||
// Next, swap two Claim events and ensure verification fails.
|
||||
// Next, swap two events and ensure verification fails.
|
||||
let event0 = entries[0].event.clone();
|
||||
let event1 = entries[1].event.clone();
|
||||
entries[0].event = event1;
|
||||
|
@ -332,11 +229,7 @@ mod tests {
|
|||
fn test_claim() {
|
||||
let keypair = generate_keypair();
|
||||
let data = hash(b"hello, world");
|
||||
let event0 = Event::Claim {
|
||||
key: get_pubkey(&keypair),
|
||||
data,
|
||||
sig: sign_serialized(&data, &keypair),
|
||||
};
|
||||
let event0 = Event::new_claim(get_pubkey(&keypair), data, sign_claim_data(&data, &keypair));
|
||||
let zero = Sha256Hash::default();
|
||||
let entries = create_entries(&zero, 0, vec![event0]);
|
||||
assert!(verify_slice(&entries, &zero));
|
||||
|
@ -345,11 +238,11 @@ mod tests {
|
|||
#[test]
|
||||
fn test_wrong_data_claim_attack() {
|
||||
let keypair = generate_keypair();
|
||||
let event0 = Event::Claim {
|
||||
key: get_pubkey(&keypair),
|
||||
data: hash(b"goodbye cruel world"),
|
||||
sig: sign_serialized(&hash(b"hello, world"), &keypair),
|
||||
};
|
||||
let event0 = Event::new_claim(
|
||||
get_pubkey(&keypair),
|
||||
hash(b"goodbye cruel world"),
|
||||
sign_claim_data(&hash(b"hello, world"), &keypair),
|
||||
);
|
||||
let zero = Sha256Hash::default();
|
||||
let entries = create_entries(&zero, 0, vec![event0]);
|
||||
assert!(!verify_slice(&entries, &zero));
|
||||
|
@ -362,7 +255,7 @@ mod tests {
|
|||
let pubkey1 = get_pubkey(&keypair1);
|
||||
let data = hash(b"hello, world");
|
||||
let event0 = Event::Transaction {
|
||||
from: get_pubkey(&keypair0),
|
||||
from: Some(get_pubkey(&keypair0)),
|
||||
to: pubkey1,
|
||||
data,
|
||||
sig: sign_transaction_data(&data, &keypair0, &pubkey1),
|
||||
|
@ -379,7 +272,7 @@ mod tests {
|
|||
let pubkey1 = get_pubkey(&keypair1);
|
||||
let data = hash(b"hello, world");
|
||||
let event0 = Event::Transaction {
|
||||
from: get_pubkey(&keypair0),
|
||||
from: Some(get_pubkey(&keypair0)),
|
||||
to: pubkey1,
|
||||
data: hash(b"goodbye cruel world"), // <-- attack!
|
||||
sig: sign_transaction_data(&data, &keypair0, &pubkey1),
|
||||
|
@ -397,7 +290,7 @@ mod tests {
|
|||
let pubkey1 = get_pubkey(&keypair1);
|
||||
let data = hash(b"hello, world");
|
||||
let event0 = Event::Transaction {
|
||||
from: get_pubkey(&keypair0),
|
||||
from: Some(get_pubkey(&keypair0)),
|
||||
to: get_pubkey(&thief_keypair), // <-- attack!
|
||||
data: hash(b"goodbye cruel world"),
|
||||
sig: sign_transaction_data(&data, &keypair0, &pubkey1),
|
||||
|
|
Loading…
Reference in New Issue