diff --git a/.gitmodules b/.gitmodules index c50cfa63b..543dd15e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,6 +12,10 @@ path = external/supercop url = https://github.com/monero-project/supercop branch = monero +[submodule "external/bc-ur"] + path = external/bc-ur + url = https://github.com/MrCyjaneK/bc-ur + branch = misc [submodule "external/miniupnp"] path = external/miniupnp url = https://github.com/miniupnp/miniupnp diff --git a/CMakeLists.txt b/CMakeLists.txt index b535ffa28..2f326d9d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 17) +add_definitions(-D_LIBCPP_ENABLE_CXX17_REMOVED_FEATURES) # boost: no template named 'unary_function' in namespace 'std'; did you mean '__unary_function'? set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -367,6 +368,7 @@ if(NOT MANUAL_SUBMODULES) endfunction () message(STATUS "Checking submodules") +# check_submodule(external/bc-ur) check_submodule(external/miniupnp) check_submodule(external/rapidjson) check_submodule(external/trezor-common) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index b577ed656..47441d1ea 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -69,5 +69,6 @@ endif() add_subdirectory(db_drivers) add_subdirectory(easylogging++) add_subdirectory(qrcodegen) +add_subdirectory(bc-ur) add_subdirectory(randomx EXCLUDE_FROM_ALL) add_subdirectory(mx25519) diff --git a/external/bc-ur b/external/bc-ur new file mode 160000 index 000000000..d82e7c753 --- /dev/null +++ b/external/bc-ur @@ -0,0 +1 @@ +Subproject commit d82e7c753e710b8000706dc3383b498438795208 diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index ae8c05143..5c9f90d8e 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -51,6 +51,7 @@ monero_add_library(wallet target_link_libraries(wallet PUBLIC rpc_base + bc-ur multisig carrot_impl common diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 4af73ef14..233be2bbd 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -42,6 +42,8 @@ #include #include +#include "bc-ur/src/bc-ur.hpp" + using namespace std; namespace Monero { @@ -162,6 +164,37 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) return m_status == Status_Ok; } +std::string PendingTransactionImpl::commitUR(int max_fragment_length) { + + LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); + + try { + std::string ptx = m_wallet.m_wallet->dump_tx_to_str(m_pending_tx); + m_status = Status_Ok; + auto urMessage = ur::string_to_bytes(ptx); + ur::ByteVector cbor; + ur::CborLite::encodeBytes(cbor, urMessage); + std::string type; + if (m_wallet.watchOnly()) { + type = "xmr-txunsigned"; + } else { + type = "xmr-txsigned"; + } + ur::UR urData = ur::UR(type, cbor); + auto encoder = ur::UREncoder(urData, max_fragment_length); + std::string output; + for(size_t i = 0; i < encoder.seq_len(); i++) { + output.append("\n"+encoder.next_part()); + } + return output; + } catch (const std::exception &e) { + m_errorString = string(tr("Unknown exception: ")) + e.what(); + m_status = Status_Error; + return ""; + } +} + + uint64_t PendingTransactionImpl::amount() const { uint64_t result = 0; diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 0a9779c07..403bfe281 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -46,6 +46,7 @@ public: int status() const override; std::string errorString() const override; bool commit(const std::string &filename = "", bool overwrite = false) override; + std::string commitUR(int max_fragment_length = 130) override; uint64_t amount() const override; uint64_t dust() const override; uint64_t fee() const override; diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 6165a2240..fd03e959d 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -40,6 +40,8 @@ #include #include +#include "bc-ur/src/bc-ur.hpp" + using namespace std; namespace Monero { @@ -96,6 +98,46 @@ bool UnsignedTransactionImpl::sign(const std::string &signedFileName) return true; } +std::string UnsignedTransactionImpl::signUR(int max_fragment_length) +{ + if(m_wallet.watchOnly()) + { + m_errorString = tr("This is a watch only wallet"); + m_status = Status_Error; + return ""; + } + std::vector ptx; + try + { + tools::wallet2::signed_tx_set signed_txes; + std::string signedTx = m_wallet.m_wallet->sign_tx_dump_to_str(m_unsigned_tx_set, ptx, signed_txes); + if (signedTx.empty()) + { + m_errorString = tr("Failed to sign transaction"); + m_status = Status_Error; + return ""; + } + auto urMessage = ur::string_to_bytes(signedTx); + ur::ByteVector cbor; + ur::CborLite::encodeBytes(cbor, urMessage); + std::string type = "xmr-txsigned"; + ur::UR urData = ur::UR(type, cbor); + auto encoder = ur::UREncoder(urData, max_fragment_length); + std::string output; + for(size_t i = 0; i < encoder.seq_len(); i++) { + output.append("\n"+encoder.next_part()); + } + return output; + } + catch (const std::exception &e) + { + m_errorString = string(tr("Failed to sign transaction")) + e.what(); + m_status = Status_Error; + return ""; + } + return ""; +} + //---------------------------------------------------------------------------------------------------- bool UnsignedTransactionImpl::checkLoadedTx(const std::function get_num_txes, const std::function &get_tx, const std::string &extra_message) { diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index 30065a7fa..a94b23f75 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -53,6 +53,7 @@ public: uint64_t txCount() const override; // sign txs and save to file bool sign(const std::string &signedFileName) override; + std::string signUR(int max_fragment_length = 130) override; std::string confirmationMessage() const override {return m_confirmationMessage;} uint64_t minMixinCount() const override; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 41b90c511..1ad8b3135 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -48,6 +48,7 @@ #include #include +#include "bc-ur/src/bc-ur.hpp" using namespace std; using namespace cryptonote; @@ -1051,6 +1052,24 @@ uint64_t WalletImpl::unlockedBalance(const std::string& asset, uint32_t accountI return m_wallet->unlocked_balance(accountIndex, asset, false); } +uint64_t WalletImpl::viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images, const std::string& asset) const +{ + clearStatus(); + + std::vector kis; + for (const auto &key_image : key_images) { + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(key_image, ki)) + { + setStatusError(tr("failed to parse key image")); + return 0; + } + kis.push_back(ki); + } + + return m_wallet->view_only_balance(accountIndex, kis, asset); +} + uint64_t WalletImpl::blockChainHeight() const { return m_wallet->get_blockchain_current_height(); @@ -1184,6 +1203,61 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file return transaction; } + +UnsignedTransaction *WalletImpl::loadUnsignedTxUR(const std::string &input) { + clearStatus(); + UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); + auto decoder = ur::URDecoder(); + + std::string delimiter = "\n"; + std::string inp = input; + size_t pos = 0; + std::string token; + while ((pos = inp.find(delimiter)) != std::string::npos) { + token = inp.substr(0, pos); + decoder.receive_part(token); + inp.erase(0, pos + delimiter.length()); + } + decoder.receive_part(inp); + + if (decoder.is_failure()) { + setStatusError(decoder.result_error().what()); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); + return transaction; + } + + if (!decoder.is_complete()) { + setStatusError("file ended but ur didn't complete"); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); + return transaction; + } + + std::string data; + auto cbor = decoder.result_ur().cbor(); + auto i = cbor.begin(); + auto end = cbor.end(); + ur::CborLite::decodeBytes(i, end, data); + + if (checkBackgroundSync("cannot load tx") || !m_wallet->parse_unsigned_tx_from_str(data, transaction->m_unsigned_tx_set)){ + setStatusError(tr("Failed to load unsigned transactions")); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); + + return transaction; + } + + // Check tx data and construct confirmation message + std::string extra_message; + if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(transaction->m_unsigned_tx_set.transfers).size()).str(); + transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); + setStatus(transaction->status(), transaction->errorString()); + + return transaction; +} + bool WalletImpl::submitTransaction(const string &fileName) { clearStatus(); if (checkBackgroundSync("cannot submit tx")) @@ -1204,6 +1278,61 @@ bool WalletImpl::submitTransaction(const string &fileName) { return true; } + +bool WalletImpl::submitTransactionUR(const string &input) { + clearStatus(); + auto decoder = ur::URDecoder(); + + std::string delimiter = "\n"; + std::string inp = input; + size_t pos = 0; + std::string token; + while ((pos = inp.find(delimiter)) != std::string::npos) { + token = inp.substr(0, pos); + decoder.receive_part(token); + inp.erase(0, pos + delimiter.length()); + } + decoder.receive_part(inp); + + if (decoder.is_failure()) { + setStatusError(decoder.result_error().what()); + return false; + } + + if (!decoder.is_complete()) { + setStatusError("file ended but ur didn't complete"); + return false; + } + + std::string data; + auto cbor = decoder.result_ur().cbor(); + auto i = cbor.begin(); + auto end = cbor.end(); + ur::CborLite::decodeBytes(i, end, data); + if (checkBackgroundSync("cannot submit tx")) + return false; + std::unique_ptr transaction(new PendingTransactionImpl(*this)); + + bool r = m_wallet->parse_tx_from_str(data, transaction->m_pending_tx, NULL); + if (!r) { + setStatus(Status_Ok, tr("Failed to load transaction from file")); + return false; + } + + if(!transaction->commit()) { + setStatusError(transaction->m_errorString); + return false; + } + + return true; +} + + +bool WalletImpl::hasUnknownKeyImages() const +{ + return m_wallet->has_unknown_key_images(); +} + bool WalletImpl::exportKeyImages(const string &filename, bool all) { if (m_wallet->watch_only()) @@ -1231,6 +1360,39 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) return true; } +std::string WalletImpl::exportKeyImagesUR(size_t max_fragment_length, bool all) +{ + if (m_wallet->watch_only()) + { + setStatusError(tr("Wallet is view only")); + return ""; + } + if (checkBackgroundSync("cannot export key images")) + return ""; + + try + { + std::string keyImages = m_wallet->export_key_images_str(all); + auto urMessage = ur::string_to_bytes(keyImages); + ur::ByteVector cbor; + ur::CborLite::encodeBytes(cbor, urMessage); + ur::UR urData = ur::UR("xmr-keyimage", cbor); + auto encoder = ur::UREncoder(urData, max_fragment_length); + std::string output; + for(size_t i = 0; i < encoder.seq_len(); i++) { + output.append("\n"+encoder.next_part()); + } + return output; + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + setStatusError(e.what()); + return ""; + } + return ""; +} + bool WalletImpl::importKeyImages(const string &filename) { if (checkBackgroundSync("cannot import key images")) @@ -1256,6 +1418,62 @@ bool WalletImpl::importKeyImages(const string &filename) return true; } + +bool WalletImpl::importKeyImagesUR(const string &input) +{ + if (checkBackgroundSync("cannot import key images")) + return false; + if (!trustedDaemon()) { + setStatusError(tr("Key images can only be imported with a trusted daemon")); + return false; + } + try + { + auto decoder = ur::URDecoder(); + std::string delimiter = "\n"; + std::string inp = input; + size_t pos = 0; + std::string token; + while ((pos = inp.find(delimiter)) != std::string::npos) { + token = inp.substr(0, pos); + decoder.receive_part(token); + inp.erase(0, pos + delimiter.length()); + } + decoder.receive_part(inp); + + if (decoder.is_failure()) { + setStatusError(decoder.result_error().what()); + return false; + } + + if (!decoder.is_complete()) { + setStatusError("file ended but ur didn't complete"); + return false; + } + + std::string data; + auto cbor = decoder.result_ur().cbor(); + auto i = cbor.begin(); + auto end = cbor.end(); + ur::CborLite::decodeBytes(i, end, data); + + uint64_t spent = 0, unspent = 0; + + uint64_t height = m_wallet->import_key_images_str(data, spent, unspent); + LOG_PRINT_L2("Signed key images imported to height " << height << ", " + << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + setStatusError(string(tr("Failed to import key images: ")) + e.what()); + return false; + } + + return true; +} + + bool WalletImpl::exportOutputs(const string &filename, bool all) { if (checkBackgroundSync("cannot export outputs")) @@ -1288,6 +1506,40 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) return true; } +std::string WalletImpl::exportOutputsUR(size_t max_fragment_length, bool all) +{ + + if (checkBackgroundSync("cannot export outputs")) + return ""; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets."))); + return ""; + } + + try + { + std::string data = m_wallet->export_outputs_to_str(all); + auto urMessage = ur::string_to_bytes(data); + ur::ByteVector cbor; + ur::CborLite::encodeBytes(cbor, urMessage); + ur::UR urData = ur::UR("xmr-output", cbor); + auto encoder = ur::UREncoder(urData, max_fragment_length); + std::string output; + for(size_t i = 0; i < encoder.seq_len(); i++) { + output.append("\n"+encoder.next_part()); + } + return output; + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting outputs: " << e.what()); + setStatusError(string(tr("Error exporting outputs: ")) + e.what()); + return ""; + } +} + + bool WalletImpl::importOutputs(const string &filename) { if (checkBackgroundSync("cannot import outputs")) @@ -1322,6 +1574,61 @@ bool WalletImpl::importOutputs(const string &filename) return true; } + +bool WalletImpl::importOutputsUR(const string &input) +{ + if (checkBackgroundSync("cannot import outputs")) + return false; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets."))); + return false; + } + + try + { + auto decoder = ur::URDecoder(); + + std::string delimiter = "\n"; + std::string inp = input; + size_t pos = 0; + std::string token; + while ((pos = inp.find(delimiter)) != std::string::npos) { + token = inp.substr(0, pos); + decoder.receive_part(token); + inp.erase(0, pos + delimiter.length()); + } + decoder.receive_part(inp); + + if (decoder.is_failure()) { + setStatusError(decoder.result_error().what()); + return false; + } + + if (!decoder.is_complete()) { + setStatusError("file ended but ur didn't complete"); + return false; + } + + std::string data; + auto cbor = decoder.result_ur().cbor(); + auto i = cbor.begin(); + auto end = cbor.end(); + ur::CborLite::decodeBytes(i, end, data); + size_t n_outputs = m_wallet->import_outputs_from_str(std::string(data)); + LOG_PRINT_L2(std::to_string(n_outputs) << " outputs imported"); + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to import outputs: " << e.what()); + setStatusError(string(tr("Failed to import outputs: ")) + e.what()); + return false; + } + + return true; +} + + bool WalletImpl::scanTransactions(const std::vector &txids) { if (checkBackgroundSync("cannot scan transactions")) diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 468710688..97e550279 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -118,6 +118,7 @@ public: bool setProxy(const std::string &address) override; uint64_t balance(const std::string& asset, uint32_t accountIndex = 0) const override; uint64_t unlockedBalance(const std::string& asset, uint32_t accountIndex = 0) const override; + uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images, const std::string& asset = "SAL1") const override; uint64_t blockChainHeight() const override; uint64_t approximateBlockChainHeight() const override; uint64_t estimateBlockChainHeight() const override; @@ -182,11 +183,18 @@ public: std::set subaddr_indices = {}) override; virtual PendingTransaction * createSweepUnmixableTransaction() override; bool submitTransaction(const std::string &fileName) override; + bool submitTransactionUR(const std::string &input) override; virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; + virtual UnsignedTransaction * loadUnsignedTxUR(const std::string &input) override; + bool hasUnknownKeyImages() const override; bool exportKeyImages(const std::string &filename, bool all = false) override; + std::string exportKeyImagesUR(size_t max_fragment_length, bool all = false) override; bool importKeyImages(const std::string &filename) override; + bool importKeyImagesUR(const std::string &input) override; bool exportOutputs(const std::string &filename, bool all = false) override; + std::string exportOutputsUR(size_t max_fragment_length, bool all) override; bool importOutputs(const std::string &filename) override; + bool importOutputsUR(const std::string &filename) override; bool scanTransactions(const std::vector &txids) override; bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional &background_cache_password = optional()) override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index c0c6792c1..451b54c12 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -126,6 +126,7 @@ struct PendingTransaction virtual std::string errorString() const = 0; // commit transaction or save to file if filename is provided. virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; + virtual std::string commitUR(int max_fragment_length = 130) = 0; virtual uint64_t amount() const = 0; virtual uint64_t dust() const = 0; virtual uint64_t fee() const = 0; @@ -195,7 +196,8 @@ struct UnsignedTransaction * @param signedFileName * return - true on success */ - virtual bool sign(const std::string &signedFileName) = 0; + virtual bool sign(const std::string &signedFileName) = 0; + virtual std::string signUR(int max_fragment_length = 130) = 0; }; /** @@ -710,6 +712,7 @@ struct Wallet result += unlockedBalance(asset, i); return result; } + virtual uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images = {}, const std::string& asset = "SAL1") const = 0; /** * @brief watchOnly - checks if wallet is watch only @@ -1007,13 +1010,15 @@ struct Wallet * after object returned */ virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; - - /*! + virtual UnsignedTransaction * loadUnsignedTxUR(const std::string &input) = 0; + + /*! * \brief submitTransaction - submits transaction in signed tx file * \return - true on success */ virtual bool submitTransaction(const std::string &fileName) = 0; - + virtual bool submitTransactionUR(const std::string &input) = 0; + /*! * \brief disposeTransaction - destroys transaction object @@ -1029,6 +1034,8 @@ struct Wallet virtual uint64_t estimateTransactionFee(const std::vector> &destinations, PendingTransaction::Priority priority) const = 0; + virtual bool hasUnknownKeyImages() const = 0; + /*! * \brief exportKeyImages - exports key images to file * \param filename @@ -1036,20 +1043,22 @@ struct Wallet * \return - true on success */ virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; - + virtual std::string exportKeyImagesUR(size_t max_fragment_length, bool all = false) = 0; /*! * \brief importKeyImages - imports key images from file * \param filename * \return - true on success */ virtual bool importKeyImages(const std::string &filename) = 0; + virtual bool importKeyImagesUR(const std::string &input) = 0; /*! - * \brief importOutputs - exports outputs to file + * \brief exportOutputs - exports outputs to file * \param filename * \return - true on success */ virtual bool exportOutputs(const std::string &filename, bool all = false) = 0; + virtual std::string exportOutputsUR(size_t max_fragment_length, bool all = false) = 0; /*! * \brief importOutputs - imports outputs from file @@ -1057,6 +1066,7 @@ struct Wallet * \return - true on success */ virtual bool importOutputs(const std::string &filename) = 0; + virtual bool importOutputsUR(const std::string &filename) = 0; /*! * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ae17a8de0..dc8377ad6 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -968,6 +968,16 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra) return idx + extra; } +bool is_preferred_input(const std::vector& preferred_input_list, const crypto::key_image& input) { + if (!preferred_input_list.empty()) { + auto it = std::find(preferred_input_list.begin(), preferred_input_list.end(), input); + if (it == preferred_input_list.end()) { + return false; + } + } + return true; +} + static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet) { shim->get_tx_pub_key_from_received_outs = std::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, std::placeholders::_1); @@ -7193,6 +7203,29 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, const std::string& asse return amount; } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::view_only_balance(uint32_t index_major, const std::vector& selected_inputs, const std::string& asset_type) +{ + uint64_t amount = 0; + if (m_transfers_indices.count(asset_type) > 0) { + for (const auto& idx: m_transfers_indices.at(asset_type)) + { + const transfer_details& td = m_transfers[idx]; + if (is_preferred_input(selected_inputs, td.m_key_image) && + !is_spent(td, false) && + !td.m_frozen && + !td.m_key_image_partial && + td.m_key_image_known && + td.is_rct() && + is_transfer_unlocked(td) && + td.m_subaddr_index.major == index_major) + { + amount += td.m_amount; + } + } + } + return amount; +} +//---------------------------------------------------------------------------------------------------- std::map wallet2::balance_per_subaddress(uint32_t index_major, const std::string& asset_type, bool strict) const { std::map amount_per_subaddr; @@ -8113,9 +8146,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector additional_derivations; - // compute public keys from out secret keys - crypto::public_key tx_pub_key; - crypto::secret_key_to_public_key(txs[n].tx_key, tx_pub_key); + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); std::vector additional_tx_pub_keys; for (const crypto::secret_key &skey: txs[n].additional_tx_keys) { @@ -11078,7 +11109,7 @@ std::vector wallet2::create_transactions_2(std::vector m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) { @@ -11128,9 +11159,15 @@ std::vector wallet2::create_transactions_2(std::vector(); + // use tobotoht's code path on view-only wallet, otherwise default to upstream + bool throwOnNoEnotes = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet; + if (throwOnNoEnotes) { + THROW_WALLET_EXCEPTION_IF(unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty(), error::wallet_internal_error, "No enotes available to spend") + } else { + if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty()) + return std::vector(); + } // if empty, put dummy entry so that the front can be referenced later in the loop if (unused_dust_indices_per_subaddr.empty()) unused_dust_indices_per_subaddr.push_back({}); @@ -14023,33 +14060,40 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle //---------------------------------------------------------------------------------------------------- bool wallet2::export_key_images(const std::string &filename, bool all) const { - PERF_TIMER(export_key_images); - std::pair>> ski = export_key_images(all); - std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); - const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; - const uint32_t offset = ski.first; - - std::string data; - data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); - data.resize(4); - data[0] = offset & 0xff; - data[1] = (offset >> 8) & 0xff; - data[2] = (offset >> 16) & 0xff; - data[3] = (offset >> 24) & 0xff; - data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); - data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); - for (const auto &i: ski.second) - { - data += std::string((const char *)&i.first, sizeof(crypto::key_image)); - data += std::string((const char *)&i.second, sizeof(crypto::signature)); - } - - // encrypt data, keep magic plaintext - PERF_TIMER(export_key_images_encrypt); - std::string ciphertext = encrypt_with_view_secret_key(data); - return save_to_file(filename, magic + ciphertext); + std::string data = export_key_images_str(all); + return save_to_file(filename, data); } +std::string wallet2::export_key_images_str(bool all) const +{ + PERF_TIMER(export_key_images); + std::pair>> ski = export_key_images(all); + std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + const uint32_t offset = ski.first; + + std::string data; + data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); + data.resize(4); + data[0] = offset & 0xff; + data[1] = (offset >> 8) & 0xff; + data[2] = (offset >> 16) & 0xff; + data[3] = (offset >> 24) & 0xff; + data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + for (const auto &i: ski.second) + { + data += std::string((const char *)&i.first, sizeof(crypto::key_image)); + data += std::string((const char *)&i.second, sizeof(crypto::signature)); + } + + // encrypt data, keep magic plaintext + PERF_TIMER(export_key_images_encrypt); + std::string ciphertext = encrypt_with_view_secret_key(data); + return magic + ciphertext; +} + + //---------------------------------------------------------------------------------------------------- std::pair>> wallet2::export_key_images(bool all) const { @@ -14109,53 +14153,60 @@ std::pair> return std::make_pair(offset, ski); } -uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent) +uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent) { + std::string data; + + bool r = load_from_file(filename, data); + + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); + + return import_key_images_str(data, spent, unspent); +} + +uint64_t wallet2::import_key_images_str(const std::string &data, uint64_t &spent, uint64_t &unspent) { PERF_TIMER(import_key_images_fsu); - std::string data; - bool r = load_from_file(filename, data); - - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); + std::string data_local = data; const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic in ") + filename); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic")); } try { PERF_TIMER(import_key_images_decrypt); - data = decrypt_with_view_secret_key(std::string(data, magiclen)); + data_local = decrypt_with_view_secret_key(std::string(data, magiclen)); } catch (const std::exception &e) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what()); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + ": " + e.what()); } const size_t headerlen = 4 + 2 * sizeof(crypto::public_key); - THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename); - const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24); - const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4]; - const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)]; + THROW_WALLET_EXCEPTION_IF(data_local.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ")); + const uint32_t offset = (uint8_t)data_local[0] | (((uint8_t)data_local[1]) << 8) | (((uint8_t)data_local[2]) << 16) | (((uint8_t)data_local[3]) << 24); + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data_local[4]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data_local[4 + sizeof(crypto::public_key)]; const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account"); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + " are for a different account"); } THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs"); const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); - THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size, - error::wallet_internal_error, std::string("Bad data size from file ") + filename); - size_t nki = (data.size() - headerlen) / record_size; + THROW_WALLET_EXCEPTION_IF((data_local.size() - headerlen) % record_size, + error::wallet_internal_error, std::string("Bad data size from file ")); + size_t nki = (data_local.size() - headerlen) / record_size; std::vector> ski; ski.reserve(nki); for (size_t n = 0; n < nki; ++n) { - crypto::key_image key_image = *reinterpret_cast(&data[headerlen + n * record_size]); - crypto::signature signature = *reinterpret_cast(&data[headerlen + n * record_size + sizeof(crypto::key_image)]); + crypto::key_image key_image = *reinterpret_cast(&data_local[headerlen + n * record_size]); + crypto::signature signature = *reinterpret_cast(&data_local[headerlen + n * record_size + sizeof(crypto::key_image)]); ski.push_back(std::make_pair(key_image, signature)); } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 3601a61a2..6296edfdd 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1248,6 +1248,7 @@ private: // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major, const std::string& asset_type, bool strict) const; uint64_t unlocked_balance(uint32_t subaddr_index_major, const std::string& asset_type, bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL); + uint64_t view_only_balance(uint32_t index_major, const std::vector& selected_inputs = {}, const std::string& asset_type = "SAL1"); // locked & unlocked balance per subaddress of given or current subaddress account std::map balance_per_subaddress(uint32_t subaddr_index_major, const std::string& asset_type, bool strict) const; std::map>> unlocked_balance_per_subaddress(uint32_t subaddr_index_major, const std::string& asset_type, bool strict); @@ -1757,9 +1758,11 @@ private: std::tuple> export_blockchain() const; void import_blockchain(const std::tuple> &bc); bool export_key_images(const std::string &filename, bool all = false) const; + std::string export_key_images_str(bool all) const; std::pair>> export_key_images(bool all = false) const; uint64_t import_key_images(const std::vector> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true); uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); + uint64_t import_key_images_str(const std::string &data, uint64_t &spent, uint64_t &unspent); bool import_key_images(std::vector key_images, size_t offset=0, boost::optional> selected_transfers=boost::none); bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;