Merge pull request #1515

ada7c7da portable serializer: tests added (kenshi84)
f390a0e2 portable serializer: make signerd/unsigned tx portable, ignore archive version checking (kenshi84)
This commit is contained in:
Riccardo Spagni 2017-01-08 16:41:44 -08:00
commit ba9744d400
No known key found for this signature in database
GPG key ID: 55432DF31CCD4FCD
12 changed files with 641 additions and 61 deletions

View file

@ -321,6 +321,8 @@ portable_binary_iarchive::init(unsigned int flags){
boost::archive::library_version_type input_library_version; boost::archive::library_version_type input_library_version;
* this >> input_library_version; * this >> input_library_version;
// ignore archive version checking
/*
// extra little .t is to get around borland quirk // extra little .t is to get around borland quirk
if(boost::archive::BOOST_ARCHIVE_VERSION() < input_library_version) if(boost::archive::BOOST_ARCHIVE_VERSION() < input_library_version)
boost::serialization::throw_exception( boost::serialization::throw_exception(
@ -328,6 +330,7 @@ portable_binary_iarchive::init(unsigned int flags){
boost::archive::archive_exception::unsupported_version boost::archive::archive_exception::unsupported_version
) )
); );
*/
#if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3205)) #if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3205))
this->set_library_version(input_library_version); this->set_library_version(input_library_version);

View file

@ -277,10 +277,14 @@ portable_binary_oarchive::init(unsigned int flags) {
boost::archive::BOOST_ARCHIVE_SIGNATURE() boost::archive::BOOST_ARCHIVE_SIGNATURE()
); );
* this << file_signature; * this << file_signature;
// ignore archive version checking
const boost::archive::library_version_type v{};
/*
// write library version // write library version
const boost::archive::library_version_type v( const boost::archive::library_version_type v(
boost::archive::BOOST_ARCHIVE_VERSION() boost::archive::BOOST_ARCHIVE_VERSION()
); );
*/
* this << v; * this << v;
} }
save(static_cast<unsigned char>(m_flags >> CHAR_BIT)); save(static_cast<unsigned char>(m_flags >> CHAR_BIT));

View file

@ -36,7 +36,8 @@
#include "crypto/crypto.h" #include "crypto/crypto.h"
#include "crypto/hash.h" #include "crypto/hash.h"
#include "ringct/rctOps.h" #include "ringct/rctOps.h"
#include <boost/serialization/vector.hpp>
#include <boost/serialization/utility.hpp>
namespace cryptonote namespace cryptonote
{ {
@ -62,16 +63,6 @@ namespace cryptonote
rct::key mask; //ringct amount mask rct::key mask; //ringct amount mask
void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); }
BEGIN_SERIALIZE_OBJECT()
FIELD(outputs)
VARINT_FIELD(real_output)
FIELD(real_out_tx_key)
VARINT_FIELD(real_output_in_tx_index)
VARINT_FIELD(amount)
FIELD(rct)
FIELD(mask)
END_SERIALIZE()
}; };
struct tx_destination_entry struct tx_destination_entry
@ -261,3 +252,23 @@ namespace cryptonote
specific_type& variable_name = boost::get<specific_type>(variant_var); specific_type& variable_name = boost::get<specific_type>(variant_var);
} }
BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0)
namespace boost
{
namespace serialization
{
template <class Archive>
inline void serialize(Archive &a, cryptonote::tx_source_entry &x, const boost::serialization::version_type ver)
{
a & x.outputs;
a & x.real_output;
a & x.real_out_tx_key;
a & x.real_output_in_tx_index;
a & x.amount;
a & x.rct;
a & x.mask;
}
}
}

View file

