Vet timestamp source from contract, not leader

Per @aeyakovenko, contracts shouldn't trust the network for
timestamps. Instead, pass the verified public key to the
contract and let it decide if that's a public key it wants
to trust the timestamp from.

Fixes #405
This commit is contained in:
Greg Fitzgerald 2018-07-08 21:04:55 -06:00 committed by Greg Fitzgerald
parent 5f99657523
commit 71f05cb23e
4 changed files with 55 additions and 31 deletions

View File

@ -388,7 +388,7 @@ impl Bank {
.expect("write() in apply_signature") .expect("write() in apply_signature")
.entry(tx_sig) .entry(tx_sig)
{ {
e.get_mut().apply_witness(&Witness::Signature(from)); e.get_mut().apply_witness(&Witness::Signature, &from);
if let Some(payment) = e.get().final_payment() { if let Some(payment) = e.get().final_payment() {
self.apply_payment(&payment, &mut self.balances.write().unwrap()); self.apply_payment(&payment, &mut self.balances.write().unwrap());
e.remove_entry(); e.remove_entry();
@ -400,7 +400,7 @@ impl Bank {
/// Process a Witness Timestamp. Any payment plans waiting on this timestamp /// Process a Witness Timestamp. Any payment plans waiting on this timestamp
/// will progress one step. /// will progress one step.
fn apply_timestamp(&self, _from: PublicKey, dt: DateTime<Utc>) -> Result<()> { fn apply_timestamp(&self, from: PublicKey, dt: DateTime<Utc>) -> Result<()> {
// Check to see if any timelocked transactions can be completed. // Check to see if any timelocked transactions can be completed.
let mut completed = vec![]; let mut completed = vec![];
@ -410,7 +410,7 @@ impl Bank {
.write() .write()
.expect("'pending' write lock in apply_timestamp"); .expect("'pending' write lock in apply_timestamp");
for (key, plan) in pending.iter_mut() { for (key, plan) in pending.iter_mut() {
plan.apply_witness(&Witness::Timestamp(dt)); plan.apply_witness(&Witness::Timestamp(dt), &from);
if let Some(payment) = plan.final_payment() { if let Some(payment) = plan.final_payment() {
self.apply_payment(&payment, &mut self.balances.write().unwrap()); self.apply_payment(&payment, &mut self.balances.write().unwrap());
completed.push(key.clone()); completed.push(key.clone());

View File

@ -12,7 +12,7 @@ use std::mem;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Condition { pub enum Condition {
/// Wait for a `Timestamp` `Witness` at or after the given `DateTime`. /// Wait for a `Timestamp` `Witness` at or after the given `DateTime`.
Timestamp(DateTime<Utc>), Timestamp(DateTime<Utc>, PublicKey),
/// Wait for a `Signature` `Witness` from `PublicKey`. /// Wait for a `Signature` `Witness` from `PublicKey`.
Signature(PublicKey), Signature(PublicKey),
@ -20,10 +20,12 @@ pub enum Condition {
impl Condition { impl Condition {
/// Return true if the given Witness satisfies this Condition. /// Return true if the given Witness satisfies this Condition.
pub fn is_satisfied(&self, witness: &Witness) -> bool { pub fn is_satisfied(&self, witness: &Witness, from: &PublicKey) -> bool {
match (self, witness) { match (self, witness) {
(Condition::Signature(pubkey), Witness::Signature(from)) => pubkey == from, (Condition::Signature(pubkey), Witness::Signature) => pubkey == from,
(Condition::Timestamp(dt), Witness::Timestamp(last_time)) => dt <= last_time, (Condition::Timestamp(dt, pubkey), Witness::Timestamp(last_time)) => {
pubkey == from && dt <= last_time
}
_ => false, _ => false,
} }
} }
@ -56,8 +58,13 @@ impl Budget {
} }
/// Create a budget that pays `tokens` to `to` after the given DateTime. /// Create a budget that pays `tokens` to `to` after the given DateTime.
pub fn new_future_payment(dt: DateTime<Utc>, tokens: i64, to: PublicKey) -> Self { pub fn new_future_payment(
Budget::After(Condition::Timestamp(dt), Payment { tokens, to }) dt: DateTime<Utc>,
from: PublicKey,
tokens: i64,
to: PublicKey,
) -> Self {
Budget::After(Condition::Timestamp(dt, from), Payment { tokens, to })
} }
/// Create a budget that pays `tokens` to `to` after the given DateTime /// Create a budget that pays `tokens` to `to` after the given DateTime
@ -69,7 +76,7 @@ impl Budget {
to: PublicKey, to: PublicKey,
) -> Self { ) -> Self {
Budget::Or( Budget::Or(
(Condition::Timestamp(dt), Payment { tokens, to }), (Condition::Timestamp(dt, from), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }), (Condition::Signature(from), Payment { tokens, to: from }),
) )
} }
@ -94,11 +101,11 @@ impl PaymentPlan for Budget {
/// Apply a witness to the budget to see if the budget can be reduced. /// Apply a witness to the budget to see if the budget can be reduced.
/// If so, modify the budget in-place. /// If so, modify the budget in-place.
fn apply_witness(&mut self, witness: &Witness) { fn apply_witness(&mut self, witness: &Witness, from: &PublicKey) {
let new_payment = match self { let new_payment = match self {
Budget::After(cond, payment) if cond.is_satisfied(witness) => Some(payment), Budget::After(cond, payment) if cond.is_satisfied(witness, from) => Some(payment),
Budget::Or((cond, payment), _) if cond.is_satisfied(witness) => Some(payment), Budget::Or((cond, payment), _) if cond.is_satisfied(witness, from) => Some(payment),
Budget::Or(_, (cond, payment)) if cond.is_satisfied(witness) => Some(payment), Budget::Or(_, (cond, payment)) if cond.is_satisfied(witness, from) => Some(payment),
_ => None, _ => None,
}.cloned(); }.cloned();
@ -111,20 +118,22 @@ impl PaymentPlan for Budget {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use signature::{KeyPair, KeyPairUtil};
#[test] #[test]
fn test_signature_satisfied() { fn test_signature_satisfied() {
let sig = PublicKey::default(); let from = PublicKey::default();
assert!(Condition::Signature(sig).is_satisfied(&Witness::Signature(sig))); assert!(Condition::Signature(from).is_satisfied(&Witness::Signature, &from));
} }
#[test] #[test]
fn test_timestamp_satisfied() { fn test_timestamp_satisfied() {
let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8);
assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt1))); let from = PublicKey::default();
assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt2))); assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt1), &from));
assert!(!Condition::Timestamp(dt2).is_satisfied(&Witness::Timestamp(dt1))); assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt2), &from));
assert!(!Condition::Timestamp(dt2, from).is_satisfied(&Witness::Timestamp(dt1), &from));
} }
#[test] #[test]
@ -134,7 +143,7 @@ mod tests {
let to = PublicKey::default(); let to = PublicKey::default();
assert!(Budget::new_payment(42, to).verify(42)); assert!(Budget::new_payment(42, to).verify(42));
assert!(Budget::new_authorized_payment(from, 42, to).verify(42)); assert!(Budget::new_authorized_payment(from, 42, to).verify(42));
assert!(Budget::new_future_payment(dt, 42, to).verify(42)); assert!(Budget::new_future_payment(dt, from, 42, to).verify(42));
assert!(Budget::new_cancelable_future_payment(dt, from, 42, to).verify(42)); assert!(Budget::new_cancelable_future_payment(dt, from, 42, to).verify(42));
} }
@ -144,20 +153,35 @@ mod tests {
let to = PublicKey::default(); let to = PublicKey::default();
let mut budget = Budget::new_authorized_payment(from, 42, to); let mut budget = Budget::new_authorized_payment(from, 42, to);
budget.apply_witness(&Witness::Signature(from)); budget.apply_witness(&Witness::Signature, &from);
assert_eq!(budget, Budget::new_payment(42, to)); assert_eq!(budget, Budget::new_payment(42, to));
} }
#[test] #[test]
fn test_future_payment() { fn test_future_payment() {
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let to = PublicKey::default(); let from = KeyPair::new().pubkey();
let to = KeyPair::new().pubkey();
let mut budget = Budget::new_future_payment(dt, 42, to); let mut budget = Budget::new_future_payment(dt, from, 42, to);
budget.apply_witness(&Witness::Timestamp(dt)); budget.apply_witness(&Witness::Timestamp(dt), &from);
assert_eq!(budget, Budget::new_payment(42, to)); assert_eq!(budget, Budget::new_payment(42, to));
} }
#[test]
fn test_unauthorized_future_payment() {
// Ensure timestamp will only be acknowledged if it came from the
// whitelisted public key.
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let from = KeyPair::new().pubkey();
let to = KeyPair::new().pubkey();
let mut budget = Budget::new_future_payment(dt, from, 42, to);
let orig_budget = budget.clone();
budget.apply_witness(&Witness::Timestamp(dt), &to); // <-- Attack!
assert_eq!(budget, orig_budget);
}
#[test] #[test]
fn test_cancelable_future_payment() { fn test_cancelable_future_payment() {
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
@ -165,11 +189,11 @@ mod tests {
let to = PublicKey::default(); let to = PublicKey::default();
let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to); let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to);
budget.apply_witness(&Witness::Timestamp(dt)); budget.apply_witness(&Witness::Timestamp(dt), &from);
assert_eq!(budget, Budget::new_payment(42, to)); assert_eq!(budget, Budget::new_payment(42, to));
let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to); let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to);
budget.apply_witness(&Witness::Signature(from)); budget.apply_witness(&Witness::Signature, &from);
assert_eq!(budget, Budget::new_payment(42, from)); assert_eq!(budget, Budget::new_payment(42, from));
} }
} }

