From e2e752c36501122c0ae0d9633268a3f47ada8a30 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Thu, 16 May 2024 18:26:32 +0200 Subject: [PATCH] wip ur support --- .../monero_libwallet2_api_c.exp | 10 +- .../src/main/cpp/wallet2_api_c.cpp | 39 +- .../src/main/cpp/wallet2_api_c.h | 6 + patches/monero/0012-WIP-UR-functions.patch | 839 ++++++++++++++++++ 4 files changed, 892 insertions(+), 2 deletions(-) create mode 100644 patches/monero/0012-WIP-UR-functions.patch diff --git a/monero_libwallet2_api_c/monero_libwallet2_api_c.exp b/monero_libwallet2_api_c/monero_libwallet2_api_c.exp index 6e47988..829dd4f 100644 --- a/monero_libwallet2_api_c/monero_libwallet2_api_c.exp +++ b/monero_libwallet2_api_c/monero_libwallet2_api_c.exp @@ -278,4 +278,12 @@ _MONERO_cw_WalletListener_height _MONERO_WalletManager_createDeterministicWalletFromSpendKey _MONERO_PendingTransaction_txKey _MONERO_PendingTransaction_hex -_MONERO_free \ No newline at end of file +_MONERO_free +_MONERO_PendingTransaction_commitUR +_MONERO_UnsignedTransaction_signUR +_MONERO_Wallet_loadUnsignedTxUR +_MONERO_Wallet_submitTransactionUR +_MONERO_Wallet_exportOutputsUR +_MONERO_Wallet_exportKeyImagesUR +_MONERO_Wallet_importKeyImagesUR +_MONERO_Wallet_importOutputsUR \ No newline at end of file diff --git a/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.cpp b/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.cpp index 3049a1a..0111333 100644 --- a/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.cpp +++ b/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.cpp @@ -78,6 +78,14 @@ bool MONERO_PendingTransaction_commit(void* pendingTx_ptr, const char* filename, Monero::PendingTransaction *pendingTx = reinterpret_cast(pendingTx_ptr); return pendingTx->commit(std::string(filename), overwrite); } +const char* MONERO_PendingTransaction_commitUR(void* pendingTx_ptr, int max_fragment_length) { + Monero::PendingTransaction *pendingTx = reinterpret_cast(pendingTx_ptr); + std::string str = pendingTx->commitUR(max_fragment_length); + const std::string::size_type size = str.size(); + char *buffer = new char[size + 1]; //we need extra char for NUL + memcpy(buffer, str.c_str(), size + 1); + return buffer; +} uint64_t MONERO_PendingTransaction_amount(void* pendingTx_ptr) { Monero::PendingTransaction *pendingTx = reinterpret_cast(pendingTx_ptr); return pendingTx->amount(); @@ -193,7 +201,14 @@ bool MONERO_UnsignedTransaction_sign(void* unsignedTx_ptr, const char* signedFil Monero::UnsignedTransaction *unsignedTx = reinterpret_cast(unsignedTx_ptr); return unsignedTx->sign(std::string(signedFileName)); } - +const char* MONERO_UnsignedTransaction_signUR(void* unsignedTx_ptr, int max_fragment_length) { + Monero::UnsignedTransaction *unsignedTx = reinterpret_cast(unsignedTx_ptr); + std::string str = unsignedTx->signUR(max_fragment_length); + const std::string::size_type size = str.size(); + char *buffer = new char[size + 1]; //we need extra char for NUL + memcpy(buffer, str.c_str(), size + 1); + return buffer; +} // TransactionInfo int MONERO_TransactionInfo_direction(void* txInfo_ptr) { Monero::TransactionInfo *txInfo = reinterpret_cast(txInfo_ptr); @@ -1338,10 +1353,19 @@ void* MONERO_Wallet_loadUnsignedTx(void* wallet_ptr, const char* fileName) { Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); return wallet->loadUnsignedTx(std::string(fileName)); } + +void* MONERO_Wallet_loadUnsignedTxUR(void* wallet_ptr, const char* input) { + Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); + return wallet->loadUnsignedTxUR(std::string(input)); +} bool MONERO_Wallet_submitTransaction(void* wallet_ptr, const char* fileName) { Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); return wallet->submitTransaction(std::string(fileName)); } +bool MONERO_Wallet_submitTransactionUR(void* wallet_ptr, const char* input) { + Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); + return wallet->submitTransactionUR(std::string(input)); +} bool MONERO_Wallet_hasUnknownKeyImages(void* wallet_ptr) { Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); return wallet->hasUnknownKeyImages(); @@ -1350,10 +1374,23 @@ bool MONERO_Wallet_exportKeyImages(void* wallet_ptr, const char* filename, bool Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); return wallet->exportKeyImages(std::string(filename), all); } + +const char* MONERO_Wallet_exportKeyImagesUR(void* wallet_ptr, size_t max_fragment_length, bool all) { + Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); + std::string str = wallet->exportKeyImagesUR(max_fragment_length, all); + const std::string::size_type size = str.size(); + char *buffer = new char[size + 1]; //we need extra char for NUL + memcpy(buffer, str.c_str(), size + 1); + return buffer; +} bool MONERO_Wallet_importKeyImages(void* wallet_ptr, const char* filename) { Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); return wallet->importKeyImages(std::string(filename)); } +bool MONERO_Wallet_importKeyImagesUR(void* wallet_ptr, const char* input) { + Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); + return wallet->importKeyImagesUR(std::string(input)); +} bool MONERO_Wallet_exportOutputs(void* wallet_ptr, const char* filename, bool all) { Monero::Wallet *wallet = reinterpret_cast(wallet_ptr); return wallet->exportOutputs(std::string(filename), all); diff --git a/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.h b/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.h index d272137..815c13c 100644 --- a/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.h +++ b/monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.h @@ -77,6 +77,7 @@ extern ADDAPI int MONERO_PendingTransaction_status(void* pendingTx_ptr); extern ADDAPI const char* MONERO_PendingTransaction_errorString(void* pendingTx_ptr); // virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; extern ADDAPI bool MONERO_PendingTransaction_commit(void* pendingTx_ptr, const char* filename, bool overwrite); +extern ADDAPI const char* MONERO_PendingTransaction_commitUR(void* pendingTx_ptr, int max_fragment_length); // virtual uint64_t amount() const = 0; extern ADDAPI uint64_t MONERO_PendingTransaction_amount(void* pendingTx_ptr); // virtual uint64_t dust() const = 0; @@ -136,6 +137,7 @@ extern ADDAPI uint64_t MONERO_UnsignedTransaction_minMixinCount(void* unsignedTx extern ADDAPI uint64_t MONERO_UnsignedTransaction_txCount(void* unsignedTx_ptr); // virtual bool sign(const std::string &signedFileName) = 0; extern ADDAPI bool MONERO_UnsignedTransaction_sign(void* unsignedTx_ptr, const char* signedFileName); +extern ADDAPI const char* MONERO_UnsignedTransaction_signUR(void* unsignedTx_ptr, int max_fragment_length); // }; // struct TransactionInfo // { @@ -717,8 +719,10 @@ extern ADDAPI void* MONERO_Wallet_createTransaction(void* wallet_ptr, const char // virtual PendingTransaction * createSweepUnmixableTransaction() = 0; // virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; extern ADDAPI void* MONERO_Wallet_loadUnsignedTx(void* wallet_ptr, const char* unsigned_filename); +extern ADDAPI void* MONERO_Wallet_loadUnsignedUR(void* wallet_ptr, const char* input); // virtual bool submitTransaction(const std::string &fileName) = 0; extern ADDAPI bool MONERO_Wallet_submitTransaction(void* wallet_ptr, const char* fileName); +extern ADDAPI bool MONERO_Wallet_submitTransactionUR(void* wallet_ptr, const char* input); // virtual void disposeTransaction(PendingTransaction * t) = 0; // virtual uint64_t estimateTransactionFee(const std::vector> &destinations, // PendingTransaction::Priority priority) const = 0; @@ -726,8 +730,10 @@ extern ADDAPI bool MONERO_Wallet_submitTransaction(void* wallet_ptr, const char* extern ADDAPI bool MONERO_Wallet_hasUnknownKeyImages(void* wallet_ptr); // virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; extern ADDAPI bool MONERO_Wallet_exportKeyImages(void* wallet_ptr, const char* filename, bool all); +extern ADDAPI bool MONERO_Wallet_exportKeyImagesUR(void* wallet_ptr, size_t max_fragment_length, bool all) ; // virtual bool importKeyImages(const std::string &filename) = 0; extern ADDAPI bool MONERO_Wallet_importKeyImages(void* wallet_ptr, const char* filename); +extern ADDAPI bool MONERO_Wallet_importKeyImagesUR(void* wallet_ptr, const char* input); // virtual bool exportOutputs(const std::string &filename, bool all = false) = 0; extern ADDAPI bool MONERO_Wallet_exportOutputs(void* wallet_ptr, const char* filename, bool all); extern ADDAPI const char* MONERO_Wallet_exportOutputsUR(void* wallet_ptr, size_t max_fragment_length, bool all); diff --git a/patches/monero/0012-WIP-UR-functions.patch b/patches/monero/0012-WIP-UR-functions.patch new file mode 100644 index 0000000..85a1042 --- /dev/null +++ b/patches/monero/0012-WIP-UR-functions.patch @@ -0,0 +1,839 @@ +From 5739318774025df8bb99eca5263c83bbbc228f01 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Thu, 16 May 2024 17:28:59 +0200 +Subject: [PATCH] WIP: UR functions + +This commit adds UR functions for UR tasks, +I believe that the right place to get +UR strings is the wallet code itself, +especially because it allows us to +skip the part when we have to store +things to file to encode them later. +Now we are fully in memory +--- + .gitmodules | 3 + + CMakeLists.txt | 4 +- + external/CMakeLists.txt | 1 + + external/bc-ur | 1 + + src/wallet/CMakeLists.txt | 1 + + src/wallet/api/pending_transaction.cpp | 33 +++ + src/wallet/api/pending_transaction.h | 1 + + src/wallet/api/unsigned_transaction.cpp | 42 ++++ + src/wallet/api/unsigned_transaction.h | 1 + + src/wallet/api/wallet.cpp | 286 +++++++++++++++++++++++- + src/wallet/api/wallet.h | 6 + + src/wallet/api/wallet2_api.h | 19 +- + src/wallet/wallet2.cpp | 96 ++++---- + src/wallet/wallet2.h | 2 + + 14 files changed, 447 insertions(+), 49 deletions(-) + create mode 160000 external/bc-ur + +diff --git a/.gitmodules b/.gitmodules +index 7ea87a009..a7e1d2cd0 100644 +--- a/.gitmodules ++++ b/.gitmodules +@@ -20,3 +20,6 @@ + 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 +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 63b8c5079..6028c0961 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -96,7 +96,8 @@ enable_language(C ASM) + set(CMAKE_C_STANDARD 11) + set(CMAKE_C_STANDARD_REQUIRED ON) + set(CMAKE_C_EXTENSIONS OFF) +-set(CMAKE_CXX_STANDARD 14) ++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) + +@@ -364,6 +365,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 1b9761d70..0df9f9116 100644 +--- a/external/CMakeLists.txt ++++ b/external/CMakeLists.txt +@@ -69,6 +69,7 @@ endif() + add_subdirectory(db_drivers) + add_subdirectory(easylogging++) + add_subdirectory(qrcodegen) ++add_subdirectory(bc-ur) + add_subdirectory(randomx EXCLUDE_FROM_ALL) + add_subdirectory(polyseed EXCLUDE_FROM_ALL) + add_subdirectory(utf8proc EXCLUDE_FROM_ALL) +\ No newline at end of file +diff --git a/external/bc-ur b/external/bc-ur +new file mode 160000 +index 000000000..cafffa854 +--- /dev/null ++++ b/external/bc-ur +@@ -0,0 +1 @@ ++Subproject commit cafffa854d04c6266597935bb68f8d360b632ad6 +diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt +index 6095f99d5..b163212b7 100644 +--- a/src/wallet/CMakeLists.txt ++++ b/src/wallet/CMakeLists.txt +@@ -50,6 +50,7 @@ monero_add_library(wallet + target_link_libraries(wallet + PUBLIC + rpc_base ++ bc-ur + multisig + common + cryptonote_core +diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp +index be20b478c..1f714d229 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 { +@@ -178,6 +180,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 2fbaa83d9..0cc6c58e9 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 90e107137..ac3e6d51e 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; +@@ -1321,6 +1322,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")) +@@ -1332,7 +1388,7 @@ bool WalletImpl::submitTransaction(const string &fileName) { + setStatus(Status_Ok, tr("Failed to load transaction from file")); + return false; + } +- ++ + if(!transaction->commit()) { + setStatusError(transaction->m_errorString); + return false; +@@ -1341,6 +1397,56 @@ 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(); +@@ -1373,6 +1479,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")) +@@ -1398,6 +1537,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")) +@@ -1430,6 +1625,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")) +@@ -1464,6 +1693,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 2ad2b62a4..febc93119 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -182,12 +182,18 @@ public: + const std::set &preferred_inputs = {}) 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 1c3a11c39..2bbb32c8b 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -91,6 +91,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; +@@ -162,7 +163,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; + }; + + /** +@@ -938,13 +940,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 +@@ -969,20 +973,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 +@@ -990,6 +996,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 e5773a26b..23b56810e 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -14051,33 +14051,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 = export_key_images_str(all); ++ return save_to_file(filename, data); ++} + +- 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)); +- } ++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 save_to_file(filename, magic + ciphertext); ++ // 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 + { +@@ -14132,53 +14139,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 0d9a9298f..539d5056c 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -1650,9 +1650,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; +-- +2.45.0 +