Files
solana/src/transaction.rs

210 lines
6.2 KiB
Rust
Raw Normal View History

2018-03-29 12:20:54 -06:00
//! The `transaction` module provides functionality for creating log transactions.
2018-03-06 12:18:17 -07:00
use bincode::serialize;
use chrono::prelude::*;
use hash::Hash;
2018-03-20 15:43:04 -06:00
use plan::{Condition, Payment, Plan};
2018-04-02 21:15:21 -06:00
use rayon::prelude::*;
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
2018-03-11 00:11:08 -07:00
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Transaction {
pub from: PublicKey,
pub plan: Plan,
2018-03-19 10:03:41 -06:00
pub tokens: i64,
pub last_id: Hash,
2018-03-06 12:18:17 -07:00
pub sig: Signature,
}
impl Transaction {
2018-03-29 12:20:54 -06:00
/// Create and sign a new Transaction. Used for unit-testing.
2018-03-19 10:03:41 -06:00
pub fn new(from_keypair: &KeyPair, to: PublicKey, tokens: i64, last_id: Hash) -> Self {
let from = from_keypair.pubkey();
2018-03-20 15:43:04 -06:00
let plan = Plan::Pay(Payment { tokens, to });
let mut tr = Transaction {
from,
plan,
2018-03-19 10:03:41 -06:00
tokens,
last_id,
sig: Signature::default(),
};
tr.sign(from_keypair);
tr
}
2018-03-29 12:20:54 -06: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 10:03:41 -06:00
tokens: i64,
last_id: Hash,
) -> Self {
let from = from_keypair.pubkey();
let plan = Plan::Race(
2018-03-20 15:43:04 -06:00
(Condition::Timestamp(dt), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }),
);
let mut tr = Transaction {
from,
plan,
2018-03-19 10:03:41 -06:00
tokens,
2018-03-06 12:18:17 -07:00
last_id,
sig: Signature::default(),
};
tr.sign(from_keypair);
tr
2018-03-06 12:18:17 -07:00
}
fn get_sign_data(&self) -> Vec<u8> {
serialize(&(&self.plan, &self.tokens, &self.last_id)).unwrap()
}
2018-03-06 12:18:17 -07:00
2018-03-29 12:20:54 -06: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 12:18:17 -07:00
2018-03-29 12:20:54 -06:00
/// Verify this transaction's signature and its spending plan.
pub fn verify(&self) -> bool {
2018-03-19 10:03:41 -06:00
self.sig.verify(&self.from, &self.get_sign_data()) && self.plan.verify(self.tokens)
}
2018-03-06 12:18:17 -07:00
}
/// Verify a batch of signatures.
pub fn verify_signatures(transactions: &[Transaction]) -> bool {
transactions.par_iter().all(|tr| tr.verify())
}
/// Verify a batch of spending plans.
pub fn verify_plans(transactions: &[Transaction]) -> bool {
transactions.par_iter().all(|tr| tr.plan.verify(tr.tokens))
}
/// Verify a batch of transactions.
pub fn verify_transactions(transactions: &[Transaction]) -> bool {
verify_signatures(transactions) && verify_plans(transactions)
}
2018-03-06 12:18:17 -07:00
#[cfg(test)]
mod tests {
use super::*;
use bincode::{deserialize, serialize};
#[test]
fn test_claim() {
let keypair = KeyPair::new();
let zero = Hash::default();
let tr0 = Transaction::new(&keypair, keypair.pubkey(), 42, zero);
assert!(tr0.verify());
}
#[test]
fn test_transfer() {
let zero = Hash::default();
let keypair0 = KeyPair::new();
let keypair1 = KeyPair::new();
let pubkey1 = keypair1.pubkey();
let tr0 = Transaction::new(&keypair0, pubkey1, 42, zero);
assert!(tr0.verify());
}
2018-03-06 12:18:17 -07:00
#[test]
fn test_serialize_claim() {
2018-03-20 15:43:04 -06:00
let plan = Plan::Pay(Payment {
2018-03-19 10:03:41 -06:00
tokens: 0,
to: Default::default(),
2018-03-20 15:43:04 -06:00
});
let claim0 = Transaction {
from: Default::default(),
plan,
2018-03-19 10:03:41 -06:00
tokens: 0,
last_id: Default::default(),
sig: Default::default(),
};
2018-03-06 12:18:17 -07:00
let buf = serialize(&claim0).unwrap();
let claim1: Transaction = deserialize(&buf).unwrap();
2018-03-06 12:18:17 -07:00
assert_eq!(claim1, claim0);
}
#[test]
fn test_bad_event_signature() {
let zero = Hash::default();
let keypair = KeyPair::new();
let pubkey = keypair.pubkey();
let mut tr = Transaction::new(&keypair, pubkey, 42, zero);
tr.sign(&keypair);
2018-03-19 10:03:41 -06:00
tr.tokens = 1_000_000; // <-- attack!
assert!(!tr.verify());
}
#[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 tr = Transaction::new(&keypair0, pubkey1, 42, zero);
tr.sign(&keypair0);
2018-03-20 15:43:04 -06:00
if let Plan::Pay(ref mut payment) = tr.plan {
payment.to = thief_keypair.pubkey(); // <-- attack!
};
assert!(!tr.verify());
}
#[test]
fn test_overspend_attack() {
let keypair0 = KeyPair::new();
let keypair1 = KeyPair::new();
let zero = Hash::default();
let mut tr = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero);
if let Plan::Pay(ref mut payment) = tr.plan {
payment.tokens = 2; // <-- attack!
}
assert!(!tr.verify());
// Also, ensure all branchs of the plan spend all tokens
if let Plan::Pay(ref mut payment) = tr.plan {
payment.tokens = 0; // <-- whoops!
}
assert!(!tr.verify());
}
#[test]
fn test_verify_transactions() {
let alice_keypair = KeyPair::new();
let bob_pubkey = KeyPair::new().pubkey();
let carol_pubkey = KeyPair::new().pubkey();
let last_id = Hash::default();
let tr0 = Transaction::new(&alice_keypair, bob_pubkey, 1, last_id);
let tr1 = Transaction::new(&alice_keypair, carol_pubkey, 1, last_id);
let transactions = vec![tr0, tr1];
assert!(verify_transactions(&transactions));
}
}
#[cfg(all(feature = "unstable", test))]
mod bench {
extern crate test;
use self::test::Bencher;
use transaction::*;
#[bench]
fn verify_signatures_bench(bencher: &mut Bencher) {
let alice_keypair = KeyPair::new();
let last_id = Hash::default();
let transactions: Vec<_> = (0..64)
.into_par_iter()
.map(|_| {
let rando_pubkey = KeyPair::new().pubkey();
Transaction::new(&alice_keypair, rando_pubkey, 1, last_id)
})
.collect();
bencher.iter(|| {
assert!(verify_signatures(&transactions));
});
}
2018-03-06 12:18:17 -07:00
}