Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
12eba4bcc7 | |||
4610de8fdd | |||
3fcc2dd944 | |||
8299bae2d4 | |||
604ccf7552 | |||
f3dd47948a | |||
c3bb207488 | |||
9009d1bfb3 | |||
fa4d9e8bcb | |||
34b77efc87 | |||
5ca0ccbcd2 | |||
6aa4e52480 | |||
f98e9a2ad7 | |||
c6134cc25b | |||
0443b39264 | |||
8b0b8efbcb | |||
97449cee43 | |||
ab5252c750 | |||
05a27cb34d | |||
b02eab57d2 | |||
b8d52cc3e4 | |||
7d9bab9508 | |||
944181a30e | |||
d8dd50505a | |||
d78082f5e4 | |||
08e501e57b | |||
29a607427d |
13
Cargo.toml
13
Cargo.toml
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "silk"
|
name = "silk"
|
||||||
description = "A silky smooth implementation of the Loom architecture"
|
description = "A silky smooth implementation of the Loom architecture"
|
||||||
version = "0.2.2"
|
version = "0.3.0"
|
||||||
documentation = "https://docs.rs/silk"
|
documentation = "https://docs.rs/silk"
|
||||||
homepage = "http://loomprotocol.com/"
|
homepage = "http://loomprotocol.com/"
|
||||||
repository = "https://github.com/loomprotocol/silk"
|
repository = "https://github.com/loomprotocol/silk"
|
||||||
@ -15,6 +15,14 @@ license = "Apache-2.0"
|
|||||||
name = "silk-demo"
|
name = "silk-demo"
|
||||||
path = "src/bin/demo.rs"
|
path = "src/bin/demo.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "silk-client-demo"
|
||||||
|
path = "src/bin/client-demo.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "silk-testnode"
|
||||||
|
path = "src/bin/testnode.rs"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
codecov = { repository = "loomprotocol/silk", branch = "master", service = "github" }
|
codecov = { repository = "loomprotocol/silk", branch = "master", service = "github" }
|
||||||
|
|
||||||
@ -29,3 +37,6 @@ sha2-asm = {version="0.3", optional=true}
|
|||||||
generic-array = { version = "0.9.0", default-features = false, features = ["serde"] }
|
generic-array = { version = "0.9.0", default-features = false, features = ["serde"] }
|
||||||
serde = "1.0.27"
|
serde = "1.0.27"
|
||||||
serde_derive = "1.0.27"
|
serde_derive = "1.0.27"
|
||||||
|
ring = "0.12.1"
|
||||||
|
untrusted = "0.5.1"
|
||||||
|
bincode = "1.0.0"
|
||||||
|
@ -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
|
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:
|
with by verifying each entry's hash can be generated from the hash in the previous entry:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate silk;
|
extern crate silk;
|
||||||
@ -37,7 +37,8 @@ use std::sync::mpsc::SendError;
|
|||||||
|
|
||||||
fn create_log(hist: &Historian) -> Result<(), SendError<Event>> {
|
fn create_log(hist: &Historian) -> Result<(), SendError<Event>> {
|
||||||
sleep(Duration::from_millis(15));
|
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));
|
sleep(Duration::from_millis(10));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -62,7 +63,7 @@ Running the program should produce a log similar to:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
Entry { num_hashes: 0, end_hash: [0, ...], event: Tick }
|
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 }
|
Entry { num_hashes: 3, end_hash: [123, ...], event: Tick }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ msc {
|
|||||||
logger=>historian [ label = "e0 = Entry{hash: h0, n: 0, event: Tick}" ] ;
|
logger=>historian [ label = "e0 = Entry{hash: h0, n: 0, event: Tick}" ] ;
|
||||||
logger=>logger [ label = "h1 = hash(h0)" ] ;
|
logger=>logger [ label = "h1 = hash(h0)" ] ;
|
||||||
logger=>logger [ label = "h2 = hash(h1)" ] ;
|
logger=>logger [ label = "h2 = hash(h1)" ] ;
|
||||||
client=>historian [ label = "UserData(d0)" ] ;
|
client=>historian [ label = "Discovery(d0)" ] ;
|
||||||
historian=>logger [ label = "UserData(d0)" ] ;
|
historian=>logger [ label = "Discovery(d0)" ] ;
|
||||||
logger=>logger [ label = "h3 = hash(h2 + 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 = "h4 = hash(h3)" ] ;
|
||||||
logger=>logger [ label = "h5 = hash(h4)" ] ;
|
logger=>logger [ label = "h5 = hash(h4)" ] ;
|
||||||
logger=>logger [ label = "h6 = hash(h5)" ] ;
|
logger=>logger [ label = "h6 = hash(h5)" ] ;
|
||||||
|
229
src/accountant.rs
Normal file
229
src/accountant.rs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
//! The `accountant` is a client of the `historian`. It uses the historian's
|
||||||
|
//! event log to record transactions. Its users can deposit funds and
|
||||||
|
//! transfer funds to other users.
|
||||||
|
|
||||||
|
use log::{Event, PublicKey, Sha256Hash, Signature};
|
||||||
|
use historian::Historian;
|
||||||
|
use ring::signature::Ed25519KeyPair;
|
||||||
|
use std::sync::mpsc::{RecvError, SendError};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct Accountant {
|
||||||
|
pub historian: Historian<u64>,
|
||||||
|
pub balances: HashMap<PublicKey, u64>,
|
||||||
|
pub end_hash: Sha256Hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Accountant {
|
||||||
|
pub fn new(start_hash: &Sha256Hash, ms_per_tick: Option<u64>) -> Self {
|
||||||
|
let hist = Historian::<u64>::new(start_hash, ms_per_tick);
|
||||||
|
Accountant {
|
||||||
|
historian: hist,
|
||||||
|
balances: HashMap::new(),
|
||||||
|
end_hash: *start_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
self: &Self,
|
||||||
|
n: u64,
|
||||||
|
keypair: &Ed25519KeyPair,
|
||||||
|
) -> Result<(), SendError<Event<u64>>> {
|
||||||
|
use log::{get_pubkey, sign_serialized};
|
||||||
|
let key = get_pubkey(keypair);
|
||||||
|
let sig = sign_serialized(&n, keypair);
|
||||||
|
self.deposit_signed(key, n, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_signed(
|
||||||
|
self: &mut Self,
|
||||||
|
from: PublicKey,
|
||||||
|
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(());
|
||||||
|
}
|
||||||
|
let event = Event::Transaction {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
sig,
|
||||||
|
};
|
||||||
|
self.historian.sender.send(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer(
|
||||||
|
self: &mut Self,
|
||||||
|
n: u64,
|
||||||
|
keypair: &Ed25519KeyPair,
|
||||||
|
to: PublicKey,
|
||||||
|
) -> Result<(), SendError<Event<u64>>> {
|
||||||
|
use log::{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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_balance(self: &mut Self, pubkey: &PublicKey) -> Result<u64, RecvError> {
|
||||||
|
self.sync();
|
||||||
|
Ok(*self.balances.get(pubkey).unwrap_or(&0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
use log::{generate_keypair, get_pubkey};
|
||||||
|
use historian::ExitReason;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_accountant() {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let mut acc = Accountant::new(&zero, Some(2));
|
||||||
|
let alice_keypair = generate_keypair();
|
||||||
|
let bob_keypair = generate_keypair();
|
||||||
|
acc.deposit(10_000, &alice_keypair).unwrap();
|
||||||
|
acc.deposit(1_000, &bob_keypair).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
let bob_pubkey = get_pubkey(&bob_keypair);
|
||||||
|
acc.transfer(500, &alice_keypair, bob_pubkey).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500);
|
||||||
|
|
||||||
|
drop(acc.historian.sender);
|
||||||
|
assert_eq!(
|
||||||
|
acc.historian.thread_hdl.join().unwrap().1,
|
||||||
|
ExitReason::RecvDisconnected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_transfer() {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let mut acc = Accountant::new(&zero, Some(2));
|
||||||
|
let alice_keypair = generate_keypair();
|
||||||
|
let bob_keypair = generate_keypair();
|
||||||
|
acc.deposit(10_000, &alice_keypair).unwrap();
|
||||||
|
acc.deposit(1_000, &bob_keypair).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
let bob_pubkey = get_pubkey(&bob_keypair);
|
||||||
|
acc.transfer(10_001, &alice_keypair, bob_pubkey).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
let alice_pubkey = get_pubkey(&alice_keypair);
|
||||||
|
assert_eq!(acc.get_balance(&alice_pubkey).unwrap(), 10_000);
|
||||||
|
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000);
|
||||||
|
|
||||||
|
drop(acc.historian.sender);
|
||||||
|
assert_eq!(
|
||||||
|
acc.historian.thread_hdl.join().unwrap().1,
|
||||||
|
ExitReason::RecvDisconnected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_claims() {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let mut acc = Accountant::new(&zero, Some(2));
|
||||||
|
let keypair = generate_keypair();
|
||||||
|
acc.deposit(1, &keypair).unwrap();
|
||||||
|
acc.deposit(2, &keypair).unwrap();
|
||||||
|
|
||||||
|
let pubkey = get_pubkey(&keypair);
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
assert_eq!(acc.get_balance(&pubkey).unwrap(), 3);
|
||||||
|
|
||||||
|
drop(acc.historian.sender);
|
||||||
|
assert_eq!(
|
||||||
|
acc.historian.thread_hdl.join().unwrap().1,
|
||||||
|
ExitReason::RecvDisconnected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transfer_to_newb() {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let mut acc = Accountant::new(&zero, Some(2));
|
||||||
|
let alice_keypair = generate_keypair();
|
||||||
|
let bob_keypair = generate_keypair();
|
||||||
|
acc.deposit(10_000, &alice_keypair).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
let bob_pubkey = get_pubkey(&bob_keypair);
|
||||||
|
acc.transfer(500, &alice_keypair, bob_pubkey).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500);
|
||||||
|
|
||||||
|
drop(acc.historian.sender);
|
||||||
|
assert_eq!(
|
||||||
|
acc.historian.thread_hdl.join().unwrap().1,
|
||||||
|
ExitReason::RecvDisconnected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
75
src/accountant_skel.rs
Normal file
75
src/accountant_skel.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use std::io;
|
||||||
|
use accountant::Accountant;
|
||||||
|
use log::{PublicKey, Signature};
|
||||||
|
//use serde::Serialize;
|
||||||
|
|
||||||
|
pub struct AccountantSkel {
|
||||||
|
pub obj: Accountant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum Request {
|
||||||
|
Deposit {
|
||||||
|
key: PublicKey,
|
||||||
|
val: u64,
|
||||||
|
sig: Signature,
|
||||||
|
},
|
||||||
|
Transfer {
|
||||||
|
from: PublicKey,
|
||||||
|
to: PublicKey,
|
||||||
|
val: u64,
|
||||||
|
sig: Signature,
|
||||||
|
},
|
||||||
|
GetBalance {
|
||||||
|
key: PublicKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum Response {
|
||||||
|
Balance { key: PublicKey, val: u64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountantSkel {
|
||||||
|
pub fn new(obj: Accountant) -> Self {
|
||||||
|
AccountantSkel { obj }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_request(self: &mut Self, msg: Request) -> Option<Response> {
|
||||||
|
match msg {
|
||||||
|
Request::Deposit { key, val, sig } => {
|
||||||
|
let _ = self.obj.deposit_signed(key, val, sig);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Request::Transfer { from, to, val, sig } => {
|
||||||
|
let _ = self.obj.transfer_signed(from, to, val, sig);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Request::GetBalance { key } => {
|
||||||
|
let val = self.obj.get_balance(&key).unwrap();
|
||||||
|
Some(Response::Balance { key, val })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TCP Server that forwards messages to Accountant methods.
|
||||||
|
pub fn serve(self: &mut Self, addr: &str) -> io::Result<()> {
|
||||||
|
use std::net::TcpListener;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use bincode::{deserialize, serialize};
|
||||||
|
let listener = TcpListener::bind(addr)?;
|
||||||
|
let mut buf = vec![0u8; 1024];
|
||||||
|
loop {
|
||||||
|
//println!("skel: Waiting for incoming connections...");
|
||||||
|
let (mut stream, _from_addr) = listener.accept()?;
|
||||||
|
let _sz = stream.read(&mut buf)?;
|
||||||
|
|
||||||
|
// TODO: Return a descriptive error message if deserialization fails.
|
||||||
|
let req = deserialize(&buf).expect("deserialize request");
|
||||||
|
|
||||||
|
if let Some(resp) = self.process_request(req) {
|
||||||
|
stream.write(&serialize(&resp).expect("serialize response"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
src/accountant_stub.rs
Normal file
116
src/accountant_stub.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
//! The `accountant` is a client of the `historian`. It uses the historian's
|
||||||
|
//! event log to record transactions. Its users can deposit funds and
|
||||||
|
//! transfer funds to other users.
|
||||||
|
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use bincode::{deserialize, serialize};
|
||||||
|
use log::{PublicKey, Signature};
|
||||||
|
use ring::signature::Ed25519KeyPair;
|
||||||
|
use accountant_skel::{Request, Response};
|
||||||
|
|
||||||
|
pub struct AccountantStub {
|
||||||
|
pub addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountantStub {
|
||||||
|
pub fn new(addr: &str) -> Self {
|
||||||
|
AccountantStub {
|
||||||
|
addr: addr.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deposit_signed(
|
||||||
|
self: &mut Self,
|
||||||
|
key: PublicKey,
|
||||||
|
val: u64,
|
||||||
|
sig: Signature,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
let req = Request::Deposit { key, val, sig };
|
||||||
|
let data = serialize(&req).unwrap();
|
||||||
|
let mut stream = TcpStream::connect(&self.addr)?;
|
||||||
|
stream.write(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deposit(self: &mut Self, n: u64, keypair: &Ed25519KeyPair) -> io::Result<usize> {
|
||||||
|
use log::{get_pubkey, sign_serialized};
|
||||||
|
let key = get_pubkey(keypair);
|
||||||
|
let sig = sign_serialized(&n, keypair);
|
||||||
|
self.deposit_signed(key, n, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_signed(
|
||||||
|
self: &mut Self,
|
||||||
|
from: PublicKey,
|
||||||
|
to: PublicKey,
|
||||||
|
val: u64,
|
||||||
|
sig: Signature,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
let req = Request::Transfer { from, to, val, sig };
|
||||||
|
let data = serialize(&req).unwrap();
|
||||||
|
let mut stream = TcpStream::connect(&self.addr)?;
|
||||||
|
stream.write(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer(
|
||||||
|
self: &mut Self,
|
||||||
|
n: u64,
|
||||||
|
keypair: &Ed25519KeyPair,
|
||||||
|
to: PublicKey,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
use log::{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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_balance(self: &mut Self, pubkey: &PublicKey) -> io::Result<u64> {
|
||||||
|
let mut stream = TcpStream::connect(&self.addr)?;
|
||||||
|
let req = Request::GetBalance { key: *pubkey };
|
||||||
|
let data = serialize(&req).expect("serialize GetBalance");
|
||||||
|
stream.write(&data)?;
|
||||||
|
let mut buf = vec![0u8; 1024];
|
||||||
|
stream.read(&mut buf)?;
|
||||||
|
let resp = deserialize(&buf).expect("deserialize balance");
|
||||||
|
let Response::Balance { key, val } = resp;
|
||||||
|
assert_eq!(key, *pubkey);
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use accountant::Accountant;
|
||||||
|
use accountant_skel::AccountantSkel;
|
||||||
|
use std::thread::{sleep, spawn};
|
||||||
|
use std::time::Duration;
|
||||||
|
use log::{generate_keypair, get_pubkey, Sha256Hash};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_accountant_stub() {
|
||||||
|
let addr = "127.0.0.1:8000";
|
||||||
|
spawn(move || {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let acc = Accountant::new(&zero, None);
|
||||||
|
let mut skel = AccountantSkel::new(acc);
|
||||||
|
skel.serve(addr).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
|
||||||
|
let mut acc = AccountantStub::new(addr);
|
||||||
|
let alice_keypair = generate_keypair();
|
||||||
|
let bob_keypair = generate_keypair();
|
||||||
|
acc.deposit(10_000, &alice_keypair).unwrap();
|
||||||
|
acc.deposit(1_000, &bob_keypair).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
let bob_pubkey = get_pubkey(&bob_keypair);
|
||||||
|
acc.transfer(500, &alice_keypair, bob_pubkey).unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(300));
|
||||||
|
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500);
|
||||||
|
}
|
||||||
|
}
|
45
src/bin/client-demo.rs
Normal file
45
src/bin/client-demo.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
extern crate silk;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
use silk::accountant_stub::AccountantStub;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
use silk::log::{generate_keypair, get_pubkey};
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:8000";
|
||||||
|
let mut acc = AccountantStub::new(addr);
|
||||||
|
let alice_keypair = generate_keypair();
|
||||||
|
let bob_keypair = generate_keypair();
|
||||||
|
let txs = 10_000;
|
||||||
|
println!("Depositing {} units in Alice's account...", txs);
|
||||||
|
acc.deposit(txs, &alice_keypair).unwrap();
|
||||||
|
//acc.deposit(1_000, &bob_keypair).unwrap();
|
||||||
|
println!("Done.");
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
let alice_pubkey = get_pubkey(&alice_keypair);
|
||||||
|
let bob_pubkey = get_pubkey(&bob_keypair);
|
||||||
|
println!("Transferring 1 unit {} times...", txs);
|
||||||
|
for _ in 0..txs {
|
||||||
|
acc.transfer(1, &alice_keypair, bob_pubkey).unwrap();
|
||||||
|
}
|
||||||
|
println!("Done.");
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(20));
|
||||||
|
let mut alice_val = acc.get_balance(&alice_pubkey).unwrap();
|
||||||
|
while alice_val > 0 {
|
||||||
|
println!("Checking on Alice's Balance {}", alice_val);
|
||||||
|
sleep(Duration::from_millis(20));
|
||||||
|
alice_val = acc.get_balance(&alice_pubkey).unwrap();
|
||||||
|
}
|
||||||
|
println!("Done. Checking balances.");
|
||||||
|
println!(
|
||||||
|
"Alice's Final Balance {}",
|
||||||
|
acc.get_balance(&alice_pubkey).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Bob's Final Balance {}",
|
||||||
|
acc.get_balance(&bob_pubkey).unwrap()
|
||||||
|
);
|
||||||
|
}
|
@ -6,9 +6,10 @@ use std::thread::sleep;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::sync::mpsc::SendError;
|
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));
|
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));
|
sleep(Duration::from_millis(10));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -18,7 +19,7 @@ fn main() {
|
|||||||
let hist = Historian::new(&seed, Some(10));
|
let hist = Historian::new(&seed, Some(10));
|
||||||
create_log(&hist).expect("send error");
|
create_log(&hist).expect("send error");
|
||||||
drop(hist.sender);
|
drop(hist.sender);
|
||||||
let entries: Vec<Entry> = hist.receiver.iter().collect();
|
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
|
||||||
for entry in &entries {
|
for entry in &entries {
|
||||||
println!("{:?}", entry);
|
println!("{:?}", entry);
|
||||||
}
|
}
|
||||||
|
14
src/bin/testnode.rs
Normal file
14
src/bin/testnode.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
extern crate silk;
|
||||||
|
|
||||||
|
use silk::accountant_skel::AccountantSkel;
|
||||||
|
use silk::accountant::Accountant;
|
||||||
|
use silk::log::Sha256Hash;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let addr = "127.0.0.1:8000";
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let acc = Accountant::new(&zero, Some(1000));
|
||||||
|
let mut skel = AccountantSkel::new(acc);
|
||||||
|
println!("Listening on {}", addr);
|
||||||
|
skel.serve(addr).unwrap();
|
||||||
|
}
|
@ -6,14 +6,16 @@
|
|||||||
//! The resulting stream of entries represents ordered events in time.
|
//! The resulting stream of entries represents ordered events in time.
|
||||||
|
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, SyncSender};
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
use log::{extend_and_hash, hash, Entry, Event, Sha256Hash};
|
use log::{hash, hash_event, verify_event, Entry, Event, Sha256Hash};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub struct Historian {
|
pub struct Historian<T> {
|
||||||
pub sender: Sender<Event>,
|
pub sender: SyncSender<Event<T>>,
|
||||||
pub receiver: Receiver<Entry>,
|
pub receiver: Receiver<Entry<T>>,
|
||||||
pub thread_hdl: JoinHandle<(Entry, ExitReason)>,
|
pub thread_hdl: JoinHandle<(Entry<T>, ExitReason)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@ -21,15 +23,13 @@ pub enum ExitReason {
|
|||||||
RecvDisconnected,
|
RecvDisconnected,
|
||||||
SendDisconnected,
|
SendDisconnected,
|
||||||
}
|
}
|
||||||
fn log_event(
|
fn log_event<T: Serialize + Clone + Debug>(
|
||||||
sender: &Sender<Entry>,
|
sender: &SyncSender<Entry<T>>,
|
||||||
num_hashes: &mut u64,
|
num_hashes: &mut u64,
|
||||||
end_hash: &mut Sha256Hash,
|
end_hash: &mut Sha256Hash,
|
||||||
event: Event,
|
event: Event<T>,
|
||||||
) -> Result<(), (Entry, ExitReason)> {
|
) -> Result<(), (Entry<T>, ExitReason)> {
|
||||||
if let Event::UserDataKey(key) = event {
|
*end_hash = hash_event(end_hash, &event);
|
||||||
*end_hash = extend_and_hash(end_hash, &key);
|
|
||||||
}
|
|
||||||
let entry = Entry {
|
let entry = Entry {
|
||||||
end_hash: *end_hash,
|
end_hash: *end_hash,
|
||||||
num_hashes: *num_hashes,
|
num_hashes: *num_hashes,
|
||||||
@ -42,15 +42,15 @@ fn log_event(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_events(
|
fn log_events<T: Serialize + Clone + Debug>(
|
||||||
receiver: &Receiver<Event>,
|
receiver: &Receiver<Event<T>>,
|
||||||
sender: &Sender<Entry>,
|
sender: &SyncSender<Entry<T>>,
|
||||||
num_hashes: &mut u64,
|
num_hashes: &mut u64,
|
||||||
end_hash: &mut Sha256Hash,
|
end_hash: &mut Sha256Hash,
|
||||||
epoch: SystemTime,
|
epoch: SystemTime,
|
||||||
num_ticks: &mut u64,
|
num_ticks: &mut u64,
|
||||||
ms_per_tick: Option<u64>,
|
ms_per_tick: Option<u64>,
|
||||||
) -> Result<(), (Entry, ExitReason)> {
|
) -> Result<(), (Entry<T>, ExitReason)> {
|
||||||
use std::sync::mpsc::TryRecvError;
|
use std::sync::mpsc::TryRecvError;
|
||||||
loop {
|
loop {
|
||||||
if let Some(ms) = ms_per_tick {
|
if let Some(ms) = ms_per_tick {
|
||||||
@ -62,8 +62,10 @@ fn log_events(
|
|||||||
}
|
}
|
||||||
match receiver.try_recv() {
|
match receiver.try_recv() {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
|
if verify_event(&event) {
|
||||||
log_event(sender, num_hashes, end_hash, event)?;
|
log_event(sender, num_hashes, end_hash, event)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Err(TryRecvError::Empty) => {
|
Err(TryRecvError::Empty) => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -81,12 +83,12 @@ fn log_events(
|
|||||||
|
|
||||||
/// A background thread that will continue tagging received Event messages and
|
/// A background thread that will continue tagging received Event messages and
|
||||||
/// sending back Entry messages until either the receiver or sender channel is closed.
|
/// sending back Entry messages until either the receiver or sender channel is closed.
|
||||||
pub fn create_logger(
|
pub fn create_logger<T: 'static + Serialize + Clone + Debug + Send>(
|
||||||
start_hash: Sha256Hash,
|
start_hash: Sha256Hash,
|
||||||
ms_per_tick: Option<u64>,
|
ms_per_tick: Option<u64>,
|
||||||
receiver: Receiver<Event>,
|
receiver: Receiver<Event<T>>,
|
||||||
sender: Sender<Entry>,
|
sender: SyncSender<Entry<T>>,
|
||||||
) -> JoinHandle<(Entry, ExitReason)> {
|
) -> JoinHandle<(Entry<T>, ExitReason)> {
|
||||||
use std::thread;
|
use std::thread;
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let mut end_hash = start_hash;
|
let mut end_hash = start_hash;
|
||||||
@ -111,11 +113,11 @@ pub fn create_logger(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Historian {
|
impl<T: 'static + Serialize + Clone + Debug + Send> Historian<T> {
|
||||||
pub fn new(start_hash: &Sha256Hash, ms_per_tick: Option<u64>) -> Self {
|
pub fn new(start_hash: &Sha256Hash, ms_per_tick: Option<u64>) -> Self {
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::sync_channel;
|
||||||
let (sender, event_receiver) = channel();
|
let (sender, event_receiver) = sync_channel(4000);
|
||||||
let (entry_sender, receiver) = channel();
|
let (entry_sender, receiver) = sync_channel(4000);
|
||||||
let thread_hdl = create_logger(*start_hash, ms_per_tick, event_receiver, entry_sender);
|
let thread_hdl = create_logger(*start_hash, ms_per_tick, event_receiver, entry_sender);
|
||||||
Historian {
|
Historian {
|
||||||
sender,
|
sender,
|
||||||
@ -139,7 +141,7 @@ mod tests {
|
|||||||
|
|
||||||
hist.sender.send(Event::Tick).unwrap();
|
hist.sender.send(Event::Tick).unwrap();
|
||||||
sleep(Duration::new(0, 1_000_000));
|
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));
|
sleep(Duration::new(0, 1_000_000));
|
||||||
hist.sender.send(Event::Tick).unwrap();
|
hist.sender.send(Event::Tick).unwrap();
|
||||||
|
|
||||||
@ -159,7 +161,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_historian_closed_sender() {
|
fn test_historian_closed_sender() {
|
||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
let hist = Historian::new(&zero, None);
|
let hist = Historian::<u8>::new(&zero, None);
|
||||||
drop(hist.receiver);
|
drop(hist.receiver);
|
||||||
hist.sender.send(Event::Tick).unwrap();
|
hist.sender.send(Event::Tick).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -173,7 +175,7 @@ mod tests {
|
|||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
let hist = Historian::new(&zero, Some(20));
|
let hist = Historian::new(&zero, Some(20));
|
||||||
sleep(Duration::from_millis(30));
|
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));
|
sleep(Duration::from_millis(15));
|
||||||
drop(hist.sender);
|
drop(hist.sender);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -181,8 +183,28 @@ mod tests {
|
|||||||
ExitReason::RecvDisconnected
|
ExitReason::RecvDisconnected
|
||||||
);
|
);
|
||||||
|
|
||||||
let entries: Vec<Entry> = hist.receiver.iter().collect();
|
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
|
||||||
assert!(entries.len() > 1);
|
assert!(entries.len() > 1);
|
||||||
assert!(verify_slice(&entries, &zero));
|
assert!(verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_event_attack() {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let hist = Historian::new(&zero, None);
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
#![cfg_attr(feature = "unstable", feature(test))]
|
#![cfg_attr(feature = "unstable", feature(test))]
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod historian;
|
pub mod historian;
|
||||||
|
pub mod accountant;
|
||||||
|
pub mod accountant_skel;
|
||||||
|
pub mod accountant_stub;
|
||||||
|
extern crate bincode;
|
||||||
extern crate generic_array;
|
extern crate generic_array;
|
||||||
extern crate rayon;
|
extern crate rayon;
|
||||||
|
extern crate ring;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate sha2;
|
extern crate sha2;
|
||||||
|
extern crate untrusted;
|
||||||
|
324
src/log.rs
324
src/log.rs
@ -14,14 +14,19 @@
|
|||||||
/// was generated by the fastest processor at the time the entry was logged.
|
/// was generated by the fastest processor at the time the entry was logged.
|
||||||
|
|
||||||
use generic_array::GenericArray;
|
use generic_array::GenericArray;
|
||||||
use generic_array::typenum::U32;
|
use generic_array::typenum::{U32, U64};
|
||||||
|
use ring::signature::Ed25519KeyPair;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
pub type Sha256Hash = GenericArray<u8, U32>;
|
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)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Entry {
|
pub struct Entry<T> {
|
||||||
pub num_hashes: u64,
|
pub num_hashes: u64,
|
||||||
pub end_hash: Sha256Hash,
|
pub end_hash: Sha256Hash,
|
||||||
pub event: Event,
|
pub event: Event<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
|
/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
|
||||||
@ -30,12 +35,25 @@ pub struct Entry {
|
|||||||
/// a hash alongside the tick, each tick and be verified in parallel using the 'end_hash'
|
/// a hash alongside the tick, each tick and be verified in parallel using the 'end_hash'
|
||||||
/// of the preceding tick to seed its hashing.
|
/// of the preceding tick to seed its hashing.
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Event {
|
pub enum Event<T> {
|
||||||
Tick,
|
Tick,
|
||||||
UserDataKey(Sha256Hash),
|
Discovery {
|
||||||
|
data: T,
|
||||||
|
},
|
||||||
|
Claim {
|
||||||
|
key: PublicKey,
|
||||||
|
data: T,
|
||||||
|
sig: Signature,
|
||||||
|
},
|
||||||
|
Transaction {
|
||||||
|
from: PublicKey,
|
||||||
|
to: PublicKey,
|
||||||
|
data: T,
|
||||||
|
sig: Signature,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl<T> Entry<T> {
|
||||||
/// Creates a Entry from the number of hashes 'num_hashes' since the previous event
|
/// Creates a Entry from the number of hashes 'num_hashes' since the previous event
|
||||||
/// and that resulting 'end_hash'.
|
/// and that resulting 'end_hash'.
|
||||||
pub fn new_tick(num_hashes: u64, end_hash: &Sha256Hash) -> Self {
|
pub fn new_tick(num_hashes: u64, end_hash: &Sha256Hash) -> Self {
|
||||||
@ -45,14 +63,39 @@ impl Entry {
|
|||||||
event: Event::Tick,
|
event: Event::Tick,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
|
||||||
pub fn verify(self: &Self, start_hash: &Sha256Hash) -> bool {
|
|
||||||
self.end_hash == next_hash(start_hash, self.num_hashes, &self.event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
pub fn hash(val: &[u8]) -> Sha256Hash {
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
let mut hasher = Sha256::default();
|
let mut hasher = Sha256::default();
|
||||||
@ -61,25 +104,58 @@ pub fn hash(val: &[u8]) -> Sha256Hash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the hash of the given hash extended with the given value.
|
/// 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();
|
let mut hash_data = end_hash.to_vec();
|
||||||
|
hash_data.push(ty);
|
||||||
hash_data.extend_from_slice(val);
|
hash_data.extend_from_slice(val);
|
||||||
hash(&hash_data)
|
hash(&hash_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_hash(start_hash: &Sha256Hash, num_hashes: u64, event: &Event) -> Sha256Hash {
|
pub fn hash_event<T: Serialize>(end_hash: &Sha256Hash, event: &Event<T>) -> Sha256Hash {
|
||||||
|
use bincode::serialize;
|
||||||
|
match *event {
|
||||||
|
Event::Tick => *end_hash,
|
||||||
|
Event::Discovery { ref data } => extend_and_hash(end_hash, 1, &serialize(&data).unwrap()),
|
||||||
|
Event::Claim { key, ref data, sig } => {
|
||||||
|
let mut event_data = serialize(&data).unwrap();
|
||||||
|
event_data.extend_from_slice(&sig);
|
||||||
|
event_data.extend_from_slice(&key);
|
||||||
|
extend_and_hash(end_hash, 2, &event_data)
|
||||||
|
}
|
||||||
|
Event::Transaction {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
ref data,
|
||||||
|
sig,
|
||||||
|
} => {
|
||||||
|
let mut event_data = serialize(&data).unwrap();
|
||||||
|
event_data.extend_from_slice(&sig);
|
||||||
|
event_data.extend_from_slice(&from);
|
||||||
|
event_data.extend_from_slice(&to);
|
||||||
|
extend_and_hash(end_hash, 2, &event_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the hash 'num_hashes' after start_hash, plus an additional hash for any event data.
|
||||||
|
pub fn next_hash<T: Serialize>(
|
||||||
|
start_hash: &Sha256Hash,
|
||||||
|
num_hashes: u64,
|
||||||
|
event: &Event<T>,
|
||||||
|
) -> Sha256Hash {
|
||||||
let mut end_hash = *start_hash;
|
let mut end_hash = *start_hash;
|
||||||
for _ in 0..num_hashes {
|
for _ in 0..num_hashes {
|
||||||
end_hash = hash(&end_hash);
|
end_hash = hash(&end_hash);
|
||||||
}
|
}
|
||||||
if let Event::UserDataKey(key) = *event {
|
hash_event(&end_hash, event)
|
||||||
return extend_and_hash(&end_hash, &key);
|
|
||||||
}
|
|
||||||
end_hash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
||||||
pub fn next_entry(start_hash: &Sha256Hash, num_hashes: u64, event: Event) -> Entry {
|
pub fn next_entry<T: Serialize>(
|
||||||
|
start_hash: &Sha256Hash,
|
||||||
|
num_hashes: u64,
|
||||||
|
event: Event<T>,
|
||||||
|
) -> Entry<T> {
|
||||||
Entry {
|
Entry {
|
||||||
num_hashes,
|
num_hashes,
|
||||||
end_hash: next_hash(start_hash, num_hashes, &event),
|
end_hash: next_hash(start_hash, num_hashes, &event),
|
||||||
@ -88,36 +164,109 @@ pub fn next_entry(start_hash: &Sha256Hash, num_hashes: u64, event: Event) -> Ent
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
||||||
pub fn next_tick(start_hash: &Sha256Hash, num_hashes: u64) -> Entry {
|
pub fn next_entry_mut<T: Serialize>(
|
||||||
|
start_hash: &mut Sha256Hash,
|
||||||
|
num_hashes: u64,
|
||||||
|
event: Event<T>,
|
||||||
|
) -> Entry<T> {
|
||||||
|
let entry = next_entry(start_hash, num_hashes, event);
|
||||||
|
*start_hash = entry.end_hash;
|
||||||
|
entry
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
||||||
|
pub fn next_tick<T: Serialize>(start_hash: &Sha256Hash, num_hashes: u64) -> Entry<T> {
|
||||||
next_entry(start_hash, num_hashes, Event::Tick)
|
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 {
|
||||||
|
if !verify_event(&entry.event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entry.end_hash == next_hash(start_hash, entry.num_hashes, &entry.event)
|
||||||
|
}
|
||||||
|
|
||||||
/// Verifies the hashes and counts of a slice of events are all consistent.
|
/// Verifies the hashes and counts of a slice of events are all consistent.
|
||||||
pub fn verify_slice(events: &[Entry], start_hash: &Sha256Hash) -> bool {
|
pub fn verify_slice(events: &[Entry<Sha256Hash>], start_hash: &Sha256Hash) -> bool {
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
let genesis = [Entry::new_tick(Default::default(), start_hash)];
|
let genesis = [Entry::new_tick(Default::default(), start_hash)];
|
||||||
let event_pairs = genesis.par_iter().chain(events).zip(events);
|
let event_pairs = genesis.par_iter().chain(events).zip(events);
|
||||||
event_pairs.all(|(x0, x1)| x1.verify(&x0.end_hash))
|
event_pairs.all(|(x0, x1)| verify_entry(&x1, &x0.end_hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies the hashes and counts of a slice of events are all consistent.
|
||||||
|
pub fn verify_slice_u64(events: &[Entry<u64>], start_hash: &Sha256Hash) -> bool {
|
||||||
|
use rayon::prelude::*;
|
||||||
|
let genesis = [Entry::new_tick(Default::default(), start_hash)];
|
||||||
|
let event_pairs = genesis.par_iter().chain(events).zip(events);
|
||||||
|
event_pairs.all(|(x0, x1)| verify_entry(&x1, &x0.end_hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies the hashes and events serially. Exists only for reference.
|
/// Verifies the hashes and events serially. Exists only for reference.
|
||||||
pub fn verify_slice_seq(events: &[Entry], start_hash: &Sha256Hash) -> bool {
|
pub fn verify_slice_seq<T: Serialize>(events: &[Entry<T>], start_hash: &Sha256Hash) -> bool {
|
||||||
let genesis = [Entry::new_tick(0, start_hash)];
|
let genesis = [Entry::new_tick(0, start_hash)];
|
||||||
let mut event_pairs = genesis.iter().chain(events).zip(events);
|
let mut event_pairs = genesis.iter().chain(events).zip(events);
|
||||||
event_pairs.all(|(x0, x1)| x1.verify(&x0.end_hash))
|
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,
|
||||||
|
events: Vec<Event<T>>,
|
||||||
|
) -> Vec<Entry<T>> {
|
||||||
|
let mut end_hash = *start_hash;
|
||||||
|
events
|
||||||
|
.into_iter()
|
||||||
|
.map(|event| next_entry_mut(&mut end_hash, num_hashes, event))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a vector of Ticks of length 'len' from 'start_hash' hash and 'num_hashes'.
|
/// 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> {
|
pub fn create_ticks(
|
||||||
|
start_hash: &Sha256Hash,
|
||||||
|
num_hashes: u64,
|
||||||
|
len: usize,
|
||||||
|
) -> Vec<Entry<Sha256Hash>> {
|
||||||
use std::iter;
|
use std::iter;
|
||||||
let mut end_hash = *start_hash;
|
let mut end_hash = *start_hash;
|
||||||
iter::repeat(Event::Tick)
|
iter::repeat(Event::Tick)
|
||||||
.take(len)
|
.take(len)
|
||||||
.map(|event| {
|
.map(|event| next_entry_mut(&mut end_hash, num_hashes, event))
|
||||||
let entry = next_entry(&end_hash, num_hashes, event);
|
|
||||||
end_hash = entry.end_hash;
|
|
||||||
entry
|
|
||||||
})
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,19 +278,19 @@ mod tests {
|
|||||||
fn test_event_verify() {
|
fn test_event_verify() {
|
||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
let one = hash(&zero);
|
let one = hash(&zero);
|
||||||
assert!(Entry::new_tick(0, &zero).verify(&zero)); // base case
|
assert!(verify_entry::<u8>(&Entry::new_tick(0, &zero), &zero)); // base case
|
||||||
assert!(!Entry::new_tick(0, &zero).verify(&one)); // base case, bad
|
assert!(!verify_entry::<u8>(&Entry::new_tick(0, &zero), &one)); // base case, bad
|
||||||
assert!(next_tick(&zero, 1).verify(&zero)); // inductive step
|
assert!(verify_entry::<u8>(&next_tick(&zero, 1), &zero)); // inductive step
|
||||||
assert!(!next_tick(&zero, 1).verify(&one)); // inductive step, bad
|
assert!(!verify_entry::<u8>(&next_tick(&zero, 1), &one)); // inductive step, bad
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_next_tick() {
|
fn test_next_tick() {
|
||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
assert_eq!(next_tick(&zero, 1).num_hashes, 1)
|
assert_eq!(next_tick::<Sha256Hash>(&zero, 1).num_hashes, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_slice_generic(verify_slice: fn(&[Entry], &Sha256Hash) -> bool) {
|
fn verify_slice_generic(verify_slice: fn(&[Entry<Sha256Hash>], &Sha256Hash) -> bool) {
|
||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
let one = hash(&zero);
|
let one = hash(&zero);
|
||||||
assert!(verify_slice(&vec![], &zero)); // base case
|
assert!(verify_slice(&vec![], &zero)); // base case
|
||||||
@ -161,7 +310,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_verify_slice_seq() {
|
fn test_verify_slice_seq() {
|
||||||
verify_slice_generic(verify_slice_seq);
|
verify_slice_generic(verify_slice_seq::<Sha256Hash>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -169,27 +318,100 @@ mod tests {
|
|||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
let one = hash(&zero);
|
let one = hash(&zero);
|
||||||
|
|
||||||
// First, verify UserData events
|
// First, verify Discovery events
|
||||||
let mut end_hash = zero;
|
let events = vec![
|
||||||
let events = [Event::UserDataKey(zero), Event::UserDataKey(one)];
|
Event::Discovery { data: zero },
|
||||||
let mut entries: Vec<Entry> = events
|
Event::Discovery { data: one },
|
||||||
.iter()
|
];
|
||||||
.map(|event| {
|
let mut entries = create_entries(&zero, 0, events);
|
||||||
let entry = next_entry(&end_hash, 0, event.clone());
|
assert!(verify_slice(&entries, &zero));
|
||||||
end_hash = entry.end_hash;
|
|
||||||
entry
|
|
||||||
})
|
|
||||||
.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 event0 = entries[0].event.clone();
|
||||||
let event1 = entries[1].event.clone();
|
let event1 = entries[1].event.clone();
|
||||||
entries[0].event = event1;
|
entries[0].event = event1;
|
||||||
entries[1].event = event0;
|
entries[1].event = event0;
|
||||||
assert!(!verify_slice(&entries, &zero)); // inductive step
|
assert!(!verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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 zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, vec![event0]);
|
||||||
|
assert!(verify_slice(&entries, &zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, vec![event0]);
|
||||||
|
assert!(!verify_slice(&entries, &zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transfer() {
|
||||||
|
let keypair0 = generate_keypair();
|
||||||
|
let keypair1 = generate_keypair();
|
||||||
|
let pubkey1 = get_pubkey(&keypair1);
|
||||||
|
let data = hash(b"hello, world");
|
||||||
|
let event0 = Event::Transaction {
|
||||||
|
from: get_pubkey(&keypair0),
|
||||||
|
to: pubkey1,
|
||||||
|
data,
|
||||||
|
sig: sign_transaction_data(&data, &keypair0, &pubkey1),
|
||||||
|
};
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, vec![event0]);
|
||||||
|
assert!(verify_slice(&entries, &zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_data_transfer_attack() {
|
||||||
|
let keypair0 = generate_keypair();
|
||||||
|
let keypair1 = generate_keypair();
|
||||||
|
let pubkey1 = get_pubkey(&keypair1);
|
||||||
|
let data = hash(b"hello, world");
|
||||||
|
let event0 = Event::Transaction {
|
||||||
|
from: get_pubkey(&keypair0),
|
||||||
|
to: pubkey1,
|
||||||
|
data: hash(b"goodbye cruel world"), // <-- attack!
|
||||||
|
sig: sign_transaction_data(&data, &keypair0, &pubkey1),
|
||||||
|
};
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, vec![event0]);
|
||||||
|
assert!(!verify_slice(&entries, &zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transfer_hijack_attack() {
|
||||||
|
let keypair0 = generate_keypair();
|
||||||
|
let keypair1 = generate_keypair();
|
||||||
|
let thief_keypair = generate_keypair();
|
||||||
|
let pubkey1 = get_pubkey(&keypair1);
|
||||||
|
let data = hash(b"hello, world");
|
||||||
|
let event0 = Event::Transaction {
|
||||||
|
from: get_pubkey(&keypair0),
|
||||||
|
to: get_pubkey(&thief_keypair), // <-- attack!
|
||||||
|
data: hash(b"goodbye cruel world"),
|
||||||
|
sig: sign_transaction_data(&data, &keypair0, &pubkey1),
|
||||||
|
};
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, vec![event0]);
|
||||||
|
assert!(!verify_slice(&entries, &zero));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "unstable", test))]
|
#[cfg(all(feature = "unstable", test))]
|
||||||
|
Reference in New Issue
Block a user