From 7a0bc7d88800fa54b5120eb86932702b36a2ce97 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 10 Mar 2018 16:55:39 -0700 Subject: [PATCH 1/9] Move smart contract fields into their own struct --- src/accountant.rs | 20 ++++++++++---------- src/mint.rs | 2 +- src/transaction.rs | 39 +++++++++++++++++++++++++++------------ 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 3113cf9200..88e8bbc6fa 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -109,12 +109,12 @@ 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) { + if self.balances.contains_key(&tr.plan.to) { + if let Some(x) = self.balances.get_mut(&tr.plan.to) { *x += tr.asset; } } else { - self.balances.insert(tr.to, tr.asset); + self.balances.insert(tr.plan.to, tr.asset); } } @@ -149,17 +149,17 @@ impl Accountant { return Err(AccountingError::InvalidTransferSignature); } - if !tr.unless_any.is_empty() { + if !tr.plan.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.to) { if let Some(x) = self.balances.get_mut(&tr.from) { *x -= tr.asset; } } - if !self.all_satisfied(&tr.if_all) { + if !self.all_satisfied(&tr.plan.if_all) { self.pending.insert(tr.sig, tr.clone()); return Ok(()); } @@ -175,7 +175,7 @@ impl Accountant { // if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map. // TODO: Use find(). - for cond in &tr.unless_any { + for cond in &tr.plan.unless_any { if let Condition::Signature(pubkey) = *cond { if from == pubkey { cancel = true; @@ -220,17 +220,17 @@ impl Accountant { // 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 { + for cond in &tr.plan.if_all { if let Condition::Timestamp(dt) = *cond { if self.last_time >= dt { - if tr.if_all.len() == 1 { + if tr.plan.if_all.len() == 1 { completed.push(*key); } } } } // TODO: Add this in once we start removing constraints - //if tr.if_all.is_empty() { + //if tr.plan.if_all.is_empty() { // // TODO: Remove tr from pending // self.complete_transaction(tr); //} diff --git a/src/mint.rs b/src/mint.rs index 991a1cb8c1..0fd1e0b835 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -63,7 +63,7 @@ mod tests { 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); + assert_eq!(tr.from, tr.plan.to); } assert_eq!(events.next(), None); } diff --git a/src/transaction.rs b/src/transaction.rs index 046d8485a0..86a2f3f756 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -13,11 +13,16 @@ pub enum Condition { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Transaction { - pub from: PublicKey, +pub struct SpendingPlan { pub to: PublicKey, pub if_all: Vec, pub unless_any: Vec, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Transaction { + pub from: PublicKey, + pub plan: SpendingPlan, pub asset: T, pub last_id: Hash, pub sig: Signature, @@ -25,11 +30,14 @@ pub struct 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 plan = SpendingPlan { to, if_all: vec![], unless_any: vec![], + }; + let mut tr = Transaction { + from: from_keypair.pubkey(), + plan, asset, last_id, sig: Signature::default(), @@ -46,11 +54,14 @@ impl Transaction { last_id: Hash, ) -> Self { let from = from_keypair.pubkey(); - let mut tr = Transaction { - from, + let plan = SpendingPlan { to, if_all: vec![Condition::Timestamp(dt)], unless_any: vec![Condition::Signature(from)], + }; + let mut tr = Transaction { + from, + plan, asset, last_id, sig: Signature::default(), @@ -60,11 +71,12 @@ impl Transaction { } fn get_sign_data(&self) -> Vec { + let plan = &self.plan; serialize(&( &self.from, - &self.to, - &self.if_all, - &self.unless_any, + &plan.to, + &plan.if_all, + &plan.unless_any, &self.asset, &self.last_id, )).unwrap() @@ -108,11 +120,14 @@ mod tests { #[test] fn test_serialize_claim() { - let claim0 = Transaction { - from: Default::default(), + let plan = SpendingPlan { to: Default::default(), if_all: Default::default(), unless_any: Default::default(), + }; + let claim0 = Transaction { + from: Default::default(), + plan, asset: 0u8, last_id: Default::default(), sig: Default::default(), @@ -142,7 +157,7 @@ mod tests { let zero = Hash::default(); let mut tr = Transaction::new(&keypair0, pubkey1, hash(b"hello, world"), zero); tr.sign(&keypair0); - tr.to = thief_keypair.pubkey(); // <-- attack! + tr.plan.to = thief_keypair.pubkey(); // <-- attack! assert!(!tr.verify()); } } From 8c40d1bd72d910277d986ccc8298733598e70575 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 10 Mar 2018 17:11:12 -0700 Subject: [PATCH 2/9] Move spending endpoints into expressions --- src/accountant.rs | 21 +++++------ src/mint.rs | 2 +- src/transaction.rs | 90 ++++++++++++++++++++++++++++++++++++---------- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 88e8bbc6fa..d9f5203536 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -109,12 +109,13 @@ impl Accountant { /// Commit funds to the 'to' party. fn complete_transaction(self: &mut Self, tr: &Transaction) { - if self.balances.contains_key(&tr.plan.to) { - if let Some(x) = self.balances.get_mut(&tr.plan.to) { + let to = tr.plan.to(); + if self.balances.contains_key(&to) { + if let Some(x) = self.balances.get_mut(&to) { *x += tr.asset; } } else { - self.balances.insert(tr.plan.to, tr.asset); + self.balances.insert(to, tr.asset); } } @@ -149,17 +150,17 @@ impl Accountant { return Err(AccountingError::InvalidTransferSignature); } - if !tr.plan.unless_any.is_empty() { + if !tr.plan.unless_any.0.is_empty() { // TODO: Check to see if the transaction is expired. } - if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan.to) { + if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan.to()) { if let Some(x) = self.balances.get_mut(&tr.from) { *x -= tr.asset; } } - if !self.all_satisfied(&tr.plan.if_all) { + if !self.all_satisfied(&tr.plan.if_all.0) { self.pending.insert(tr.sig, tr.clone()); return Ok(()); } @@ -175,7 +176,7 @@ impl Accountant { // if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map. // TODO: Use find(). - for cond in &tr.plan.unless_any { + for cond in &tr.plan.unless_any.0 { if let Condition::Signature(pubkey) = *cond { if from == pubkey { cancel = true; @@ -220,17 +221,17 @@ impl Accountant { // Check to see if any timelocked transactions can be completed. let mut completed = vec![]; for (key, tr) in &self.pending { - for cond in &tr.plan.if_all { + for cond in &tr.plan.if_all.0 { if let Condition::Timestamp(dt) = *cond { if self.last_time >= dt { - if tr.plan.if_all.len() == 1 { + if tr.plan.if_all.0.len() == 1 { completed.push(*key); } } } } // TODO: Add this in once we start removing constraints - //if tr.plan.if_all.is_empty() { + //if tr.plan.if_all.0.is_empty() { // // TODO: Remove tr from pending // self.complete_transaction(tr); //} diff --git a/src/mint.rs b/src/mint.rs index 0fd1e0b835..bb14ffe374 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -63,7 +63,7 @@ mod tests { 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.plan.to); + assert_eq!(tr.from, tr.plan.to()); } assert_eq!(events.next(), None); } diff --git a/src/transaction.rs b/src/transaction.rs index 86a2f3f756..47570e226a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -13,30 +13,59 @@ pub enum Condition { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct SpendingPlan { +pub enum Action { + Pay(Payment), +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Payment { + pub asset: T, pub to: PublicKey, - pub if_all: Vec, - pub unless_any: Vec, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct SpendingPlan { + pub if_all: (Vec, Action), + pub unless_any: (Vec, Action), +} + +impl SpendingPlan { + pub fn to(&self) -> PublicKey { + let Action::Pay(ref payment) = self.if_all.1; + payment.to + } } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Transaction { pub from: PublicKey, - pub plan: SpendingPlan, + pub plan: SpendingPlan, 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 from = from_keypair.pubkey(); let plan = SpendingPlan { - to, - if_all: vec![], - unless_any: vec![], + if_all: ( + vec![], + Action::Pay(Payment { + asset: asset.clone(), + to, + }), + ), + unless_any: ( + vec![], + Action::Pay(Payment { + asset: asset.clone(), + to: from, + }), + ), }; let mut tr = Transaction { - from: from_keypair.pubkey(), + from, plan, asset, last_id, @@ -55,9 +84,20 @@ impl Transaction { ) -> Self { let from = from_keypair.pubkey(); let plan = SpendingPlan { - to, - if_all: vec![Condition::Timestamp(dt)], - unless_any: vec![Condition::Signature(from)], + if_all: ( + vec![Condition::Timestamp(dt)], + Action::Pay(Payment { + asset: asset.clone(), + to, + }), + ), + unless_any: ( + vec![Condition::Signature(from)], + Action::Pay(Payment { + asset: asset.clone(), + to: from, + }), + ), }; let mut tr = Transaction { from, @@ -74,7 +114,6 @@ impl Transaction { let plan = &self.plan; serialize(&( &self.from, - &plan.to, &plan.if_all, &plan.unless_any, &self.asset, @@ -121,9 +160,20 @@ mod tests { #[test] fn test_serialize_claim() { let plan = SpendingPlan { - to: Default::default(), - if_all: Default::default(), - unless_any: Default::default(), + if_all: ( + Default::default(), + Action::Pay(Payment { + asset: 0, + to: Default::default(), + }), + ), + unless_any: ( + Default::default(), + Action::Pay(Payment { + asset: 0, + to: Default::default(), + }), + ), }; let claim0 = Transaction { from: Default::default(), @@ -155,9 +205,13 @@ 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.plan.to = thief_keypair.pubkey(); // <-- attack! + tr.plan.if_all.1 = Action::Pay(Payment { + asset, + to: thief_keypair.pubkey(), + }); // <-- attack! assert!(!tr.verify()); } } From 9d77fd7eec5e6f85dfb5b6a5b01c594b571be8ea Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 10 Mar 2018 17:57:16 -0700 Subject: [PATCH 3/9] Store only spending plans, not full transactions --- src/accountant.rs | 44 +++++++++++++++++++++++--------------------- src/transaction.rs | 12 ++++++------ 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index d9f5203536..0b2aab422d 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, Condition, 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, } @@ -108,21 +108,23 @@ impl Accountant { } /// Commit funds to the 'to' party. - fn complete_transaction(self: &mut Self, tr: &Transaction) { - let to = tr.plan.to(); + fn complete_transaction(self: &mut Self, plan: &Plan) { + let Action::Pay(ref payment) = plan.if_all.1; + let to = payment.to; if self.balances.contains_key(&to) { if let Some(x) = self.balances.get_mut(&to) { - *x += tr.asset; + *x += payment.asset; } } else { - self.balances.insert(to, tr.asset); + self.balances.insert(to, payment.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; + fn cancel_transaction(self: &mut Self, plan: &Plan) { + let Action::Pay(ref payment) = plan.unless_any.1; + if let Some(x) = self.balances.get_mut(&payment.to) { + *x += payment.asset; } } @@ -161,22 +163,22 @@ impl Accountant { } if !self.all_satisfied(&tr.plan.if_all.0) { - self.pending.insert(tr.sig, tr.clone()); + self.pending.insert(tr.sig, tr.plan.clone()); return Ok(()); } - self.complete_transaction(tr); + self.complete_transaction(&tr.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) { + if let Some(plan) = 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. // TODO: Use find(). - for cond in &tr.plan.unless_any.0 { + for cond in &plan.unless_any.0 { if let Condition::Signature(pubkey) = *cond { if from == pubkey { cancel = true; @@ -187,8 +189,8 @@ impl Accountant { } if cancel { - if let Some(tr) = self.pending.remove(&tx_sig) { - self.cancel_transaction(&tr); + if let Some(plan) = self.pending.remove(&tx_sig) { + self.cancel_transaction(&plan); } } @@ -220,11 +222,11 @@ impl Accountant { // Check to see if any timelocked transactions can be completed. let mut completed = vec![]; - for (key, tr) in &self.pending { - for cond in &tr.plan.if_all.0 { + for (key, plan) in &self.pending { + for cond in &plan.if_all.0 { if let Condition::Timestamp(dt) = *cond { if self.last_time >= dt { - if tr.plan.if_all.0.len() == 1 { + if plan.if_all.0.len() == 1 { completed.push(*key); } } @@ -233,13 +235,13 @@ impl Accountant { // TODO: Add this in once we start removing constraints //if tr.plan.if_all.0.is_empty() { // // TODO: Remove tr from pending - // self.complete_transaction(tr); + // self.complete_transaction(plan); //} } 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); } } diff --git a/src/transaction.rs b/src/transaction.rs index 47570e226a..33c5021716 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -24,12 +24,12 @@ pub struct Payment { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct SpendingPlan { +pub struct Plan { pub if_all: (Vec, Action), pub unless_any: (Vec, Action), } -impl SpendingPlan { +impl Plan { pub fn to(&self) -> PublicKey { let Action::Pay(ref payment) = self.if_all.1; payment.to @@ -39,7 +39,7 @@ impl SpendingPlan { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Transaction { pub from: PublicKey, - pub plan: SpendingPlan, + pub plan: Plan, pub asset: T, pub last_id: Hash, pub sig: Signature, @@ -48,7 +48,7 @@ pub struct Transaction { impl Transaction { pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self { let from = from_keypair.pubkey(); - let plan = SpendingPlan { + let plan = Plan { if_all: ( vec![], Action::Pay(Payment { @@ -83,7 +83,7 @@ impl Transaction { last_id: Hash, ) -> Self { let from = from_keypair.pubkey(); - let plan = SpendingPlan { + let plan = Plan { if_all: ( vec![Condition::Timestamp(dt)], Action::Pay(Payment { @@ -159,7 +159,7 @@ mod tests { #[test] fn test_serialize_claim() { - let plan = SpendingPlan { + let plan = Plan { if_all: ( Default::default(), Action::Pay(Payment { From 8579795c406113f465eda515ca08a040ed97d38a Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 10 Mar 2018 19:44:45 -0700 Subject: [PATCH 4/9] Ensure transactions won't get canceled after next refactor --- src/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction.rs b/src/transaction.rs index 33c5021716..8e6359231c 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -57,7 +57,7 @@ impl Transaction { }), ), unless_any: ( - vec![], + vec![Condition::Signature(from)], Action::Pay(Payment { asset: asset.clone(), to: from, From f5f71a19b83dfbd0fdec98b0f834fa52ca67c177 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 10 Mar 2018 22:00:27 -0700 Subject: [PATCH 5/9] First go at smart contracts Needs lots of cleanup. --- src/accountant.rs | 84 +++++++++++++++++++++++++------------------- src/mint.rs | 5 ++- src/transaction.rs | 86 +++++++++++++--------------------------------- 3 files changed, 76 insertions(+), 99 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 0b2aab422d..57284cac0c 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -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<()> { @@ -109,38 +113,49 @@ impl Accountant { /// Commit funds to the 'to' party. fn complete_transaction(self: &mut Self, plan: &Plan) { - let Action::Pay(ref payment) = plan.if_all.1; - let to = payment.to; - if self.balances.contains_key(&to) { - if let Some(x) = self.balances.get_mut(&to) { - *x += payment.asset; + let payment = match *plan { + Plan::Action(Action::Pay(ref payment)) => Some(payment), + Plan::After(_, Action::Pay(ref payment)) => Some(payment), + Plan::Race(ref plan_a, _) => { + if let Plan::After(_, Action::Pay(ref payment)) = **plan_a { + Some(payment) + } else { + None + } + } + }; + if let Some(payment) = payment { + if self.balances.contains_key(&payment.to) { + if let Some(x) = self.balances.get_mut(&payment.to) { + *x += payment.asset; + } + } else { + self.balances.insert(payment.to, payment.asset); } - } else { - self.balances.insert(to, payment.asset); } } /// Return funds to the 'from' party. fn cancel_transaction(self: &mut Self, plan: &Plan) { - let Action::Pay(ref payment) = plan.unless_any.1; - if let Some(x) = self.balances.get_mut(&payment.to) { - *x += payment.asset; + if let Plan::Race(_, ref cancel_plan) = *plan { + if let Plan::After(_, Action::Pay(ref payment)) = **cancel_plan { + if let Some(x) = self.balances.get_mut(&payment.to) { + *x += payment.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; - } - } else { - satisfied = false; + fn all_satisfied(&self, plan: &Plan) -> bool { + match *plan { + Plan::Action(_) => true, + Plan::After(Condition::Timestamp(dt), _) => dt <= self.last_time, + Plan::After(Condition::Signature(_), _) => false, + Plan::Race(ref plan_a, ref plan_b) => { + self.all_satisfied(plan_a) || self.all_satisfied(plan_b) } } - satisfied } fn process_verified_transaction( @@ -152,17 +167,13 @@ impl Accountant { return Err(AccountingError::InvalidTransferSignature); } - if !tr.plan.unless_any.0.is_empty() { - // TODO: Check to see if the transaction is expired. - } - - if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan.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.plan.if_all.0) { + if !self.all_satisfied(&tr.plan) { self.pending.insert(tr.sig, tr.plan.clone()); return Ok(()); } @@ -178,11 +189,10 @@ impl Accountant { // if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map. // TODO: Use find(). - for cond in &plan.unless_any.0 { - if let Condition::Signature(pubkey) = *cond { + if let Plan::Race(_, ref plan_b) = *plan { + if let Plan::After(Condition::Signature(pubkey), _) = **plan_b { if from == pubkey { cancel = true; - break; } } } @@ -223,12 +233,14 @@ impl Accountant { // Check to see if any timelocked transactions can be completed. let mut completed = vec![]; for (key, plan) in &self.pending { - for cond in &plan.if_all.0 { - if let Condition::Timestamp(dt) = *cond { + if let Plan::After(Condition::Timestamp(dt), _) = *plan { + if self.last_time >= dt { + completed.push(*key); + } + } else if let Plan::Race(ref plan_a, _) = *plan { + if let Plan::After(Condition::Timestamp(dt), _) = **plan_a { if self.last_time >= dt { - if plan.if_all.0.len() == 1 { - completed.push(*key); - } + completed.push(*key); } } } diff --git a/src/mint.rs b/src/mint.rs index bb14ffe374..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.plan.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 8e6359231c..f606c4b3ac 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -24,16 +24,10 @@ pub struct Payment { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Plan { - pub if_all: (Vec, Action), - pub unless_any: (Vec, Action), -} - -impl Plan { - pub fn to(&self) -> PublicKey { - let Action::Pay(ref payment) = self.if_all.1; - payment.to - } +pub enum Plan { + Action(Action), + After(Condition, Action), + Race(Box>, Box>), } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -48,22 +42,10 @@ pub struct Transaction { impl Transaction { pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self { let from = from_keypair.pubkey(); - let plan = Plan { - if_all: ( - vec![], - Action::Pay(Payment { - asset: asset.clone(), - to, - }), - ), - unless_any: ( - vec![Condition::Signature(from)], - Action::Pay(Payment { - asset: asset.clone(), - to: from, - }), - ), - }; + let plan = Plan::Action(Action::Pay(Payment { + asset: asset.clone(), + to, + })); let mut tr = Transaction { from, plan, @@ -83,22 +65,22 @@ impl Transaction { last_id: Hash, ) -> Self { let from = from_keypair.pubkey(); - let plan = Plan { - if_all: ( - vec![Condition::Timestamp(dt)], + let plan = Plan::Race( + Box::new(Plan::After( + Condition::Timestamp(dt), Action::Pay(Payment { asset: asset.clone(), to, }), - ), - unless_any: ( - vec![Condition::Signature(from)], + )), + Box::new(Plan::After( + Condition::Signature(from), Action::Pay(Payment { asset: asset.clone(), to: from, }), - ), - }; + )), + ); let mut tr = Transaction { from, plan, @@ -111,14 +93,7 @@ impl Transaction { } fn get_sign_data(&self) -> Vec { - let plan = &self.plan; - serialize(&( - &self.from, - &plan.if_all, - &plan.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) { @@ -159,22 +134,10 @@ mod tests { #[test] fn test_serialize_claim() { - let plan = Plan { - if_all: ( - Default::default(), - Action::Pay(Payment { - asset: 0, - to: Default::default(), - }), - ), - unless_any: ( - Default::default(), - Action::Pay(Payment { - asset: 0, - to: Default::default(), - }), - ), - }; + let plan = Plan::Action(Action::Pay(Payment { + asset: 0, + to: Default::default(), + })); let claim0 = Transaction { from: Default::default(), plan, @@ -208,10 +171,9 @@ mod tests { let asset = hash(b"hello, world"); let mut tr = Transaction::new(&keypair0, pubkey1, asset, zero); tr.sign(&keypair0); - tr.plan.if_all.1 = Action::Pay(Payment { - asset, - 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()); } } From 30449b605482388b772d956bace9e678c74625ff Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sun, 11 Mar 2018 00:11:08 -0700 Subject: [PATCH 6/9] cleanup sig processing --- src/accountant.rs | 47 +++++++------------------------------------ src/transaction.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 57284cac0c..a6685924e0 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -135,17 +135,6 @@ impl Accountant { } } - /// Return funds to the 'from' party. - fn cancel_transaction(self: &mut Self, plan: &Plan) { - if let Plan::Race(_, ref cancel_plan) = *plan { - if let Plan::After(_, Action::Pay(ref payment)) = **cancel_plan { - if let Some(x) = self.balances.get_mut(&payment.to) { - *x += payment.asset; - } - } - } - } - // TODO: Move this to transaction.rs fn all_satisfied(&self, plan: &Plan) -> bool { match *plan { @@ -183,30 +172,18 @@ impl Accountant { } fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> { - let mut cancel = false; - if let Some(plan) = 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(). - if let Plan::Race(_, ref plan_b) = *plan { - if let Plan::After(Condition::Signature(pubkey), _) = **plan_b { - if from == pubkey { - cancel = true; - } - } - } - } - - if cancel { + if actionable { if let Some(plan) = self.pending.remove(&tx_sig) { - self.cancel_transaction(&plan); + self.complete_transaction(&plan); } } - // 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(()) } @@ -224,11 +201,6 @@ 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![]; @@ -244,11 +216,6 @@ impl Accountant { } } } - // TODO: Add this in once we start removing constraints - //if tr.plan.if_all.0.is_empty() { - // // TODO: Remove tr from pending - // self.complete_transaction(plan); - //} } for key in completed { diff --git a/src/transaction.rs b/src/transaction.rs index f606c4b3ac..8593991382 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 { @@ -30,6 +31,55 @@ pub enum Plan { Race(Box>, Box>), } +impl Plan { + 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::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 + } + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Transaction { pub from: PublicKey, From 0eb3669fbfa1e465656912d8bda4f435c4d3bf34 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sun, 11 Mar 2018 00:30:01 -0700 Subject: [PATCH 7/9] cleanup timestamp processing --- src/accountant.rs | 48 ++++++++++------------------------------------ src/transaction.rs | 28 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index a6685924e0..2c00ea124e 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -113,18 +113,7 @@ impl Accountant { /// Commit funds to the 'to' party. fn complete_transaction(self: &mut Self, plan: &Plan) { - let payment = match *plan { - Plan::Action(Action::Pay(ref payment)) => Some(payment), - Plan::After(_, Action::Pay(ref payment)) => Some(payment), - Plan::Race(ref plan_a, _) => { - if let Plan::After(_, Action::Pay(ref payment)) = **plan_a { - Some(payment) - } else { - None - } - } - }; - if let Some(payment) = payment { + 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; @@ -135,18 +124,6 @@ impl Accountant { } } - // TODO: Move this to transaction.rs - fn all_satisfied(&self, plan: &Plan) -> bool { - match *plan { - Plan::Action(_) => true, - Plan::After(Condition::Timestamp(dt), _) => dt <= self.last_time, - Plan::After(Condition::Signature(_), _) => false, - Plan::Race(ref plan_a, ref plan_b) => { - self.all_satisfied(plan_a) || self.all_satisfied(plan_b) - } - } - } - fn process_verified_transaction( self: &mut Self, tr: &Transaction, @@ -162,12 +139,15 @@ impl Accountant { } } - if !self.all_satisfied(&tr.plan) { - self.pending.insert(tr.sig, tr.plan.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.plan); + self.complete_transaction(&plan); Ok(()) } @@ -204,17 +184,9 @@ impl Accountant { // Check to see if any timelocked transactions can be completed. let mut completed = vec![]; - for (key, plan) in &self.pending { - if let Plan::After(Condition::Timestamp(dt), _) = *plan { - if self.last_time >= dt { - completed.push(*key); - } - } else if let Plan::Race(ref plan_a, _) = *plan { - if let Plan::After(Condition::Timestamp(dt), _) = **plan_a { - if self.last_time >= dt { - completed.push(*key); - } - } + for (key, plan) in &mut self.pending { + if plan.process_verified_timestamp(self.last_time) { + completed.push(key.clone()); } } diff --git a/src/transaction.rs b/src/transaction.rs index 8593991382..68a1dcd66a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -56,6 +56,7 @@ impl Plan { 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); @@ -78,6 +79,33 @@ impl Plan { 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)] From aa0a184ebefa1e7ebfba7747a510071c3c54b608 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sun, 11 Mar 2018 11:53:45 -0600 Subject: [PATCH 8/9] Ensure the server isn't passed a Plan that spends more than is bonded --- src/accountant.rs | 17 ++++++++++++++++- src/transaction.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 2c00ea124e..43fbb47fce 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -5,7 +5,7 @@ use hash::Hash; use entry::Entry; use event::Event; -use transaction::{Action, Condition, Plan, Transaction}; +use transaction::{Action, Plan, Transaction}; use signature::{KeyPair, PublicKey, Signature}; use mint::Mint; use historian::{reserve_signature, Historian}; @@ -281,6 +281,21 @@ 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), + Err(AccountingError::InvalidTransfer) + ); + } + #[test] fn test_transfer_to_newb() { let alice = Mint::new(10_000); diff --git a/src/transaction.rs b/src/transaction.rs index 68a1dcd66a..ce571473e6 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -6,6 +6,7 @@ use bincode::serialize; use hash::Hash; use chrono::prelude::*; use std::mem; +use std::cmp; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Condition { @@ -18,6 +19,14 @@ pub enum Action { Pay(Payment), } +impl Action { + pub fn max_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, @@ -31,7 +40,21 @@ pub enum Plan { Race(Box>, Box>), } -impl Plan { +impl Plan { + pub fn max_spendable(&self) -> T { + match *self { + Plan::Action(ref action) => action.max_spendable(), + Plan::Race(ref plan_a, ref plan_b) => { + cmp::max(plan_a.max_spendable(), plan_b.max_spendable()) + } + Plan::After(_, ref action) => action.max_spendable(), + } + } + + pub fn verify(&self, spendable_assets: &T) -> bool { + self.max_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 { @@ -117,7 +140,7 @@ pub struct Transaction { pub sig: Signature, } -impl Transaction { +impl Transaction { pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self { let from = from_keypair.pubkey(); let plan = Plan::Action(Action::Pay(Payment { @@ -180,7 +203,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) } } From 45765b625a2077b983e6b0484f41821354e39e85 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sun, 11 Mar 2018 12:04:44 -0600 Subject: [PATCH 9/9] Don't let users accidentally burn their funds either --- src/accountant.rs | 13 +++++++++++-- src/transaction.rs | 25 ++++++++++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 43fbb47fce..72b03eab9c 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -288,10 +288,19 @@ mod tests { 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! + payment.asset = 2; // <-- attack! } assert_eq!( - acc.process_transaction(tr), + 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) ); } diff --git a/src/transaction.rs b/src/transaction.rs index ce571473e6..9cf7a68f69 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -6,7 +6,6 @@ use bincode::serialize; use hash::Hash; use chrono::prelude::*; use std::mem; -use std::cmp; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Condition { @@ -20,7 +19,7 @@ pub enum Action { } impl Action { - pub fn max_spendable(&self) -> T { + pub fn spendable(&self) -> T { match *self { Action::Pay(ref payment) => payment.asset.clone(), } @@ -40,19 +39,15 @@ pub enum Plan { Race(Box>, Box>), } -impl Plan { - pub fn max_spendable(&self) -> T { - match *self { - Plan::Action(ref action) => action.max_spendable(), - Plan::Race(ref plan_a, ref plan_b) => { - cmp::max(plan_a.max_spendable(), plan_b.max_spendable()) - } - Plan::After(_, ref action) => action.max_spendable(), - } - } - +impl Plan { pub fn verify(&self, spendable_assets: &T) -> bool { - self.max_spendable() <= *spendable_assets + 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 { @@ -140,7 +135,7 @@ pub struct Transaction { pub sig: Signature, } -impl Transaction { +impl Transaction { pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self { let from = from_keypair.pubkey(); let plan = Plan::Action(Action::Pay(Payment {