@ -76,8 +76,8 @@ using namespace cryptonote;
// arbitrary, used to generate different hashes from the same input // arbitrary, used to generate different hashes from the same input
#define CHACHA8_KEY_TAIL 0x8c #define CHACHA8_KEY_TAIL 0x8c
#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\002" #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003"
#define SIGNED_TX_PREFIX "Monero signed tx set\002" #define SIGNED_TX_PREFIX "Monero signed tx set\003"
#define RECENT_OUTPUT_RATIO (0.25) // 25% of outputs are from the recent zone #define RECENT_OUTPUT_RATIO (0.25) // 25% of outputs are from the recent zone
#define RECENT_OUTPUT_ZONE (5 * 86400) // last 5 days are the recent zone #define RECENT_OUTPUT_ZONE (5 * 86400) // last 5 days are the recent zone
@ -2955,14 +2955,19 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
for (auto &tx: ptx_vector) for (auto &tx: ptx_vector)
txs.txes.push_back(tx.construction_data); txs.txes.push_back(tx.construction_data);
txs.transfers = m_transfers; txs.transfers = m_transfers;
std::string s = obj_to_json_str(txs); // save as binary
if (s.empty()) std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
try
{
ar << txs;
}
catch (...)
{
return false; return false;
LOG_PRINT_L2("Saving unsigned tx data: " << s); }
// save as binary as there's no implementation of loading a json_archive LOG_PRINT_L2("Saving unsigned tx data: " << oss.str());
if (!::serialization::dump_binary(txs, s)) return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + oss.str());
return false;
return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + s);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func) bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func)
@ -2987,7 +2992,14 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
return false; return false;
} }
unsigned_tx_set exported_txs; unsigned_tx_set exported_txs;
if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), exported_txs)) s = s.substr(magiclen);
try
{
std::istringstream iss(s);
boost::archive::portable_binary_iarchive ar(iss);
ar >> exported_txs;
}
catch (...)
{ {
LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
return false; return false;
@ -3060,14 +3072,19 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
signed_txes.key_images[i] = m_transfers[i].m_key_image; signed_txes.key_images[i] = m_transfers[i].m_key_image;
} }
s = obj_to_json_str(signed_txes); // save as binary
if (s.empty()) std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
try
{
ar << signed_txes;
}
catch(...)
{
return false; return false;
LOG_PRINT_L2("Saving signed tx data: " << s); }
// save as binary as there's no implementation of loading a json_archive LOG_PRINT_L2("Saving signed tx data: " << oss.str());
if (!::serialization::dump_binary(signed_txes, s)) return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + oss.str());
return false;
return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + s);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func) bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func)
@ -3093,7 +3110,14 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
LOG_PRINT_L0("Bad magic from " << signed_filename); LOG_PRINT_L0("Bad magic from " << signed_filename);
return false; return false;
} }
if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), signed_txs)) s = s.substr(magiclen);
try
{
std::istringstream iss(s);
boost::archive::portable_binary_iarchive ar(iss);
ar >> signed_txs;
}
catch (...)
{ {
LOG_PRINT_L0("Failed to parse data from " << signed_filename); LOG_PRINT_L0("Failed to parse data from " << signed_filename);
return false; return false;

View file

@ -58,6 +58,8 @@
#include <iostream> #include <iostream>
#define WALLET_RCP_CONNECTION_TIMEOUT 200000 #define WALLET_RCP_CONNECTION_TIMEOUT 200000
class Serialization_portability_wallet_Test;
namespace tools namespace tools
{ {
class i_wallet2_callback class i_wallet2_callback
@ -86,6 +88,7 @@ namespace tools
class wallet2 class wallet2
{ {
friend class ::Serialization_portability_wallet_Test;
public: public:
enum RefreshType { enum RefreshType {
RefreshFull, RefreshFull,
@ -201,17 +204,6 @@ namespace tools
uint64_t unlock_time; uint64_t unlock_time;
bool use_rct; bool use_rct;
std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change
BEGIN_SERIALIZE_OBJECT()
FIELD(sources)
FIELD(change_dts)
FIELD(splitted_dsts)
FIELD(selected_transfers)
FIELD(extra)
VARINT_FIELD(unlock_time)
FIELD(use_rct)
FIELD(dests)
END_SERIALIZE()
}; };
typedef std::vector<transfer_details> transfer_container; typedef std::vector<transfer_details> transfer_container;
@ -232,39 +224,18 @@ namespace tools
std::vector<cryptonote::tx_destination_entry> dests; std::vector<cryptonote::tx_destination_entry> dests;
tx_construction_data construction_data; tx_construction_data construction_data;
BEGIN_SERIALIZE_OBJECT()
FIELD(tx)
VARINT_FIELD(dust)
VARINT_FIELD(fee)
FIELD(dust_added_to_fee)
FIELD(change_dts)
FIELD(selected_transfers)
FIELD(key_images)
FIELD(tx_key)
FIELD(dests)
FIELD(construction_data)
END_SERIALIZE()
}; };
struct unsigned_tx_set struct unsigned_tx_set
{ {
std::vector<tx_construction_data> txes; std::vector<tx_construction_data> txes;
wallet2::transfer_container transfers; wallet2::transfer_container transfers;
BEGIN_SERIALIZE_OBJECT()
FIELD(txes)
FIELD(transfers)
END_SERIALIZE()
}; };
struct signed_tx_set struct signed_tx_set
{ {
std::vector<pending_tx> ptx; std::vector<pending_tx> ptx;
std::vector<crypto::key_image> key_images; std::vector<crypto::key_image> key_images;
BEGIN_SERIALIZE_OBJECT()
FIELD(ptx)
FIELD(key_images)
END_SERIALIZE()
}; };
struct keys_file_data struct keys_file_data
@ -668,6 +639,10 @@ BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 3) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 3)
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 16) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 16)
BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 0)
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 0)
namespace boost namespace boost
{ {
@ -865,6 +840,48 @@ namespace boost
a & x.m_payment_id; a & x.m_payment_id;
a & x.m_description; a & x.m_description;
} }
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver)
{
a & x.txes;
a & x.transfers;
}
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::signed_tx_set &x, const boost::serialization::version_type ver)
{
a & x.ptx;
a & x.key_images;
}
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::tx_construction_data &x, const boost::serialization::version_type ver)
{
a & x.sources;
a & x.change_dts;
a & x.splitted_dsts;
a & x.selected_transfers;
a & x.extra;
a & x.unlock_time;
a & x.use_rct;
a & x.dests;
}
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver)
{
a & x.tx;
a & x.dust;
a & x.fee;
a & x.dust_added_to_fee;
a & x.change_dts;
a & x.selected_transfers;
a & x.key_images;
a & x.tx_key;
a & x.dests;
a & x.construction_data;
}
} }
} }

