Merge pull request #3 from moneroexamples/subaddress

subaddresses
master
moneroexamples 5 years ago committed by GitHub
commit 80630eef80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.8)
set(PROJECT_NAME xmregcore)
set(PROJECT_NAME xmregcore_example)
project(${PROJECT_NAME})
@ -18,7 +18,7 @@ include(MyUtils)
find_package(Monero)
add_executable(${PROJECT_NAME}
main.cpp)
example.cpp)
add_subdirectory(src/)

@ -1,27 +1,134 @@
# Moneroexamples core repositories
# Core repository of moneroexamples
This repository includes code that is oftenly used among moneroexamples projects.
It includes:
This repository includes code that is oftenly used among moneroexamples projects. It includes:
- classess for decoding outputs/inputs, payment ids
- general utility tools (e.g., get default monero blockchain path)
- more to come ...
- classess for decoding outputs/inputs, payment ids,
- general utility tools (e.g., get default monero blockchain path),
- unified representation of monero addresses/accounts,
- identification of outputs for subaddresses based on primary address,
- estimation of possible spendings based on address and viewkey.
# C++14
# Example usage
C++14 is required to run this code.
More examples along with its full code are located in [example.cpp](https://github.com/moneroexamples/xmregcore/blob/master/example.cpp).
#### Monero download and compilation
### Identify outputs in a tx based on address and viewkey with subaddresses
```C++
// use Monero forum donation address and viewkwey.
// will search for outputs in a give tx to
// to the primary address and its subaddresses.
auto primary_account = xmreg::make_primaryaccount(
"45ttEikQEZWN1m7VxaVN9rjQkpSdmpGZ82GwUps66neQ1PqbQMno4wMY8F5jiDt2GoHzCtMwa7PDPJUJYb1GYrMP4CwAwNp",
"c9347bc1e101eab46d3a6532c5b6066e925f499b47d285d5720e6a6f4cc4350c");
// if we want to analyze subaddresses, we need to generate
// an initial set of 10'000 possible subaddresses
primary_account->populate_subaddress_indices();
auto tx = get_tx("54cef43983ca9eeded46c6cc1091bf6f689d91faf78e306a5ac6169e981105d8");
// so now we can create an instance of a universal identifier
// which is going to identify outputs in a given tx using
// address and viewkey data. the search will include subaddresses.
auto identifier = make_identifier(*tx,
make_unique<xmreg::Output>(primary_account.get()));
identifier.identify();
// get the results of the identification
auto outputs_found = identifier.get<xmreg::Output>()->get();
cout << "Found following outputs in tx " << tx << ":\n"
<< outputs_found << '\n';
```
Identified output for `0.081774999238` xmr is for a subaddress of index `0/10`
which in this case is for the
["xiphon part time coding (3 months)"](https://ccs.getmonero.org/proposals/xiphon-part-time.html)
proposal.
### Possible spending based on address and viewkey
```C++
// use offical Monero project donation address and viewkwey.
// will search for outputs and inputs in a give tx addressed
// to the primary address only. this search will not account
// for any outputs sent to subaddresses.
auto primary_account = xmreg::make_primaryaccount(
"44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A",
"f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501");
auto tx = get_tx("aa739a3ce8d3171a422ed3a3f5016384cdb17a5d3eb5905021f1103574d47eaf");
// we can join individual identifiers as shown below, since to estimate
// spendings we need to identify possible inputs with their amount values,
// as well as outputs corresponding to the change returned to Monero
// donation address
auto identifier = make_identifier(*tx,
make_unique<xmreg::Output>(primary_account.get()),
make_unique<xmreg::GuessInput>(primary_account.get(), &mcore));
identifier.identify();
// get identified outputs and inputs in the tx
auto outputs_found = identifier.get<xmreg::Output>()->get();
auto inputs_found = identifier.get<xmreg::GuessInput>()->get();
// possible spending is just basic math
auto possible_total_spent = xmreg::calc_total_xmr(inputs_found)
- xmreg::calc_total_xmr(outputs_found)
- get_tx_fee(*tx);
cout << "Possible spending from Monero project donation is: "
<< print_money(possible_total_spent) << " xmr\n";
```
Result is `118.778154000000` xmr which possibly were withdrawn from
Monero donation address.
### Identify and decrypt integrated payment id
```C++
// use Monero forum donation address and viewkwey
auto account = xmreg::make_account(
"45ttEikQEZWN1m7VxaVN9rjQkpSdmpGZ82GwUps66neQ1PqbQMno4wMY8F5jiDt2GoHzCtMwa7PDPJUJYb1GYrMP4CwAwNp",
"c9347bc1e101eab46d3a6532c5b6066e925f499b47d285d5720e6a6f4cc4350c");
auto tx = get_tx("401bf77c9a49dd28df5f9fb15846f9de05fce5f0e11da16d980c4c9ac9156354");
auto identifier = make_identifier(*tx,
make_unique<xmreg::IntegratedPaymentID>(account.get()));
identifier.identify();
auto payment_id = identifier.get<xmreg::IntegratedPaymentID>()->get();
cout << "Found following itegrated payment id in tx " << payment_id << '\n';
```
The result is decrypted short payment id of `1fcbb836a748f4dc`. The tx is also
a possible withdrawn from Monero forum donation wallet for `350` xmr
(see examples.cpp for the code).
# Compilation
The project depends on monero libraries and it has same dependecies as the monero
(except C++14 requirement). Thus monero needs to be setup first.
### Monero download and compilation
Follow instructions in the following link:
https://github.com/moneroexamples/monero-compilation/blob/master/README.md
### Compilation of the xmregcore
#### Compilation of the xmregcore
C++14 is required to compile the project. This means that
GCC 7.1 or higher can be used. For example, Ubuntu 18.04 ships with
GCC 7.3 which is sufficient.
```bash
# go to home folder if still in ~/monero
cd ~
@ -33,7 +140,7 @@ mkdir build && cd build
cmake ..
# altearnatively can use cmake -DMONERO_DIR=/path/to/monero_folder ..
# alternatively can use cmake -DMONERO_DIR=/path/to/monero_folder ..
# if monero is not in ~/monero
make
@ -50,4 +157,4 @@ finished and may not work as intended.
# How can you help?
Constructive criticism, code and website edits are always good. They can be made through github.
Constructive criticism and code are always welcomed. They can be made through github.

@ -0,0 +1,317 @@
#include "src/MicroCore.h"
#include "src/UniversalIdentifier.hpp"
#include "version.h"
#include <iostream>
#include <memory>
using namespace std;
using namespace cryptonote;
using xmreg::operator<<;
/**
* A helper functor to get transaction based
* on its hash in string
*/
struct TxGetter
{
xmreg::MicroCore const* mcore {nullptr};
boost::optional<transaction>
operator()(string tx_hash_str) const
{
assert(mcore);
crypto::hash tx_hash;
if (!epee::string_tools::hex_to_pod(tx_hash_str, tx_hash))
{
cerr << "Cant decode tx hash: " << tx_hash_str << '\n';
return {};
}
boost::optional<transaction> tx = transaction {};
if (!mcore->get_tx(tx_hash, *tx))
{
cerr << "Cant get tx: " << tx_hash_str << '\n';
return {};
}
return tx;
}
};
inline std::ostream&
operator<<(std::ostream& os, boost::optional<transaction> const& tx)
{
if (tx)
{
os << get_transaction_hash(*tx);
}
else
{
os << "N/A";
}
return os;
}
template <typename T>
inline std::ostream&
operator<<(std::ostream& os, boost::optional<T> const& pid)
{
if (pid)
{
os << epee::string_tools::pod_to_hex<T>(*pid);
}
else
{
os << "N/A";
}
return os;
}
int
main(int ac, const char* av[])
{
// setup monero logger for minimum output
mlog_configure(mlog_get_default_log_path(""), true);
mlog_set_log("1");
cout << "Program is starting\n";
network_type nettype = network_type::MAINNET;
string blockchain_path = xmreg::get_default_lmdb_folder(nettype);
cout << "Mainnet blockchain path: " << blockchain_path << '\n'
<< "Monero Version: " << MONERO_VERSION_FULL << '\n';
cout << "Initializaing MicroCore\n\n";
xmreg::MicroCore mcore {blockchain_path, nettype};
// transaction getter helper
TxGetter get_tx {&mcore};
cout << "\n***Identify outputs in a tx based on address and viewkey (no subaddreses)***\n\n";
{
// use Monero donation address and viewkwey
// will search of output in a give tx addressed
// to the primary address only.
auto primary_account = xmreg::make_primaryaccount(
"44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A",
"f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501");
cout << "Monero donation account: " << *primary_account << '\n';
auto tx = get_tx("e8ceef12683b3180d83dd1c24f8f871d52d206b80d8a6db6c5504eb0596b0312");
if (!tx)
return EXIT_FAILURE;
auto identifier = make_identifier(*tx,
make_unique<xmreg::Output>(primary_account.get()));
identifier.identify();
auto outputs_found = identifier.get<xmreg::Output>()->get();
if (!outputs_found.empty())
{
cout << "Found following outputs in tx " << tx << ":\n"
<< outputs_found << '\n';
}
}
cout << "\n***Identify outputs in a tx based on address and viewkey (with subaddresses)***\n\n";
{
// use Monero forum donation address and viewkwey
// will search of inputs in a give tx addressed
// to the primary address and its subaddress.
auto primary_account = xmreg::make_primaryaccount(
"45ttEikQEZWN1m7VxaVN9rjQkpSdmpGZ82GwUps66neQ1PqbQMno4wMY8F5jiDt2GoHzCtMwa7PDPJUJYb1GYrMP4CwAwNp",
"c9347bc1e101eab46d3a6532c5b6066e925f499b47d285d5720e6a6f4cc4350c");
// if we want to analyze subaddress, we need to generate
// an initial list of 10'000 possible subaddress
primary_account->populate_subaddress_indices();
cout << "Monero forum donation account: " << *primary_account << '\n';
auto tx = get_tx("54cef43983ca9eeded46c6cc1091bf6f689d91faf78e306a5ac6169e981105d8");
if (!tx)
return EXIT_FAILURE;
auto identifier = make_identifier(*tx,
make_unique<xmreg::Output>(primary_account.get()));
identifier.identify();
auto outputs_found = identifier.get<xmreg::Output>()->get();
if (!outputs_found.empty())
{
cout << "Found following outputs in tx " << tx << ":\n"
<< outputs_found << '\n';
// identified output is for subaddress of index 0/10 which
// in this case is for the "xiphon part time coding (3 months)"
// proposal https://ccs.getmonero.org/proposals/xiphon-part-time.html
}
}
cout << "\n***Possible spending based on address and viewkey (no subaddress)***\n\n";
{
// use Monero donation address and viewkwey
// will search of inputs in a give tx addressed
// to the primary address only.
auto primary_account = xmreg::make_primaryaccount(
"44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A",
"f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501");
// not using subaddresess here as people donate directly to the primary
// address
auto tx = get_tx("aa739a3ce8d3171a422ed3a3f5016384cdb17a5d3eb5905021f1103574d47eaf");
if (!tx)
return EXIT_FAILURE;
// we can join individual identifiers as below, sice to estimate
// spendings we need to identify possible inputs with their values,
// as well as outputs corresponding to the change returned to Monero
// donation address
auto identifier = make_identifier(*tx,
make_unique<xmreg::Output>(primary_account.get()),
make_unique<xmreg::GuessInput>(primary_account.get(), &mcore));
identifier.identify();
auto outputs_found = identifier.get<xmreg::Output>()->get();
auto inputs_found = identifier.get<xmreg::GuessInput>()->get();
// basic sanity check. If the spending was correctly guesseid,
// at least the number of identify inputs should match the
// number of inputs in the tx
if (tx->vin.size() == inputs_found.size())
{
// possible spending is just basic math
auto possible_total_spent = xmreg::calc_total_xmr(inputs_found)
- xmreg::calc_total_xmr(outputs_found)
- get_tx_fee(*tx);
cout << "Possible spending from Monero project donation is: "
<< print_money(possible_total_spent) << " xmr\n";
}
}
cout << "\n***Possible spending based on address and viewkey (with subaddress)***\n\n";
{
// use Monero forum donation address and viewkwey
// will search of inputs in a give tx addressed
// to the primary address only.
auto primary_account = xmreg::make_primaryaccount(
"45ttEikQEZWN1m7VxaVN9rjQkpSdmpGZ82GwUps66neQ1PqbQMno4wMY8F5jiDt2GoHzCtMwa7PDPJUJYb1GYrMP4CwAwNp",
"c9347bc1e101eab46d3a6532c5b6066e925f499b47d285d5720e6a6f4cc4350c");
// include outputs from subaddress as monero forum is primarly based
// on donation to subaddresses
primary_account->populate_subaddress_indices();
cout << "Monero formum donation account: " << *primary_account << '\n';
auto tx = get_tx("401bf77c9a49dd28df5f9fb15846f9de05fce5f0e11da16d980c4c9ac9156354");
if (!tx)
return EXIT_FAILURE;
// we can join individual identifiers as below, sice to estimate
// spendings we need to identify possible inputs with their values,
// as well as outputs corresponding to the change returned to Monero
// donation address
auto identifier = make_identifier(*tx,
make_unique<xmreg::Output>(primary_account.get()),
make_unique<xmreg::GuessInput>(primary_account.get(), &mcore));
identifier.identify();
auto outputs_found = identifier.get<xmreg::Output>()->get();
auto inputs_found = identifier.get<xmreg::GuessInput>()->get();
// basic sanity check. If the spending was correctly guesses
// at least the number of identify inputs should match the
// number of inputs in the tx
if (tx->vin.size() == inputs_found.size())
{
// possible spending is just basic math
auto possible_total_spent = xmreg::calc_total_xmr(inputs_found)
- xmreg::calc_total_xmr(outputs_found)
- get_tx_fee(*tx);
cout << "Possible spending from Monero fourm donation is: "
<< print_money(possible_total_spent) << " xmr\n";
}
}
cout << "\n***Identify legacy payment id***\n\n";
{
auto tx = get_tx("ce0d32093ca9cc5b7bcae3f4d3508c04846e8bceecc0819fd2c3191b64caad05");
if (!tx)
return EXIT_FAILURE;
auto identifier = make_identifier(*tx,
make_unique<xmreg::LegacyPaymentID>());
identifier.identify();
auto payment_id = identifier.get<xmreg::LegacyPaymentID>()->get();
if (payment_id)
{
cout << "Found following legacy payment id in tx " << payment_id << '\n';
}
}
cout << "\n***Identify and decrypt short payment id***\n\n";
{
// use Monero forum donation address and viewkwey
auto account = xmreg::make_account(
"45ttEikQEZWN1m7VxaVN9rjQkpSdmpGZ82GwUps66neQ1PqbQMno4wMY8F5jiDt2GoHzCtMwa7PDPJUJYb1GYrMP4CwAwNp",
"c9347bc1e101eab46d3a6532c5b6066e925f499b47d285d5720e6a6f4cc4350c");
auto tx = get_tx("401bf77c9a49dd28df5f9fb15846f9de05fce5f0e11da16d980c4c9ac9156354");
if (!tx)
return EXIT_FAILURE;
auto identifier = make_identifier(*tx,
make_unique<xmreg::IntegratedPaymentID>(account.get()));
identifier.identify();
auto payment_id = identifier.get<xmreg::IntegratedPaymentID>()->get();
if (payment_id)
{
cout << "Found following itegrated payment id in tx " << payment_id << '\n';
}
}
return EXIT_SUCCESS;
}

@ -1,35 +0,0 @@
#include "src/MicroCore.h"
#include "src/tools.h"
#include "version.h"
#include <iostream>
#include <memory>
using namespace std;
using namespace cryptonote;
int
main(int ac, const char* av[])
{
mlog_configure(mlog_get_default_log_path(""), true);
mlog_set_log("1");
cout << "Program is starting\n";
network_type nettype = network_type::STAGENET;
string blockchain_path = xmreg::get_default_lmdb_folder(nettype);
cout << "Blockchain path: " << blockchain_path << '\n';
cout << "Monero Version: " << MONERO_VERSION_FULL << endl;
cout << "Initializaing MicroCore\n";
xmreg::MicroCore mcore {blockchain_path, nettype};
return EXIT_SUCCESS;
}

@ -12,7 +12,8 @@ Account::Account(
addr_info {_addr_info},
viewkey {_viewkey},
spendkey {_spendkey}
{}
{
}
Account::Account(
network_type _nettype,
@ -47,4 +48,90 @@ Account::Account(network_type _nettype,
}
unique_ptr<SubaddressAccount>
PrimaryAccount::gen_subaddress(subaddress_index idx)
{
if (!this->vk() || idx.is_zero())
return nullptr;
auto& device = hw::get_device("default");
address_parse_info subaddr_info {
device.get_subaddress(
*(this->keys()), idx),
true /*is_subaddress*/,
false /*has_payment_id*/};
std::unique_ptr<SubaddressAccount> sacc;
if (this->sk())
{
sacc = std::make_unique<SubaddressAccount>(
this->nt(),
std::move(subaddr_info),
*(this->vk()), *(this->sk()));
}
else
{
sacc = std::make_unique<SubaddressAccount>(
this->nt(),
std::move(subaddr_info),
*(this->vk()));
}
sacc->set_index(std::move(idx));
return sacc;
}
PrimaryAccount::subaddr_map_t::const_iterator
PrimaryAccount::add_subaddress_index(uint32_t acc_id, uint32_t addr_id)
{
auto& device = hw::get_device("default");
subaddress_index idx {acc_id, addr_id};
auto pub_spendkey = device.get_subaddress_spend_public_key(
*(this->keys()), idx);
auto it = subaddresses.insert(
std::make_pair(std::move(pub_spendkey),
idx));
return it.first;
}
void
PrimaryAccount::populate_subaddress_indices(uint32_t last_acc_id)
{
auto& device = hw::get_device("default");
auto const& account_keys = *(this->keys());
// first we populate for account of 0 as we
// skip subaddr of 0.
auto public_keys = device.get_subaddress_spend_public_keys(
account_keys, 0, 1, 200);
for (uint32_t addr_id {1}; addr_id < 200; ++addr_id)
{
subaddresses.insert({public_keys[addr_id-1],
{0, addr_id}});
}
// for all remaning accounts, we generated subaddresses
// from 0
for (uint32_t acc_id {1}; acc_id < last_acc_id; ++acc_id)
{
auto public_keys = device.get_subaddress_spend_public_keys(
account_keys, acc_id, 0, 200);
for (uint32_t addr_id {0}; addr_id < 200; ++addr_id)
{
subaddresses.insert({public_keys[addr_id],
{acc_id, addr_id}});
}
}
}
}

@ -64,13 +64,50 @@ public:
inline auto const& vk() const
{return viewkey;}
inline auto vk2str() const
{return viewkey ? pod_to_hex(*viewkey) : ""s;}
inline auto const& pvk() const
{return addr_info.address.m_view_public_key;}
inline auto pvk2str() const
{return pod_to_hex(pvk());};
inline auto const& psk() const
{return addr_info.address.m_spend_public_key;}
inline auto psk2str() const
{return pod_to_hex(psk());};
inline auto const& sk() const
{return spendkey;}
inline auto index() const
{return subaddr_idx;}
inline auto const& keys()
{
if (!acc_keys)
{
assert(bool{viewkey});
account_keys akeys;
akeys.m_account_address = ai().address;
akeys.m_view_secret_key = *viewkey;
if (spendkey)
akeys.m_spend_secret_key = *spendkey;
acc_keys = std::move(akeys);
}
return acc_keys;
}
inline void set_index(subaddress_index idx)
{subaddr_idx = std::move(idx);}
inline auto sk2str() const
{return spendkey ? pod_to_hex(*spendkey) : ""s;}
@ -95,10 +132,11 @@ public:
protected:
network_type nettype {network_type::STAGENET};
address_parse_info addr_info;
address_parse_info addr_info {};
boost::optional<secret_key> viewkey;
boost::optional<secret_key> spendkey;
boost::optional<subaddress_index> subaddr_idx;
boost::optional<account_keys> acc_keys;
};
@ -117,39 +155,102 @@ public:
{return NONE;}
};
class PrimaryAccount : public Account
class SubaddressAccount : public Account
{
public:
using Account::Account;
virtual ADDRESS_TYPE type() const override
{return PRIMARY;}
using Account::Account;
virtual inline ADDRESS_TYPE type() const override
{return SUBADDRESS;}
};
class SubaddressAccount : public Account
class PrimaryAccount : public Account
{
public:
using Account::Account;
virtual inline ADDRESS_TYPE type() const override
{return SUBADDRESS;}
using subaddr_map_t = std::unordered_map<
public_key,
subaddress_index>;
template <typename... T>
PrimaryAccount(T&&... args)
: Account(std::forward<T>(args)...)
{
subaddr_idx = subaddress_index {0, 0};
// register the PrimaryAccount into
// subaddresses map as special case
// for uniform handling of all addresses
subaddresses[psk()] = *subaddr_idx;
}
virtual ADDRESS_TYPE type() const override
{return PRIMARY;}
std::unique_ptr<SubaddressAccount>
gen_subaddress(subaddress_index idx);
std::unique_ptr<SubaddressAccount>
gen_subaddress(uint32_t acc_id, uint32_t addr_id)
{
return gen_subaddress({acc_id, addr_id});
}
unique_ptr<subaddress_index>
has_subaddress(public_key const& pub_spend_key)
{
auto it = subaddresses.find(pub_spend_key);
if (it == subaddresses.end())
return nullptr;
return make_unique<subaddress_index>(it->second);
}
/**
* Unlike above, it does not produce SubaddressAcount
* It just calcualtes public spend key for a subaddress
* with given index and saves it in subaddresses map
*/
subaddr_map_t::const_iterator
add_subaddress_index(uint32_t acc_id, uint32_t addr_id);
/**
* Generates all set public spend keys for
* 50 accounts x 200 subaddresess into
* subaddresses map
*/
void
populate_subaddress_indices(uint32_t last_acc_id = 50);
auto begin() { return subaddresses.begin(); }
auto begin() const { return subaddresses.cbegin(); }
auto end() { return subaddresses.end(); }
auto end() const { return subaddresses.cend(); }
private:
subaddr_map_t subaddresses;
};
// account_factory functions are helper functions
// to easly create Account objects through uniqute_ptr
static unique_ptr<Account>
account_factory()
make_account()
{
return make_unique<EmptyAccount>();
}
template <typename... T>
static unique_ptr<Account>
account_factory(string const& addr_str,
make_account(string const& addr_str,
T&&... args)
{
auto&& net_and_addr_type = nettype_based_on_address(addr_str);
auto&& net_and_addr_type
= nettype_based_on_address(addr_str);
if (net_and_addr_type.first == network_type::UNDEFINED)
{
@ -157,20 +258,23 @@ account_factory(string const& addr_str,
}
if (net_and_addr_type.second == address_type::SUBADDRESS)
return make_unique<SubaddressAccount>(net_and_addr_type.first,
addr_str,
std::forward<T>(args)...);
return make_unique<SubaddressAccount>(
net_and_addr_type.first,
addr_str,
std::forward<T>(args)...);
else if (net_and_addr_type.second == address_type::REGULAR
|| net_and_addr_type.second == address_type::INTEGRATED)
return make_unique<PrimaryAccount>(net_and_addr_type.first,
addr_str,
std::forward<T>(args)...);
return make_unique<PrimaryAccount>(
net_and_addr_type.first,
addr_str,
std::forward<T>(args)...);
return nullptr;
}
template <typename... T>
static unique_ptr<Account>
account_factory(network_type net_type,
make_account(network_type net_type,
address_parse_info const& addr_info,
T&&... args)
{
@ -179,15 +283,76 @@ account_factory(network_type net_type,
return nullptr;
if (addr_info.is_subaddress)
return make_unique<SubaddressAccount>(net_type, addr_info,
std::forward<T>(args)...);
return make_unique<SubaddressAccount>(
net_type, addr_info,
std::forward<T>(args)...);
else
return make_unique<PrimaryAccount>(net_type, addr_info,
std::forward<T>(args)...);
return make_unique<PrimaryAccount>(
net_type, addr_info,
std::forward<T>(args)...);
}
template <typename... T>
static unique_ptr<Account>
make_account(subaddress_index idx, T&&... args)
{
auto acc = make_account(std::forward<T>(args)...);
if (!acc)
return nullptr;
if (acc->is_subaddress())
acc->set_index(std::move(idx));
return acc;
}
template <typename... T>
static unique_ptr<PrimaryAccount>
make_primaryaccount(string const& addr_str,
T&&... args)
{
auto&& net_and_addr_type
= nettype_based_on_address(addr_str);
if (net_and_addr_type.first == network_type::UNDEFINED)
{
return nullptr;
}
if (net_and_addr_type.second == address_type::REGULAR
|| net_and_addr_type.second == address_type::INTEGRATED)
{
auto pacc = make_unique<PrimaryAccount>(
net_and_addr_type.first,
addr_str,
std::forward<T>(args)...);
pacc->populate_subaddress_indices();
return pacc;
}
return nullptr;
}
template <typename... T>
static unique_ptr<Account>
make_primaryaccount(network_type net_type,
address_parse_info const& addr_info,
T&&... args)
{
if (!crypto::check_key(addr_info.address.m_view_public_key)
|| !crypto::check_key(addr_info.address.m_spend_public_key))
return nullptr;
return make_unique<PrimaryAccount>(
net_type, addr_info,
std::forward<T>(args)...);
}
unique_ptr<SubaddressAccount>
create(PrimaryAccount const& acc, subaddress_index idx);
inline secret_key
Account::parse_secret_key(string const& sk)
@ -216,10 +381,30 @@ Account::ai_to_str(address_parse_info const& addr_info,
inline std::ostream&
operator<<(std::ostream& os, Account const& _acc)
{
string subaddr_str {"n/a"};
if (_acc.subaddr_idx)
{
stringstream ss;
ss << *_acc.subaddr_idx;
subaddr_str = ss.str();
}
string spendkey_str {"N/A"};
if (_acc.sk())
{
spendkey_str = _acc.sk2str();
}
return os << "nt:" << static_cast<size_t>(_acc.nettype)
<< "," << subaddr_str
<< ",a:" << _acc.ai2str()
<< ",v:" << _acc.vk2str()
<< ",s:" << _acc.sk2str();
<< ",s:" << spendkey_str;
}
}

@ -98,6 +98,22 @@ Output::identify(transaction const& tx,
}
}
}
auto const& pub_spend_key
= get_address()->address.m_spend_public_key;
// if we have PrimaryAccount we can check
// if a given output belongs to any of its
// its subaddresses
PrimaryAccount* pacc {nullptr};
if (acc && !acc->is_subaddress())
{
// so we have primary address
pacc = static_cast<PrimaryAccount*>(acc);
}
for (auto i = 0u; i < tx.vout.size(); ++i)
{
@ -112,15 +128,26 @@ Output::identify(transaction const& tx,
uint64_t amount = tx.vout[i].amount;
// get the tx output public key
// that normally would be generated for us,
// if someone had sent us some xmr.
public_key generated_tx_pubkey;
// calculate public spendkey using derivation
// and tx output key. If this is our output
// the caulcualted public spendkey should match
// our actuall public spend key avaiable in our
// public monero address. Primary address is
// a special case of subaddress.
derive_public_key(derivation,
i,
get_address()->address.m_spend_public_key,
generated_tx_pubkey);
// we are always going to have the subaddress_spend
// key if an output is ours
crypto::public_key subaddress_spendkey;
// however we might not have its index, in case
// we are not using primary addresses directly
// but instead use a subaddress for searching
// outputs
std::unique_ptr<subaddress_index> subaddr_idx;
hwdev.derive_subaddress_public_key(
txout_key.key, derivation, i,
subaddress_spendkey);
// this derivation is going to be saved
// it can be one of addiitnal derivations
@ -128,26 +155,51 @@ Output::identify(transaction const& tx,
// which cointains subaddress
auto derivation_to_save = derivation;
// cout << pod_to_hex(derivation) << ", " << i << ", "
// << pod_to_hex(get_address()->address.m_spend_public_key) << ", "
// << pod_to_hex(txout_key.key) << " == "
// << pod_to_hex(generated_tx_pubkey) << '\n' << '\n';
bool mine_output {false};
// check if generated public key matches the current output's key
bool mine_output = (txout_key.key == generated_tx_pubkey);
if (!pacc)
{
// if pacc is not given, we check generated
// subaddress_spendkey against the spendkey
// of the address for which the Output identifier
// was instantiated
mine_output = (pub_spend_key == subaddress_spendkey);
}
else
{
// if pacc is given, we are going to use its
// subaddress unordered map to check if generated
// subaddress_spendkey is one of its keys. this is
// because the map can contain spendkeys of subaddreses
// assiciated with primary address. primary address's
// spendkey will be one of the keys as a special case
subaddr_idx = pacc->has_subaddress(subaddress_spendkey);
mine_output = bool {subaddr_idx};
}
auto with_additional = false;
if (!mine_output && !additional_tx_pub_keys.empty())
{
// check for output using additional tx public keys
derive_public_key(additional_derivations[i],
i,
get_address()->address.m_spend_public_key,
generated_tx_pubkey);
mine_output = (txout_key.key == generated_tx_pubkey);
hwdev.derive_subaddress_public_key(
txout_key.key, additional_derivations[i],
i,
subaddress_spendkey);
// do same comparison as above depending of the
// avaliabity of the PrimaryAddress Account
if (!pacc)
{
mine_output = (pub_spend_key == subaddress_spendkey);
}
else
{
subaddr_idx = pacc->has_subaddress(subaddress_spendkey);
mine_output = bool {subaddr_idx};
}
with_additional = true;
}
@ -216,13 +268,19 @@ Output::identify(transaction const& tx,
identified_outputs.emplace_back(
info{
generated_tx_pubkey, amount, i,
txout_key.key, amount, i,
derivation_to_save,
rtc_outpk, rtc_mask, rtc_amount
rtc_outpk, rtc_mask, rtc_amount,
subaddress_spendkey
});
total_xmr += amount;
if (subaddr_idx)
{
auto& out = identified_outputs.back();
out.subaddr_idx = *subaddr_idx;
}
total_xmr += amount;
} // if (mine_output)
} // for (uint64_t i = 0; i < tx.vout.size(); ++i)
@ -241,7 +299,7 @@ Output::decode_ringct(rct::rctSig const& rv,
{
crypto::secret_key scalar1;
hw::get_device("default").derivation_to_scalar(derivation, i, scalar1);
hwdev.derivation_to_scalar(derivation, i, scalar1);
switch (rv.type)
{
@ -252,14 +310,14 @@ Output::decode_ringct(rct::rctSig const& rv,
rct::sk2rct(scalar1),
i,
mask,
hw::get_device("default"));
hwdev);
break;
case rct::RCTTypeFull:
amount = rct::decodeRct(rv,
rct::sk2rct(scalar1),
i,
mask,
hw::get_device("default"));
hwdev);
break;
default:
cerr << "Unsupported rct type: " << rv.type << '\n';
@ -403,7 +461,6 @@ Input::generate_key_image(const crypto::key_derivation& derivation,
try
{
crypto::derive_secret_key(derivation, i,
sec_key,
in_ephemeral.sec);
@ -449,7 +506,7 @@ GuessInput::identify(transaction const& tx,
known_outputs_t known_outputs_map;
auto input_no = tx.vin.size();
for (auto i = 0u; i < input_no; ++i)
{
if(tx.vin[i].type() != typeid(txin_to_key))
@ -491,13 +548,24 @@ GuessInput::identify(transaction const& tx,
// use Output universal identifier to identify our outputs
// in a mixin tx
std::unique_ptr<Output> output_identifier;
if (acc)
{
output_identifier = make_unique<Output>(acc);
}
else
{
output_identifier = make_unique<Output>(
get_address(), get_viewkey());
}
auto identifier = make_identifier(
mixin_tx,
make_unique<Output>(get_address(), get_viewkey()));
mixin_tx, std::move(output_identifier));
identifier.identify();
for (auto const& found_output: identifier.get<Output>()->get())
{
// add found output into the map of known ouputs
@ -590,9 +658,21 @@ void RealInput::identify(transaction const& tx,
// use Output universal identifier to identify our outputs
// in a mixin tx
std::unique_ptr<Output> output_identifier;
if (acc)
{
output_identifier = make_unique<Output>(acc);
}
else
{
output_identifier = make_unique<Output>(
get_address(), get_viewkey());
}
auto identifier = make_identifier(
mixin_tx,
make_unique<Output>(get_address(), get_viewkey()));
mixin_tx, std::move(output_identifier));
identifier.identify();
@ -600,21 +680,56 @@ void RealInput::identify(transaction const& tx,
for (auto const& found_output: identifier.get<Output>()->get())
{
//cout << "found_output: " << found_output << endl;
// generate key_image using this output
// to check for sure if the given key image is ours
// or not
crypto::key_image key_img_generated;
if (!generate_key_image(found_output.derivation,
found_output.idx_in_tx, /* position in the tx */
*spendkey,
get_address()->address.m_spend_public_key,
key_img_generated))
if (acc)
{
throw std::runtime_error("Cant generate "
"key image for output: "
+ pod_to_hex(found_output.pub_key));
// if we have primary account, as we should when
// we want to include
// for spendings from subaddresses, use the below procedure
// to calcualted key_img_generated
cryptonote::keypair in_ephemeral;
if (!generate_key_image_helper_precomp(*acc->keys(),
found_output.pub_key,
found_output.derivation,
found_output.idx_in_tx,
found_output.subaddr_idx,
in_ephemeral,
key_img_generated,
hwdev))
{
throw std::runtime_error("Cant get key_img_generated");
}
(void) in_ephemeral;
}
else
{
// if we don't have acc, i.e., dont have info about subaddresses
// then use the simpler way
// to calcualate key_img_generated
if (!generate_key_image(found_output.derivation,
found_output.idx_in_tx, /* position in the tx */
*spendkey,
get_address()->address.m_spend_public_key,
key_img_generated))
{
throw std::runtime_error("Cant generate "
"key image for output: "
+ pod_to_hex(found_output.pub_key));
}
}
//cout << pod_to_hex(in_key.k_image) << " == "
//<< pod_to_hex(key_img_generated) << '\n';
// now check if current key image in the tx which we
// analyze matches generated key image

@ -1,6 +1,7 @@
#pragma once
#include "MicroCore.h"
#include "Account.h"
#include <tuple>
#include <utility>
@ -34,9 +35,16 @@ public:
BaseIdentifier(
address_parse_info const* _address,
secret_key const* _viewkey)
: address_info {_address}, viewkey {_viewkey}
: address_info {_address}, viewkey {_viewkey},
hwdev {hw::get_device("default")}
{}
BaseIdentifier(Account* _acc)
: BaseIdentifier(&_acc->ai(), &*_acc->vk())
{
acc = _acc;
}
virtual void identify(transaction const& tx,
public_key const& tx_pub_key,
vector<public_key> const& additional_tx_pub_keys
@ -52,6 +60,8 @@ protected:
address_parse_info const* address_info {nullptr};
secret_key const* viewkey {nullptr};
uint64_t total_xmr {0};
Account* acc {nullptr};
hw::device& hwdev;
};
/**
@ -62,10 +72,7 @@ class Output : public BaseIdentifier
{
public:
Output(address_parse_info const* _address,
secret_key const* _viewkey)
: BaseIdentifier(_address, _viewkey)
{}
using BaseIdentifier::BaseIdentifier;
void identify(transaction const& tx,
public_key const& tx_pub_key,
@ -78,8 +85,6 @@ public:
}
bool
decode_ringct(rct::rctSig const& rv,
crypto::key_derivation const& derivation,
@ -97,6 +102,17 @@ public:
rct::key rtc_mask;
rct::key rtc_amount;
public_key subaddress_spendkey;
subaddress_index subaddr_idx {
UINT32_MAX, UINT32_MAX};
// the max value means not given
bool has_subaddress_index() const
{
return subaddr_idx.major != UINT32_MAX
&& subaddr_idx.minor != UINT32_MAX;
}
friend std::ostream& operator<<(std::ostream& os,
info const& _info);
};
@ -125,6 +141,14 @@ public:
known_outputs {_known_outputs},
mcore {_mcore}
{}
Input(Account* _acc,
known_outputs_t const* _known_outputs,
AbstractCore const* _mcore)
: BaseIdentifier(_acc),
known_outputs {_known_outputs},
mcore {_mcore}
{}
void identify(transaction const& tx,
public_key const& tx_pub_key,
@ -177,6 +201,10 @@ public:
MicroCore* _mcore)
: Input(_a, _viewkey, nullptr, _mcore)
{}
GuessInput(Account* _acc, MicroCore* _mcore)
: Input(_acc, nullptr, _mcore)
{}
void identify(transaction const& tx,
public_key const& tx_pub_key,
@ -204,6 +232,13 @@ public:
: Input(_a, _viewkey, nullptr, _mcore),
spendkey {_spendkey}
{}
RealInput(Account* _acc, MicroCore* _mcore)
: Input(_acc, nullptr, _mcore)
{
assert(_acc->sk());
spendkey = &(*_acc->sk());
}
void identify(transaction const& tx,
public_key const& tx_pub_key,
@ -223,11 +258,19 @@ class PaymentID : public BaseIdentifier
public:
using payments_t = tuple<crypto::hash, crypto::hash8>;
PaymentID()
: BaseIdentifier(nullptr, nullptr)
{}
PaymentID(address_parse_info const* _address,
secret_key const* _viewkey)
: BaseIdentifier(_address, _viewkey)
{}
PaymentID(Account* _acc)
: BaseIdentifier(_acc)
{}
void identify(transaction const& tx,
public_key const& tx_pub_key,
@ -239,18 +282,34 @@ public:
payment_id_tuple = get_payment_id(tx);
payment_id = std::get<HashT>(payment_id_tuple);
//cout << "payment_id_found: "
//<< pod_to_hex(*payment_id) << endl;
// if no payment_id found, return
if (payment_id == null_hash || get_viewkey() == nullptr)
if (*payment_id == null_hash)
{
payment_id = boost::none;
return;
}
// if no viewkey and we have integrated payment id
if (get_viewkey() == nullptr
&& sizeof(*payment_id) == sizeof(crypto::hash8))
{
payment_id = boost::none;
return;
}
// decrypt integrated payment id. if its legacy payment id
// nothing will happen.
if (!decrypt(payment_id, tx_pub_key))
if (!decrypt(*payment_id, tx_pub_key))
{
throw std::runtime_error("Cant decrypt pay_id: "
+ pod_to_hex(payment_id));
}
}
}
payments_t
@ -288,7 +347,7 @@ public:
{return std::get<HashT>(payment_id_tuple);}
private:
HashT payment_id {};
boost::optional<HashT> payment_id;
HashT null_hash {};
payments_t payment_id_tuple;
};
@ -376,9 +435,14 @@ calc_total_xmr(T&& infos)
inline std::ostream&
operator<<(std::ostream& os, xmreg::Output::info const& _info)
{
return os << _info.idx_in_tx << ", "
<< pod_to_hex(_info.pub_key) << ", "
<< _info.amount;
os << _info.idx_in_tx << ", "
<< pod_to_hex(_info.pub_key) << ", "
<< _info.amount;
if (_info.has_subaddress_index())
os << ", " << _info.subaddr_idx;
return os;
}
inline std::ostream&
@ -389,5 +453,16 @@ operator<<(std::ostream& os, xmreg::Input::info const& _info)
<< _info.amount;
}
template <typename T>
inline std::ostream&
operator<<(std::ostream& os, std::vector<T> const& _infos)
{
for (auto&& _info: _infos)
os << _info << '\n';
return os;
}
}

@ -220,4 +220,41 @@ nettype_based_on_address(string const& address)
return {determined_network_type, determined_address_type};
}
boost::optional<subaddress_index>
parse_subaddress_index(string idx_str)
{
vector<string> split_index;
boost::split(split_index, idx_str,
boost::is_any_of(",/"));
if (split_index.empty()
|| split_index.size() != 2)
{
cerr << "Incorrect subaddress index given: "
<< idx_str << '\n';
return {};
}
try
{
auto idx_major
= boost::lexical_cast<uint32_t>(split_index[0]);
auto idx_minor
= boost::lexical_cast<uint32_t>(split_index[1]);
return subaddress_index {idx_major, idx_minor};
}
catch (boost::bad_lexical_cast const& e)
{
cerr << e.what() << '\n';
}
return {};
}
}

@ -76,4 +76,7 @@ for_each_network_type(F f)
pair<network_type, address_type>
nettype_based_on_address(string const& address);
boost::optional<subaddress_index>
parse_subaddress_index(string idx_str);
}

@ -106,7 +106,7 @@ JsonTx::get_output_key(
out_pk,
jring_member["unlock_time"],
jring_member["height"],
commitment });
commitment});
}
}
}
@ -122,13 +122,12 @@ JsonTx::init()
hex_to_pod(jtx["payment_id"], payment_id);
}
if (jtx.count("payment_id8"))
{
hex_to_pod(jtx["payment_id8"], payment_id8);
hex_to_pod(jtx["payment_id8e"], payment_id8e);
cout << "jtx[\"payment_id8e\"] "<< jtx["tx_hash"]
<< ", " << jtx["payment_id8e"] << endl;
//cout << "jtx[\"payment_id8e\"] "<< jtx["tx_hash"]
//<< ", " << jtx["payment_id8e"] << endl;
}
@ -181,6 +180,16 @@ JsonTx::init()
// recipients dont have inputs so we do not populate
// them here.
if (jrecpient.count("subaddress_index"))
{
auto saddr_idx = parse_subaddress_index(
jrecpient["subaddress_index"]);
if (saddr_idx)
{
recipients.back().subaddr_idx = saddr_idx;
}
}
}
}

@ -47,6 +47,7 @@ public:
uint64_t change {0};
vector<output> outputs;
vector<input> inputs;
boost::optional<subaddress_index> subaddr_idx;
inline string
address_str() const

@ -14,7 +14,7 @@ using namespace xmreg;
TEST(ACCOUNT, DefaultConstruction)
{
auto acc = account_factory();
auto acc = make_account();
EXPECT_EQ(acc->type(), Account::ADDRESS_TYPE::NONE);
EXPECT_FALSE(*acc);
@ -26,7 +26,7 @@ TEST(ACCOUNT, FullConstruction)
ASSERT_TRUE(jtx);
auto acc = account_factory(jtx->ntype,
auto acc = make_account(jtx->ntype,
jtx->sender.address,
jtx->sender.viewkey,
jtx->sender.spendkey);
@ -40,6 +40,10 @@ TEST(ACCOUNT, FullConstruction)
EXPECT_EQ(acc->vk2str(), sender["viewkey"]);
EXPECT_EQ(acc->sk2str(), sender["spendkey"]);
subaddress_index idx {0, 0};
EXPECT_EQ(acc->index(), idx);
EXPECT_FALSE(acc->ai().is_subaddress);
EXPECT_TRUE(acc);
@ -53,7 +57,7 @@ TEST(ACCOUNT, FullConstructionFromStrings)
auto const& sender = jtx->jtx["sender"];
auto acc = account_factory(sender["address"],
auto acc = make_account(sender["address"],
sender["viewkey"],
sender["spendkey"]);
@ -73,7 +77,7 @@ TEST(ACCOUNT, OnlyAddressAndViewkeyFromStrings)
auto const& sender = jtx->jtx["sender"];
auto acc = account_factory(sender["address"],
auto acc = make_account(sender["address"],
sender["viewkey"]);
EXPECT_EQ(acc->type(), Account::ADDRESS_TYPE::PRIMARY);
@ -91,7 +95,7 @@ TEST(ACCOUNT, NoSpendandViewKeysConstruction)
auto const& sender = jtx->jtx["sender"];
auto acc = account_factory(sender["address"],
auto acc = make_account(sender["address"],
sender["viewkey"]);
EXPECT_EQ(acc->nt(), jtx->ntype);
@ -99,7 +103,7 @@ TEST(ACCOUNT, NoSpendandViewKeysConstruction)
EXPECT_EQ(acc->vk(), jtx->sender.viewkey);
EXPECT_FALSE(acc->sk());
auto acc2 = account_factory(sender["address"]);
auto acc2 = make_account(sender["address"]);
EXPECT_EQ(acc2->nt(), jtx->ntype);
EXPECT_EQ(acc2->ai().address, jtx->sender.address.address);
@ -115,7 +119,7 @@ TEST(ACCOUNT, FullConstructionSubAddress)
auto const& recipient1 = jtx->recipients[1];
auto acc = account_factory(jtx->ntype,
auto acc = make_account(jtx->ntype,
recipient1.address,
recipient1.viewkey,
recipient1.spendkey);
@ -128,6 +132,8 @@ TEST(ACCOUNT, FullConstructionSubAddress)
EXPECT_EQ(acc->sk2str(), jrecipient["spendkey"]);
EXPECT_EQ(acc->type(), Account::ADDRESS_TYPE::SUBADDRESS);
EXPECT_FALSE(acc->index());
EXPECT_TRUE(acc->ai().is_subaddress);
EXPECT_TRUE(acc);
}
@ -138,7 +144,7 @@ TEST(ACCOUNT, FailedConstructionFromString1)
string const wrong_viewkey = "fgdgsfdfgs";
string const wrong_spendkey = "fgdgsfdfgs";
auto acc = account_factory(wrong_address, wrong_viewkey, wrong_spendkey);
auto acc = make_account(wrong_address, wrong_viewkey, wrong_spendkey);
EXPECT_EQ(acc, nullptr);
}
@ -149,7 +155,7 @@ TEST(ACCOUNT, FailedConstructionFromString2)
string const wrong_address = "56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbP";
string const wrong_viewkey = "b45e6f38b2cd1c667459527decb438cdeadf9c64d93c8bccf40a9bf98943dc09";
auto acc = account_factory(wrong_address, wrong_viewkey);
auto acc = make_account(wrong_address, wrong_viewkey);
EXPECT_EQ(acc, nullptr);
}
@ -176,7 +182,7 @@ TEST(ACCOUNT, FailedConstructionFromNonString)
wrong_address.address.m_spend_public_key
= wrong_address.address.m_view_public_key;
auto acc = account_factory(network_type::STAGENET, wrong_address);
auto acc = make_account(network_type::STAGENET, wrong_address);
EXPECT_EQ(acc, nullptr);
}
@ -203,10 +209,159 @@ TEST(SUBADDRESS, BasicGenerationTest)
string subaddr_str = get_account_address_as_str(jtx->ntype,
!index.is_zero(), subaddres);
auto acc = make_account(index, subaddr_str);
//cout << i << ": " << subaddr_str << '\n';
EXPECT_EQ(subaddr_str,
"77JBM7fQNgNKyqHN8dc7DN1mJ4CQZyHg5fXFUstQcHCYEp3rUXVGd8U8ezAdNPDwW7AxejmjQLhz9HjtuW4BwvCdBAcGxH5"s);
EXPECT_TRUE(acc->is_subaddress());
EXPECT_EQ(acc->ai2str(), subaddr_str);
EXPECT_EQ(acc->index(), index);
}
TEST(SUBADDRESS, UsingGenSubAddress)
{
auto jtx = construct_jsontx("d7dcb2daa64b5718dad71778112d48ad62f4d5f54337037c420cb76efdd8a21c");
ASSERT_TRUE(jtx);
auto const& sender = jtx->jtx["sender"];
auto acc = make_account(sender["address"],
sender["viewkey"]);
ASSERT_FALSE(acc->is_subaddress());
auto pacc = static_cast<PrimaryAccount*>(acc.get());
auto sacc = pacc->gen_subaddress({0, 1});
EXPECT_EQ(sacc->ai2str(),
"77JBM7fQNgNKyqHN8dc7DN1mJ4CQZyHg5fXFUstQcHCYEp3rUXVGd8U8ezAdNPDwW7AxejmjQLhz9HjtuW4BwvCdBAcGxH5"s);
}
TEST(SUBADDRESS, UsingGenSubAddress1)
{
// monerowalletstagenet3
string address = "56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbPV";
string spendkey= "df0f5720ae0b69454ca7db35db677272c7c19513cd0dc4147b0e00792a10f406";
string viewkey = "b45e6f38b2cd1c667459527decb438cdeadf9c64d93c8bccf40a9bf98943dc09";
auto acc = make_account(address, viewkey, spendkey);
EXPECT_EQ(acc->type(), Account::ADDRESS_TYPE::PRIMARY);
auto pacc = static_cast<PrimaryAccount*>(acc.get());
string expected_subaddress_0_1 = "77JBM7fQNgNKyqHN8dc7DN1mJ4CQZyHg5fXFUstQcHCYEp3rUXVGd8U8ezAdNPDwW7AxejmjQLhz9HjtuW4BwvCdBAcGxH5";
auto sacc1 = pacc->gen_subaddress(0, 1);
EXPECT_EQ(sacc1->ai2str(), expected_subaddress_0_1);
EXPECT_EQ(sacc1->vk2str(), viewkey);
EXPECT_EQ(sacc1->sk2str(), spendkey);
string expected_subaddress_0_10 = "7BTd51FGijwGquK2aPpiTPNvdKrf2CQpkCy2Z8EWFgSK1bapBkjvw2pLenzdLWeP91C3o5SHVuhRggQNHi3jP8jERBiZkBS";
auto sacc2 = pacc->gen_subaddress(0, 10);
EXPECT_EQ(sacc2->ai2str(), expected_subaddress_0_10);
EXPECT_EQ(sacc2->vk2str(), viewkey);
EXPECT_EQ(sacc2->sk2str(), spendkey);
EXPECT_TRUE(sacc2->is_subaddress());
string expected_subaddress_2_0 = "7A3KWyKJVWbc6ZKnygwoCSVsuSVHEvR3zGm4ak8cLdN3CDjDiKfdcQDfCTojpQX35PZtxqGJohm3aGxdvw7TMTLGBVkWZ3t";
auto sacc3 = pacc->gen_subaddress(2, 0);
EXPECT_EQ(sacc3->ai2str(), expected_subaddress_2_0);
EXPECT_EQ(sacc3->vk2str(), viewkey);
EXPECT_EQ(sacc3->sk2str(), spendkey);
EXPECT_TRUE(sacc3->is_subaddress());
string expected_subaddress_3_5 = "73TUymFmFiqejXhrr38VAP15CrcLF7efYNi7DzC4yKvZVM6a5PhXp3v76uVagnZFSTTJrtGSXqnYEYX8JUqnFBtbUG3QaTi";
auto sacc4 = pacc->gen_subaddress(3, 5);
EXPECT_EQ(sacc4->ai2str(), expected_subaddress_3_5);
EXPECT_EQ(sacc4->vk2str(), viewkey);
EXPECT_EQ(sacc4->sk2str(), spendkey);
EXPECT_TRUE(sacc4->is_subaddress());
subaddress_index idx4 {3, 5};
EXPECT_EQ(sacc4->index().value(), idx4);
}
TEST(SUBADDRESS, UsingGenSubAddressNoSpendkey)
{
// monerowalletstagenet3
string address = "56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbPV";
string viewkey = "b45e6f38b2cd1c667459527decb438cdeadf9c64d93c8bccf40a9bf98943dc09";
auto acc = make_account(address, viewkey);
EXPECT_EQ(acc->type(), Account::ADDRESS_TYPE::PRIMARY);
auto pacc = static_cast<PrimaryAccount*>(acc.get());
string expected_subaddress_0_1 = "77JBM7fQNgNKyqHN8dc7DN1mJ4CQZyHg5fXFUstQcHCYEp3rUXVGd8U8ezAdNPDwW7AxejmjQLhz9HjtuW4BwvCdBAcGxH5";
auto sacc1 = pacc->gen_subaddress(0, 1);
EXPECT_EQ(sacc1->ai2str(), expected_subaddress_0_1);
EXPECT_EQ(sacc1->vk2str(), viewkey);
EXPECT_EQ(sacc1->sk2str(), "");
}
TEST(SUBADDRESS, UsingGenSubAddressNoViewkey)
{
// monerowalletstagenet3
string address = "56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbPV";
auto acc = make_account(address);
EXPECT_EQ(acc->type(), Account::ADDRESS_TYPE::PRIMARY);
auto pacc = static_cast<PrimaryAccount*>(acc.get());
auto sacc1 = pacc->gen_subaddress(0, 1);
EXPECT_EQ(sacc1, nullptr);
}
TEST(SUBADDRESS, AddSubAddressNoSpendkey)
{
// monerowalletstagenet3
string address = "56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbPV";
string viewkey = "b45e6f38b2cd1c667459527decb438cdeadf9c64d93c8bccf40a9bf98943dc09";
auto acc = make_account(address, viewkey);
EXPECT_EQ(acc->type(), Account::ADDRESS_TYPE::PRIMARY);
auto pacc = static_cast<PrimaryAccount*>(acc.get());
subaddress_index idx1 {2, 4};
auto sacc_it = pacc->add_subaddress_index(2, 4);
auto sacc = pacc->gen_subaddress(sacc_it->second);
EXPECT_EQ(*sacc->index(), idx1);
}
TEST(SUBADDRESS, PupulateSubaddresses)
{
// monerowalletstagenet3
string address = "56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbPV";
string viewkey = "b45e6f38b2cd1c667459527decb438cdeadf9c64d93c8bccf40a9bf98943dc09";
auto pacc = make_primaryaccount(address, viewkey);
EXPECT_EQ(pacc->type(), Account::ADDRESS_TYPE::PRIMARY);
for (auto const& kv: *pacc)
{
auto sacc = pacc->gen_subaddress(kv.second);
if (!sacc) continue;
cout << *sacc << endl;
EXPECT_EQ(kv.first, sacc->psk());
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -64,6 +64,7 @@ operator==(const vector<Output::info>& lhs, const vector<JsonTx::output>& rhs)
// equality operators for inputs
inline bool
operator==(const Input::info& lhs, const JsonTx::input& rhs)
{
@ -79,7 +80,8 @@ operator!=(const Input::info& lhs, const JsonTx::input& rhs)
}
inline bool
operator==(const vector<Input::info>& lhs, const vector<JsonTx::input>& rhs)
operator==(const vector<Input::info>& lhs,
const vector<JsonTx::input>& rhs)
{
if (lhs.size() != rhs.size())
return false;
@ -91,8 +93,38 @@ operator==(const vector<Input::info>& lhs, const vector<JsonTx::input>& rhs)
}
return true;
}
inline bool
operator==(const Input::info& lhs, const Input::info& rhs)
{
return lhs.amount == rhs.amount
&& lhs.out_pub_key == rhs.out_pub_key
&& lhs.key_img == rhs.key_img;
}
inline bool
operator!=(const Input::info& lhs, const Input::info& rhs)
{
return !(lhs == rhs);
}
inline bool
operator==(const vector<Input::info>& lhs,
const vector<Input::info>& rhs)
{
if (lhs.size() != rhs.size())
return false;
for (size_t i = 0; i < lhs.size(); i++)
{
if (lhs[i] != rhs[i])
return false;
}
return true;
}
class DifferentJsonTxs :
public ::testing::TestWithParam<string>
{
@ -114,7 +146,10 @@ INSTANTIATE_TEST_CASE_P(
"a7a4e3bdb305b97c43034440b0bc5125c23b24d0730189261151c0aa3f2a05fc"s,
"c06df274acc273fbce0666b2c8846ac6925a1931fb61e3020b7cc5410d4646b1"s,
"d89f32f1434b6a668cbbc5c55cb1c0c64e41fccb89f6b1eef210fefdacbdd89f"s,
"bd461b938c3c8c8e4d9909852221d5c37350ade05e99ef836d6ccb628f6a5a0e"s
"bd461b938c3c8c8e4d9909852221d5c37350ade05e99ef836d6ccb628f6a5a0e"s,
"f81ecd0381c0b89f23cffe86a799e924af7b5843c663e8c07db98a14e913585e"s,
"386ac4fbf7d3d2ab6fd4f2d9c2e97d00527ca2867e33cd7aedb1fd05a4b791ec"s,
"e658966b256ca30c85848751ff986e3ba7c7cfdadeb46ee1a845a042b3da90db"s
));
TEST_P(ModularIdentifierTest, OutputsRingCT)
@ -192,15 +227,23 @@ TEST_P(ModularIdentifierTest, LegacyPaymentID)
auto jtx = construct_jsontx(tx_hash_str);
auto identifier = make_identifier(jtx->tx,
make_unique<LegacyPaymentID>(nullptr, nullptr));
make_unique<LegacyPaymentID>());
identifier.identify();
EXPECT_TRUE(identifier.get<0>()->get()
== jtx->payment_id);
auto pid = identifier.get<0>()->get();
//cout << pod_to_hex(jtx->payment_id) << '\n';
EXPECT_TRUE(identifier.get<0>()->raw()
== jtx->payment_id);
if (jtx->payment_id == crypto::null_hash)
{
EXPECT_FALSE(pid);
}
else
{
EXPECT_TRUE(pid);
EXPECT_TRUE(*pid == jtx->payment_id);
}
}
@ -219,18 +262,17 @@ TEST_P(ModularIdentifierTest, IntegratedPaymentID)
identifier.identify();
//cout << "decrypted: " << pod_to_hex(identifier.get<0>()->get())
// << ", " << pod_to_hex(jtx->payment_id8e) << endl;
auto pid = identifier.get<0>()->get();
EXPECT_TRUE(identifier.get<0>()->get()
== jtx->payment_id8e);
EXPECT_TRUE(identifier.get<0>()->raw()
== jtx->payment_id8);
//cout << "row: " << pod_to_hex(identifier.get<0>()->raw())
// << ", " << pod_to_hex(jtx->payment_id8) << endl;
if (jtx->payment_id8 == crypto::null_hash8)
{
EXPECT_FALSE(pid);
}
else
{
EXPECT_TRUE(pid);
EXPECT_TRUE(*pid == jtx->payment_id8e);
}
}
TEST_P(ModularIdentifierTest, InputWithKnownOutputs)
@ -304,9 +346,6 @@ TEST_P(ModularIdentifierTest, GuessInputRingCT)
identifier.identify();
// for (auto const& input_info: identifier.get<0>()->get())
// cout << input_info << endl;
auto const& found_inputs = identifier.get<0>()->get();
EXPECT_GE(found_inputs.size(),
@ -395,4 +434,237 @@ TEST_P(ModularIdentifierTest, RealInputRingCT)
== jtx->sender.inputs);
}
TEST(Subaddresses, RegularTwoOutputTxToSubaddress)
{
// this tx has funds for one subaddress. so we try to identify the outputs
// and the subaddress using primary address of the recipient
auto jtx = construct_jsontx("024dc13cb11d411682f04d41b52931849527d530e4cb198a63526c13da31a413");
ASSERT_TRUE(jtx);
// recipeint primary address and viewkey
string const raddress {"56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbPV"};
string const rviewkey {"b45e6f38b2cd1c667459527decb438cdeadf9c64d93c8bccf40a9bf98943dc09"};
auto racc = make_primaryaccount(raddress, rviewkey);
// make sure we have primary address
ASSERT_FALSE(racc->is_subaddress());
racc->populate_subaddress_indices();
auto identifier = make_identifier(jtx->tx,
make_unique<Output>(racc.get()));
identifier.identify();
EXPECT_EQ(identifier.get<0>()->get().size(),
jtx->recipients.at(0).outputs.size());
EXPECT_TRUE(identifier.get<0>()->get()
== jtx->recipients.at(0).outputs);
auto const& output_info
= identifier.get<0>()->get().at(0);
EXPECT_TRUE(output_info.has_subaddress_index());
subaddress_index expected_idx {0, 6};
EXPECT_EQ(output_info.subaddr_idx, expected_idx);
}
TEST(Subaddresses, MultiOutputTxToSubaddress)
{
// this tx has funds for few subaddress of the same primary account.
// so we try to identify the outputs
// and the subaddresses indices using primary address of the recipient
auto jtx = construct_jsontx("f81ecd0381c0b89f23cffe86a799e924af7b5843c663e8c07db98a14e913585e");
ASSERT_TRUE(jtx);
// recipeint primary address and viewkey
string const raddress {"56heRv2ANffW1Py2kBkJDy8xnWqZsSrgjLygwjua2xc8Wbksead1NK1ehaYpjQhymGK4S8NPL9eLuJ16CuEJDag8Hq3RbPV"};
string const rviewkey {"b45e6f38b2cd1c667459527decb438cdeadf9c64d93c8bccf40a9bf98943dc09"};
auto racc = make_primaryaccount(raddress, rviewkey);
// make sure we have primary address
ASSERT_FALSE(racc->is_subaddress());
auto identifier = make_identifier(jtx->tx,
make_unique<Output>(racc.get()));
identifier.identify();
auto const& outputs_found
= identifier.get<Output>()->get();
auto total_outputs = calc_total_xmr(outputs_found);
uint64_t expected_total {0};
set<pair<string, string>> output_indices;
for (auto&& out: outputs_found)
{
stringstream ss;
ss << out.subaddr_idx;
output_indices.insert({pod_to_hex(out.pub_key),
ss.str()});
}
set<pair<string, string>> expected_indices;
for (auto const& jrecipient: jtx->recipients)
{
auto identifier_rec = make_identifier(jtx->tx,
make_unique<Output>(&jrecipient.address,
&jrecipient.viewkey));
identifier_rec.identify();
auto const& output_rec
= identifier_rec.get<Output>()->get();
expected_total += calc_total_xmr(output_rec);
for (auto&& out: output_rec)
{
stringstream ss;
ss << *jrecipient.subaddr_idx;
expected_indices.insert({pod_to_hex(out.pub_key),
ss.str()});
}
}
EXPECT_EQ(total_outputs, expected_total);
vector<decltype(output_indices)::value_type> result;
std::set_symmetric_difference(output_indices.begin(),
output_indices.end(),
expected_indices.begin(),
expected_indices.end(),
std::back_inserter(result));
EXPECT_TRUE(result.empty());
}
TEST(Subaddresses, GuessInputFromSubaddress)
{
auto jtx = construct_jsontx("386ac4fbf7d3d2ab6fd4f2d9c2e97d00527ca2867e33cd7aedb1fd05a4b791ec");
ASSERT_TRUE(jtx);
MockMicroCore mcore;
ADD_MOCKS(mcore);
// sender primary address and viewkey
string const sender_addr = jtx->sender.address_str();
string const sender_viewkey = pod_to_hex(jtx->sender.viewkey);
string const sender_spendkey = pod_to_hex(jtx->sender.spendkey);
auto sender = make_primaryaccount(sender_addr,
sender_viewkey,
sender_spendkey);
auto identifier = make_identifier(jtx->tx,
make_unique<GuessInput>(sender.get(), &mcore),
make_unique<RealInput>(sender.get(), &mcore));
identifier.identify();
auto const& found_inputs = identifier.get<GuessInput>()->get();
auto const& expected_inputs = identifier.get<RealInput>()->get();
EXPECT_EQ(found_inputs.size(), expected_inputs.size());
EXPECT_TRUE(found_inputs == expected_inputs);
// now lets check recipient outputs
// recipeint primary address and viewkey
string const raddress {"55ZbQdMnZHPFS8pmrhHN5jMpgJwnnTXpTDmmM5wkrBBx4xD6aEnpZq7dPkeDeWs67TV9HunDQtT3qF2UGYWzGGxq3zYWCBE"};
string const rviewkey {"c8a4d62e3c86de907bd84463f194505ab07fc231b3da753342d93fccb5d39203"};
auto acc = make_account(raddress, rviewkey);
ASSERT_TRUE(acc->type() == Account::PRIMARY);
auto primary_account = dynamic_cast<PrimaryAccount*>(acc.get());
ASSERT_TRUE(primary_account);
primary_account->populate_subaddress_indices();
auto ridentifier = make_identifier(jtx->tx,
make_unique<Output>(primary_account));
ridentifier.identify();
auto const& outputs_found
= ridentifier.get<Output>()->get();
cout << outputs_found << endl;
EXPECT_EQ(outputs_found.size(), 3);
auto total_outputs = calc_total_xmr(outputs_found);
EXPECT_EQ(total_outputs, 8000000000000);
vector<subaddress_index> expected_indices {{0, 1}, {0, 2}, {0, 3}};
for (size_t i {0}; i < outputs_found.size(); ++i)
{
EXPECT_EQ(outputs_found[i].subaddr_idx,
expected_indices[i]);
}
}
TEST(Subaddresses, RealInputsToSubaddress)
{
auto jtx = construct_jsontx("e658966b256ca30c85848751ff986e3ba7c7cfdadeb46ee1a845a042b3da90db");
ASSERT_TRUE(jtx);
MockMicroCore mcore;
ADD_MOCKS(mcore);
// sender primary address and viewkey
string const sender_addr = jtx->sender.address_str();
string const sender_viewkey = pod_to_hex(jtx->sender.viewkey);
string const sender_spendkey = pod_to_hex(jtx->sender.spendkey);
auto sender = make_primaryaccount(sender_addr,
sender_viewkey,
sender_spendkey);
auto identifier = make_identifier(jtx->tx,
make_unique<GuessInput>(sender.get(), &mcore),
make_unique<RealInput>(sender.get(), &mcore));
identifier.identify();
auto const& found_inputs = identifier.get<GuessInput>()->get();
auto const& expected_inputs = identifier.get<RealInput>()->get();
EXPECT_EQ(found_inputs.size(), expected_inputs.size());
EXPECT_TRUE(found_inputs == expected_inputs);
}
}

Loading…
Cancel
Save