//! The `transaction` module provides functionality for creating log transactions. use bincode::{deserialize, serialize}; use budget::{Budget, Condition}; use budget_contract::BudgetContract; use chrono::prelude::*; use hash::Hash; use instruction::{Contract, Instruction, Plan, Vote}; use payment_plan::{Payment, PaymentPlan}; use signature::{Keypair, KeypairUtil, Pubkey, Signature}; use std::mem::size_of; use system_contract::SystemContract; pub const SIGNED_DATA_OFFSET: usize = size_of::(); pub const SIG_OFFSET: usize = 0; pub const PUB_KEY_OFFSET: usize = size_of::() + size_of::(); /// 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 `Pubkeys` that are executing this transaction userdata. The meaning of each key is /// contract-specific. /// * keys[0] - Typically this is the `caller` public key. `signature` is verified with keys[0]. /// In the future which key pays the fee and which keys have signatures would be configurable. /// * keys[1] - Typically this is the contract context or the recipient of the tokens pub keys: Vec, /// the contract id to execute pub contract_id: Pubkey, /// 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, /// Userdata to be stored in the account pub userdata: Vec, } impl Transaction { /// Create a signed transaction from the given `Instruction`. /// * `from_keypair` - The key used to sign the transaction. This key is stored as keys[0] /// * `transaction_keys` - The keys for the transaction. These are the contract state /// instances or token recipient keys. /// * `userdata` - The input data that the contract will execute with /// * `last_id` - The PoH hash. /// * `fee` - The transaction fee. fn new_with_userdata( from_keypair: &Keypair, transaction_keys: &[Pubkey], contract_id: Pubkey, userdata: Vec, last_id: Hash, fee: i64, ) -> Self { let from = from_keypair.pubkey(); let mut keys = vec![from]; keys.extend_from_slice(transaction_keys); let mut tx = Transaction { signature: Signature::default(), keys, contract_id, last_id, fee, userdata, }; tx.sign(from_keypair); tx } /// Create and sign a new Transaction. Used for unit-testing. pub fn budget_new_taxed( from_keypair: &Keypair, contract: Pubkey, tokens: i64, fee: i64, last_id: Hash, ) -> Self { let payment = Payment { tokens: tokens - fee, to: contract, }; let budget = Budget::Pay(payment); let plan = Plan::Budget(budget); let instruction = Instruction::NewContract(Contract { plan, tokens }); let userdata = serialize(&instruction).unwrap(); Self::new_with_userdata( from_keypair, &[contract], BudgetContract::id(), userdata, last_id, fee, ) } /// Create and sign a new Transaction. Used for unit-testing. pub fn budget_new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self { Self::budget_new_taxed(from_keypair, to, tokens, 0, last_id) } /// Create and sign a new Witness Timestamp. Used for unit-testing. pub fn budget_new_timestamp( from_keypair: &Keypair, contract: Pubkey, to: Pubkey, dt: DateTime, last_id: Hash, ) -> Self { let instruction = Instruction::ApplyTimestamp(dt); let userdata = serialize(&instruction).unwrap(); Self::new_with_userdata( from_keypair, &[contract, to], BudgetContract::id(), userdata, last_id, 0, ) } /// Create and sign a new Witness Signature. Used for unit-testing. pub fn budget_new_signature( from_keypair: &Keypair, contract: Pubkey, to: Pubkey, signature: Signature, last_id: Hash, ) -> Self { let instruction = Instruction::ApplySignature(signature); let userdata = serialize(&instruction).unwrap(); Self::new_with_userdata( from_keypair, &[contract, to], BudgetContract::id(), userdata, last_id, 0, ) } pub fn budget_new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self { let instruction = Instruction::NewVote(vote); let userdata = serialize(&instruction).expect("serialize instruction"); Self::new_with_userdata( from_keypair, &[], BudgetContract::id(), userdata, last_id, fee, ) } /// Create and sign a postdated Transaction. Used for unit-testing. pub fn budget_new_on_date( from_keypair: &Keypair, to: Pubkey, contract: 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 }); let userdata = serialize(&instruction).expect("serialize instruction"); Self::new_with_userdata( from_keypair, &[contract], BudgetContract::id(), userdata, last_id, 0, ) } /// Create and sign new SystemContract::CreateAccount transaction pub fn system_create( from_keypair: &Keypair, to: Pubkey, last_id: Hash, tokens: i64, space: u64, contract_id: Option, fee: i64, ) -> Self { let create = SystemContract::CreateAccount { tokens, //TODO, the tokens to allocate might need to be higher then 0 in the future space, contract_id, }; Transaction::new_with_userdata( from_keypair, &[to], SystemContract::id(), serialize(&create).unwrap(), last_id, fee, ) } /// Create and sign new SystemContract::CreateAccount transaction pub fn system_assign( from_keypair: &Keypair, last_id: Hash, contract_id: Pubkey, fee: i64, ) -> Self { let create = SystemContract::Assign { contract_id }; Transaction::new_with_userdata( from_keypair, &[], SystemContract::id(), serialize(&create).unwrap(), last_id, fee, ) } /// Create and sign new SystemContract::CreateAccount transaction with some defaults pub fn system_new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self { Transaction::system_create(from_keypair, to, last_id, tokens, 0, None, 0) } /// Create and sign new SystemContract::Move transaction pub fn system_move( from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash, fee: i64, ) -> Self { let create = SystemContract::Move { tokens }; Transaction::new_with_userdata( from_keypair, &[to], SystemContract::id(), serialize(&create).unwrap(), last_id, fee, ) } /// Create and sign new SystemContract::Move transaction pub fn new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self { Transaction::system_move(from_keypair, to, tokens, last_id, 0) } /// Get the transaction data to sign. fn get_sign_data(&self) -> Vec { let mut data = serialize(&(&self.keys)).expect("serialize keys"); let contract_id = serialize(&(&self.contract_id)).expect("serialize contract_id"); data.extend_from_slice(&contract_id); 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); let userdata = serialize(&(&self.userdata)).expect("serialize userdata"); data.extend_from_slice(&userdata); 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()) } pub fn vote(&self) -> Option<(Pubkey, Vote, Hash)> { if let Some(Instruction::NewVote(vote)) = self.instruction() { Some((*self.from(), vote, self.last_id)) } else { None } } pub fn from(&self) -> &Pubkey { &self.keys[0] } pub fn instruction(&self) -> Option { deserialize(&self.userdata).ok() } /// Verify only the payment plan. pub fn verify_plan(&self) -> bool { if let Some(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::system_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}; use packet::PACKET_DATA_SIZE; #[test] fn test_claim() { let keypair = Keypair::new(); let zero = Hash::default(); let tx0 = Transaction::budget_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::budget_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::budget_new_taxed(&keypair0, pubkey1, 1, 1, zero).verify_plan()); assert!(!Transaction::budget_new_taxed(&keypair0, pubkey1, 1, 2, zero).verify_plan()); assert!(!Transaction::budget_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 userdata = serialize(&instruction).unwrap(); let claim0 = Transaction { keys: vec![], last_id: Default::default(), signature: Default::default(), contract_id: Default::default(), fee: 0, userdata, }; 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::budget_new(&keypair, pubkey, 42, zero); let mut instruction = tx.instruction().unwrap(); if let Instruction::NewContract(ref mut contract) = 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! } } tx.userdata = serialize(&instruction).unwrap(); 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::budget_new(&keypair0, pubkey1, 42, zero); let mut instruction = tx.instruction(); if let Some(Instruction::NewContract(ref mut contract)) = instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.to = thief_keypair.pubkey(); // <-- attack! } } tx.userdata = serialize(&instruction).unwrap(); 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_eq!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET)); assert_eq!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET)); assert_eq!( memfind(&tx_bytes, &tx.from().as_ref()), Some(PUB_KEY_OFFSET) ); assert!(tx.verify_signature()); } #[test] fn test_userdata_layout() { let mut tx0 = test_tx(); tx0.userdata = vec![1, 2, 3]; let sign_data0a = tx0.get_sign_data(); let tx_bytes = serialize(&tx0).unwrap(); assert!(tx_bytes.len() < PACKET_DATA_SIZE); assert_eq!(memfind(&tx_bytes, &sign_data0a), Some(SIGNED_DATA_OFFSET)); assert_eq!( memfind(&tx_bytes, &tx0.signature.as_ref()), Some(SIG_OFFSET) ); assert_eq!( memfind(&tx_bytes, &tx0.from().as_ref()), Some(PUB_KEY_OFFSET) ); let tx1 = deserialize(&tx_bytes).unwrap(); assert_eq!(tx0, tx1); assert_eq!(tx1.userdata, vec![1, 2, 3]); tx0.userdata = vec![1, 2, 4]; let sign_data0b = tx0.get_sign_data(); assert_ne!(sign_data0a, sign_data0b); } #[test] fn test_overspend_attack() { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let zero = Hash::default(); let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero); let mut instruction = tx.instruction().unwrap(); if let Instruction::NewContract(ref mut contract) = instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = 2; // <-- attack! } } tx.userdata = serialize(&instruction).unwrap(); assert!(!tx.verify_plan()); // Also, ensure all branchs of the plan spend all tokens let mut instruction = tx.instruction().unwrap(); if let Instruction::NewContract(ref mut contract) = instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = 0; // <-- whoops! } } tx.userdata = serialize(&instruction).unwrap(); assert!(!tx.verify_plan()); } }