diff --git a/src/accountant.rs b/src/accountant.rs index 3113cf9200..72b03eab9c 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -5,7 +5,7 @@ use hash::Hash; use entry::Entry; use event::Event; -use transaction::{Condition, Transaction}; +use transaction::{Action, Plan, Transaction}; use signature::{KeyPair, PublicKey, Signature}; use mint::Mint; use historian::{reserve_signature, Historian}; @@ -30,7 +30,7 @@ pub struct Accountant { pub balances: HashMap, pub first_id: Hash, pub last_id: Hash, - pending: HashMap>, + pending: HashMap>, time_sources: HashSet, last_time: DateTime, } @@ -83,8 +83,12 @@ impl Accountant { self.last_id } - fn is_deposit(allow_deposits: bool, from: &PublicKey, to: &PublicKey) -> bool { - allow_deposits && from == to + fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { + if let Plan::Action(Action::Pay(ref payment)) = *plan { + allow_deposits && *from == payment.to + } else { + false + } } pub fn process_transaction(self: &mut Self, tr: Transaction) -> Result<()> { @@ -108,36 +112,16 @@ impl Accountant { } /// Commit funds to the 'to' party. - fn complete_transaction(self: &mut Self, tr: &Transaction) { - if self.balances.contains_key(&tr.to) { - if let Some(x) = self.balances.get_mut(&tr.to) { - *x += tr.asset; - } - } else { - self.balances.insert(tr.to, tr.asset); - } - } - - /// Return funds to the 'from' party. - fn cancel_transaction(self: &mut Self, tr: &Transaction) { - if let Some(x) = self.balances.get_mut(&tr.from) { - *x += tr.asset; - } - } - - // TODO: Move this to transaction.rs - fn all_satisfied(&self, conds: &[Condition]) -> bool { - let mut satisfied = true; - for cond in conds { - if let &Condition::Timestamp(dt) = cond { - if dt > self.last_time { - satisfied = false; + fn complete_transaction(self: &mut Self, plan: &Plan) { + if let Plan::Action(Action::Pay(ref payment)) = *plan { + if self.balances.contains_key(&payment.to) { + if let Some(x) = self.balances.get_mut(&payment.to) { + *x += payment.asset; } } else { - satisfied = false; + self.balances.insert(payment.to, payment.asset); } } - satisfied } fn process_verified_transaction( @@ -149,51 +133,37 @@ impl Accountant { return Err(AccountingError::InvalidTransferSignature); } - if !tr.unless_any.is_empty() { - // TODO: Check to see if the transaction is expired. - } - - if !Self::is_deposit(allow_deposits, &tr.from, &tr.to) { + if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan) { if let Some(x) = self.balances.get_mut(&tr.from) { *x -= tr.asset; } } - if !self.all_satisfied(&tr.if_all) { - self.pending.insert(tr.sig, tr.clone()); + let mut plan = tr.plan.clone(); + let actionable = plan.process_verified_timestamp(self.last_time); + + if !actionable { + self.pending.insert(tr.sig, plan); return Ok(()); } - self.complete_transaction(tr); + self.complete_transaction(&plan); Ok(()) } fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> { - let mut cancel = false; - if let Some(tr) = self.pending.get(&tx_sig) { - // Cancel: - // if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map. + let actionable = if let Some(plan) = self.pending.get_mut(&tx_sig) { + plan.process_verified_sig(from) + } else { + false + }; - // TODO: Use find(). - for cond in &tr.unless_any { - if let Condition::Signature(pubkey) = *cond { - if from == pubkey { - cancel = true; - break; - } - } + if actionable { + if let Some(plan) = self.pending.remove(&tx_sig) { + self.complete_transaction(&plan); } } - if cancel { - if let Some(tr) = self.pending.remove(&tx_sig) { - self.cancel_transaction(&tr); - } - } - - // Process Multisig: - // otherwise, if "Signature(from) is in if_all, remove it. If that causes that list - // to be empty, add the asset to to, and remove the tx from this map. Ok(()) } @@ -211,34 +181,18 @@ impl Accountant { } else { return Ok(()); } - // TODO: Lookup pending Transaction waiting on time, signed by a whitelisted PublicKey. - - // Expire: - // if a Timestamp after this DateTime is in unless_any, return funds to tx.from, - // and remove the tx from this map. // Check to see if any timelocked transactions can be completed. let mut completed = vec![]; - for (key, tr) in &self.pending { - for cond in &tr.if_all { - if let Condition::Timestamp(dt) = *cond { - if self.last_time >= dt { - if tr.if_all.len() == 1 { - completed.push(*key); - } - } - } + for (key, plan) in &mut self.pending { + if plan.process_verified_timestamp(self.last_time) { + completed.push(key.clone()); } - // TODO: Add this in once we start removing constraints - //if tr.if_all.is_empty() { - // // TODO: Remove tr from pending - // self.complete_transaction(tr); - //} } for key in completed { - if let Some(tr) = self.pending.remove(&key) { - self.complete_transaction(&tr); + if let Some(plan) = self.pending.remove(&key) { + self.complete_transaction(&plan); } } @@ -327,6 +281,30 @@ mod tests { ); } + #[test] + fn test_overspend_attack() { + let alice = Mint::new(1); + let mut acc = Accountant::new(&alice, None); + let bob_pubkey = KeyPair::new().pubkey(); + let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.seed()); + if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan { + payment.asset = 2; // <-- attack! + } + assert_eq!( + acc.process_transaction(tr.clone()), + Err(AccountingError::InvalidTransfer) + ); + + // Also, ensure all branchs of the plan spend all assets + if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan { + payment.asset = 0; // <-- whoops! + } + assert_eq!( + acc.process_transaction(tr.clone()), + Err(AccountingError::InvalidTransfer) + ); + } + #[test] fn test_transfer_to_newb() { let alice = Mint::new(10_000); diff --git a/src/mint.rs b/src/mint.rs index 991a1cb8c1..ff21fc3b5d 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -58,12 +58,15 @@ impl Mint { mod tests { use super::*; use log::verify_slice; + use transaction::{Action, Plan}; #[test] fn test_create_events() { let mut events = Mint::new(100).create_events().into_iter(); if let Event::Transaction(tr) = events.next().unwrap() { - assert_eq!(tr.from, tr.to); + if let Plan::Action(Action::Pay(payment)) = tr.plan { + assert_eq!(tr.from, payment.to); + } } assert_eq!(events.next(), None); } diff --git a/src/transaction.rs b/src/transaction.rs index 046d8485a0..9cf7a68f69 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -5,6 +5,7 @@ use serde::Serialize; use bincode::serialize; use hash::Hash; use chrono::prelude::*; +use std::mem; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Condition { @@ -12,24 +13,138 @@ pub enum Condition { Signature(PublicKey), } +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Action { + Pay(Payment), +} + +impl Action { + pub fn spendable(&self) -> T { + match *self { + Action::Pay(ref payment) => payment.asset.clone(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Payment { + pub asset: T, + pub to: PublicKey, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Plan { + Action(Action), + After(Condition, Action), + Race(Box>, Box>), +} + +impl Plan { + pub fn verify(&self, spendable_assets: &T) -> bool { + match *self { + Plan::Action(ref action) => action.spendable() == *spendable_assets, + Plan::Race(ref plan_a, ref plan_b) => { + plan_a.verify(spendable_assets) && plan_b.verify(spendable_assets) + } + Plan::After(_, ref action) => action.spendable() == *spendable_assets, + } + } + + pub fn run_race(&mut self) -> bool { + let new_plan = if let Plan::Race(ref a, ref b) = *self { + if let Plan::Action(_) = **a { + Some((**a).clone()) + } else if let Plan::Action(_) = **b { + Some((**b).clone()) + } else { + None + } + } else { + None + }; + + if let Some(plan) = new_plan { + mem::replace(self, plan); + true + } else { + false + } + } + + pub fn process_verified_sig(&mut self, from: PublicKey) -> bool { + let mut new_plan = None; + match *self { + Plan::Action(_) => return true, + Plan::Race(ref mut plan_a, ref mut plan_b) => { + plan_a.process_verified_sig(from); + plan_b.process_verified_sig(from); + } + Plan::After(Condition::Signature(pubkey), ref action) => { + if from == pubkey { + new_plan = Some(Plan::Action(action.clone())); + } + } + _ => (), + } + if self.run_race() { + return true; + } + + if let Some(plan) = new_plan { + mem::replace(self, plan); + true + } else { + false + } + } + + pub fn process_verified_timestamp(&mut self, last_time: DateTime) -> bool { + let mut new_plan = None; + match *self { + Plan::Action(_) => return true, + Plan::Race(ref mut plan_a, ref mut plan_b) => { + plan_a.process_verified_timestamp(last_time); + plan_b.process_verified_timestamp(last_time); + } + Plan::After(Condition::Timestamp(dt), ref action) => { + if dt <= last_time { + new_plan = Some(Plan::Action(action.clone())); + } + } + _ => (), + } + if self.run_race() { + return true; + } + + if let Some(plan) = new_plan { + mem::replace(self, plan); + true + } else { + false + } + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Transaction { pub from: PublicKey, - pub to: PublicKey, - pub if_all: Vec, - pub unless_any: Vec, + pub plan: Plan, pub asset: T, pub last_id: Hash, pub sig: Signature, } -impl Transaction { +impl Transaction { pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self { - let mut tr = Transaction { - from: from_keypair.pubkey(), + let from = from_keypair.pubkey(); + let plan = Plan::Action(Action::Pay(Payment { + asset: asset.clone(), to, - if_all: vec![], - unless_any: vec![], + })); + let mut tr = Transaction { + from, + plan, asset, last_id, sig: Signature::default(), @@ -46,11 +161,25 @@ impl Transaction { last_id: Hash, ) -> Self { let from = from_keypair.pubkey(); + let plan = Plan::Race( + Box::new(Plan::After( + Condition::Timestamp(dt), + Action::Pay(Payment { + asset: asset.clone(), + to, + }), + )), + Box::new(Plan::After( + Condition::Signature(from), + Action::Pay(Payment { + asset: asset.clone(), + to: from, + }), + )), + ); let mut tr = Transaction { from, - to, - if_all: vec![Condition::Timestamp(dt)], - unless_any: vec![Condition::Signature(from)], + plan, asset, last_id, sig: Signature::default(), @@ -60,14 +189,7 @@ impl Transaction { } fn get_sign_data(&self) -> Vec { - serialize(&( - &self.from, - &self.to, - &self.if_all, - &self.unless_any, - &self.asset, - &self.last_id, - )).unwrap() + serialize(&(&self.from, &self.plan, &self.asset, &self.last_id)).unwrap() } pub fn sign(&mut self, keypair: &KeyPair) { @@ -76,7 +198,7 @@ impl Transaction { } pub fn verify(&self) -> bool { - self.sig.verify(&self.from, &self.get_sign_data()) + self.sig.verify(&self.from, &self.get_sign_data()) && self.plan.verify(&self.asset) } } @@ -108,11 +230,13 @@ mod tests { #[test] fn test_serialize_claim() { + let plan = Plan::Action(Action::Pay(Payment { + asset: 0, + to: Default::default(), + })); let claim0 = Transaction { from: Default::default(), - to: Default::default(), - if_all: Default::default(), - unless_any: Default::default(), + plan, asset: 0u8, last_id: Default::default(), sig: Default::default(), @@ -140,9 +264,12 @@ mod tests { let thief_keypair = KeyPair::new(); let pubkey1 = keypair1.pubkey(); let zero = Hash::default(); - let mut tr = Transaction::new(&keypair0, pubkey1, hash(b"hello, world"), zero); + let asset = hash(b"hello, world"); + let mut tr = Transaction::new(&keypair0, pubkey1, asset, zero); tr.sign(&keypair0); - tr.to = thief_keypair.pubkey(); // <-- attack! + if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan { + payment.to = thief_keypair.pubkey(); // <-- attack! + }; assert!(!tr.verify()); } }