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;
|
2018-03-07 20:55:49 -08:00
|
|
|
use chrono::prelude::*;
|
2018-03-26 21:03:26 -07:00
|
|
|
use hash::Hash;
|
2018-03-20 14:43:04 -07:00
|
|
|
use plan::{Condition, Payment, Plan};
|
2018-04-02 20:15:21 -07:00
|
|
|
use rayon::prelude::*;
|
2018-03-26 21:03:26 -07:00
|
|
|
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;
|
|
|
|
|
2018-03-10 15:55:39 -08:00
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
2018-03-26 21:07:11 -07:00
|
|
|
pub struct TransactionData {
|
2018-03-19 09:03:41 -07:00
|
|
|
pub tokens: i64,
|
2018-03-06 16:36:45 -08:00
|
|
|
pub last_id: Hash,
|
2018-03-26 21:07:11 -07:00
|
|
|
pub plan: Plan,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
pub data: TransactionData,
|
2018-03-06 11:18:17 -08:00
|
|
|
}
|
|
|
|
|
2018-03-17 13:42:50 -07:00
|
|
|
impl Transaction {
|
2018-03-29 11:20:54 -07:00
|
|
|
/// Create and sign a new Transaction. Used for unit-testing.
|
2018-03-19 09:03:41 -07:00
|
|
|
pub fn new(from_keypair: &KeyPair, to: PublicKey, tokens: i64, last_id: Hash) -> Self {
|
2018-03-10 16:11:12 -08:00
|
|
|
let from = from_keypair.pubkey();
|
2018-03-20 14:43:04 -07:00
|
|
|
let plan = Plan::Pay(Payment { tokens, to });
|
2018-03-10 15:55:39 -08:00
|
|
|
let mut tr = Transaction {
|
2018-03-07 20:55:49 -08:00
|
|
|
sig: Signature::default(),
|
2018-03-26 21:07:11 -07:00
|
|
|
data: TransactionData {
|
|
|
|
plan,
|
|
|
|
tokens,
|
|
|
|
last_id,
|
|
|
|
},
|
|
|
|
from: from,
|
2018-03-07 20:55:49 -08:00
|
|
|
};
|
|
|
|
tr.sign(from_keypair);
|
|
|
|
tr
|
|
|
|
}
|
|
|
|
|
2018-03-29 11:20:54 -07:00
|
|
|
/// Create and sign a postdated Transaction. Used for unit-testing.
|
2018-03-07 20:55:49 -08:00
|
|
|
pub fn new_on_date(
|
|
|
|
from_keypair: &KeyPair,
|
|
|
|
to: PublicKey,
|
|
|
|
dt: DateTime<Utc>,
|
2018-03-19 09:03:41 -07:00
|
|
|
tokens: i64,
|
2018-03-07 20:55:49 -08:00
|
|
|
last_id: Hash,
|
|
|
|
) -> Self {
|
2018-03-07 21:25:45 -08:00
|
|
|
let from = from_keypair.pubkey();
|
2018-03-10 21:00:27 -08:00
|
|
|
let plan = Plan::Race(
|
2018-03-20 14:43:04 -07:00
|
|
|
(Condition::Timestamp(dt), Payment { tokens, to }),
|
|
|
|
(Condition::Signature(from), Payment { tokens, to: from }),
|
2018-03-10 21:00:27 -08:00
|
|
|
);
|
2018-03-10 15:55:39 -08:00
|
|
|
let mut tr = Transaction {
|
2018-03-26 21:07:11 -07:00
|
|
|
data: TransactionData {
|
|
|
|
plan,
|
|
|
|
tokens,
|
|
|
|
last_id,
|
|
|
|
},
|
|
|
|
from: from,
|
2018-03-06 15:34:14 -08:00
|
|
|
sig: Signature::default(),
|
|
|
|
};
|
|
|
|
tr.sign(from_keypair);
|
|
|
|
tr
|
2018-03-06 11:18:17 -08:00
|
|
|
}
|
2018-03-06 11:26:39 -08:00
|
|
|
|
2018-03-06 15:34:14 -08:00
|
|
|
fn get_sign_data(&self) -> Vec<u8> {
|
2018-03-26 21:07:11 -07:00
|
|
|
serialize(&(&self.data)).unwrap()
|
2018-03-06 11:26:39 -08:00
|
|
|
}
|
2018-03-06 11:18:17 -08:00
|
|
|
|
2018-03-29 11:20:54 -07:00
|
|
|
/// Sign this transaction.
|
2018-03-07 10:05:06 -08:00
|
|
|
pub fn sign(&mut self, keypair: &KeyPair) {
|
2018-03-06 15:34:14 -08:00
|
|
|
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 {
|
|
|
|
self.data.plan.verify(self.data.tokens)
|
2018-03-06 15:34:14 -08:00
|
|
|
}
|
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-28 21:02:47 -07:00
|
|
|
/// Verify a batch of signatures.
|
|
|
|
pub fn verify_signatures(transactions: &[Transaction]) -> bool {
|
2018-03-26 21:07:11 -07:00
|
|
|
transactions.par_iter().all(|tr| tr.verify_sig())
|
2018-03-28 21:02:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify a batch of spending plans.
|
|
|
|
pub fn verify_plans(transactions: &[Transaction]) -> bool {
|
2018-03-26 21:07:11 -07:00
|
|
|
transactions.par_iter().all(|tr| tr.verify_plan())
|
2018-03-28 21:02:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify a batch of transactions.
|
|
|
|
pub fn verify_transactions(transactions: &[Transaction]) -> bool {
|
|
|
|
verify_signatures(transactions) && verify_plans(transactions)
|
|
|
|
}
|
|
|
|
|
2018-03-06 11:18:17 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use bincode::{deserialize, serialize};
|
2018-03-06 15:34:14 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_claim() {
|
2018-03-07 14:32:22 -08:00
|
|
|
let keypair = KeyPair::new();
|
2018-03-06 16:36:45 -08:00
|
|
|
let zero = Hash::default();
|
2018-03-17 13:42:50 -07:00
|
|
|
let tr0 = Transaction::new(&keypair, keypair.pubkey(), 42, zero);
|
2018-03-26 21:07:11 -07:00
|
|
|
assert!(tr0.verify_plan());
|
2018-03-06 15:34:14 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_transfer() {
|
2018-03-06 16:36:45 -08:00
|
|
|
let zero = Hash::default();
|
2018-03-07 14:32:22 -08:00
|
|
|
let keypair0 = KeyPair::new();
|
|
|
|
let keypair1 = KeyPair::new();
|
|
|
|
let pubkey1 = keypair1.pubkey();
|
2018-03-17 13:42:50 -07:00
|
|
|
let tr0 = Transaction::new(&keypair0, pubkey1, 42, zero);
|
2018-03-26 21:07:11 -07:00
|
|
|
assert!(tr0.verify_plan());
|
2018-03-06 15:34:14 -08:00
|
|
|
}
|
2018-03-06 11:18:17 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_serialize_claim() {
|
2018-03-20 14:43:04 -07:00
|
|
|
let plan = Plan::Pay(Payment {
|
2018-03-19 09:03:41 -07:00
|
|
|
tokens: 0,
|
2018-03-10 21:00:27 -08:00
|
|
|
to: Default::default(),
|
2018-03-20 14:43:04 -07:00
|
|
|
});
|
2018-03-10 15:55:39 -08:00
|
|
|
let claim0 = Transaction {
|
2018-03-26 21:07:11 -07:00
|
|
|
data: TransactionData {
|
|
|
|
plan,
|
|
|
|
tokens: 0,
|
|
|
|
last_id: Default::default(),
|
|
|
|
},
|
2018-03-10 15:55:39 -08:00
|
|
|
from: Default::default(),
|
2018-03-06 15:34:14 -08:00
|
|
|
sig: Default::default(),
|
|
|
|
};
|
2018-03-06 11:18:17 -08:00
|
|
|
let buf = serialize(&claim0).unwrap();
|
2018-03-17 13:42:50 -07:00
|
|
|
let claim1: Transaction = deserialize(&buf).unwrap();
|
2018-03-06 11:18:17 -08:00
|
|
|
assert_eq!(claim1, claim0);
|
|
|
|
}
|
2018-03-06 15:34:14 -08:00
|
|
|
|
|
|
|
#[test]
|
2018-04-11 21:11:01 -07:00
|
|
|
fn test_token_attack() {
|
2018-03-06 16:36:45 -08:00
|
|
|
let zero = Hash::default();
|
2018-03-07 14:32:22 -08:00
|
|
|
let keypair = KeyPair::new();
|
|
|
|
let pubkey = keypair.pubkey();
|
2018-03-17 13:42:50 -07:00
|
|
|
let mut tr = Transaction::new(&keypair, pubkey, 42, zero);
|
2018-04-11 21:11:01 -07:00
|
|
|
tr.data.tokens = 1_000_000; // <-- attack, part 1!
|
|
|
|
if let Plan::Pay(ref mut payment) = tr.data.plan {
|
|
|
|
payment.tokens = tr.data.tokens; // <-- attack, part 2!
|
|
|
|
};
|
|
|
|
assert!(tr.verify_plan());
|
|
|
|
assert!(!tr.verify_sig());
|
2018-03-06 15:34:14 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_hijack_attack() {
|
2018-03-07 14:32:22 -08:00
|
|
|
let keypair0 = KeyPair::new();
|
|
|
|
let keypair1 = KeyPair::new();
|
|
|
|
let thief_keypair = KeyPair::new();
|
|
|
|
let pubkey1 = keypair1.pubkey();
|
2018-03-06 16:36:45 -08:00
|
|
|
let zero = Hash::default();
|
2018-03-17 13:42:50 -07:00
|
|
|
let mut tr = Transaction::new(&keypair0, pubkey1, 42, zero);
|
2018-03-26 21:07:11 -07:00
|
|
|
if let Plan::Pay(ref mut payment) = tr.data.plan {
|
2018-03-10 21:00:27 -08:00
|
|
|
payment.to = thief_keypair.pubkey(); // <-- attack!
|
|
|
|
};
|
2018-03-26 21:07:11 -07:00
|
|
|
assert!(tr.verify_plan());
|
|
|
|
assert!(!tr.verify_sig());
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_layout() {
|
|
|
|
let tr = test_tx();
|
|
|
|
let sign_data = tr.get_sign_data();
|
|
|
|
let tx = serialize(&tr).unwrap();
|
2018-04-06 14:43:05 -07:00
|
|
|
assert_matches!(memfind(&tx, &sign_data), Some(SIGNED_DATA_OFFSET));
|
|
|
|
assert_matches!(memfind(&tx, &tr.sig), Some(SIG_OFFSET));
|
|
|
|
assert_matches!(memfind(&tx, &tr.from), Some(PUB_KEY_OFFSET));
|
2018-03-06 15:34:14 -08:00
|
|
|
}
|
2018-03-28 21:02:47 -07:00
|
|
|
|
2018-04-02 20:45:17 -07:00
|
|
|
#[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);
|
2018-03-26 21:07:11 -07:00
|
|
|
if let Plan::Pay(ref mut payment) = tr.data.plan {
|
2018-04-02 20:45:17 -07:00
|
|
|
payment.tokens = 2; // <-- attack!
|
|
|
|
}
|
2018-03-26 21:07:11 -07:00
|
|
|
assert!(!tr.verify_plan());
|
2018-04-02 20:45:17 -07:00
|
|
|
|
|
|
|
// Also, ensure all branchs of the plan spend all tokens
|
2018-03-26 21:07:11 -07:00
|
|
|
if let Plan::Pay(ref mut payment) = tr.data.plan {
|
2018-04-02 20:45:17 -07:00
|
|
|
payment.tokens = 0; // <-- whoops!
|
|
|
|
}
|
2018-03-26 21:07:11 -07:00
|
|
|
assert!(!tr.verify_plan());
|
2018-04-02 20:45:17 -07:00
|
|
|
}
|
|
|
|
|
2018-03-28 21:02:47 -07:00
|
|
|
#[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 11:18:17 -08:00
|
|
|
}
|