//! The `transaction` module provides functionality for creating log transactions. use bincode::serialize; use budget::{Budget, Condition}; use chrono::prelude::*; use hash::Hash; use payment_plan::{Payment, PaymentPlan, Witness}; use signature::{Keypair, KeypairUtil, Pubkey, Signature}; pub const SIGNED_DATA_OFFSET: usize = 112; pub const SIG_OFFSET: usize = 8; pub const PUB_KEY_OFFSET: usize = 80; /// The type of payment plan. Each item must implement the PaymentPlan trait. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Plan { /// The builtin contract language Budget. Budget(Budget), } // A proxy for the underlying DSL. impl PaymentPlan for Plan { fn final_payment(&self) -> Option { match self { Plan::Budget(budget) => budget.final_payment(), } } fn verify(&self, spendable_tokens: i64) -> bool { match self { Plan::Budget(budget) => budget.verify(spendable_tokens), } } fn apply_witness(&mut self, witness: &Witness, from: &Pubkey) { match self { Plan::Budget(budget) => budget.apply_witness(witness, from), } } } /// A smart contract. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Contract { /// The number of tokens allocated to the `Plan` and any transaction fees. pub tokens: i64, pub plan: Plan, } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Vote { /// We send some gossip specific membership information through the vote to shortcut /// liveness voting /// The version of the CRDT struct that the last_id of this network voted with pub version: u64, /// The version of the CRDT struct that has the same network configuration as this one pub contact_info_version: u64, // TODO: add signature of the state here as well } /// An instruction to progress the smart contract. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Instruction { /// Declare and instanstansiate `Contract`. NewContract(Contract), /// Tell a payment plan acknowledge the given `DateTime` has past. ApplyTimestamp(DateTime), /// Tell the payment plan that the `NewContract` with `Signature` has been /// signed by the containing transaction's `Pubkey`. ApplySignature(Signature), /// Vote for a PoH that is equal to the lastid of this transaction NewVote(Vote), } /// An instruction signed by a client with `Pubkey`. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Transaction { /// A digital signature of `instruction`, `last_id` and `fee`, signed by `Pubkey`. pub signature: Signature, /// The `Pubkey` of the entity that signed the transaction data. pub from: Pubkey, /// The action the server should take. pub instruction: Instruction, /// The ID of a recent ledger entry. pub last_id: Hash, /// The number of tokens paid for processing and storage of this transaction. pub fee: i64, } impl Transaction { /// Create a signed transaction from the given `Instruction`. fn new_from_instruction( from_keypair: &Keypair, instruction: Instruction, last_id: Hash, fee: i64, ) -> Self { let from = from_keypair.pubkey(); let mut tx = Transaction { signature: Signature::default(), instruction, last_id, from, fee, }; tx.sign(from_keypair); tx } /// Create and sign a new Transaction. Used for unit-testing. pub fn new_taxed( from_keypair: &Keypair, to: Pubkey, tokens: i64, fee: i64, last_id: Hash, ) -> Self { let payment = Payment { tokens: tokens - fee, to, }; let budget = Budget::Pay(payment); let plan = Plan::Budget(budget); let instruction = Instruction::NewContract(Contract { plan, tokens }); Self::new_from_instruction(from_keypair, instruction, last_id, fee) } /// Create and sign a new Transaction. Used for unit-testing. pub fn new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self { Self::new_taxed(from_keypair, to, tokens, 0, last_id) } /// Create and sign a new Witness Timestamp. Used for unit-testing. pub fn new_timestamp(from_keypair: &Keypair, dt: DateTime, last_id: Hash) -> Self { let instruction = Instruction::ApplyTimestamp(dt); Self::new_from_instruction(from_keypair, instruction, last_id, 0) } /// Create and sign a new Witness Signature. Used for unit-testing. pub fn new_signature(from_keypair: &Keypair, signature: Signature, last_id: Hash) -> Self { let instruction = Instruction::ApplySignature(signature); Self::new_from_instruction(from_keypair, instruction, last_id, 0) } pub fn new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self { Transaction::new_from_instruction(&from_keypair, Instruction::NewVote(vote), last_id, fee) } /// Create and sign a postdated Transaction. Used for unit-testing. pub fn new_on_date( from_keypair: &Keypair, to: Pubkey, dt: DateTime, tokens: i64, last_id: Hash, ) -> Self { let from = from_keypair.pubkey(); let budget = Budget::Or( (Condition::Timestamp(dt, from), Payment { tokens, to }), (Condition::Signature(from), Payment { tokens, to: from }), ); let plan = Plan::Budget(budget); let instruction = Instruction::NewContract(Contract { plan, tokens }); Self::new_from_instruction(from_keypair, instruction, last_id, 0) } /// Get the transaction data to sign. fn get_sign_data(&self) -> Vec { let mut data = serialize(&(&self.instruction)).expect("serialize Contract"); let last_id_data = serialize(&(&self.last_id)).expect("serialize last_id"); data.extend_from_slice(&last_id_data); let fee_data = serialize(&(&self.fee)).expect("serialize last_id"); data.extend_from_slice(&fee_data); data } /// Sign this transaction. pub fn sign(&mut self, keypair: &Keypair) { let sign_data = self.get_sign_data(); self.signature = Signature::new(keypair.sign(&sign_data).as_ref()); } /// Verify only the transaction signature. pub fn verify_signature(&self) -> bool { warn!("transaction signature verification called"); self.signature .verify(&self.from.as_ref(), &self.get_sign_data()) } /// Verify only the payment plan. pub fn verify_plan(&self) -> bool { if let Instruction::NewContract(contract) = &self.instruction { self.fee >= 0 && self.fee <= contract.tokens && contract.plan.verify(contract.tokens - self.fee) } else { true } } } pub fn test_tx() -> Transaction { let keypair1 = Keypair::new(); let pubkey1 = keypair1.pubkey(); let zero = Hash::default(); Transaction::new(&keypair1, pubkey1, 42, zero) } #[cfg(test)] pub fn memfind(a: &[A], b: &[A]) -> Option { assert!(a.len() >= b.len()); let end = a.len() - b.len() + 1; for i in 0..end { if a[i..i + b.len()] == b[..] { return Some(i); } } None } #[cfg(test)] mod tests { use super::*; use bincode::{deserialize, serialize}; #[test] fn test_claim() { let keypair = Keypair::new(); let zero = Hash::default(); let tx0 = Transaction::new(&keypair, keypair.pubkey(), 42, zero); assert!(tx0.verify_plan()); } #[test] fn test_transfer() { let zero = Hash::default(); let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let pubkey1 = keypair1.pubkey(); let tx0 = Transaction::new(&keypair0, pubkey1, 42, zero); assert!(tx0.verify_plan()); } #[test] fn test_transfer_with_fee() { let zero = Hash::default(); let keypair0 = Keypair::new(); let pubkey1 = Keypair::new().pubkey(); assert!(Transaction::new_taxed(&keypair0, pubkey1, 1, 1, zero).verify_plan()); assert!(!Transaction::new_taxed(&keypair0, pubkey1, 1, 2, zero).verify_plan()); assert!(!Transaction::new_taxed(&keypair0, pubkey1, 1, -1, zero).verify_plan()); } #[test] fn test_serialize_claim() { let budget = Budget::Pay(Payment { tokens: 0, to: Default::default(), }); let plan = Plan::Budget(budget); let instruction = Instruction::NewContract(Contract { plan, tokens: 0 }); let claim0 = Transaction { instruction, from: Default::default(), last_id: Default::default(), signature: Default::default(), fee: 0, }; let buf = serialize(&claim0).unwrap(); let claim1: Transaction = deserialize(&buf).unwrap(); assert_eq!(claim1, claim0); } #[test] fn test_token_attack() { let zero = Hash::default(); let keypair = Keypair::new(); let pubkey = keypair.pubkey(); let mut tx = Transaction::new(&keypair, pubkey, 42, zero); if let Instruction::NewContract(contract) = &mut tx.instruction { contract.tokens = 1_000_000; // <-- attack, part 1! if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = contract.tokens; // <-- attack, part 2! } } assert!(tx.verify_plan()); assert!(!tx.verify_signature()); } #[test] fn test_hijack_attack() { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let thief_keypair = Keypair::new(); let pubkey1 = keypair1.pubkey(); let zero = Hash::default(); let mut tx = Transaction::new(&keypair0, pubkey1, 42, zero); if let Instruction::NewContract(contract) = &mut tx.instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.to = thief_keypair.pubkey(); // <-- attack! } } assert!(tx.verify_plan()); assert!(!tx.verify_signature()); } #[test] fn test_layout() { let tx = test_tx(); let sign_data = tx.get_sign_data(); let tx_bytes = serialize(&tx).unwrap(); assert_matches!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET)); assert_matches!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET)); assert_matches!(memfind(&tx_bytes, &tx.from.as_ref()), Some(PUB_KEY_OFFSET)); } #[test] fn test_overspend_attack() { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let zero = Hash::default(); let mut tx = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero); if let Instruction::NewContract(contract) = &mut tx.instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = 2; // <-- attack! } } assert!(!tx.verify_plan()); // Also, ensure all branchs of the plan spend all tokens if let Instruction::NewContract(contract) = &mut tx.instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = 0; // <-- whoops! } } assert!(!tx.verify_plan()); } }