solana/src/transaction.rs

245 lines
7.7 KiB
Rust
Raw Normal View History

2018-03-29 11:20:54 -07:00
//! The `transaction` module provides functionality for creating log transactions.
2018-03-06 11:18:17 -08:00
use bincode::serialize;
use chrono::prelude::*;
use hash::Hash;
use plan::{Budget, Condition, Payment, PaymentPlan};
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
2018-03-10 23:11:08 -08:00
2018-04-06 14:43:05 -07:00
pub const SIGNED_DATA_OFFSET: usize = 112;
pub const SIG_OFFSET: usize = 8;
pub const PUB_KEY_OFFSET: usize = 80;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Contract {
2018-03-19 09:03:41 -07:00
pub tokens: i64,
pub plan: Budget,
2018-03-26 21:07:11 -07:00
}
2018-05-22 20:42:04 -07:00
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Instruction {
NewContract(Contract),
ApplyTimestamp(DateTime<Utc>),
ApplySignature(Signature),
}
2018-03-26 21:07:11 -07:00
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Transaction {
2018-03-06 11:18:17 -08:00
pub sig: Signature,
2018-03-26 21:07:11 -07:00
pub from: PublicKey,
2018-05-22 20:42:04 -07:00
pub instruction: Instruction,
pub last_id: Hash,
2018-03-06 11:18:17 -08:00
}
impl Transaction {
2018-05-23 22:11:59 -07:00
fn new_from_instruction(
from_keypair: &KeyPair,
instruction: Instruction,
last_id: Hash,
) -> Self {
let from = from_keypair.pubkey();
2018-05-25 15:05:37 -07:00
let mut tx = Transaction {
sig: Signature::default(),
2018-05-22 20:42:04 -07:00
instruction,
last_id,
from,
};
2018-05-25 15:05:37 -07:00
tx.sign(from_keypair);
tx
}
2018-05-23 22:11:59 -07:00
/// Create and sign a new Transaction. Used for unit-testing.
pub fn new(from_keypair: &KeyPair, to: PublicKey, tokens: i64, last_id: Hash) -> Self {
let plan = Budget::Pay(Payment { tokens, to });
2018-05-23 22:11:59 -07:00
let instruction = Instruction::NewContract(Contract { plan, tokens });
Self::new_from_instruction(from_keypair, instruction, last_id)
}
/// Create and sign a new Witness Timestamp. Used for unit-testing.
pub fn new_timestamp(from_keypair: &KeyPair, dt: DateTime<Utc>, last_id: Hash) -> Self {
let instruction = Instruction::ApplyTimestamp(dt);
Self::new_from_instruction(from_keypair, instruction, last_id)
}
/// Create and sign a new Witness Signature. Used for unit-testing.
pub fn new_signature(from_keypair: &KeyPair, tx_sig: Signature, last_id: Hash) -> Self {
let instruction = Instruction::ApplySignature(tx_sig);
Self::new_from_instruction(from_keypair, instruction, last_id)
}
2018-03-29 11:20:54 -07:00
/// Create and sign a postdated Transaction. Used for unit-testing.
pub fn new_on_date(
from_keypair: &KeyPair,
to: PublicKey,
dt: DateTime<Utc>,
2018-03-19 09:03:41 -07:00
tokens: i64,
last_id: Hash,
) -> Self {
let from = from_keypair.pubkey();
let plan = Budget::Race(
2018-03-20 14:43:04 -07:00
(Condition::Timestamp(dt), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }),
);
2018-05-22 20:42:04 -07:00
let instruction = Instruction::NewContract(Contract { plan, tokens });
2018-05-25 15:05:37 -07:00
let mut tx = Transaction {
2018-05-22 20:42:04 -07:00
instruction,
from,
last_id,
sig: Signature::default(),
};
2018-05-25 15:05:37 -07:00
tx.sign(from_keypair);
tx
2018-03-06 11:18:17 -08:00
}
fn get_sign_data(&self) -> Vec<u8> {
2018-05-22 20:42:04 -07:00
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);
data
}
2018-03-06 11:18:17 -08:00
2018-03-29 11:20:54 -07:00
/// Sign this transaction.
pub fn sign(&mut self, keypair: &KeyPair) {
let sign_data = self.get_sign_data();
self.sig = Signature::clone_from_slice(keypair.sign(&sign_data).as_ref());
}
2018-03-06 11:18:17 -08:00
2018-03-26 21:07:11 -07:00
pub fn verify_sig(&self) -> bool {
self.sig.verify(&self.from, &self.get_sign_data())
}
pub fn verify_plan(&self) -> bool {
2018-05-22 20:42:04 -07:00
if let Instruction::NewContract(contract) = &self.instruction {
contract.plan.verify(contract.tokens)
} else {
true
}
}
2018-03-06 11:18:17 -08:00
}
2018-03-26 21:07:11 -07:00
#[cfg(test)]
pub fn test_tx() -> Transaction {
let keypair1 = KeyPair::new();
let pubkey1 = keypair1.pubkey();
let zero = Hash::default();
2018-04-11 21:05:40 -07:00
Transaction::new(&keypair1, pubkey1, 42, zero)
2018-03-26 21:07:11 -07:00
}
#[cfg(test)]
pub fn memfind<A: Eq>(a: &[A], b: &[A]) -> Option<usize> {
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
}
2018-03-06 11:18:17 -08:00
#[cfg(test)]
mod tests {
use super::*;
use bincode::{deserialize, serialize};
#[test]
fn test_claim() {
let keypair = KeyPair::new();
let zero = Hash::default();
2018-05-29 09:12:27 -07:00
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();
2018-05-29 09:12:27 -07:00
let tx0 = Transaction::new(&keypair0, pubkey1, 42, zero);
assert!(tx0.verify_plan());
}
2018-03-06 11:18:17 -08:00
#[test]
fn test_serialize_claim() {
let plan = Budget::Pay(Payment {
2018-03-19 09:03:41 -07:00
tokens: 0,
to: Default::default(),
2018-03-20 14:43:04 -07:00
});
2018-05-22 20:42:04 -07:00
let instruction = Instruction::NewContract(Contract { plan, tokens: 0 });
let claim0 = Transaction {
2018-05-22 20:42:04 -07:00
instruction,
from: Default::default(),
last_id: Default::default(),
sig: Default::default(),
};
2018-03-06 11:18:17 -08:00
let buf = serialize(&claim0).unwrap();
let claim1: Transaction = deserialize(&buf).unwrap();
2018-03-06 11:18:17 -08:00
assert_eq!(claim1, claim0);
}
#[test]
fn test_token_attack() {
let zero = Hash::default();
let keypair = KeyPair::new();
let pubkey = keypair.pubkey();
2018-05-25 15:05:37 -07:00
let mut tx = Transaction::new(&keypair, pubkey, 42, zero);
if let Instruction::NewContract(contract) = &mut tx.instruction {
2018-05-22 20:42:04 -07:00
contract.tokens = 1_000_000; // <-- attack, part 1!
if let Budget::Pay(ref mut payment) = contract.plan {
2018-05-22 20:42:04 -07:00
payment.tokens = contract.tokens; // <-- attack, part 2!
}
}
2018-05-25 15:05:37 -07:00
assert!(tx.verify_plan());
assert!(!tx.verify_sig());
}
#[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();
2018-05-25 15:05:37 -07:00
let mut tx = Transaction::new(&keypair0, pubkey1, 42, zero);
if let Instruction::NewContract(contract) = &mut tx.instruction {
if let Budget::Pay(ref mut payment) = contract.plan {
2018-05-22 20:42:04 -07:00
payment.to = thief_keypair.pubkey(); // <-- attack!
}
}
2018-05-25 15:05:37 -07:00
assert!(tx.verify_plan());
assert!(!tx.verify_sig());
2018-03-26 21:07:11 -07:00
}
#[test]
fn test_layout() {
2018-05-25 15:05:37 -07:00
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.sig), Some(SIG_OFFSET));
assert_matches!(memfind(&tx_bytes, &tx.from), Some(PUB_KEY_OFFSET));
}
#[test]
fn test_overspend_attack() {
let keypair0 = KeyPair::new();
let keypair1 = KeyPair::new();
let zero = Hash::default();
2018-05-25 15:05:37 -07:00
let mut tx = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero);
if let Instruction::NewContract(contract) = &mut tx.instruction {
if let Budget::Pay(ref mut payment) = contract.plan {
2018-05-22 20:42:04 -07:00
payment.tokens = 2; // <-- attack!
}
}
2018-05-25 15:05:37 -07:00
assert!(!tx.verify_plan());
// Also, ensure all branchs of the plan spend all tokens
2018-05-25 15:05:37 -07:00
if let Instruction::NewContract(contract) = &mut tx.instruction {
if let Budget::Pay(ref mut payment) = contract.plan {
2018-05-22 20:42:04 -07:00
payment.tokens = 0; // <-- whoops!
}
}
2018-05-25 15:05:37 -07:00
assert!(!tx.verify_plan());
}
2018-03-06 11:18:17 -08:00
}