danicoin/tests/UnitTests/TransactionApi.cpp
2016-01-18 15:33:29 +00:00

363 lines
10 KiB
C++

// Copyright (c) 2011-2016 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "gtest/gtest.h"
#include <numeric>
#include <random>
#include "CryptoNoteCore/TransactionApi.h"
#include "CryptoNoteCore/CryptoNoteFormatUtils.h" // TODO: delete
#include "CryptoNoteCore/Account.h"
#include "crypto/crypto.h"
#include "TransactionApiHelpers.h"
using namespace CryptoNote;
namespace {
template <size_t size>
void fillRandomBytes(std::array<uint8_t, size>& data) {
for (size_t i = 0; i < size; ++i) {
data[i] = std::rand() % std::numeric_limits<uint8_t>::max();
}
}
template <typename Array>
Array randomArray() {
Array a;
fillRandomBytes(a);
return a;
}
void derivePublicKey(const AccountKeys& reciever, const Crypto::PublicKey& srcTxKey, size_t outputIndex, PublicKey& ephemeralKey) {
Crypto::KeyDerivation derivation;
Crypto::generate_key_derivation(srcTxKey, reinterpret_cast<const Crypto::SecretKey&>(reciever.viewSecretKey), derivation);
Crypto::derive_public_key(derivation, outputIndex,
reinterpret_cast<const Crypto::PublicKey&>(reciever.address.spendPublicKey),
reinterpret_cast<Crypto::PublicKey&>(ephemeralKey));
}
std::unique_ptr<ITransaction> reloadedTx(const std::unique_ptr<ITransaction>& tx) {
auto txBlob = tx->getTransactionData();
return createTransaction(txBlob);
}
void checkTxReload(const std::unique_ptr<ITransaction>& tx) {
auto txBlob = tx->getTransactionData();
auto tx2 = createTransaction(txBlob);
ASSERT_EQ(tx2->getTransactionData(), txBlob);
}
class TransactionApi : public testing::Test {
protected:
virtual void SetUp() override {
sender = generateAccountKeys();
tx = createTransaction();
txHash = tx->getTransactionHash();
}
TransactionTypes::InputKeyInfo createInputInfo(uint64_t amount) {
TransactionTypes::InputKeyInfo info;
CryptoNote::KeyPair srcTxKeys = CryptoNote::generateKeyPair();
PublicKey targetKey;
derivePublicKey(sender, srcTxKeys.publicKey, 5, targetKey);
TransactionTypes::GlobalOutput gout = { targetKey, 0 };
info.amount = 1000;
info.outputs.push_back(gout);
info.realOutput.transactionIndex = 0;
info.realOutput.outputInTransaction = 5;
info.realOutput.transactionPublicKey = reinterpret_cast<const PublicKey&>(srcTxKeys.publicKey);
return info;
}
void checkHashChanged() {
auto txNewHash = tx->getTransactionHash();
EXPECT_NE(txHash, txNewHash);
txHash = txNewHash;
}
void checkHashUnchanged() {
EXPECT_EQ(txHash, tx->getTransactionHash());
}
AccountKeys sender;
std::unique_ptr<ITransaction> tx;
Hash txHash;
};
}
TEST_F(TransactionApi, createEmptyReload) {
auto hash = tx->getTransactionHash();
auto pk = tx->getTransactionPublicKey();
checkTxReload(tx);
// transaction key should not change on reload
auto reloaded = reloadedTx(tx);
ASSERT_EQ(pk, reloaded->getTransactionPublicKey());
ASSERT_EQ(hash, reloaded->getTransactionHash());
}
TEST_F(TransactionApi, addAndSignInput) {
ASSERT_EQ(0, tx->getInputCount());
ASSERT_EQ(0, tx->getInputTotalAmount());
TransactionTypes::InputKeyInfo info = createInputInfo(1000);
KeyPair ephKeys;
size_t index = tx->addInput(sender, info, ephKeys);
ASSERT_EQ(0, index);
ASSERT_EQ(1, tx->getInputCount());
ASSERT_EQ(1000, tx->getInputTotalAmount());
ASSERT_EQ(TransactionTypes::InputType::Key, tx->getInputType(index));
ASSERT_EQ(1, tx->getRequiredSignaturesCount(index));
ASSERT_TRUE(tx->validateInputs());
ASSERT_FALSE(tx->validateSignatures()); // signature not present
tx->signInputKey(index, info, ephKeys);
ASSERT_TRUE(tx->validateSignatures()); // now it's ok
auto txBlob = tx->getTransactionData();
ASSERT_FALSE(txBlob.empty());
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
}
TEST_F(TransactionApi, addAndSignInputMsig) {
MultisignatureInput inputMsig;
inputMsig.amount = 1000;
inputMsig.outputIndex = 0;
inputMsig.signatureCount = 3;
auto index = tx->addInput(inputMsig);
ASSERT_EQ(0, index);
ASSERT_EQ(1, tx->getInputCount());
ASSERT_EQ(1000, tx->getInputTotalAmount());
ASSERT_EQ(TransactionTypes::InputType::Multisignature, tx->getInputType(index));
ASSERT_EQ(3, tx->getRequiredSignaturesCount(index));
KeyPair kp1;
Crypto::generate_keys(kp1.publicKey, kp1.secretKey );
auto srcTxKey = kp1.publicKey;
AccountKeys accounts[] = { generateAccountKeys(), generateAccountKeys(), generateAccountKeys() };
tx->signInputMultisignature(index, srcTxKey, 0, accounts[0]);
ASSERT_FALSE(tx->validateSignatures());
tx->signInputMultisignature(index, srcTxKey, 0, accounts[1]);
tx->signInputMultisignature(index, srcTxKey, 0, accounts[2]);
ASSERT_TRUE(tx->validateSignatures());
auto txBlob = tx->getTransactionData();
ASSERT_FALSE(txBlob.empty());
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
}
TEST_F(TransactionApi, addOutputKey) {
ASSERT_EQ(0, tx->getOutputCount());
ASSERT_EQ(0, tx->getOutputTotalAmount());
size_t index = tx->addOutput(1000, sender.address);
ASSERT_EQ(0, index);
ASSERT_EQ(1, tx->getOutputCount());
ASSERT_EQ(1000, tx->getOutputTotalAmount());
ASSERT_EQ(TransactionTypes::OutputType::Key, tx->getOutputType(index));
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
}
TEST_F(TransactionApi, addOutputMsig) {
ASSERT_EQ(0, tx->getOutputCount());
ASSERT_EQ(0, tx->getOutputTotalAmount());
AccountKeys accounts[] = { generateAccountKeys(), generateAccountKeys(), generateAccountKeys() };
std::vector<AccountPublicAddress> targets;
for (size_t i = 0; i < sizeof(accounts)/sizeof(accounts[0]); ++i)
targets.push_back(accounts[i].address);
size_t index = tx->addOutput(1000, targets, 2);
ASSERT_EQ(0, index);
ASSERT_EQ(1, tx->getOutputCount());
ASSERT_EQ(1000, tx->getOutputTotalAmount());
ASSERT_EQ(TransactionTypes::OutputType::Multisignature, tx->getOutputType(index));
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
}
TEST_F(TransactionApi, secretKey) {
tx->addOutput(1000, sender.address);
ASSERT_EQ(1000, tx->getOutputTotalAmount());
// reloaded transaction does not have secret key, cannot add outputs
auto tx2 = reloadedTx(tx);
ASSERT_ANY_THROW(tx2->addOutput(1000, sender.address));
// take secret key from first transaction and add to second (reloaded)
SecretKey txSecretKey;
ASSERT_TRUE(tx->getTransactionSecretKey(txSecretKey));
KeyPair kp1;
Crypto::generate_keys(kp1.publicKey, kp1.secretKey);
SecretKey sk = kp1.secretKey;
ASSERT_ANY_THROW(tx2->setTransactionSecretKey(sk)); // unrelated secret key should not be accepted
tx2->setTransactionSecretKey(txSecretKey);
// adding output should succeed
tx2->addOutput(500, sender.address);
ASSERT_EQ(1500, tx2->getOutputTotalAmount());
}
TEST_F(TransactionApi, prefixHash) {
auto hash = tx->getTransactionPrefixHash();
tx->addOutput(1000, sender.address);
// transaction hash should change
ASSERT_NE(hash, tx->getTransactionPrefixHash());
hash = tx->getTransactionPrefixHash();
// prefix hash should not change on reload
ASSERT_EQ(hash, reloadedTx(tx)->getTransactionPrefixHash());
}
TEST_F(TransactionApi, findOutputs) {
AccountKeys accounts[] = { generateAccountKeys(), generateAccountKeys(), generateAccountKeys() };
tx->addOutput(1111, accounts[0].address);
tx->addOutput(2222, accounts[1].address);
tx->addOutput(3333, accounts[2].address);
std::vector<uint32_t> outs;
uint64_t amount = 0;
tx->findOutputsToAccount(accounts[2].address, accounts[2].viewSecretKey, outs, amount);
ASSERT_EQ(1, outs.size());
ASSERT_EQ(2, outs[0]);
ASSERT_EQ(3333, amount);
}
TEST_F(TransactionApi, setGetPaymentId) {
Hash paymentId = Crypto::rand<Hash>();
ASSERT_FALSE(tx->getPaymentId(paymentId));
tx->setPaymentId(paymentId);
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
Hash paymentId2;
ASSERT_TRUE(tx->getPaymentId(paymentId2));
ASSERT_EQ(paymentId, paymentId2);
auto tx2 = reloadedTx(tx);
Hash paymentId3;
ASSERT_TRUE(tx->getPaymentId(paymentId3));
ASSERT_EQ(paymentId, paymentId3);
}
TEST_F(TransactionApi, setExtraNonce) {
BinaryArray extraNonce = Common::asBinaryArray("Hello, world"); // just a sequence of bytes
BinaryArray s;
ASSERT_FALSE(tx->getExtraNonce(s));
tx->setExtraNonce(extraNonce);
ASSERT_TRUE(tx->getExtraNonce(s));
ASSERT_EQ(extraNonce, s);
s.clear();
ASSERT_TRUE(reloadedTx(tx)->getExtraNonce(s));
ASSERT_EQ(extraNonce, s);
}
TEST_F(TransactionApi, appendExtra) {
BinaryArray ba;
ba.resize(100);
std::iota(ba.begin(), ba.end(), 0);
auto tx = createTransaction();
auto extra = tx->getExtra();
ASSERT_FALSE(extra.empty());
tx->appendExtra(ba);
auto newExtra = tx->getExtra();
ASSERT_EQ(ba.size() + extra.size(), newExtra.size());
ASSERT_EQ(0, memcmp(newExtra.data() + extra.size(), ba.data(), ba.size()));
}
TEST_F(TransactionApi, doubleSpendInTransactionKey) {
TransactionTypes::InputKeyInfo info = createInputInfo(1000);
KeyPair ephKeys;
tx->addInput(sender, info, ephKeys);
ASSERT_TRUE(tx->validateInputs());
// now, add the same output again
tx->addInput(sender, info, ephKeys);
ASSERT_FALSE(tx->validateInputs());
}
TEST_F(TransactionApi, doubleSpendInTransactionMultisignature) {
MultisignatureInput inputMsig = { 1000, 0, 2 };
tx->addInput(inputMsig);
ASSERT_TRUE(tx->validateInputs());
tx->addInput(inputMsig);
ASSERT_FALSE(tx->validateInputs());
}
TEST_F(TransactionApi, unableToModifySignedTransaction) {
MultisignatureInput inputMsig;
inputMsig.amount = 1000;
inputMsig.outputIndex = 0;
inputMsig.signatureCount = 2;
auto index = tx->addInput(inputMsig);
KeyPair kp1;
Crypto::generate_keys(kp1.publicKey, kp1.secretKey);
auto srcTxKey = kp1.publicKey;
tx->signInputMultisignature(index, srcTxKey, 0, generateAccountKeys());
// from now on, we cannot modify transaction prefix
ASSERT_ANY_THROW(tx->addInput(inputMsig));
ASSERT_ANY_THROW(tx->addOutput(500, sender.address));
Hash paymentId;
ASSERT_ANY_THROW(tx->setPaymentId(paymentId));
ASSERT_ANY_THROW(tx->setExtraNonce(Common::asBinaryArray("smth")));
// but can add more signatures
tx->signInputMultisignature(index, srcTxKey, 0, generateAccountKeys());
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
}