View file

@ -62,6 +62,14 @@ else ()
FOLDER "${folder}") FOLDER "${folder}")
endif () endif ()
file(COPY
data/wallet_9svHk1.keys
data/wallet_9svHk1
data/outputs
data/unsigned_monero_tx
data/signed_monero_tx
DESTINATION data)
add_subdirectory(core_tests) add_subdirectory(core_tests)
add_subdirectory(crypto) add_subdirectory(crypto)
add_subdirectory(functional_tests) add_subdirectory(functional_tests)

BIN
tests/data/outputs Normal file

Binary file not shown.

BIN
tests/data/signed_monero_tx Normal file

Binary file not shown.

Binary file not shown.

BIN
tests/data/wallet_9svHk1 Normal file

Binary file not shown.

Binary file not shown.

View file

@ -34,6 +34,7 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/archive/portable_binary_iarchive.hpp>
#include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_basic.h"
#include "cryptonote_core/cryptonote_basic_impl.h" #include "cryptonote_core/cryptonote_basic_impl.h"
#include "ringct/rctSigs.h" #include "ringct/rctSigs.h"
@ -44,6 +45,7 @@
#include "serialization/variant.h" #include "serialization/variant.h"
#include "serialization/vector.h" #include "serialization/vector.h"
#include "serialization/binary_utils.h" #include "serialization/binary_utils.h"
#include "wallet/wallet2.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
using namespace std; using namespace std;
@ -649,3 +651,514 @@ TEST(Serialization, serializes_ringct_types)
ASSERT_TRUE(serialization::dump_binary(tx1, blob2)); ASSERT_TRUE(serialization::dump_binary(tx1, blob2));
ASSERT_TRUE(blob == blob2); ASSERT_TRUE(blob == blob2);
} }
TEST(Serialization, portability_wallet)
{
const bool testnet = true;
const bool restricted = false;
tools::wallet2 w(testnet, restricted);
string wallet_file = "../data/wallet_9svHk1";
string password = "test";
bool r = false;
try
{
w.load(wallet_file, password);
r = true;
}
catch (const exception& e)
{}
ASSERT_TRUE(r);
/*
fields of tools::wallet2 to be checked:
std::vector<crypto::hash> m_blockchain
std::vector<transfer_details> m_transfers // TODO
cryptonote::account_public_address m_account_public_address
std::unordered_map<crypto::key_image, size_t> m_key_images
std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs
std::unordered_multimap<crypto::hash, payment_details> m_payments
std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys
std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs
std::unordered_map<crypto::hash, std::string> m_tx_notes
std::unordered_map<crypto::hash, payment_details> m_unconfirmed_payments
std::unordered_map<crypto::public_key, size_t> m_pub_keys
std::vector<tools::wallet2::address_book_row> m_address_book
*/
// blockchain
ASSERT_TRUE(w.m_blockchain.size() == 1);
ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_blockchain[0]) == "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b");
// transfers (TODO)
ASSERT_TRUE(w.m_transfers.size() == 3);
// account public address
ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_view_public_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad");
ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_spend_public_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060");
// key images
ASSERT_TRUE(w.m_key_images.size() == 3);
{
crypto::key_image ki[3];
epee::string_tools::hex_to_pod("c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8", ki[0]);
epee::string_tools::hex_to_pod("d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0", ki[1]);
epee::string_tools::hex_to_pod("6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76", ki[2]);
ASSERT_TRUE(w.m_key_images.find(ki[0])->second == 0);
ASSERT_TRUE(w.m_key_images.find(ki[1])->second == 1);
ASSERT_TRUE(w.m_key_images.find(ki[2])->second == 2);
}
// unconfirmed txs
ASSERT_TRUE(w.m_unconfirmed_txs.size() == 0);
// payments
ASSERT_TRUE(w.m_payments.size() == 2);
{
auto pd0 = w.m_payments.begin();
auto pd1 = pd0;
++pd1;
ASSERT_TRUE(epee::string_tools::pod_to_hex(pd0->first) == "0000000000000000000000000000000000000000000000000000000000000000");
ASSERT_TRUE(epee::string_tools::pod_to_hex(pd1->first) == "0000000000000000000000000000000000000000000000000000000000000000");
if (epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc")
swap(pd0, pd1);
ASSERT_TRUE(epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e");
ASSERT_TRUE(epee::string_tools::pod_to_hex(pd1->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc");
ASSERT_TRUE(pd0->second.m_amount == 13400845012231);
ASSERT_TRUE(pd1->second.m_amount == 1200000000000);
ASSERT_TRUE(pd0->second.m_block_height == 818424);
ASSERT_TRUE(pd1->second.m_block_height == 818522);
ASSERT_TRUE(pd0->second.m_unlock_time == 818484);
ASSERT_TRUE(pd1->second.m_unlock_time == 0);
ASSERT_TRUE(pd0->second.m_timestamp == 1483263366);
ASSERT_TRUE(pd1->second.m_timestamp == 1483272963);
}
// tx keys
ASSERT_TRUE(w.m_tx_keys.size() == 2);
{
auto tx_key0 = w.m_tx_keys.begin();
auto tx_key1 = tx_key0;
++tx_key1;
if (epee::string_tools::pod_to_hex(tx_key0->first) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba")
swap(tx_key0, tx_key1);
ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key0->first) == "b9aac8c020ab33859e0c0b6331f46a8780d349e7ac17b067116e2d87bf48daad");
ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key1->first) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba");
ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key0->second) == "bf3614c6de1d06c09add5d92a5265d8c76af706f7bc6ac830d6b0d109aa87701");
ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key1->second) == "e556884246df5a787def6732c6ea38f1e092fa13e5ea98f732b99c07a6332003");
}
// confirmed txs
ASSERT_TRUE(w.m_confirmed_txs.size() == 1);
// tx notes
ASSERT_TRUE(w.m_tx_notes.size() == 2);
{
crypto::hash h[2];
epee::string_tools::hex_to_pod("15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e", h[0]);
epee::string_tools::hex_to_pod("6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba", h[1]);
ASSERT_TRUE(w.m_tx_notes.find(h[0])->second == "sample note");
ASSERT_TRUE(w.m_tx_notes.find(h[1])->second == "sample note 2");
}
// unconfirmed payments
ASSERT_TRUE(w.m_unconfirmed_payments.size() == 0);
// pub keys
ASSERT_TRUE(w.m_pub_keys.size() == 3);
{
crypto::public_key pubkey[3];
epee::string_tools::hex_to_pod("33f75f264574cb3a9ea5b24220a5312e183d36dc321c9091dfbb720922a4f7b0", pubkey[0]);
epee::string_tools::hex_to_pod("5066ff2ce9861b1d131cf16eeaa01264933a49f28242b97b153e922ec7b4b3cb", pubkey[1]);
epee::string_tools::hex_to_pod("0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8", pubkey[2]);
ASSERT_TRUE(w.m_pub_keys.find(pubkey[0])->second == 0);
ASSERT_TRUE(w.m_pub_keys.find(pubkey[1])->second == 1);
ASSERT_TRUE(w.m_pub_keys.find(pubkey[2])->second == 2);
}
// address book
ASSERT_TRUE(w.m_address_book.size() == 1);
{
auto address_book_row = w.m_address_book.begin();
ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_spend_public_key) == "9bc53a6ff7b0831c9470f71b6b972dbe5ad1e8606f72682868b1dda64e119fb3");
ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_view_public_key) == "49fece1ef97dc0c0f7a5e2106e75e96edd910f7e86b56e1e308cd0cf734df191");
ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_payment_id) == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
ASSERT_TRUE(address_book_row->m_description == "testnet wallet 9y52S6");
}
}
#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
TEST(Serialization, portability_outputs)
{
// read file
const std::string filename = "../data/outputs";
std::string data;
bool r = epee::file_io_utils::load_file_to_string(filename, data);
ASSERT_TRUE(r);
const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
ASSERT_FALSE(data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen));
// decrypt (copied from wallet2::decrypt)
auto decrypt = [] (const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) -> string
{
const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0);
if(ciphertext.size() < prefix_size)
return {};
crypto::chacha8_key key;
crypto::generate_chacha8_key(&skey, sizeof(skey), key);
const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0];
std::string plaintext;
plaintext.resize(ciphertext.size() - prefix_size);
if (authenticated)
{
crypto::hash hash;
crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash);
crypto::public_key pkey;
crypto::secret_key_to_public_key(skey, pkey);
const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)];
if(!crypto::check_signature(hash, pkey, signature))
return {};
}
crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]);
return std::move(plaintext);
};
crypto::secret_key view_secret_key;
epee::string_tools::hex_to_pod("339673bb1187e2f73ba7841ab6841c5553f96e9f13f8fe6612e69318db4e9d0a", view_secret_key);
bool authenticated = true;
data = decrypt(std::string(data, magiclen), view_secret_key, authenticated);
ASSERT_FALSE(data.empty());
// check public view/spend keys
const size_t headerlen = 2 * sizeof(crypto::public_key);
ASSERT_FALSE(data.size() < headerlen);
const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
ASSERT_TRUE(epee::string_tools::pod_to_hex(public_spend_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060");
ASSERT_TRUE(epee::string_tools::pod_to_hex(public_view_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad");
r = false;
std::vector<tools::wallet2::transfer_details> outputs;
try
{
std::istringstream iss(std::string(data, headerlen));
boost::archive::portable_binary_iarchive ar(iss);
ar >> outputs;
r = true;
}
catch (...)
{}
ASSERT_TRUE(r);
/*
fields of tools::wallet2::transfer_details to be checked:
uint64_t m_block_height
cryptonote::transaction_prefix m_tx // TODO
crypto::hash m_txid
size_t m_internal_output_index
uint64_t m_global_output_index
bool m_spent
uint64_t m_spent_height
crypto::key_image m_key_image
rct::key m_mask
uint64_t m_amount
bool m_rct
bool m_key_image_known
size_t m_pk_index
*/
ASSERT_TRUE(outputs.size() == 3);
auto& td0 = outputs[0];
auto& td1 = outputs[1];
auto& td2 = outputs[2];
ASSERT_TRUE(td0.m_block_height == 818424);
ASSERT_TRUE(td1.m_block_height == 818522);
ASSERT_TRUE(td2.m_block_height == 818522);
ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_txid) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_txid) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_txid) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba");
ASSERT_TRUE(td0.m_internal_output_index == 0);
ASSERT_TRUE(td1.m_internal_output_index == 0);
ASSERT_TRUE(td2.m_internal_output_index == 1);
ASSERT_TRUE(td0.m_global_output_index == 19642);
ASSERT_TRUE(td1.m_global_output_index == 19757);
ASSERT_TRUE(td2.m_global_output_index == 19760);
ASSERT_TRUE (td0.m_spent);
ASSERT_FALSE(td1.m_spent);
ASSERT_FALSE(td2.m_spent);
ASSERT_TRUE(td0.m_spent_height == 0);
ASSERT_TRUE(td1.m_spent_height == 0);
ASSERT_TRUE(td2.m_spent_height == 0);
ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_key_image) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_key_image) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_key_image) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_mask) == "0100000000000000000000000000000000000000000000000000000000000000");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_mask) == "d3997a7b27fa199a377643b88cbd3f20f447496746dabe92d288730ecaeda007");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
ASSERT_TRUE(td0.m_amount == 13400845012231);
ASSERT_TRUE(td1.m_amount == 1200000000000);
ASSERT_TRUE(td2.m_amount == 11066009260865);
ASSERT_TRUE(td0.m_rct);
ASSERT_TRUE(td1.m_rct);
ASSERT_TRUE(td2.m_rct);
ASSERT_TRUE(td0.m_key_image_known);
ASSERT_TRUE(td1.m_key_image_known);
ASSERT_TRUE(td2.m_key_image_known);
ASSERT_TRUE(td0.m_pk_index == 0);
ASSERT_TRUE(td1.m_pk_index == 0);
ASSERT_TRUE(td2.m_pk_index == 0);
}
#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003"
TEST(Serialization, portability_unsigned_tx)
{
const string filename = "../data/unsigned_monero_tx";
std::string s;
const bool testnet = true;
bool r = epee::file_io_utils::load_file_to_string(filename, s);
ASSERT_TRUE(r);
const size_t magiclen = strlen(UNSIGNED_TX_PREFIX);
ASSERT_FALSE(strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen));
tools::wallet2::unsigned_tx_set exported_txs;
s = s.substr(magiclen);
r = false;
try
{
std::istringstream iss(s);
boost::archive::portable_binary_iarchive ar(iss);
ar >> exported_txs;
r = true;
}
catch (...)
{}
ASSERT_TRUE(r);
/*
fields of tools::wallet2::unsigned_tx_set to be checked:
std::vector<tx_construction_data> txes
std::vector<wallet2::transfer_details> m_transfers
fields of toolw::wallet2::tx_construction_data to be checked:
std::vector<cryptonote::tx_source_entry> sources
cryptonote::tx_destination_entry change_dts
std::vector<cryptonote::tx_destination_entry> splitted_dsts
std::list<size_t> selected_transfers
std::vector<uint8_t> extra
uint64_t unlock_time
bool use_rct
std::vector<cryptonote::tx_destination_entry> dests
fields of cryptonote::tx_source_entry to be checked:
std::vector<std::pair<uint64_t, rct::ctkey>> outputs
size_t real_output
crypto::public_key real_out_tx_key
size_t real_output_in_tx_index
uint64_t amount
bool rct
rct::key mask
fields of cryptonote::tx_destination_entry to be checked:
uint64_t amount
account_public_address addr
*/
// txes
ASSERT_TRUE(exported_txs.txes.size() == 1);
auto& tcd = exported_txs.txes[0];
// tcd.sources
ASSERT_TRUE(tcd.sources.size() == 1);
auto& tse = tcd.sources[0];
// tcd.sources[0].outputs
ASSERT_TRUE(tse.outputs.size() == 5);
auto& out0 = tse.outputs[0];
auto& out1 = tse.outputs[1];
auto& out2 = tse.outputs[2];
auto& out3 = tse.outputs[3];
auto& out4 = tse.outputs[4];
ASSERT_TRUE(out0.first == 6295);
ASSERT_TRUE(out1.first == 14302);
ASSERT_TRUE(out2.first == 17598);
ASSERT_TRUE(out3.first == 18671);
ASSERT_TRUE(out4.first == 19760);
ASSERT_TRUE(epee::string_tools::pod_to_hex(out0.second) == "e7272cb589954ddeedd20de9411ed57265f154d41f33cec9ff69e5d642e09814096490b0ac85308342acf436cc0270d53abef9dc04c6202f2459e879bfd40ce6");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out1.second) == "c3a9f49d1fe75939cc3feb39871ce0a7366c2879a63faa1a5cf34e65723b120a272ff0c7d84ab8b6ee3528d196450b0e28b3fed276bc2597a2b5b17afb9354ab");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out2.second) == "176e239c8c39000c2275e2f63ed7d55c55e0843524091522bbd3d3b869044969021fad70fc1244115449d4754829ae7c47346342ee5d52a2cdd47dfc351d0ab0");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out3.second) == "ef12d7946302fb064f2ba9df1a73d72233ac74664ed3b370580fa3bdc377542ad93f64898bd95851d6efe0d7bf2dbbea9b7c6b3c57e2c807e7b17d55b4622259");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out4.second) == "0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8525096cbc88d00a841eed66f3cdb6f0a018e6ce9fb9433ed61afba15cbbebd04");
// tcd.sources[0].{real_output, real_out_tx_key, real_output_in_tx_index, amount, rct, mask}
ASSERT_TRUE(tse.real_output == 4);
ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.real_out_tx_key) == "4d86c7ba1c285fe4bc1cd7b54ba894fa89fa02fc6b0bbeea67d53251acd14a05");
ASSERT_TRUE(tse.real_output_in_tx_index == 1);
ASSERT_TRUE(tse.amount == 11066009260865);
ASSERT_TRUE(tse.rct);
ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
// tcd.change_dts
ASSERT_TRUE(tcd.change_dts.amount == 9631208773403);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
// tcd.splitted_dsts
ASSERT_TRUE(tcd.splitted_dsts.size() == 2);
auto& splitted_dst0 = tcd.splitted_dsts[0];
auto& splitted_dst1 = tcd.splitted_dsts[1];
ASSERT_TRUE(splitted_dst0.amount == 1400000000000);
ASSERT_TRUE(splitted_dst1.amount == 9631208773403);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
// tcd.selected_transfers
ASSERT_TRUE(tcd.selected_transfers.size() == 1);
ASSERT_TRUE(tcd.selected_transfers.front() == 2);
// tcd.extra
ASSERT_TRUE(tcd.extra.size() == 68);
string tcd_extra_str = epee::string_tools::buff_to_hex(string(reinterpret_cast<char*>(tcd.extra.data()), tcd.extra.size()));
ASSERT_TRUE(tcd_extra_str == "0x2 0x21 0x0 0xf8 0xd 0xbc 0xfc 0xa2 0x2d 0x84 0x1e 0xa0 0x46 0x18 0x7a 0x5b 0x19 0xea 0x4d 0xd1 0xa2 0x8a 0x58 0xa8 0x72 0x9 0xd5 0xdf 0x2 0x30 0x60 0xac 0x9e 0x48 0x84 0x1 0xb2 0xfd 0x5d 0x4e 0x45 0x8b 0xf1 0x28 0xa0 0xc8 0x30 0xd1 0x35 0x4f 0x47 0xb9 0xed 0xc9 0x82 0x8c 0x83 0x37 0x7d 0xb6 0xb5 0xe5 0x3d 0xff 0x64 0xb0 0xde 0x7f ");
// tcd.{unlock_time, use_rct}
ASSERT_TRUE(tcd.unlock_time == 0);
ASSERT_TRUE(tcd.use_rct);
// tcd.dests
ASSERT_TRUE(tcd.dests.size() == 1);
auto& dest = tcd.dests[0];
ASSERT_TRUE(dest.amount == 1400000000000);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
// transfers
ASSERT_TRUE(exported_txs.transfers.size() == 3);
auto& td0 = exported_txs.transfers[0];
auto& td1 = exported_txs.transfers[1];
auto& td2 = exported_txs.transfers[2];
ASSERT_TRUE(td0.m_block_height == 818424);
ASSERT_TRUE(td1.m_block_height == 818522);
ASSERT_TRUE(td2.m_block_height == 818522);
ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_txid) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_txid) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_txid) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba");
ASSERT_TRUE(td0.m_internal_output_index == 0);
ASSERT_TRUE(td1.m_internal_output_index == 0);
ASSERT_TRUE(td2.m_internal_output_index == 1);
ASSERT_TRUE(td0.m_global_output_index == 19642);
ASSERT_TRUE(td1.m_global_output_index == 19757);
ASSERT_TRUE(td2.m_global_output_index == 19760);
ASSERT_TRUE (td0.m_spent);
ASSERT_FALSE(td1.m_spent);
ASSERT_FALSE(td2.m_spent);
ASSERT_TRUE(td0.m_spent_height == 0);
ASSERT_TRUE(td1.m_spent_height == 0);
ASSERT_TRUE(td2.m_spent_height == 0);
ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_key_image) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_key_image) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_key_image) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_mask) == "0100000000000000000000000000000000000000000000000000000000000000");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_mask) == "d3997a7b27fa199a377643b88cbd3f20f447496746dabe92d288730ecaeda007");
ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
ASSERT_TRUE(td0.m_amount == 13400845012231);
ASSERT_TRUE(td1.m_amount == 1200000000000);
ASSERT_TRUE(td2.m_amount == 11066009260865);
ASSERT_TRUE(td0.m_rct);
ASSERT_TRUE(td1.m_rct);
ASSERT_TRUE(td2.m_rct);
ASSERT_TRUE(td0.m_key_image_known);
ASSERT_TRUE(td1.m_key_image_known);
ASSERT_TRUE(td2.m_key_image_known);
ASSERT_TRUE(td0.m_pk_index == 0);
ASSERT_TRUE(td1.m_pk_index == 0);
ASSERT_TRUE(td2.m_pk_index == 0);
}
#define SIGNED_TX_PREFIX "Monero signed tx set\003"
TEST(Serialization, portability_signed_tx)
{
const string filename = "../data/signed_monero_tx";
const bool testnet = true;
std::string s;
bool r = epee::file_io_utils::load_file_to_string(filename, s);
ASSERT_TRUE(r);
const size_t magiclen = strlen(SIGNED_TX_PREFIX);
ASSERT_FALSE(strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen));
tools::wallet2::signed_tx_set exported_txs;
s = s.substr(magiclen);
r = false;
try
{
std::istringstream iss(s);
boost::archive::portable_binary_iarchive ar(iss);
ar >> exported_txs;
r = true;
}
catch (...)
{}
ASSERT_TRUE(r);
/*
fields of tools::wallet2::signed_tx_set to be checked:
std::vector<pending_tx> ptx
std::vector<crypto::key_image> key_images
fields of tools::walllet2::pending_tx to be checked:
cryptonote::transaction tx // TODO
uint64_t dust
uint64_t fee
bool dust_added_to_fee
cryptonote::tx_destination_entry change_dts
std::list<size_t> selected_transfers
std::string key_images
crypto::secret_key tx_key
std::vector<cryptonote::tx_destination_entry> dests
tx_construction_data construction_data
*/
// ptx
ASSERT_TRUE(exported_txs.ptx.size() == 1);
auto& ptx = exported_txs.ptx[0];
// ptx.{dust, fee, dust_added_to_fee}
ASSERT_TRUE (ptx.dust == 0);
ASSERT_TRUE (ptx.fee == 34800487462);
ASSERT_FALSE(ptx.dust_added_to_fee);
// ptx.change.{amount, addr}
ASSERT_TRUE(ptx.change_dts.amount == 9631208773403);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
// ptx.selected_transfers
ASSERT_TRUE(ptx.selected_transfers.size() == 1);
ASSERT_TRUE(ptx.selected_transfers.front() == 2);
// ptx.{key_images, tx_key}
ASSERT_TRUE(ptx.key_images == "<6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76> ");
ASSERT_TRUE(epee::string_tools::pod_to_hex(ptx.tx_key) == "0100000000000000000000000000000000000000000000000000000000000000");
// ptx.dests
ASSERT_TRUE(ptx.dests.size() == 1);
ASSERT_TRUE(ptx.dests[0].amount == 1400000000000);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.dests[0].addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
// ptx.construction_data
auto& tcd = ptx.construction_data;
ASSERT_TRUE(tcd.sources.size() == 1);
auto& tse = tcd.sources[0];
// ptx.construction_data.sources[0].outputs
ASSERT_TRUE(tse.outputs.size() == 5);
auto& out0 = tse.outputs[0];
auto& out1 = tse.outputs[1];
auto& out2 = tse.outputs[2];
auto& out3 = tse.outputs[3];
auto& out4 = tse.outputs[4];
ASSERT_TRUE(out0.first == 6295);
ASSERT_TRUE(out1.first == 14302);
ASSERT_TRUE(out2.first == 17598);
ASSERT_TRUE(out3.first == 18671);
ASSERT_TRUE(out4.first == 19760);
ASSERT_TRUE(epee::string_tools::pod_to_hex(out0.second) == "e7272cb589954ddeedd20de9411ed57265f154d41f33cec9ff69e5d642e09814096490b0ac85308342acf436cc0270d53abef9dc04c6202f2459e879bfd40ce6");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out1.second) == "c3a9f49d1fe75939cc3feb39871ce0a7366c2879a63faa1a5cf34e65723b120a272ff0c7d84ab8b6ee3528d196450b0e28b3fed276bc2597a2b5b17afb9354ab");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out2.second) == "176e239c8c39000c2275e2f63ed7d55c55e0843524091522bbd3d3b869044969021fad70fc1244115449d4754829ae7c47346342ee5d52a2cdd47dfc351d0ab0");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out3.second) == "ef12d7946302fb064f2ba9df1a73d72233ac74664ed3b370580fa3bdc377542ad93f64898bd95851d6efe0d7bf2dbbea9b7c6b3c57e2c807e7b17d55b4622259");
ASSERT_TRUE(epee::string_tools::pod_to_hex(out4.second) == "0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8525096cbc88d00a841eed66f3cdb6f0a018e6ce9fb9433ed61afba15cbbebd04");
// ptx.construction_data.sources[0].{real_output, real_out_tx_key, real_output_in_tx_index, amount, rct, mask}
ASSERT_TRUE(tse.real_output == 4);
ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.real_out_tx_key) == "4d86c7ba1c285fe4bc1cd7b54ba894fa89fa02fc6b0bbeea67d53251acd14a05");
ASSERT_TRUE(tse.real_output_in_tx_index == 1);
ASSERT_TRUE(tse.amount == 11066009260865);
ASSERT_TRUE(tse.rct);
ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
// ptx.construction_data.change_dts
ASSERT_TRUE(tcd.change_dts.amount == 9631208773403);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
// ptx.construction_data.splitted_dsts
ASSERT_TRUE(tcd.splitted_dsts.size() == 2);
auto& splitted_dst0 = tcd.splitted_dsts[0];
auto& splitted_dst1 = tcd.splitted_dsts[1];
ASSERT_TRUE(splitted_dst0.amount == 1400000000000);
ASSERT_TRUE(splitted_dst1.amount == 9631208773403);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
// ptx.construction_data.selected_transfers
ASSERT_TRUE(tcd.selected_transfers.size() == 1);
ASSERT_TRUE(tcd.selected_transfers.front() == 2);
// ptx.construction_data.extra
ASSERT_TRUE(tcd.extra.size() == 68);
string tcd_extra_str = epee::string_tools::buff_to_hex(string(reinterpret_cast<char*>(tcd.extra.data()), tcd.extra.size()));
ASSERT_TRUE(tcd_extra_str == "0x2 0x21 0x0 0xf8 0xd 0xbc 0xfc 0xa2 0x2d 0x84 0x1e 0xa0 0x46 0x18 0x7a 0x5b 0x19 0xea 0x4d 0xd1 0xa2 0x8a 0x58 0xa8 0x72 0x9 0xd5 0xdf 0x2 0x30 0x60 0xac 0x9e 0x48 0x84 0x1 0xb2 0xfd 0x5d 0x4e 0x45 0x8b 0xf1 0x28 0xa0 0xc8 0x30 0xd1 0x35 0x4f 0x47 0xb9 0xed 0xc9 0x82 0x8c 0x83 0x37 0x7d 0xb6 0xb5 0xe5 0x3d 0xff 0x64 0xb0 0xde 0x7f ");
// ptx.construction_data.{unlock_time, use_rct}
ASSERT_TRUE(tcd.unlock_time == 0);
ASSERT_TRUE(tcd.use_rct);
// ptx.construction_data.dests
ASSERT_TRUE(tcd.dests.size() == 1);
auto& dest = tcd.dests[0];
ASSERT_TRUE(dest.amount == 1400000000000);
ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
// key_images
ASSERT_TRUE(exported_txs.key_images.size() == 3);
auto& ki0 = exported_txs.key_images[0];
auto& ki1 = exported_txs.key_images[1];
auto& ki2 = exported_txs.key_images[2];
ASSERT_TRUE(epee::string_tools::pod_to_hex(ki0) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8");
ASSERT_TRUE(epee::string_tools::pod_to_hex(ki1) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0");
ASSERT_TRUE(epee::string_tools::pod_to_hex(ki2) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76");
}