2015-05-27 12:08:46 +00:00
|
|
|
// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers
|
2015-04-06 16:13:07 +00:00
|
|
|
//
|
|
|
|
// This file is part of Bytecoin.
|
|
|
|
//
|
|
|
|
// Bytecoin is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// Bytecoin is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
|
|
// along with Bytecoin. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#include "gtest/gtest.h"
|
2015-07-15 12:23:00 +00:00
|
|
|
|
|
|
|
#include <numeric>
|
2015-04-06 16:13:07 +00:00
|
|
|
#include <random>
|
2015-07-15 12:23:00 +00:00
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
#include "CryptoNoteCore/TransactionApi.h"
|
|
|
|
#include "CryptoNoteCore/CryptoNoteFormatUtils.h" // TODO: delete
|
|
|
|
#include "CryptoNoteCore/Account.h"
|
2015-04-06 16:13:07 +00:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
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));
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
2015-05-27 12:08:46 +00:00
|
|
|
txHash = tx->getTransactionHash();
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TransactionTypes::InputKeyInfo createInputInfo(uint64_t amount) {
|
|
|
|
TransactionTypes::InputKeyInfo info;
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
CryptoNote::KeyPair srcTxKeys = CryptoNote::generateKeyPair();
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
PublicKey targetKey;
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
derivePublicKey(sender, srcTxKeys.publicKey, 5, targetKey);
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
TransactionTypes::GlobalOutput gout = { targetKey, 0 };
|
|
|
|
|
|
|
|
info.amount = 1000;
|
|
|
|
info.outputs.push_back(gout);
|
|
|
|
|
|
|
|
info.realOutput.transactionIndex = 0;
|
|
|
|
info.realOutput.outputInTransaction = 5;
|
2015-07-30 15:22:07 +00:00
|
|
|
info.realOutput.transactionPublicKey = reinterpret_cast<const PublicKey&>(srcTxKeys.publicKey);
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
2015-05-27 12:08:46 +00:00
|
|
|
void checkHashChanged() {
|
|
|
|
auto txNewHash = tx->getTransactionHash();
|
|
|
|
EXPECT_NE(txHash, txNewHash);
|
|
|
|
txHash = txNewHash;
|
|
|
|
}
|
|
|
|
|
|
|
|
void checkHashUnchanged() {
|
|
|
|
EXPECT_EQ(txHash, tx->getTransactionHash());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
AccountKeys sender;
|
|
|
|
std::unique_ptr<ITransaction> tx;
|
2015-05-27 12:08:46 +00:00
|
|
|
Hash txHash;
|
2015-04-06 16:13:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TransactionApi, createEmptyReload) {
|
2015-05-27 12:08:46 +00:00
|
|
|
auto hash = tx->getTransactionHash();
|
2015-04-06 16:13:07 +00:00
|
|
|
auto pk = tx->getTransactionPublicKey();
|
|
|
|
checkTxReload(tx);
|
|
|
|
// transaction key should not change on reload
|
2015-05-27 12:08:46 +00:00
|
|
|
auto reloaded = reloadedTx(tx);
|
|
|
|
ASSERT_EQ(pk, reloaded->getTransactionPublicKey());
|
|
|
|
ASSERT_EQ(hash, reloaded->getTransactionHash());
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TransactionApi, addAndSignInput) {
|
|
|
|
ASSERT_EQ(0, tx->getInputCount());
|
|
|
|
ASSERT_EQ(0, tx->getInputTotalAmount());
|
|
|
|
|
|
|
|
TransactionTypes::InputKeyInfo info = createInputInfo(1000);
|
2015-07-30 15:22:07 +00:00
|
|
|
KeyPair ephKeys;
|
2015-04-06 16:13:07 +00:00
|
|
|
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());
|
2015-05-27 12:08:46 +00:00
|
|
|
|
|
|
|
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TransactionApi, addAndSignInputMsig) {
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
MultisignatureInput inputMsig;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
inputMsig.amount = 1000;
|
|
|
|
inputMsig.outputIndex = 0;
|
2015-07-30 15:22:07 +00:00
|
|
|
inputMsig.signatureCount = 3;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
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));
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
KeyPair kp1;
|
|
|
|
Crypto::generate_keys(kp1.publicKey, kp1.secretKey );
|
|
|
|
|
|
|
|
auto srcTxKey = kp1.publicKey;
|
2015-04-06 16:13:07 +00:00
|
|
|
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());
|
2015-05-27 12:08:46 +00:00
|
|
|
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
2015-05-27 12:08:46 +00:00
|
|
|
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TransactionApi, addOutputMsig) {
|
|
|
|
ASSERT_EQ(0, tx->getOutputCount());
|
|
|
|
ASSERT_EQ(0, tx->getOutputTotalAmount());
|
|
|
|
|
|
|
|
AccountKeys accounts[] = { generateAccountKeys(), generateAccountKeys(), generateAccountKeys() };
|
2015-07-30 15:22:07 +00:00
|
|
|
std::vector<AccountPublicAddress> targets;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
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));
|
2015-05-27 12:08:46 +00:00
|
|
|
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TransactionApi, secretKey) {
|
2015-07-30 15:22:07 +00:00
|
|
|
tx->addOutput(1000, sender.address);
|
2015-04-06 16:13:07 +00:00
|
|
|
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));
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
KeyPair kp1;
|
|
|
|
Crypto::generate_keys(kp1.publicKey, kp1.secretKey);
|
|
|
|
SecretKey sk = kp1.secretKey;
|
2015-04-06 16:13:07 +00:00
|
|
|
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) {
|
2015-07-30 15:22:07 +00:00
|
|
|
Hash paymentId = Crypto::rand<Hash>();
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
ASSERT_FALSE(tx->getPaymentId(paymentId));
|
|
|
|
|
|
|
|
tx->setPaymentId(paymentId);
|
|
|
|
|
2015-05-27 12:08:46 +00:00
|
|
|
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
|
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
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) {
|
2015-07-30 15:22:07 +00:00
|
|
|
BinaryArray extraNonce = Common::asBinaryArray("Hello, world"); // just a sequence of bytes
|
|
|
|
BinaryArray s;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-07-15 12:23:00 +00:00
|
|
|
TEST_F(TransactionApi, appendExtra) {
|
2015-07-30 15:22:07 +00:00
|
|
|
BinaryArray ba;
|
2015-07-15 12:23:00 +00:00
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
ba.resize(100);
|
|
|
|
std::iota(ba.begin(), ba.end(), 0);
|
2015-07-15 12:23:00 +00:00
|
|
|
|
|
|
|
auto tx = createTransaction();
|
|
|
|
|
|
|
|
auto extra = tx->getExtra();
|
|
|
|
|
|
|
|
ASSERT_FALSE(extra.empty());
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
tx->appendExtra(ba);
|
2015-07-15 12:23:00 +00:00
|
|
|
|
|
|
|
auto newExtra = tx->getExtra();
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
ASSERT_EQ(ba.size() + extra.size(), newExtra.size());
|
|
|
|
ASSERT_EQ(0, memcmp(newExtra.data() + extra.size(), ba.data(), ba.size()));
|
2015-07-15 12:23:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
TEST_F(TransactionApi, doubleSpendInTransactionKey) {
|
|
|
|
TransactionTypes::InputKeyInfo info = createInputInfo(1000);
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
KeyPair ephKeys;
|
2015-04-06 16:13:07 +00:00
|
|
|
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) {
|
2015-07-30 15:22:07 +00:00
|
|
|
MultisignatureInput inputMsig = { 1000, 0, 2 };
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
tx->addInput(inputMsig);
|
|
|
|
ASSERT_TRUE(tx->validateInputs());
|
|
|
|
tx->addInput(inputMsig);
|
|
|
|
ASSERT_FALSE(tx->validateInputs());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionApi, unableToModifySignedTransaction) {
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
MultisignatureInput inputMsig;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
inputMsig.amount = 1000;
|
|
|
|
inputMsig.outputIndex = 0;
|
2015-07-30 15:22:07 +00:00
|
|
|
inputMsig.signatureCount = 2;
|
2015-04-06 16:13:07 +00:00
|
|
|
auto index = tx->addInput(inputMsig);
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
KeyPair kp1;
|
|
|
|
Crypto::generate_keys(kp1.publicKey, kp1.secretKey);
|
|
|
|
|
|
|
|
auto srcTxKey = kp1.publicKey;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
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));
|
2015-05-27 12:08:46 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
Hash paymentId;
|
|
|
|
ASSERT_ANY_THROW(tx->setPaymentId(paymentId));
|
2015-07-30 15:22:07 +00:00
|
|
|
ASSERT_ANY_THROW(tx->setExtraNonce(Common::asBinaryArray("smth")));
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
// but can add more signatures
|
|
|
|
tx->signInputMultisignature(index, srcTxKey, 0, generateAccountKeys());
|
2015-05-27 12:08:46 +00:00
|
|
|
|
|
|
|
EXPECT_NO_FATAL_FAILURE(checkHashChanged());
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|