Extend the event log with a Transaction event to transfer possession
This implementation assumes 'from' is the current owner of 'data'. Once that's verified, the signature ensures that nobody modified 'data' (the asset being transferred) or 'to' the entity taking ownership. Fixes #14
This commit is contained in:
193
src/log.rs
193
src/log.rs
@ -43,6 +43,12 @@ pub enum Event {
|
|||||||
data: Sha256Hash,
|
data: Sha256Hash,
|
||||||
sig: Signature,
|
sig: Signature,
|
||||||
},
|
},
|
||||||
|
Transaction {
|
||||||
|
from: PublicKey,
|
||||||
|
to: PublicKey,
|
||||||
|
data: Sha256Hash,
|
||||||
|
sig: Signature,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
@ -64,14 +70,36 @@ impl Entry {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Event::Transaction {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
sig,
|
||||||
|
} = self.event
|
||||||
|
{
|
||||||
|
let mut sign_data = data.to_vec();
|
||||||
|
sign_data.extend_from_slice(&to);
|
||||||
|
if !verify_signature(&from, &sign_data, &sig) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
self.end_hash == next_hash(start_hash, self.num_hashes, &self.event)
|
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 a Claim Event for the given hash and key-pair.
|
/// Return a Claim Event for the given hash and key-pair.
|
||||||
pub fn sign_hash(data: &Sha256Hash, key_pair: &Ed25519KeyPair) -> Event {
|
pub fn sign_hash(data: &Sha256Hash, keypair: &Ed25519KeyPair) -> Event {
|
||||||
let sig = key_pair.sign(data);
|
let sig = keypair.sign(data);
|
||||||
let peer_public_key_bytes = key_pair.public_key_bytes();
|
let peer_public_key_bytes = keypair.public_key_bytes();
|
||||||
let sig_bytes = sig.as_ref();
|
let sig_bytes = sig.as_ref();
|
||||||
Event::Claim {
|
Event::Claim {
|
||||||
key: GenericArray::clone_from_slice(peer_public_key_bytes),
|
key: GenericArray::clone_from_slice(peer_public_key_bytes),
|
||||||
@ -80,6 +108,21 @@ pub fn sign_hash(data: &Sha256Hash, key_pair: &Ed25519KeyPair) -> Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a Transaction Event that indicates a transfer in ownership of the given hash.
|
||||||
|
pub fn transfer_hash(data: &Sha256Hash, keypair: &Ed25519KeyPair, to: PublicKey) -> Event {
|
||||||
|
let from_public_key_bytes = keypair.public_key_bytes();
|
||||||
|
let mut sign_data = data.to_vec();
|
||||||
|
sign_data.extend_from_slice(&to);
|
||||||
|
let sig = keypair.sign(&sign_data);
|
||||||
|
let sig_bytes = sig.as_ref();
|
||||||
|
Event::Transaction {
|
||||||
|
from: GenericArray::clone_from_slice(from_public_key_bytes),
|
||||||
|
to,
|
||||||
|
data: GenericArray::clone_from_slice(data),
|
||||||
|
sig: GenericArray::clone_from_slice(sig_bytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a Sha256 hash for the given data.
|
/// 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};
|
||||||
@ -106,6 +149,18 @@ pub fn hash_event(end_hash: &Sha256Hash, event: &Event) -> Sha256Hash {
|
|||||||
event_data.extend_from_slice(&key);
|
event_data.extend_from_slice(&key);
|
||||||
extend_and_hash(end_hash, 2, &event_data)
|
extend_and_hash(end_hash, 2, &event_data)
|
||||||
}
|
}
|
||||||
|
Event::Transaction {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
sig,
|
||||||
|
} => {
|
||||||
|
let mut event_data = data.to_vec();
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +181,12 @@ pub fn next_entry(start_hash: &Sha256Hash, num_hashes: u64, event: Event) -> Ent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next_entry_mut(start_hash: &mut Sha256Hash, num_hashes: u64, event: Event) -> Entry {
|
||||||
|
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'.
|
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
||||||
pub fn next_tick(start_hash: &Sha256Hash, num_hashes: u64) -> Entry {
|
pub fn next_tick(start_hash: &Sha256Hash, num_hashes: u64) -> Entry {
|
||||||
next_entry(start_hash, num_hashes, Event::Tick)
|
next_entry(start_hash, num_hashes, Event::Tick)
|
||||||
@ -156,17 +217,21 @@ pub fn verify_signature(peer_public_key_bytes: &[u8], msg_bytes: &[u8], sig_byte
|
|||||||
signature::verify(&signature::ED25519, peer_public_key, msg, sig).is_ok()
|
signature::verify(&signature::ED25519, peer_public_key, msg, sig).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_entries(start_hash: &Sha256Hash, num_hashes: u64, events: &[Event]) -> Vec<Entry> {
|
||||||
|
let mut end_hash = *start_hash;
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.map(|event| next_entry_mut(&mut end_hash, num_hashes, event.clone()))
|
||||||
|
.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> {
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,19 +284,11 @@ mod tests {
|
|||||||
let one = hash(&zero);
|
let one = hash(&zero);
|
||||||
|
|
||||||
// First, verify Discovery events
|
// First, verify Discovery events
|
||||||
let mut end_hash = zero;
|
|
||||||
let events = [
|
let events = [
|
||||||
Event::Discovery { data: zero },
|
Event::Discovery { data: zero },
|
||||||
Event::Discovery { data: one },
|
Event::Discovery { data: one },
|
||||||
];
|
];
|
||||||
let mut entries: Vec<Entry> = events
|
let mut entries = create_entries(&zero, 0, &events);
|
||||||
.iter()
|
|
||||||
.map(|event| {
|
|
||||||
let entry = next_entry(&end_hash, 0, event.clone());
|
|
||||||
end_hash = entry.end_hash;
|
|
||||||
entry
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
assert!(verify_slice(&entries, &zero));
|
assert!(verify_slice(&entries, &zero));
|
||||||
|
|
||||||
// Next, swap two Discovery events and ensure verification fails.
|
// Next, swap two Discovery events and ensure verification fails.
|
||||||
@ -243,53 +300,79 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_signature() {
|
fn test_claim() {
|
||||||
use untrusted;
|
let keypair = generate_keypair();
|
||||||
use ring::{rand, signature};
|
let event0 = sign_hash(&hash(b"hello, world"), &keypair);
|
||||||
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 zero = Sha256Hash::default();
|
||||||
let mut end_hash = zero;
|
let entries = create_entries(&zero, 0, &[event0]);
|
||||||
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));
|
assert!(verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bad_signature() {
|
fn test_wrong_data_claim_attack() {
|
||||||
use untrusted;
|
let keypair = generate_keypair();
|
||||||
use ring::{rand, signature};
|
let mut event0 = sign_hash(&hash(b"hello, world"), &keypair);
|
||||||
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 {
|
if let Event::Claim { key, sig, .. } = event0 {
|
||||||
const GOODBYE: &'static [u8] = b"goodbye cruel world";
|
let data = hash(b"goodbye cruel world");
|
||||||
let data = hash(GOODBYE);
|
|
||||||
event0 = Event::Claim { key, data, sig };
|
event0 = Event::Claim { key, data, sig };
|
||||||
}
|
}
|
||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
let mut end_hash = zero;
|
let entries = create_entries(&zero, 0, &[event0]);
|
||||||
let entries: Vec<Entry> = [event0]
|
assert!(!verify_slice(&entries, &zero));
|
||||||
.iter()
|
}
|
||||||
.map(|event| {
|
|
||||||
let entry = next_entry(&end_hash, 0, event.clone());
|
#[test]
|
||||||
end_hash = entry.end_hash;
|
fn test_transfer() {
|
||||||
entry
|
let keypair0 = generate_keypair();
|
||||||
})
|
let keypair1 = generate_keypair();
|
||||||
.collect();
|
let pubkey1 = GenericArray::clone_from_slice(keypair1.public_key_bytes());
|
||||||
|
let event0 = transfer_hash(&hash(b"hello, world"), &keypair0, pubkey1);
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, &[event0]);
|
||||||
|
assert!(verify_slice(&entries, &zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_data_transfer_attack() {
|
||||||
|
let keypair0 = generate_keypair();
|
||||||
|
let keypair1 = generate_keypair();
|
||||||
|
let pubkey1 = GenericArray::clone_from_slice(keypair1.public_key_bytes());
|
||||||
|
let mut event0 = transfer_hash(&hash(b"hello, world"), &keypair0, pubkey1);
|
||||||
|
if let Event::Transaction { from, to, sig, .. } = event0 {
|
||||||
|
let data = hash(b"goodbye cruel world");
|
||||||
|
event0 = Event::Transaction {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
sig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, &[event0]);
|
||||||
|
assert!(!verify_slice(&entries, &zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transfer_hijack_attack() {
|
||||||
|
let keypair0 = generate_keypair();
|
||||||
|
let keypair1 = generate_keypair();
|
||||||
|
let pubkey1 = GenericArray::clone_from_slice(keypair1.public_key_bytes());
|
||||||
|
let mut event0 = transfer_hash(&hash(b"hello, world"), &keypair0, pubkey1);
|
||||||
|
if let Event::Transaction {
|
||||||
|
from, data, sig, ..
|
||||||
|
} = event0
|
||||||
|
{
|
||||||
|
let theif_keypair = generate_keypair();
|
||||||
|
let to = GenericArray::clone_from_slice(theif_keypair.public_key_bytes());
|
||||||
|
event0 = Event::Transaction {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
sig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
|
let entries = create_entries(&zero, 0, &[event0]);
|
||||||
assert!(!verify_slice(&entries, &zero));
|
assert!(!verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user