View File

@ -13,7 +13,7 @@ pub enum Witness {
Timestamp(DateTime<Utc>), Timestamp(DateTime<Utc>),
/// A siganture from PublicKey. /// A siganture from PublicKey.
Signature(PublicKey), Signature,
} }
/// Some amount of tokens that should be sent to the `to` `PublicKey`. /// Some amount of tokens that should be sent to the `to` `PublicKey`.
@ -36,5 +36,5 @@ pub trait PaymentPlan {
/// Apply a witness to the payment plan to see if the plan can be reduced. /// Apply a witness to the payment plan to see if the plan can be reduced.
/// If so, modify the plan in-place. /// If so, modify the plan in-place.
fn apply_witness(&mut self, witness: &Witness); fn apply_witness(&mut self, witness: &Witness, from: &PublicKey);
} }

View File

@ -32,9 +32,9 @@ impl PaymentPlan for Plan {
} }
} }
fn apply_witness(&mut self, witness: &Witness) { fn apply_witness(&mut self, witness: &Witness, from: &PublicKey) {
match self { match self {
Plan::Budget(budget) => budget.apply_witness(witness), Plan::Budget(budget) => budget.apply_witness(witness, from),
} }
} }
} }
@ -145,7 +145,7 @@ impl Transaction {
) -> Self { ) -> Self {
let from = from_keypair.pubkey(); let from = from_keypair.pubkey();
let budget = Budget::Or( let budget = Budget::Or(
(Condition::Timestamp(dt), Payment { tokens, to }), (Condition::Timestamp(dt, from), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }), (Condition::Signature(from), Payment { tokens, to: from }),
); );
let plan = Plan::Budget(budget); let plan = Plan::Budget(budget);