aboutsummaryrefslogtreecommitdiff
path: root/tests/unit_tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit_tests')
-rw-r--r--tests/unit_tests/address_from_url.cpp17
-rw-r--r--tests/unit_tests/hardfork.cpp1
-rw-r--r--tests/unit_tests/http_auth.cpp351
-rw-r--r--tests/unit_tests/serialization.cpp513
4 files changed, 825 insertions, 57 deletions
diff --git a/tests/unit_tests/address_from_url.cpp b/tests/unit_tests/address_from_url.cpp
index ff163dab9..ad3aca6b4 100644
--- a/tests/unit_tests/address_from_url.cpp
+++ b/tests/unit_tests/address_from_url.cpp
@@ -31,6 +31,7 @@
#include "gtest/gtest.h"
#include "wallet/wallet2.h"
+#include "common/dns_utils.h"
#include <string>
TEST(AddressFromTXT, Success)
@@ -42,7 +43,7 @@ TEST(AddressFromTXT, Success)
txtr += addr;
txtr += ";";
- std::string res = tools::wallet2::address_from_txt_record(txtr);
+ std::string res = tools::dns_utils::address_from_txt_record(txtr);
EXPECT_STREQ(addr.c_str(), res.c_str());
@@ -52,7 +53,7 @@ TEST(AddressFromTXT, Success)
txtr2 += "more foobar";
- res = tools::wallet2::address_from_txt_record(txtr2);
+ res = tools::dns_utils::address_from_txt_record(txtr2);
EXPECT_STREQ(addr.c_str(), res.c_str());
@@ -61,7 +62,7 @@ TEST(AddressFromTXT, Success)
txtr3 += addr;
txtr3 += "; foobar";
- res = tools::wallet2::address_from_txt_record(txtr3);
+ res = tools::dns_utils::address_from_txt_record(txtr3);
EXPECT_STREQ(addr.c_str(), res.c_str());
}
@@ -70,13 +71,13 @@ TEST(AddressFromTXT, Failure)
{
std::string txtr = "oa1:xmr recipient_address=not a real address";
- std::string res = tools::wallet2::address_from_txt_record(txtr);
+ std::string res = tools::dns_utils::address_from_txt_record(txtr);
ASSERT_STREQ("", res.c_str());
txtr += ";";
- res = tools::wallet2::address_from_txt_record(txtr);
+ res = tools::dns_utils::address_from_txt_record(txtr);
ASSERT_STREQ("", res.c_str());
}
@@ -86,7 +87,7 @@ TEST(AddressFromURL, Success)
bool dnssec_result = false;
- std::vector<std::string> addresses = tools::wallet2::addresses_from_url("donate.getmonero.org", dnssec_result);
+ std::vector<std::string> addresses = tools::dns_utils::addresses_from_url("donate.getmonero.org", dnssec_result);
EXPECT_EQ(1, addresses.size());
if (addresses.size() == 1)
@@ -95,7 +96,7 @@ TEST(AddressFromURL, Success)
}
// OpenAlias address with an @ instead of first .
- addresses = tools::wallet2::addresses_from_url("donate@getmonero.org", dnssec_result);
+ addresses = tools::dns_utils::addresses_from_url("donate@getmonero.org", dnssec_result);
EXPECT_EQ(1, addresses.size());
if (addresses.size() == 1)
{
@@ -107,7 +108,7 @@ TEST(AddressFromURL, Failure)
{
bool dnssec_result = false;
- std::vector<std::string> addresses = tools::wallet2::addresses_from_url("example.invalid", dnssec_result);
+ std::vector<std::string> addresses = tools::dns_utils::addresses_from_url("example.invalid", dnssec_result);
// for a non-existing domain such as "example.invalid", the non-existence is proved with NSEC records
ASSERT_TRUE(dnssec_result);
diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp
index a854b7b52..be17f01ad 100644
--- a/tests/unit_tests/hardfork.cpp
+++ b/tests/unit_tests/hardfork.cpp
@@ -78,6 +78,7 @@ public:
virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const { return false; }
virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const { return 0; }
virtual transaction get_tx(const crypto::hash& h) const { return transaction(); }
+ virtual bool get_tx(const crypto::hash& h, transaction &tx) const { return false; }
virtual uint64_t get_tx_count() const { return 0; }
virtual std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const { return std::vector<transaction>(); }
virtual uint64_t get_tx_block_height(const crypto::hash& h) const { return 0; }
diff --git a/tests/unit_tests/http_auth.cpp b/tests/unit_tests/http_auth.cpp
index 7158850b6..97954642f 100644
--- a/tests/unit_tests/http_auth.cpp
+++ b/tests/unit_tests/http_auth.cpp
@@ -61,6 +61,7 @@
#include "string_tools.h"
namespace {
+namespace http = epee::net_utils::http;
using fields = std::unordered_map<std::string, std::string>;
using auth_responses = std::vector<fields>;
@@ -71,17 +72,28 @@ std::string quoted(std::string str)
return str;
}
-epee::net_utils::http::http_request_info make_request(const fields& args)
+void write_fields(std::string& out, const fields& args)
{
namespace karma = boost::spirit::karma;
-
- std::string out{" DIGEST "};
karma::generate(
std::back_inserter(out),
(karma::string << " = " << karma::string) % " , ",
args);
+}
+
+std::string write_fields(const fields& args)
+{
+ std::string out{};
+ write_fields(out, args);
+ return out;
+}
- epee::net_utils::http::http_request_info request{};
+http::http_request_info make_request(const fields& args)
+{
+ std::string out{" DIGEST "};
+ write_fields(out, args);
+
+ http::http_request_info request{};
request.m_http_method_str = "NOP";
request.m_header_info.m_etc_fields.push_back(
std::make_pair(u8"authorization", std::move(out))
@@ -89,6 +101,21 @@ epee::net_utils::http::http_request_info make_request(const fields& args)
return request;
}
+http::http_response_info make_response(const auth_responses& choices)
+{
+ http::http_response_info response{};
+ for (const auto& choice : choices)
+ {
+ std::string out{" DIGEST "};
+ write_fields(out, choice);
+
+ response.m_additional_fields.push_back(
+ std::make_pair(u8"WWW-authenticate", std::move(out))
+ );
+ }
+ return response;
+}
+
bool has_same_fields(const auth_responses& in)
{
const std::vector<std::string> check{u8"nonce", u8"qop", u8"realm", u8"stale"};
@@ -113,7 +140,7 @@ bool has_same_fields(const auth_responses& in)
return true;
}
-bool is_unauthorized(const epee::net_utils::http::http_response_info& response)
+bool is_unauthorized(const http::http_response_info& response)
{
EXPECT_EQ(401, response.m_response_code);
EXPECT_STREQ(u8"Unauthorized", response.m_response_comment.c_str());
@@ -123,39 +150,44 @@ bool is_unauthorized(const epee::net_utils::http::http_response_info& response)
response.m_mime_tipe == u8"text/html";
}
-
-auth_responses parse_response(const epee::net_utils::http::http_response_info& response)
+fields parse_fields(const std::string& value)
{
namespace qi = boost::spirit::qi;
+ fields out{};
+ const bool rc = qi::parse(
+ value.begin(), value.end(),
+ qi::lit(u8"Digest ") >> ((
+ +qi::ascii::alpha >>
+ qi::lit('=') >> (
+ (qi::lit('"') >> +(qi::ascii::char_ - '"') >> qi::lit('"')) |
+ +(qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}"))
+ )
+ ) % ','
+ ) >> qi::eoi,
+ out
+ );
+ if (!rc)
+ throw std::runtime_error{"Bad field given in HTTP header"};
+
+ return out;
+}
+
+auth_responses parse_response(const http::http_response_info& response)
+{
auth_responses result{};
const auto end = response.m_additional_fields.end();
- for (auto current = response.m_additional_fields.begin(); current != end; ++current)
+ for (auto current = response.m_additional_fields.begin();; ++current)
{
current = std::find_if(current, end, [] (const std::pair<std::string, std::string>& field) {
- return boost::iequals(u8"www-authenticate", field.first);
+ return boost::equals(u8"WWW-authenticate", field.first);
});
if (current == end)
return result;
- std::unordered_map<std::string, std::string> fields{};
- const bool rc = qi::parse(
- current->second.begin(), current->second.end(),
- qi::lit(u8"Digest ") >> ((
- +qi::ascii::alpha >>
- qi::lit('=') >> (
- (qi::lit('"') >> +(qi::ascii::char_ - '"') >> qi::lit('"')) |
- +(qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}"))
- )
- ) % ','
- ) >> qi::eoi,
- fields
- );
-
- if (rc)
- result.push_back(std::move(fields));
+ result.push_back(parse_fields(current->second));
}
return result;
}
@@ -175,15 +207,20 @@ std::string md5_hex(const std::string& in)
return epee::string_tools::pod_to_hex(digest);
}
-std::string get_a1(const epee::net_utils::http::http_auth::login& user, const auth_responses& responses)
+std::string get_a1(const http::login& user, const fields& src)
{
- const std::string& realm = responses.at(0).at(u8"realm");
+ const std::string& realm = src.at(u8"realm");
return boost::join(
std::vector<std::string>{user.username, realm, user.password}, u8":"
);
}
-std::string get_a1_sess(const epee::net_utils::http::http_auth::login& user, const std::string& cnonce, const auth_responses& responses)
+std::string get_a1(const http::login& user, const auth_responses& responses)
+{
+ return get_a1(user, responses.at(0));
+}
+
+std::string get_a1_sess(const http::login& user, const std::string& cnonce, const auth_responses& responses)
{
const std::string& nonce = responses.at(0).at(u8"nonce");
return boost::join(
@@ -210,36 +247,36 @@ std::string get_nc(std::uint32_t count)
}
}
-TEST(HTTP_Auth, NotRequired)
+TEST(HTTP_Server_Auth, NotRequired)
{
- epee::net_utils::http::http_auth auth{};
- EXPECT_FALSE(auth.get_response(epee::net_utils::http::http_request_info{}));
+ http::http_server_auth auth{};
+ EXPECT_FALSE(auth.get_response(http::http_request_info{}));
}
-TEST(HTTP_Auth, MissingAuth)
+TEST(HTTP_Server_Auth, MissingAuth)
{
- epee::net_utils::http::http_auth auth{{"foo", "bar"}};
- EXPECT_TRUE(bool(auth.get_response(epee::net_utils::http::http_request_info{})));
+ http::http_server_auth auth{{"foo", "bar"}};
+ EXPECT_TRUE(bool(auth.get_response(http::http_request_info{})));
{
- epee::net_utils::http::http_request_info request{};
+ http::http_request_info request{};
request.m_header_info.m_etc_fields.push_back({"\xFF", "\xFF"});
EXPECT_TRUE(bool(auth.get_response(request)));
}
}
-TEST(HTTP_Auth, BadSyntax)
+TEST(HTTP_Server_Auth, BadSyntax)
{
- epee::net_utils::http::http_auth auth{{"foo", "bar"}};
+ http::http_server_auth auth{{"foo", "bar"}};
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"algorithm", "fo\xFF"}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"cnonce", "\"000\xFF\""}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"cnonce \xFF =", "\"000\xFF\""}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8" \xFF cnonce", "\"000\xFF\""}}))));
}
-TEST(HTTP_Auth, MD5)
+TEST(HTTP_Server_Auth, MD5)
{
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -283,12 +320,12 @@ TEST(HTTP_Auth, MD5)
EXPECT_STREQ(u8"true", fields2[0].at(u8"stale").c_str());
}
-TEST(HTTP_Auth, MD5_sess)
+TEST(HTTP_Server_Auth, MD5_sess)
{
constexpr const char cnonce[] = "not a good cnonce";
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -334,13 +371,13 @@ TEST(HTTP_Auth, MD5_sess)
EXPECT_STREQ(u8"true", fields2[0].at(u8"stale").c_str());
}
-TEST(HTTP_Auth, MD5_auth)
+TEST(HTTP_Server_Auth, MD5_auth)
{
constexpr const char cnonce[] = "not a nonce";
constexpr const char qop[] = "auth";
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -402,13 +439,13 @@ TEST(HTTP_Auth, MD5_auth)
EXPECT_STREQ(u8"true", parsed_replay[0].at(u8"stale").c_str());
}
-TEST(HTTP_Auth, MD5_sess_auth)
+TEST(HTTP_Server_Auth, MD5_sess_auth)
{
constexpr const char cnonce[] = "not a nonce";
constexpr const char qop[] = "auth";
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -469,3 +506,219 @@ TEST(HTTP_Auth, MD5_sess_auth)
EXPECT_NE(nonce, parsed_replay[0].at(u8"nonce"));
EXPECT_STREQ(u8"true", parsed_replay[0].at(u8"stale").c_str());
}
+
+
+TEST(HTTP_Auth, DogFood)
+{
+ const auto add_field = [] (http::http_request_info& request, http::http_client_auth& client)
+ {
+ auto field = client.get_auth_field(request.m_http_method_str, request.m_URI);
+ EXPECT_TRUE(bool(field));
+ if (!field)
+ return false;
+ request.m_header_info.m_etc_fields.push_back(std::move(*field));
+ return true;
+ };
+
+ const http::login user{"some_user", "ultimate password"};
+
+ http::http_server_auth server{user};
+ http::http_client_auth client{user};
+
+ http::http_request_info request{};
+ request.m_http_method_str = "GET";
+ request.m_URI = "/FOO";
+
+ const auto response = server.get_response(request);
+ ASSERT_TRUE(bool(response));
+ EXPECT_TRUE(is_unauthorized(*response));
+
+ EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
+ EXPECT_TRUE(add_field(request, client));
+ EXPECT_FALSE(bool(server.get_response(request)));
+
+ for (unsigned i = 0; i < 1000; ++i)
+ {
+ request.m_http_method_str += std::to_string(i);
+ request.m_header_info.m_etc_fields.clear();
+ EXPECT_TRUE(add_field(request, client));
+ EXPECT_FALSE(bool(server.get_response(request)));
+ }
+
+ // resetting counter should be rejected by server
+ request.m_header_info.m_etc_fields.clear();
+ client = http::http_client_auth{user};
+ EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
+ EXPECT_TRUE(add_field(request, client));
+
+ const auto response2 = server.get_response(request);
+ ASSERT_TRUE(bool(response2));
+ EXPECT_TRUE(is_unauthorized(*response2));
+
+ const auth_responses parsed1 = parse_response(*response);
+ const auth_responses parsed2 = parse_response(*response2);
+ ASSERT_LE(1u, parsed1.size());
+ ASSERT_LE(1u, parsed2.size());
+ EXPECT_NE(parsed1[0].at(u8"nonce"), parsed2[0].at(u8"nonce"));
+
+ // with stale=true client should reset
+ request.m_header_info.m_etc_fields.clear();
+ EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response2));
+ EXPECT_TRUE(add_field(request, client));
+ EXPECT_FALSE(bool(server.get_response(request)));
+
+ // client should give up if stale=false
+ EXPECT_EQ(http::http_client_auth::kBadPassword, client.handle_401(*response));
+}
+
+TEST(HTTP_Client_Auth, Unavailable)
+{
+ http::http_client_auth auth{};
+ EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(http::http_response_info{}));
+ EXPECT_FALSE(bool(auth.get_auth_field("GET", "/file")));
+}
+
+TEST(HTTP_Client_Auth, MissingAuthenticate)
+{
+ http::http_client_auth auth{{"foo", "bar"}};
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(http::http_response_info{}));
+ EXPECT_FALSE(bool(auth.get_auth_field("POST", "/\xFFname")));
+ {
+ http::http_response_info response{};
+ response.m_additional_fields.push_back({"\xFF", "\xFF"});
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(response));
+ }
+ EXPECT_FALSE(bool(auth.get_auth_field("DELETE", "/file/does/not/exist")));
+}
+
+TEST(HTTP_Client_Auth, BadSyntax)
+{
+ http::http_client_auth auth{{"foo", "bar"}};
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"realm", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"domain", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"nonce", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"nonce \xFF =", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8" \xFF nonce", "fo\xFF"}}})));
+}
+
+TEST(HTTP_Client_Auth, MD5)
+{
+ constexpr char method[] = "NOP";
+ constexpr char nonce[] = "some crazy nonce";
+ constexpr char realm[] = "the only realm";
+ constexpr char uri[] = "/some_file";
+
+ const http::login user{"foo", "bar"};
+ http::http_client_auth auth{user};
+
+ auto response = make_response({
+ {
+ {u8"domain", quoted("ignored")},
+ {u8"nonce", quoted(nonce)},
+ {u8"REALM", quoted(realm)}
+ },
+ {
+ {u8"algorithm", "null"},
+ {u8"domain", quoted("ignored")},
+ {u8"nonce", quoted(std::string{"e"} + nonce)},
+ {u8"realm", quoted(std::string{"e"} + realm)}
+ },
+ });
+
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+ const auto auth_field = auth.get_auth_field(method, uri);
+ ASSERT_TRUE(bool(auth_field));
+
+ const auto parsed = parse_fields(auth_field->second);
+ EXPECT_STREQ(u8"Authorization", auth_field->first.c_str());
+ EXPECT_EQ(parsed.end(), parsed.find(u8"opaque"));
+ EXPECT_EQ(parsed.end(), parsed.find(u8"qop"));
+ EXPECT_EQ(parsed.end(), parsed.find(u8"nc"));
+ EXPECT_STREQ(u8"MD5", parsed.at(u8"algorithm").c_str());
+ EXPECT_STREQ(nonce, parsed.at(u8"nonce").c_str());
+ EXPECT_STREQ(uri, parsed.at(u8"uri").c_str());
+ EXPECT_EQ(user.username, parsed.at(u8"username"));
+ EXPECT_STREQ(realm, parsed.at(u8"realm").c_str());
+
+ const std::string a1 = get_a1(user, parsed);
+ const std::string a2 = get_a2(uri);
+ const std::string auth_code = md5_hex(
+ boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)}, u8":")
+ );
+ EXPECT_TRUE(boost::iequals(auth_code, parsed.at(u8"response")));
+ {
+ const auto auth_field_dup = auth.get_auth_field(method, uri);
+ ASSERT_TRUE(bool(auth_field_dup));
+ EXPECT_EQ(*auth_field, *auth_field_dup);
+ }
+
+
+ EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
+ response.m_additional_fields.front().second.append(u8"," + write_fields({{u8"stale", u8"TRUE"}}));
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+}
+
+TEST(HTTP_Client_Auth, MD5_auth)
+{
+ constexpr char cnonce[] = "";
+ constexpr char method[] = "NOP";
+ constexpr char nonce[] = "some crazy nonce";
+ constexpr char opaque[] = "this is the opaque";
+ constexpr char qop[] = u8"ignore,auth,ignore";
+ constexpr char realm[] = "the only realm";
+ constexpr char uri[] = "/some_file";
+
+ const http::login user{"foo", "bar"};
+ http::http_client_auth auth{user};
+
+ auto response = make_response({
+ {
+ {u8"algorithm", u8"MD5"},
+ {u8"domain", quoted("ignored")},
+ {u8"nonce", quoted(std::string{"e"} + nonce)},
+ {u8"realm", quoted(std::string{"e"} + realm)},
+ {u8"qop", quoted("some,thing,to,ignore")}
+ },
+ {
+ {u8"algorIthm", quoted(u8"md5")},
+ {u8"domain", quoted("ignored")},
+ {u8"noNce", quoted(nonce)},
+ {u8"opaque", quoted(opaque)},
+ {u8"realm", quoted(realm)},
+ {u8"QoP", quoted(qop)}
+ }
+ });
+
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+
+ for (unsigned i = 1; i < 1000; ++i)
+ {
+ const std::string nc = get_nc(i);
+
+ const auto auth_field = auth.get_auth_field(method, uri);
+ ASSERT_TRUE(bool(auth_field));
+
+ const auto parsed = parse_fields(auth_field->second);
+ EXPECT_STREQ(u8"Authorization", auth_field->first.c_str());
+ EXPECT_STREQ(u8"MD5", parsed.at(u8"algorithm").c_str());
+ EXPECT_STREQ(nonce, parsed.at(u8"nonce").c_str());
+ EXPECT_STREQ(opaque, parsed.at(u8"opaque").c_str());
+ EXPECT_STREQ(u8"auth", parsed.at(u8"qop").c_str());
+ EXPECT_STREQ(uri, parsed.at(u8"uri").c_str());
+ EXPECT_EQ(user.username, parsed.at(u8"username"));
+ EXPECT_STREQ(realm, parsed.at(u8"realm").c_str());
+ EXPECT_EQ(nc, parsed.at(u8"nc"));
+
+ const std::string a1 = get_a1(user, parsed);
+ const std::string a2 = get_a2(uri);
+ const std::string auth_code = md5_hex(
+ boost::join(std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, u8"auth", md5_hex(a2)}, u8":")
+ );
+ EXPECT_TRUE(boost::iequals(auth_code, parsed.at(u8"response")));
+ }
+
+ EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
+ response.m_additional_fields.back().second.append(u8"," + write_fields({{u8"stale", u8"trUe"}}));
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+}
+
diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp
index 70757dfc1..2be7bde8c 100644
--- a/tests/unit_tests/serialization.cpp
+++ b/tests/unit_tests/serialization.cpp
@@ -34,6 +34,7 @@
#include <iostream>
#include <vector>
#include <boost/foreach.hpp>
+#include <boost/archive/portable_binary_iarchive.hpp>
#include "cryptonote_core/cryptonote_basic.h"
#include "cryptonote_core/cryptonote_basic_impl.h"
#include "ringct/rctSigs.h"
@@ -44,6 +45,7 @@
#include "serialization/variant.h"
#include "serialization/vector.h"
#include "serialization/binary_utils.h"
+#include "wallet/wallet2.h"
#include "gtest/gtest.h"
using namespace std;
@@ -649,3 +651,514 @@ TEST(Serialization, serializes_ringct_types)
ASSERT_TRUE(serialization::dump_binary(tx1, 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");
+}