From c83ca9c0717f2b802ba6aa87a06516feb9ebf45a Mon Sep 17 00:00:00 2001 From: moneroexamples Date: Thu, 9 May 2019 16:02:01 +0800 Subject: [PATCH] exmaple.cpp and README updated --- CMakeLists.txt | 4 +- README.md | 135 ++++++++++-- example.cpp | 317 ++++++++++++++++++++++++++++ main.cpp | 35 --- src/Account.h | 9 +- src/UniversalIdentifier.hpp | 32 ++- tests/JsonTx.cpp | 5 +- tests/universalidentifier_tests.cpp | 39 ++-- 8 files changed, 501 insertions(+), 75 deletions(-) create mode 100755 example.cpp delete mode 100755 main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e4d72d1..6db71a4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/) diff --git a/README.md b/README.md index b0b0e0c..4b15a01 100755 --- a/README.md +++ b/README.md @@ -1,27 +1,134 @@ -# Core moneroexamples repositry +# 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) - - unified representation of monero addresses/accounts - - identification of outputs for subaddresses based on primary address + - 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. +# Example usage -#### Monero download and compilation +More examples along with its full code are located in [example.cpp](https://github.com/moneroexamples/xmregcore/blob/master/example.cpp). + +### 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(primary_account.get())); + +identifier.identify(); + +// get the results of the identification +auto outputs_found = identifier.get()->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(primary_account.get()), + make_unique(primary_account.get(), &mcore)); + +identifier.identify(); + +// get identified outputs and inputs in the tx +auto outputs_found = identifier.get()->get(); +auto inputs_found = identifier.get()->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(account.get())); + +identifier.identify(); + +auto payment_id = identifier.get()->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 run this code. +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. diff --git a/example.cpp b/example.cpp new file mode 100755 index 0000000..25c050f --- /dev/null +++ b/example.cpp @@ -0,0 +1,317 @@ +#include "src/MicroCore.h" +#include "src/UniversalIdentifier.hpp" + +#include "version.h" + +#include +#include + + +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 + 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 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 const& tx) +{ + if (tx) + { + os << get_transaction_hash(*tx); + } + else + { + os << "N/A"; + } + + return os; +} + +template +inline std::ostream& +operator<<(std::ostream& os, boost::optional const& pid) +{ + if (pid) + { + os << epee::string_tools::pod_to_hex(*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(primary_account.get())); + + identifier.identify(); + + auto outputs_found = identifier.get()->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(primary_account.get())); + + identifier.identify(); + + auto outputs_found = identifier.get()->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(primary_account.get()), + make_unique(primary_account.get(), &mcore)); + + identifier.identify(); + + auto outputs_found = identifier.get()->get(); + auto inputs_found = identifier.get()->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(primary_account.get()), + make_unique(primary_account.get(), &mcore)); + + identifier.identify(); + + auto outputs_found = identifier.get()->get(); + auto inputs_found = identifier.get()->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()); + + identifier.identify(); + + auto payment_id = identifier.get()->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(account.get())); + + identifier.identify(); + + auto payment_id = identifier.get()->get(); + + + if (payment_id) + { + cout << "Found following itegrated payment id in tx " << payment_id << '\n'; + } + } + + return EXIT_SUCCESS; +} diff --git a/main.cpp b/main.cpp deleted file mode 100755 index 9e8ece4..0000000 --- a/main.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "src/MicroCore.h" -#include "src/tools.h" - -#include "version.h" - -#include -#include - - -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; -} diff --git a/src/Account.h b/src/Account.h index 4c53e5d..c0cf478 100644 --- a/src/Account.h +++ b/src/Account.h @@ -393,11 +393,18 @@ operator<<(std::ostream& os, Account const& _acc) subaddr_str = ss.str(); } + string spendkey_str {"N/A"}; + + if (_acc.sk()) + { + spendkey_str = _acc.sk2str(); + } + return os << "nt:" << static_cast(_acc.nettype) << "," << subaddr_str << ",a:" << _acc.ai2str() << ",v:" << _acc.vk2str() - << ",s:" << _acc.sk2str(); + << ",s:" << spendkey_str; } } diff --git a/src/UniversalIdentifier.hpp b/src/UniversalIdentifier.hpp index 7857045..5c0f404 100644 --- a/src/UniversalIdentifier.hpp +++ b/src/UniversalIdentifier.hpp @@ -258,11 +258,19 @@ class PaymentID : public BaseIdentifier public: using payments_t = tuple; + + 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, @@ -274,18 +282,34 @@ public: payment_id_tuple = get_payment_id(tx); payment_id = std::get(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 @@ -323,7 +347,7 @@ public: {return std::get(payment_id_tuple);} private: - HashT payment_id {}; + boost::optional payment_id; HashT null_hash {}; payments_t payment_id_tuple; }; diff --git a/tests/JsonTx.cpp b/tests/JsonTx.cpp index 65382e9..1c3b2fc 100644 --- a/tests/JsonTx.cpp +++ b/tests/JsonTx.cpp @@ -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; } diff --git a/tests/universalidentifier_tests.cpp b/tests/universalidentifier_tests.cpp index 82c594d..3dabe60 100644 --- a/tests/universalidentifier_tests.cpp +++ b/tests/universalidentifier_tests.cpp @@ -227,15 +227,23 @@ TEST_P(ModularIdentifierTest, LegacyPaymentID) auto jtx = construct_jsontx(tx_hash_str); auto identifier = make_identifier(jtx->tx, - make_unique(nullptr, nullptr)); + make_unique()); 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); + } } @@ -254,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)