diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 83 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 4 | ||||
-rw-r--r-- | src/wallet/api/wallet2_api.h | 29 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.cpp | 8 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.h | 1 | ||||
-rw-r--r-- | src/wallet/ringdb.cpp | 117 | ||||
-rw-r--r-- | src/wallet/ringdb.h | 9 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 564 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 254 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 6 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 321 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 6 |
13 files changed, 630 insertions, 774 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index a16f4fe19..be10b9f62 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -90,6 +90,8 @@ target_link_libraries(wallet_rpc_server cncrypto common version + daemonizer + ${EPEE_READLINE} ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 3780d7271..8b25096a2 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -376,7 +376,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) , m_rebuildWalletCache(false) , m_is_connected(false) { - m_wallet = new tools::wallet2(static_cast<cryptonote::network_type>(nettype), kdf_rounds); + m_wallet = new tools::wallet2(static_cast<cryptonote::network_type>(nettype), kdf_rounds, true); m_history = new TransactionHistoryImpl(this); m_wallet2Callback = new Wallet2CallbackImpl(this); m_wallet->callback(m_wallet2Callback); @@ -629,7 +629,7 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p m_recoveringFromDevice = true; try { - m_wallet->restore(path, password, device_name); + m_wallet->restore(path, password, device_name, false); LOG_PRINT_L1("Generated new wallet from device: " + device_name); } catch (const std::exception& e) { @@ -639,6 +639,11 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p return true; } +Wallet::Device WalletImpl::getDeviceType() const +{ + return static_cast<Wallet::Device>(m_wallet->get_device_type()); +} + bool WalletImpl::open(const std::string &path, const std::string &password) { clearStatus(); @@ -1244,6 +1249,20 @@ size_t WalletImpl::importMultisigImages(const vector<string>& images) { return 0; } +bool WalletImpl::hasMultisigPartialKeyImages() const { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + return m_wallet->has_multisig_partial_key_images(); + } catch (const exception& e) { + LOG_ERROR("Error on checking for partial multisig key images: ") << e.what(); + setStatusError(string(tr("Failed to check for partial multisig key images: ")) + e.what()); + } + + return false; +} + PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signData) { try { clearStatus(); @@ -1381,8 +1400,8 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { setStatusError(tr("RPC error: ") + e.to_string()); - } catch (const tools::error::get_random_outs_error &e) { - setStatusError((boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str()); + } catch (const tools::error::get_outs_error &e) { + setStatusError((boost::format(tr("failed to get outputs to mix: %s")) % e.what()).str()); } catch (const tools::error::not_enough_unlocked_money& e) { std::ostringstream writer; @@ -1463,8 +1482,8 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { setStatusError(tr("RPC error: ") + e.to_string()); - } catch (const tools::error::get_random_outs_error&) { - setStatusError(tr("failed to get random outputs to mix")); + } catch (const tools::error::get_outs_error&) { + setStatusError(tr("failed to get outputs to mix")); } catch (const tools::error::not_enough_unlocked_money& e) { setStatusError(""); std::ostringstream writer; @@ -2033,7 +2052,7 @@ bool WalletImpl::isNewWallet() const bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl) { // claim RPC so there's no in-memory encryption for now - if (!m_wallet->init(true, daemon_address, m_daemon_login, upper_transaction_size_limit, ssl)) + if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit, ssl)) return false; // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) @@ -2095,21 +2114,36 @@ bool WalletImpl::useForkRules(uint8_t version, int64_t early_blocks) const return m_wallet->use_fork_rules(version,early_blocks); } -bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool add) +bool WalletImpl::blackballOutputs(const std::vector<std::string> &outputs, bool add) { - std::vector<crypto::public_key> raw_pubkeys; - raw_pubkeys.reserve(pubkeys.size()); - for (const std::string &str: pubkeys) + std::vector<std::pair<uint64_t, uint64_t>> raw_outputs; + raw_outputs.reserve(outputs.size()); + uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; + for (const std::string &str: outputs) { - crypto::public_key pkey; - if (!epee::string_tools::hex_to_pod(str, pkey)) + if (sscanf(str.c_str(), "@%" PRIu64, &amount) == 1) + continue; + if (amount == std::numeric_limits<uint64_t>::max()) { - setStatusError(tr("Failed to parse output public key")); - return false; + setStatusError("First line is not an amount"); + return true; + } + if (sscanf(str.c_str(), "%" PRIu64 "*%" PRIu64, &offset, &num_offsets) == 2 && num_offsets <= std::numeric_limits<uint64_t>::max() - offset) + { + while (num_offsets--) + raw_outputs.push_back(std::make_pair(amount, offset++)); + } + else if (sscanf(str.c_str(), "%" PRIu64, &offset) == 1) + { + raw_outputs.push_back(std::make_pair(amount, offset)); + } + else + { + setStatusError(tr("Invalid output: ") + str); + return false; } - raw_pubkeys.push_back(pkey); } - bool ret = m_wallet->set_blackballed_outputs(raw_pubkeys, add); + bool ret = m_wallet->set_blackballed_outputs(raw_outputs, add); if (!ret) { setStatusError(tr("Failed to set blackballed outputs")); @@ -2118,15 +2152,20 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool return true; } -bool WalletImpl::unblackballOutput(const std::string &pubkey) +bool WalletImpl::unblackballOutput(const std::string &amount, const std::string &offset) { - crypto::public_key raw_pubkey; - if (!epee::string_tools::hex_to_pod(pubkey, raw_pubkey)) + uint64_t raw_amount, raw_offset; + if (!epee::string_tools::get_xtype_from_string(raw_amount, amount)) + { + setStatusError(tr("Failed to parse output amount")); + return false; + } + if (!epee::string_tools::get_xtype_from_string(raw_offset, offset)) { - setStatusError(tr("Failed to parse output public key")); + setStatusError(tr("Failed to parse output offset")); return false; } - bool ret = m_wallet->unblackball_output(raw_pubkey); + bool ret = m_wallet->unblackball_output(std::make_pair(raw_amount, raw_offset)); if (!ret) { setStatusError(tr("Failed to unblackball output")); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 0f3b1ce04..e3a300317 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -79,6 +79,7 @@ public: bool recoverFromDevice(const std::string &path, const std::string &password, const std::string &device_name); + Device getDeviceType() const; bool close(bool store = true); std::string seed() const override; std::string getSeedLanguage() const override; @@ -139,6 +140,7 @@ public: bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override; bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector<std::string>& images) override; + bool hasMultisigPartialKeyImages() const override; PendingTransaction* restoreMultisigTransaction(const std::string& signData) override; PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, @@ -181,7 +183,7 @@ public: virtual bool lightWalletLogin(bool &isNewWallet) const override; virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) override; virtual bool blackballOutputs(const std::vector<std::string> &pubkeys, bool add) override; - virtual bool unblackballOutput(const std::string &pubkey) override; + virtual bool unblackballOutput(const std::string &amount, const std::string &offset) override; virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const override; virtual bool getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const override; virtual bool setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative) override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 5a52c6b17..e0d491705 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -373,6 +373,10 @@ struct WalletListener */ struct Wallet { + enum Device { + Device_Software = 0, + Device_Ledger = 1 + }; enum Status { Status_Ok, @@ -720,6 +724,11 @@ struct Wallet * @return number of imported images */ virtual size_t importMultisigImages(const std::vector<std::string>& images) = 0; + /** + * @brief hasMultisigPartialKeyImages - checks if wallet needs to import multisig key images from other participants + * @return true if there are partial key images + */ + virtual bool hasMultisigPartialKeyImages() const = 0; /** * @brief restoreMultisigTransaction creates PendingTransaction from signData @@ -875,7 +884,7 @@ struct Wallet virtual bool blackballOutputs(const std::vector<std::string> &pubkeys, bool add) = 0; //! unblackballs an output - virtual bool unblackballOutput(const std::string &pubkey) = 0; + virtual bool unblackballOutput(const std::string &amount, const std::string &offset) = 0; //! gets the ring used for a key image, if any virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const = 0; @@ -906,6 +915,12 @@ struct Wallet virtual bool unlockKeysFile() = 0; //! returns true if the keys file is locked virtual bool isKeysFileLocked() = 0; + + /*! + * \brief Queries backing device for wallet keys + * \return Device they are on + */ + virtual Device getDeviceType() const = 0; }; /** @@ -1092,6 +1107,18 @@ struct WalletManager virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const = 0; /*! + * \brief determine the key storage for the specified wallet file + * \param device_type (OUT) wallet backend as enumerated in Wallet::Device + * \param keys_file_name Keys file to verify password for + * \param password Password to verify + * \return true if password correct, else false + * + * for verification only - determines key storage hardware + * + */ + virtual bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const = 0; + + /*! * \brief findWallets - searches for the wallet files by given path name recursively * \param path - starting point to search * \return - list of strings with found wallets (absolute paths); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 3851ca9cc..5b262f1b7 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -167,6 +167,14 @@ bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, return tools::wallet2::verify_password(keys_file_name, password, no_spend_key, hw::get_device("default"), kdf_rounds); } +bool WalletManagerImpl::queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds) const +{ + hw::device::device_type type; + bool r = tools::wallet2::query_device(type, keys_file_name, password, kdf_rounds); + device_type = static_cast<Wallet::Device>(type); + return r; +} + std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path) { std::vector<std::string> result; diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 8b1c8be7f..573e80d1a 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -76,6 +76,7 @@ public: virtual bool closeWallet(Wallet *wallet, bool store = true) override; bool walletExists(const std::string &path) override; bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; + bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const; std::vector<std::string> findWallets(const std::string &path) override; std::string errorString() const override; void setDaemonAddress(const std::string &address) override; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 3f2634c8b..e9fc6866d 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -55,6 +55,13 @@ static int compare_hash32(const MDB_val *a, const MDB_val *b) return 0; } +static int compare_uint64(const MDB_val *a, const MDB_val *b) +{ + const uint64_t va = *(const uint64_t*) a->mv_data; + const uint64_t vb = *(const uint64_t*) b->mv_data; + return va < vb ? -1 : va > vb; +} + static std::string compress_ring(const std::vector<uint64_t> &ring) { std::string s; @@ -146,7 +153,7 @@ static int resize_env(MDB_env *env, const char *db_path, size_t needed) MDB_stat mst; int ret; - needed = std::max(needed, (size_t)(2ul * 1024 * 1024)); // at least 2 MB + needed = std::max(needed, (size_t)(100ul * 1024 * 1024)); // at least 100 MB ret = mdb_env_info(env, &mei); if (ret) @@ -217,9 +224,9 @@ ringdb::ringdb(std::string filename, const std::string &genesis): THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); mdb_set_compare(txn, dbi_rings, compare_hash32); - dbr = mdb_dbi_open(txn, ("blackballs-" + genesis).c_str(), MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_blackballs); + dbr = mdb_dbi_open(txn, ("blackballs2-" + genesis).c_str(), MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_blackballs); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); - mdb_set_dupsort(txn, dbi_blackballs, compare_hash32); + mdb_set_dupsort(txn, dbi_blackballs, compare_uint64); dbr = mdb_txn_commit(txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn creating/opening database: " + std::string(mdb_strerror(dbr))); @@ -374,7 +381,7 @@ bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return true; } -bool ringdb::blackball_worker(const crypto::public_key &output, int op) +bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, int op) { MDB_txn *txn; MDB_cursor *cursor; @@ -382,49 +389,61 @@ bool ringdb::blackball_worker(const crypto::public_key &output, int op) bool tx_active = false; bool ret = true; - dbr = resize_env(env, filename.c_str(), 32 * 2); // a pubkey, and some slack + THROW_WALLET_EXCEPTION_IF(outputs.size() > 1 && op == BLACKBALL_QUERY, tools::error::wallet_internal_error, "Blackball query only makes sense for a single output"); + + dbr = resize_env(env, filename.c_str(), 32 * 2 * outputs.size()); // a pubkey, and some slack THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - MDB_val key = zerokeyval; - MDB_val data; - data.mv_data = (void*)&output; - data.mv_size = sizeof(output); - switch (op) + MDB_val key, data; + for (const std::pair<uint64_t, uint64_t> &output: outputs) + { + key.mv_data = (void*)&output.first; + key.mv_size = sizeof(output.first); + data.mv_data = (void*)&output.second; + data.mv_size = sizeof(output.second); + + switch (op) + { + case BLACKBALL_BLACKBALL: + MDEBUG("Blackballing output " << output.first << "/" << output.second); + dbr = mdb_put(txn, dbi_blackballs, &key, &data, MDB_APPENDDUP); + if (dbr == MDB_KEYEXIST) + dbr = 0; + break; + case BLACKBALL_UNBLACKBALL: + MDEBUG("Unblackballing output " << output.first << "/" << output.second); + dbr = mdb_del(txn, dbi_blackballs, &key, &data); + if (dbr == MDB_NOTFOUND) + dbr = 0; + break; + case BLACKBALL_QUERY: + dbr = mdb_cursor_open(txn, dbi_blackballs, &cursor); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr))); + dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to lookup in blackballs table: " + std::string(mdb_strerror(dbr))); + ret = dbr != MDB_NOTFOUND; + if (dbr == MDB_NOTFOUND) + dbr = 0; + mdb_cursor_close(cursor); + break; + case BLACKBALL_CLEAR: + break; + default: + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid blackball op"); + } + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to query blackballs table: " + std::string(mdb_strerror(dbr))); + } + + if (op == BLACKBALL_CLEAR) { - case BLACKBALL_BLACKBALL: - MDEBUG("Blackballing output " << output); - dbr = mdb_put(txn, dbi_blackballs, &key, &data, MDB_NODUPDATA); - if (dbr == MDB_KEYEXIST) - dbr = 0; - break; - case BLACKBALL_UNBLACKBALL: - MDEBUG("Unblackballing output " << output); - dbr = mdb_del(txn, dbi_blackballs, &key, &data); - if (dbr == MDB_NOTFOUND) - dbr = 0; - break; - case BLACKBALL_QUERY: - dbr = mdb_cursor_open(txn, dbi_blackballs, &cursor); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr))); - dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH); - THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to lookup in blackballs table: " + std::string(mdb_strerror(dbr))); - ret = dbr != MDB_NOTFOUND; - if (dbr == MDB_NOTFOUND) - dbr = 0; - mdb_cursor_close(cursor); - break; - case BLACKBALL_CLEAR: - dbr = mdb_drop(txn, dbi_blackballs, 0); - break; - default: - THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid blackball op"); + dbr = mdb_drop(txn, dbi_blackballs, 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to clear blackballs table: " + std::string(mdb_strerror(dbr))); } - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to query blackballs table: " + std::string(mdb_strerror(dbr))); dbr = mdb_txn_commit(txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn blackballing output to database: " + std::string(mdb_strerror(dbr))); @@ -432,24 +451,32 @@ bool ringdb::blackball_worker(const crypto::public_key &output, int op) return ret; } -bool ringdb::blackball(const crypto::public_key &output) +bool ringdb::blackball(const std::vector<std::pair<uint64_t, uint64_t>> &outputs) +{ + return blackball_worker(outputs, BLACKBALL_BLACKBALL); +} + +bool ringdb::blackball(const std::pair<uint64_t, uint64_t> &output) { - return blackball_worker(output, BLACKBALL_BLACKBALL); + std::vector<std::pair<uint64_t, uint64_t>> outputs(1, output); + return blackball_worker(outputs, BLACKBALL_BLACKBALL); } -bool ringdb::unblackball(const crypto::public_key &output) +bool ringdb::unblackball(const std::pair<uint64_t, uint64_t> &output) { - return blackball_worker(output, BLACKBALL_UNBLACKBALL); + std::vector<std::pair<uint64_t, uint64_t>> outputs(1, output); + return blackball_worker(outputs, BLACKBALL_UNBLACKBALL); } -bool ringdb::blackballed(const crypto::public_key &output) +bool ringdb::blackballed(const std::pair<uint64_t, uint64_t> &output) { - return blackball_worker(output, BLACKBALL_QUERY); + std::vector<std::pair<uint64_t, uint64_t>> outputs(1, output); + return blackball_worker(outputs, BLACKBALL_QUERY); } bool ringdb::clear_blackballs() { - return blackball_worker(crypto::public_key(), BLACKBALL_CLEAR); + return blackball_worker(std::vector<std::pair<uint64_t, uint64_t>>(), BLACKBALL_CLEAR); } } diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 6b4bce124..7b448b0d7 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -49,13 +49,14 @@ namespace tools bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); - bool blackball(const crypto::public_key &output); - bool unblackball(const crypto::public_key &output); - bool blackballed(const crypto::public_key &output); + bool blackball(const std::pair<uint64_t, uint64_t> &output); + bool blackball(const std::vector<std::pair<uint64_t, uint64_t>> &outputs); + bool unblackball(const std::pair<uint64_t, uint64_t> &output); + bool blackballed(const std::pair<uint64_t, uint64_t> &output); bool clear_blackballs(); private: - bool blackball_worker(const crypto::public_key &output, int op); + bool blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, int op); private: std::string filename; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a50a88b59..4e93309ed 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -161,6 +161,7 @@ struct options { } }; const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1}; + const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) @@ -200,7 +201,7 @@ std::string get_weight_string(const cryptonote::transaction &tx, size_t blob_siz return get_weight_string(get_transaction_weight(tx, blob_size)); } -std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, bool rpc, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool stagenet = command_line::get_arg(vm, opts.stagenet); @@ -211,6 +212,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl auto daemon_address = command_line::get_arg(vm, opts.daemon_address); auto daemon_host = command_line::get_arg(vm, opts.daemon_host); auto daemon_port = command_line::get_arg(vm, opts.daemon_port); + auto device_name = command_line::get_arg(vm, opts.hw_device); THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port, tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once")); @@ -261,10 +263,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl catch (const std::exception &e) { } } - std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds)); - wallet->init(rpc, std::move(daemon_address), std::move(login), 0, false, *trusted_daemon); + std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended)); + wallet->init(std::move(daemon_address), std::move(login), 0, false, *trusted_daemon); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); + wallet->device_name(device_name); return wallet; } @@ -297,7 +300,7 @@ boost::optional<tools::password_container> get_password(const boost::program_opt return password_prompter(verify ? tr("Enter a new password for the wallet") : tr("Wallet password"), verify); } -std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool rpc, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool stagenet = command_line::get_arg(vm, opts.stagenet); @@ -435,7 +438,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, THROW_WALLET_EXCEPTION_IF(deprecated_wallet, tools::error::wallet_internal_error, tools::wallet2::tr("Cannot generate deprecated wallets from JSON")); - wallet.reset(make_basic(vm, rpc, opts, password_prompter).release()); + wallet.reset(make_basic(vm, unattended, opts, password_prompter).release()); wallet->set_refresh_from_block_height(field_scan_from_height); wallet->explicit_refresh_from_block_height(field_scan_from_height_found); @@ -721,8 +724,11 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<too w(w), locked(password != boost::none) { - if (!locked || w.is_rpc()) + if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt) + { + locked = false; return; + } const epee::wipeable_string pass = password->password(); w.generate_chacha_key_from_password(pass, key); w.decrypt_keys(key); @@ -745,7 +751,7 @@ wallet_keys_unlocker::~wallet_keys_unlocker() w.encrypt_keys(key); } -wallet2::wallet2(network_type nettype, uint64_t kdf_rounds): +wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_run(true), @@ -764,7 +770,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds): m_explicit_refresh_from_block_height(true), m_confirm_missing_payment_id(true), m_confirm_non_default_ring_size(true), - m_ask_password(true), + m_ask_password(AskPasswordToDecrypt), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), @@ -788,12 +794,12 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds): m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0), - m_key_on_device(false), + m_key_device_type(hw::device::device_type::SOFTWARE), m_ring_history_saved(false), m_ringdb(), m_last_block_reward(0), m_encrypt_keys_after_refresh(boost::none), - m_rpc(false) + m_unattended(unattended) { } @@ -811,6 +817,11 @@ bool wallet2::has_stagenet_option(const boost::program_options::variables_map& v return command_line::get_arg(vm, options().stagenet); } +std::string wallet2::device_name_option(const boost::program_options::variables_map& vm) +{ + return command_line::get_arg(vm, options().hw_device); +} + void wallet2::init_options(boost::program_options::options_description& desc_params) { const options opts{}; @@ -826,16 +837,17 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.stagenet); command_line::add_arg(desc_params, opts.shared_ringdb_dir); command_line::add_arg(desc_params, opts.kdf_rounds); + command_line::add_arg(desc_params, opts.hw_device); } -std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool rpc, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return generate_from_json(json_file, vm, rpc, opts, password_prompter); + return generate_from_json(json_file, vm, unattended, opts, password_prompter); } std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( - const boost::program_options::variables_map& vm, bool rpc, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) + const boost::program_options::variables_map& vm, bool unattended, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; auto pwd = get_password(vm, opts, password_prompter, false); @@ -843,7 +855,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( { return {nullptr, password_container{}}; } - auto wallet = make_basic(vm, rpc, opts, password_prompter); + auto wallet = make_basic(vm, unattended, opts, password_prompter); if (wallet) { wallet->load(wallet_file, pwd->password()); @@ -851,7 +863,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( return {std::move(wallet), std::move(*pwd)}; } -std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter) +std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter) { const options opts{}; auto pwd = get_password(vm, opts, password_prompter, true); @@ -859,19 +871,18 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const { return {nullptr, password_container{}}; } - return {make_basic(vm, rpc, opts, password_prompter), std::move(*pwd)}; + return {make_basic(vm, unattended, opts, password_prompter), std::move(*pwd)}; } -std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return make_basic(vm, rpc, opts, password_prompter); + return make_basic(vm, unattended, opts, password_prompter); } //---------------------------------------------------------------------------------------------------- -bool wallet2::init(bool rpc, std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_weight_limit, bool ssl, bool trusted_daemon) +bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_weight_limit, bool ssl, bool trusted_daemon) { - m_rpc = rpc; m_checkpoints.init_default_checkpoints(m_nettype); if(m_http_client.is_connected()) m_http_client.disconnect(); @@ -981,6 +992,27 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl return true; } //---------------------------------------------------------------------------------------------------- +bool wallet2::reconnect_device() +{ + bool r = true; + hw::device &hwdev = hw::get_device(m_device_name); + hwdev.set_name(m_device_name); + r = hwdev.init(); + if (!r){ + LOG_PRINT_L2("Could not init device"); + return false; + } + + r = hwdev.connect(); + if (!r){ + LOG_PRINT_L2("Could not connect to the device"); + return false; + } + + m_account.set_device(hwdev); + return true; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Gets the seed language */ @@ -1210,7 +1242,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::publi THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); // if keys are encrypted, ask for password - if (m_ask_password && !m_rpc && !m_watch_only && !m_multisig_rescan_k) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k) { static critical_section password_lock; CRITICAL_REGION_LOCAL(password_lock); @@ -2067,9 +2099,11 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks { drop_from_short_history(short_chain_history, 3); + THROW_WALLET_EXCEPTION_IF(prev_blocks.size() != prev_parsed_blocks.size(), error::wallet_internal_error, "size mismatch"); + // prepend the last 3 blocks, should be enough to guard against a block or two's reorg std::vector<parsed_block>::const_reverse_iterator i = prev_parsed_blocks.rbegin(); - for (size_t n = 0; n < std::min((size_t)3, prev_blocks.size()); ++n) + for (size_t n = 0; n < std::min((size_t)3, prev_parsed_blocks.size()); ++n) { short_chain_history.push_front(i->hash); ++i; @@ -2389,6 +2423,7 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, while (missing_blocks-- > 0) m_blockchain.push_back(crypto::null_hash); // maybe a bit suboptimal, but deque won't do huge reallocs like vector m_blockchain.push_back(m_checkpoints.get_points().at(checkpoint_height)); + m_blockchain.trim(checkpoint_height); short_chain_history.clear(); get_short_chain_history(short_chain_history); } @@ -2600,10 +2635,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo break; } - // switch to the new blocks from the daemon - blocks_start_height = next_blocks_start_height; - blocks = std::move(next_blocks); - parsed_blocks = std::move(next_parsed_blocks); first = false; // handle error from async fetching thread @@ -2611,6 +2642,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo { throw std::runtime_error("proxy exception in refresh thread"); } + + // switch to the new blocks from the daemon + blocks_start_height = next_blocks_start_height; + blocks = std::move(next_blocks); + parsed_blocks = std::move(next_parsed_blocks); } catch (const tools::error::password_needed&) { @@ -2843,7 +2879,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); - if (m_ask_password && !m_rpc && !m_watch_only) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { account.encrypt_viewkey(key); account.decrypt_keys(key); @@ -2872,7 +2908,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable rapidjson::Value value2(rapidjson::kNumberType); - value2.SetInt(m_key_on_device?1:0); + value2.SetInt(m_key_device_type); json.AddMember("key_on_device", value2, json.GetAllocator()); value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? @@ -2922,7 +2958,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(m_confirm_non_default_ring_size ? 1 :0); json.AddMember("confirm_non_default_ring_size", value2, json.GetAllocator()); - value2.SetInt(m_ask_password ? 1 :0); + value2.SetInt(m_ask_password); json.AddMember("ask_password", value2, json.GetAllocator()); value2.SetUint(m_min_output_count); @@ -2973,6 +3009,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(1); json.AddMember("encrypted_secret_keys", value2, json.GetAllocator()); + value.SetString(m_device_name.c_str(), m_device_name.size()); + json.AddMember("device_name", value, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -3003,7 +3042,7 @@ void wallet2::setup_keys(const epee::wipeable_string &password) crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); // re-encrypt, but keep viewkey unencrypted - if (m_ask_password && !m_rpc && !m_watch_only) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { m_account.encrypt_keys(key); m_account.decrypt_viewkey(key); @@ -3019,7 +3058,7 @@ void wallet2::setup_keys(const epee::wipeable_string &password) //---------------------------------------------------------------------------------------------------- void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password) { - if (m_ask_password && !m_rpc && !m_watch_only) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) decrypt_keys(original_password); setup_keys(new_password); rewrite(filename, new_password); @@ -3067,7 +3106,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_refresh_type = RefreshType::RefreshDefault; m_confirm_missing_payment_id = true; m_confirm_non_default_ring_size = true; - m_ask_password = true; + m_ask_password = AskPasswordToDecrypt; m_min_output_count = 0; m_min_output_value = 0; m_merge_destinations = false; @@ -3081,7 +3120,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_ignore_fractional_outputs = true; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; - m_key_on_device = false; + m_device_name = ""; + m_key_device_type = hw::device::device_type::SOFTWARE; encrypted_secret_keys = false; } else if(json.IsObject()) @@ -3101,8 +3141,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ if (json.HasMember("key_on_device")) { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, false); - m_key_on_device = field_key_on_device; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE); + m_key_device_type = static_cast<hw::device::device_type>(field_key_on_device); } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false, std::string()); @@ -3176,7 +3216,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_confirm_missing_payment_id = field_confirm_missing_payment_id; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_non_default_ring_size, int, Int, false, true); m_confirm_non_default_ring_size = field_confirm_non_default_ring_size; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ask_password, int, Int, false, true); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ask_password, AskPasswordType, Int, false, AskPasswordToDecrypt); m_ask_password = field_ask_password; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_decimal_point, int, Int, false, CRYPTONOTE_DISPLAY_DECIMAL_POINT); cryptonote::set_default_decimal_point(field_default_decimal_point); @@ -3212,8 +3252,15 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_subaddress_lookahead_major = field_subaddress_lookahead_major; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); m_subaddress_lookahead_minor = field_subaddress_lookahead_minor; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false); encrypted_secret_keys = field_encrypted_secret_keys; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_name, std::string, String, false, std::string()); + if (m_device_name.empty() && field_device_name_found) + { + m_device_name = field_device_name; + } } else { @@ -3222,13 +3269,17 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } r = epee::serialization::load_t_from_binary(m_account, account_data); - if (r && m_key_on_device) { + THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); + if (m_key_device_type == hw::device::device_type::LEDGER) { LOG_PRINT_L0("Account on device. Initing device..."); - hw::device &hwdev = hw::get_device("Ledger"); + hw::device &hwdev = hw::get_device(m_device_name); + hwdev.set_name(m_device_name); hwdev.init(); hwdev.connect(); m_account.set_device(hwdev); LOG_PRINT_L0("Device inited..."); + } else if (key_on_device()) { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported"); } if (r) @@ -3240,7 +3291,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ else { // rewrite with encrypted keys, ignore errors - if (m_ask_password && !m_rpc && !m_watch_only) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) encrypt_keys(key); bool saved_ret = store_keys(keys_file_name, password, m_watch_only); if (!saved_ret) @@ -3248,7 +3299,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ // just moan a bit, but not fatal MERROR("Error saving keys file with encrypted keys, not fatal"); } - if (m_ask_password && !m_rpc && !m_watch_only) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) decrypt_keys(key); m_keys_file_locker.reset(); } @@ -3371,6 +3422,84 @@ void wallet2::decrypt_keys(const epee::wipeable_string &password) decrypt_keys(key); } +void wallet2::setup_new_blockchain() +{ + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); + add_subaddress_account(tr("Primary account")); +} + +void wallet2::create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file) +{ + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, watch_only); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + if (create_address_file) + { + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); + if(!r) MERROR("String with address text not saved"); + } + } +} + + +/*! + * \brief determine the key storage for the specified wallet file + * \param device_type (OUT) wallet backend as enumerated in hw::device::device_type + * \param keys_file_name Keys file to verify password for + * \param password Password to verify + * \return true if password correct, else false + * + * for verification only - determines key storage hardware + * + */ +bool wallet2::query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds) +{ + rapidjson::Document json; + wallet2::keys_file_data keys_file_data; + std::string buf; + bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); + THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); + + // Decrypt the contents + r = ::serialization::parse_binary(buf, keys_file_data); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds); + std::string account_data; + account_data.resize(keys_file_data.account_data.size()); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + + // The contents should be JSON if the wallet follows the new format. + if (json.Parse(account_data.c_str()).HasParseError()) + { + // old format before JSON wallet key file format + } + else + { + account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() + + json["key_data"].GetStringLength()); + + if (json.HasMember("key_on_device")) + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE); + device_type = static_cast<hw::device::device_type>(field_key_on_device); + } + } + + cryptonote::account_base account_data_check; + + r = epee::serialization::load_t_from_binary(account_data_check, account_data); + if (!r) return false; + return true; +} + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file @@ -3445,26 +3574,11 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = true; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); - if (!wallet_.empty()) - { - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - - if (m_nettype != MAINNET || create_address_file) - { - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); - if(!r) MERROR("String with address text not saved"); - } - } - - cryptonote::block b; - generate_genesis(b); - m_blockchain.push_back(get_block_hash(b)); - m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); - add_subaddress_account(tr("Primary account")); + create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); + setup_new_blockchain(); if (!wallet_.empty()) store(); @@ -3500,7 +3614,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); // calculate a starting refresh height @@ -3508,23 +3622,9 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_refresh_from_block_height = estimate_blockchain_height(); } - if (!wallet_.empty()) - { - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - - if (m_nettype != MAINNET || create_address_file) - { - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); - if(!r) MERROR("String with address text not saved"); - } - } + create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); - cryptonote::block b; - generate_genesis(b); - m_blockchain.push_back(get_block_hash(b)); - m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); - add_subaddress_account(tr("Primary account")); + setup_new_blockchain(); if (!wallet_.empty()) store(); @@ -3602,26 +3702,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); - if (!wallet_.empty()) - { - bool r = store_keys(m_keys_file, password, true); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - - if (m_nettype != MAINNET || create_address_file) - { - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); - if(!r) MERROR("String with address text not saved"); - } - } + create_keys_file(wallet_, true, password, m_nettype != MAINNET || create_address_file); - cryptonote::block b; - generate_genesis(b); - m_blockchain.push_back(get_block_hash(b)); - m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); - add_subaddress_account(tr("Primary account")); + setup_new_blockchain(); if (!wallet_.empty()) store(); @@ -3656,26 +3742,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); - if (!wallet_.empty()) - { - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + create_keys_file(wallet_, false, password, create_address_file); - if (m_nettype != MAINNET || create_address_file) - { - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); - if(!r) MERROR("String with address text not saved"); - } - } - - cryptonote::block b; - generate_genesis(b); - m_blockchain.push_back(get_block_hash(b)); - m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); - add_subaddress_account(tr("Primary account")); + setup_new_blockchain(); if (!wallet_.empty()) store(); @@ -3687,7 +3759,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& * \param password Password of wallet file * \param device_name device string address */ -void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name) +void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name, bool create_address_file) { clear(); prepare_file_names(wallet_); @@ -3697,33 +3769,28 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); } - m_key_on_device = true; - m_account.create_from_device(device_name); + + auto &hwdev = hw::get_device(device_name); + hwdev.set_name(device_name); + + m_account.create_from_device(hwdev); + m_key_device_type = m_account.get_device().get_type(); m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); setup_keys(password); + m_device_name = device_name; - if (!wallet_.empty()) { - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); - if(!r) MERROR("String with address text not saved"); - } - cryptonote::block b; - generate_genesis(b); - m_blockchain.push_back(get_block_hash(b)); + create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); if (m_subaddress_lookahead_major == SUBADDRESS_LOOKAHEAD_MAJOR && m_subaddress_lookahead_minor == SUBADDRESS_LOOKAHEAD_MINOR) { // the default lookahead setting (50:200) is clearly too much for hardware wallet m_subaddress_lookahead_major = 5; m_subaddress_lookahead_minor = 20; } - m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); - add_subaddress_account(tr("Primary account")); + setup_new_blockchain(); if (!wallet_.empty()) { store(); } @@ -3746,7 +3813,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // decrypt keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; - if (m_ask_password && !m_rpc && !m_watch_only) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { crypto::chacha_key chacha_key; crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); @@ -3804,7 +3871,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, m_watch_only = false; m_multisig = true; m_multisig_threshold = threshold; - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; if (threshold == spend_keys.size() + 1) { @@ -3819,23 +3886,9 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // re-encrypt keys keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); - if (!m_wallet_file.empty()) - { - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - - if (boost::filesystem::exists(m_wallet_file + ".address.txt")) - { - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); - if(!r) MERROR("String with address text not saved"); - } - } + create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); - cryptonote::block b; - generate_genesis(b); - m_blockchain.push_back(get_block_hash(b)); - m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); - add_subaddress_account(tr("Primary account")); + setup_new_blockchain(); if (!m_wallet_file.empty()) store(); @@ -3903,7 +3956,7 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor // keys are decrypted epee::misc_utils::auto_scope_leave_caller keys_reencryptor; - if (m_ask_password && !m_rpc && !m_watch_only) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { crypto::chacha_key chacha_key; crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); @@ -3937,17 +3990,7 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor // keys are encrypted again keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); - if (!m_wallet_file.empty()) - { - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - - if (boost::filesystem::exists(m_wallet_file + ".address.txt")) - { - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype)); - if(!r) MERROR("String with address text not saved"); - } - } + create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); m_subaddresses.clear(); m_subaddress_labels.clear(); @@ -4273,7 +4316,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype)); lock_keys_file(); - wallet_keys_unlocker unlocker(*this, m_ask_password && !m_rpc && !m_watch_only, password); + wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); //keys loaded ok! //try to load wallet file. but even if we failed, it is not big problem @@ -4758,12 +4801,7 @@ void wallet2::rescan_blockchain(bool refresh) { clear(); - cryptonote::block genesis; - generate_genesis(genesis); - crypto::hash genesis_hash = get_block_hash(genesis); - m_blockchain.push_back(genesis_hash); - m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); - add_subaddress_account(tr("Primary account")); + setup_new_blockchain(); if (refresh) this->refresh(false); @@ -4800,7 +4838,7 @@ bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_heig uint64_t current_time = static_cast<uint64_t>(time(NULL)); // XXX: this needs to be fast, so we'd need to get the starting heights // from the daemon to be correct once voting kicks in - uint64_t v2height = m_nettype == TESTNET ? 624634 : m_nettype == STAGENET ? (uint64_t)-1/*TODO*/ : 1009827; + uint64_t v2height = m_nettype == TESTNET ? 624634 : m_nettype == STAGENET ? 32000 : 1009827; uint64_t leeway = block_height < v2height ? CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 : CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2; if(current_time + leeway >= unlock_time) return true; @@ -4993,69 +5031,6 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo } //---------------------------------------------------------------------------------------------------- -void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx) -{ - transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), tx, ptx); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra) -{ - cryptonote::transaction tx; - pending_tx ptx; - transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, tx, ptx); -} - -namespace { -// split_amounts(vector<cryptonote::tx_destination_entry> dsts, size_t num_splits) -// -// split amount for each dst in dsts into num_splits parts -// and make num_splits new vector<crypt...> instances to hold these new amounts -std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( - std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits) -{ - std::vector<std::vector<cryptonote::tx_destination_entry>> retVal; - - if (num_splits <= 1) - { - retVal.push_back(dsts); - return retVal; - } - - // for each split required - for (size_t i=0; i < num_splits; i++) - { - std::vector<cryptonote::tx_destination_entry> new_dsts; - - // for each destination - for (size_t j=0; j < dsts.size(); j++) - { - cryptonote::tx_destination_entry de; - uint64_t amount; - - amount = dsts[j].amount; - amount = amount / num_splits; - - // if last split, add remainder - if (i + 1 == num_splits) - { - amount += dsts[j].amount % num_splits; - } - - de.addr = dsts[j].addr; - de.amount = amount; - - new_dsts.push_back(de); - } - - retVal.push_back(new_dsts); - } - - return retVal; -} -} // anonymous namespace -//---------------------------------------------------------------------------------------------------- crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const { std::vector<tx_extra_field> tx_extra_fields; @@ -6055,116 +6030,6 @@ uint32_t wallet2::adjust_priority(uint32_t priority) return priority; } //---------------------------------------------------------------------------------------------------- -// separated the call(s) to wallet2::transfer into their own function -// -// this function will make multiple calls to wallet2::transfer if multiple -// transactions will be required -std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra) -{ - const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, true); - - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); - const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE); - const uint64_t fee_quantization_mask = get_fee_quantization_mask(); - - // failsafe split attempt counter - size_t attempt_count = 0; - - for(attempt_count = 1; ;attempt_count++) - { - size_t num_tx = 0.5 + pow(1.7,attempt_count-1); - - auto split_values = split_amounts(dsts, num_tx); - - // Throw if split_amounts comes back with a vector of size different than it should - if (split_values.size() != num_tx) - { - throw std::runtime_error("Splitting transactions returned a number of potential tx not equal to what was requested"); - } - - std::vector<pending_tx> ptx_vector; - try - { - // for each new destination vector (i.e. for each new tx) - for (auto & dst_vector : split_values) - { - cryptonote::transaction tx; - pending_tx ptx; - - // loop until fee is met without increasing tx size to next KB boundary. - uint64_t needed_fee = estimate_fee(use_per_byte_fee, false, unused_transfers_indices.size(), fake_outs_count, dst_vector.size()+1, extra.size(), false, base_fee, fee_multiplier, fee_quantization_mask); - do - { - transfer(dst_vector, fake_outs_count, unused_transfers_indices, unlock_time, needed_fee, extra, tx, ptx); - auto txBlob = t_serializable_object_to_blob(ptx.tx); - needed_fee = calculate_fee(use_per_byte_fee, ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); - } while (ptx.fee < needed_fee); - - ptx_vector.push_back(ptx); - - // mark transfers to be used as "spent" - for(size_t idx: ptx.selected_transfers) - { - set_spent(idx, 0); - } - } - - // if we made it this far, we've selected our transactions. committing them will mark them spent, - // so this is a failsafe in case they don't go through - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - for(size_t idx2: ptx.selected_transfers) - { - set_unspent(idx2); - } - - } - - // if we made it this far, we're OK to actually send the transactions - return ptx_vector; - - } - // only catch this here, other exceptions need to pass through to the calling function - catch (const tools::error::tx_too_big& e) - { - - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - for(size_t idx2: ptx.selected_transfers) - { - set_unspent(idx2); - } - } - - if (attempt_count >= MAX_SPLIT_ATTEMPTS) - { - throw; - } - } - catch (...) - { - // in case of some other exception, make sure any tx in queue are marked unspent again - - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - for(size_t idx2: ptx.selected_transfers) - { - set_unspent(idx2); - } - } - - throw; - } - } -} - bool wallet2::set_ring_database(const std::string &filename) { m_ring_database = filename; @@ -6337,7 +6202,7 @@ bool wallet2::find_and_save_rings(bool force) return true; } -bool wallet2::blackball_output(const crypto::public_key &output) +bool wallet2::blackball_output(const std::pair<uint64_t, uint64_t> &output) { if (!m_ringdb) return false; @@ -6345,7 +6210,7 @@ bool wallet2::blackball_output(const crypto::public_key &output) catch (const std::exception &e) { return false; } } -bool wallet2::set_blackballed_outputs(const std::vector<crypto::public_key> &outputs, bool add) +bool wallet2::set_blackballed_outputs(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, bool add) { if (!m_ringdb) return false; @@ -6354,14 +6219,13 @@ bool wallet2::set_blackballed_outputs(const std::vector<crypto::public_key> &out bool ret = true; if (!add) ret &= m_ringdb->clear_blackballs(); - for (const auto &output: outputs) - ret &= m_ringdb->blackball(output); + ret &= m_ringdb->blackball(outputs); return ret; } catch (const std::exception &e) { return false; } } -bool wallet2::unblackball_output(const crypto::public_key &output) +bool wallet2::unblackball_output(const std::pair<uint64_t, uint64_t> &output) { if (!m_ringdb) return false; @@ -6369,7 +6233,7 @@ bool wallet2::unblackball_output(const crypto::public_key &output) catch (const std::exception &e) { return false; } } -bool wallet2::is_output_blackballed(const crypto::public_key &output) const +bool wallet2::is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const { if (!m_ringdb) return false; @@ -6414,8 +6278,8 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out CHECK_AND_ASSERT_MES(!outs.empty(), false, "internal error: outs is empty"); if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates return false; - if (is_output_blackballed(output_public_key)) // don't add blackballed outputs - return false; +// if (is_output_blackballed(output_public_key)) // don't add blackballed outputs +// return false; outs.back().push_back(item); return true; } @@ -6912,6 +6776,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (seen_indices.count(i)) continue; + if (is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs + continue; seen_indices.emplace(i); LOG_PRINT_L2("picking " << i << " as " << type); @@ -6934,7 +6800,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); + THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_outs_error, daemon_resp.status); THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error, "daemon returned wrong response for get_outs.bin, wrong amounts count = " + std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size())); @@ -7412,7 +7278,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF(selected_transfers.size() != sources.size(), error::wallet_internal_error, "mismatched selected_transfers and sources sixes"); for(size_t idx: selected_transfers) { - cryptonote::tx_source_entry& src = sources[src_idx]; + cryptonote::tx_source_entry& src = sources_copy[src_idx]; src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L); ++src_idx; } @@ -9711,6 +9577,7 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, if (account_minreserve) { + THROW_WALLET_EXCEPTION_IF(account_minreserve->second == 0, error::wallet_internal_error, "Proved amount must be greater than 0"); // minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount std::sort(selected_transfers.begin(), selected_transfers.end(), [&](const size_t a, const size_t b) { return m_transfers[a].amount() > m_transfers[b].amount(); }); @@ -11019,7 +10886,12 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) refresh(false); } - catch (...) {} + catch (...) + { + m_multisig_rescan_info = NULL; + m_multisig_rescan_k = NULL; + throw; + } m_multisig_rescan_info = NULL; m_multisig_rescan_k = NULL; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f9b516bff..9eb9b04f2 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -162,28 +162,36 @@ namespace tools RefreshDefault = RefreshOptimizeCoinbase, }; + enum AskPasswordType { + AskPasswordNever = 0, + AskPasswordOnAction = 1, + AskPasswordToDecrypt = 2, + }; + static const char* tr(const char* str); static bool has_testnet_option(const boost::program_options::variables_map& vm); static bool has_stagenet_option(const boost::program_options::variables_map& vm); + static std::string device_name_option(const boost::program_options::variables_map& vm); static void init_options(boost::program_options::options_description& desc_params); //! Uses stdin and stdout. Returns a wallet2 if no errors. - static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, bool rpc, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for `wallet_file` if no errors. static std::pair<std::unique_ptr<wallet2>, password_container> - make_from_file(const boost::program_options::variables_map& vm, bool rpc, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + make_from_file(const boost::program_options::variables_map& vm, bool unattended, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors. - static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Just parses variables. - static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds); + static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1); - wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1); + wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false); ~wallet2(); struct multisig_info @@ -544,8 +552,9 @@ namespace tools * \param wallet_ Name of wallet file * \param password Password of wallet file * \param device_name name of HW to use + * \param create_address_file Whether to create an address file */ - void restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name); + void restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name, bool create_address_file); /*! * \brief Creates a multisig wallet @@ -631,7 +640,7 @@ namespace tools bool explicit_refresh_from_block_height() const {return m_explicit_refresh_from_block_height;} bool deinit(); - bool init(bool rpc, std::string daemon_address = "http://localhost:8080", + bool init(std::string daemon_address = "http://localhost:8080", boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_weight_limit = 0, bool ssl = false, bool trusted_daemon = false); void stop() { m_run.store(false, std::memory_order_relaxed); } @@ -701,7 +710,9 @@ namespace tools bool has_multisig_partial_key_images() const; bool has_unknown_key_images() const; bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; - bool key_on_device() const { return m_key_on_device; } + bool key_on_device() const { return get_device_type() != hw::device::device_type::SOFTWARE; } + hw::device::device_type get_device_type() const { return m_key_device_type; } + bool reconnect_device(); // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; @@ -713,12 +724,6 @@ namespace tools uint64_t balance_all() const; uint64_t unlocked_balance_all() const; template<typename T> - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy); - template<typename T> - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx& ptx); - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra); - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx); - template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); @@ -746,7 +751,6 @@ namespace tools bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func); - std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); // pass subaddr_indices by value on purpose std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); @@ -912,8 +916,8 @@ namespace tools void auto_refresh(bool r) { m_auto_refresh = r; } bool confirm_missing_payment_id() const { return m_confirm_missing_payment_id; } void confirm_missing_payment_id(bool always) { m_confirm_missing_payment_id = always; } - bool ask_password() const { return m_ask_password; } - void ask_password(bool always) { m_ask_password = always; } + AskPasswordType ask_password() const { return m_ask_password; } + void ask_password(AskPasswordType ask) { m_ask_password = ask; } void set_min_output_count(uint32_t count) { m_min_output_count = count; } uint32_t get_min_output_count() const { return m_min_output_count; } void set_min_output_value(uint64_t value) { m_min_output_value = value; } @@ -938,6 +942,8 @@ namespace tools void ignore_fractional_outputs(bool value) { m_ignore_fractional_outputs = value; } bool confirm_non_default_ring_size() const { return m_confirm_non_default_ring_size; } void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } + const std::string & device_name() const { return m_device_name; } + void device_name(const std::string & device_name) { m_device_name = device_name; } bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys); @@ -1089,7 +1095,7 @@ namespace tools uint64_t adjust_mixin(uint64_t mixin) const; uint32_t adjust_priority(uint32_t priority); - bool is_rpc() const { return m_rpc; } + bool is_unattended() const { return m_unattended; } // Light wallet specific functions // fetch unspent outs from lw node and store in m_transfers @@ -1159,10 +1165,10 @@ namespace tools bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); bool find_and_save_rings(bool force = true); - bool blackball_output(const crypto::public_key &output); - bool set_blackballed_outputs(const std::vector<crypto::public_key> &outputs, bool add = false); - bool unblackball_output(const crypto::public_key &output); - bool is_output_blackballed(const crypto::public_key &output) const; + bool blackball_output(const std::pair<uint64_t, uint64_t> &output); + bool set_blackballed_outputs(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, bool add = false); + bool unblackball_output(const std::pair<uint64_t, uint64_t> &output); + bool is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const; bool lock_keys_file(); bool unlock_keys_file(); @@ -1242,6 +1248,9 @@ namespace tools void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const; + void setup_new_blockchain(); + void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file); + cryptonote::account_base m_account; boost::optional<epee::net_utils::http::login> m_daemon_login; std::string m_daemon_address; @@ -1277,7 +1286,7 @@ namespace tools bool m_trusted_daemon; i_wallet2_callback* m_callback; - bool m_key_on_device; + hw::device::device_type m_key_device_type; cryptonote::network_type m_nettype; uint64_t m_kdf_rounds; std::string seed_language; /*!< Language of the mnemonics (seed). */ @@ -1300,7 +1309,7 @@ namespace tools bool m_explicit_refresh_from_block_height; bool m_confirm_missing_payment_id; bool m_confirm_non_default_ring_size; - bool m_ask_password; + AskPasswordType m_ask_password; uint32_t m_min_output_count; uint64_t m_min_output_value; bool m_merge_destinations; @@ -1316,6 +1325,7 @@ namespace tools NodeRPCProxy m_node_rpc_proxy; std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; + std::string m_device_name; // Light wallet bool m_light_wallet; /* sends view key to daemon for scanning */ @@ -1342,7 +1352,7 @@ namespace tools crypto::chacha_key m_cache_key; boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; - bool m_rpc; + bool m_unattended; }; } BOOST_CLASS_VERSION(tools::wallet2, 25) @@ -1817,198 +1827,4 @@ namespace tools //---------------------------------------------------------------------------------------------------- } //---------------------------------------------------------------------------------------------------- - template<typename T> - void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy) - { - pending_tx ptx; - cryptonote::transaction tx; - transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx, ptx); - } - - template<typename T> - void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) - { - using namespace cryptonote; - // throw if attempting a transaction with no destinations - THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); - - THROW_WALLET_EXCEPTION_IF(m_multisig, error::wallet_internal_error, "Multisig wallets cannot spend non rct outputs"); - - uint64_t upper_transaction_weight_limit = get_upper_transaction_weight_limit(); - uint64_t needed_money = fee; - - // calculate total amount being sent to all destinations - // throw if total amount overflows uint64_t - for(auto& dt: dsts) - { - THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); - needed_money += dt.amount; - THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_nettype); - } - - // randomly select inputs for transaction - // throw if requested send amount is greater than (unlocked) amount available to send - std::vector<size_t> selected_transfers; - uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers); - THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); - - uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; - for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) - THROW_WALLET_EXCEPTION_IF(subaddr_account != *i, error::wallet_internal_error, "the tx uses funds from multiple accounts"); - - typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - typedef cryptonote::tx_source_entry::output_entry tx_output_entry; - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - if(fake_outputs_count) - { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); - req.outs_count = fake_outputs_count + 1;// add one to make possible (if need) to skip real output key - for(size_t idx: selected_transfers) - { - const transfer_container::const_iterator it = m_transfers.begin() + idx; - THROW_WALLET_EXCEPTION_IF(it->m_tx.vout.size() <= it->m_internal_output_index, error::wallet_internal_error, - "m_internal_output_index = " + std::to_string(it->m_internal_output_index) + - " is greater or equal to outputs count = " + std::to_string(it->m_tx.vout.size())); - req.amounts.push_back(it->amount()); - } - - m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_bin("/getrandom_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_outs.bin"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); - THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != selected_transfers.size(), error::wallet_internal_error, - "daemon returned wrong response for getrandom_outs.bin, wrong amounts count = " + - std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(selected_transfers.size())); - - std::unordered_map<uint64_t, uint64_t> scanty_outs; - for(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs: daemon_resp.outs) - { - if (amount_outs.outs.size() < fake_outputs_count) - { - scanty_outs[amount_outs.amount] = amount_outs.outs.size(); - } - } - THROW_WALLET_EXCEPTION_IF(!scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); - } - - //prepare inputs - size_t i = 0; - std::vector<cryptonote::tx_source_entry> sources; - for(size_t idx: selected_transfers) - { - sources.resize(sources.size()+1); - cryptonote::tx_source_entry& src = sources.back(); - const transfer_details& td = m_transfers[idx]; - src.amount = td.amount(); - src.rct = false; - //paste mixin transaction - if(daemon_resp.outs.size()) - { - daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b){return a.global_amount_index < b.global_amount_index;}); - for(out_entry& daemon_oe: daemon_resp.outs[i].outs) - { - if(td.m_global_output_index == daemon_oe.global_amount_index) - continue; - tx_output_entry oe; - oe.first = daemon_oe.global_amount_index; - oe.second.dest = rct::pk2rct(daemon_oe.out_key); - oe.second.mask = rct::identity(); - src.outputs.push_back(oe); - if(src.outputs.size() >= fake_outputs_count) - break; - } - } - - //paste real transaction to the random index - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) - { - return a.first >= td.m_global_output_index; - }); - //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; - tx_output_entry real_oe; - real_oe.first = td.m_global_output_index; - real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); - real_oe.second.mask = rct::identity(); - auto interted_it = src.outputs.insert(it_to_insert, real_oe); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); - src.real_output = interted_it - src.outputs.begin(); - src.real_output_in_tx_index = td.m_internal_output_index; - src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); - detail::print_source_entry(src); - ++i; - } - - cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); - if (needed_money < found_money) - { - change_dts.addr = get_subaddress({subaddr_account, 0}); - change_dts.amount = found_money - needed_money; - } - - std::vector<cryptonote::tx_destination_entry> splitted_dsts, dust_dsts; - uint64_t dust = 0; - destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, splitted_dsts, dust_dsts); - for(auto& d: dust_dsts) { - THROW_WALLET_EXCEPTION_IF(dust_policy.dust_threshold < d.amount, error::wallet_internal_error, "invalid dust value: dust = " + - std::to_string(d.amount) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); - } - for(auto& d: dust_dsts) { - if (!dust_policy.add_to_fee) - splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust, d.is_subaddress)); - dust += d.amount; - } - - crypto::secret_key tx_key; - std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, rct::RangeProofBorromean, m_multisig ? &msout : NULL); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); - THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); - - std::string key_images; - bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool - { - CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); - key_images += boost::to_string(in.k_image) + " "; - return true; - }); - THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); - - bool dust_sent_elsewhere = (dust_policy.addr_for_dust.m_view_public_key != change_dts.addr.m_view_public_key - || dust_policy.addr_for_dust.m_spend_public_key != change_dts.addr.m_spend_public_key); - - if (dust_policy.add_to_fee || dust_sent_elsewhere) change_dts.amount -= dust; - - ptx.key_images = key_images; - ptx.fee = (dust_policy.add_to_fee ? fee+dust : fee); - ptx.dust = ((dust_policy.add_to_fee || dust_sent_elsewhere) ? dust : 0); - ptx.dust_added_to_fee = dust_policy.add_to_fee; - ptx.tx = tx; - ptx.change_dts = change_dts; - ptx.selected_transfers = selected_transfers; - ptx.tx_key = tx_key; - ptx.additional_tx_keys = additional_tx_keys; - ptx.dests = dsts; - ptx.construction_data.sources = sources; - ptx.construction_data.change_dts = change_dts; - ptx.construction_data.splitted_dsts = splitted_dsts; - ptx.construction_data.selected_transfers = selected_transfers; - ptx.construction_data.extra = tx.extra; - ptx.construction_data.unlock_time = unlock_time; - ptx.construction_data.use_rct = false; - ptx.construction_data.use_bulletproofs = false; - ptx.construction_data.dests = dsts; - // record which subaddress indices are being used as inputs - ptx.construction_data.subaddr_account = subaddr_account; - ptx.construction_data.subaddr_indices.clear(); - for (size_t idx: selected_transfers) - ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor); - } - - } diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index a30e807b1..bc518d04a 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -73,7 +73,7 @@ namespace tools // get_tx_pool_error // out_of_hashchain_bounds_error // transfer_error * - // get_random_outs_general_error + // get_outs_general_error // not_enough_unlocked_money // not_enough_money // tx_not_possible @@ -128,7 +128,7 @@ namespace tools get_blocks_error_message_index, get_hashes_error_message_index, get_out_indices_error_message_index, - get_random_outs_error_message_index + get_outs_error_message_index }; template<typename Base, int msg_index> @@ -427,7 +427,7 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- - typedef failed_rpc_request<transfer_error, get_random_outs_error_message_index> get_random_outs_error; + typedef failed_rpc_request<transfer_error, get_outs_error_message_index> get_outs_error; //---------------------------------------------------------------------------------------------------- struct not_enough_unlocked_money : public transfer_error { diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 0e0b2e4eb..86b46b173 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -51,6 +51,7 @@ using namespace epee; #include "mnemonics/electrum-words.h" #include "rpc/rpc_args.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "daemonizer/daemonizer.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" @@ -160,8 +161,13 @@ namespace tools std::string bind_port = command_line::get_arg(*m_vm, arg_rpc_bind_port); const bool disable_auth = command_line::get_arg(*m_vm, arg_disable_rpc_login); m_restricted = command_line::get_arg(*m_vm, arg_restricted); - if (command_line::has_arg(*m_vm, arg_wallet_dir)) + if (!command_line::is_arg_defaulted(*m_vm, arg_wallet_dir)) { + if (!command_line::is_arg_defaulted(*m_vm, wallet_args::arg_wallet_file())) + { + MERROR(arg_wallet_dir.name << " and " << wallet_args::arg_wallet_file().name << " are incompatible, use only one of them"); + return false; + } m_wallet_dir = command_line::get_arg(*m_vm, arg_wallet_dir); #ifdef _WIN32 #define MKDIR(path, mode) mkdir(path) @@ -754,10 +760,10 @@ namespace tools { if (get_tx_key) { - std::string s = epee::string_tools::pod_to_hex(ptx.tx_key); + epee::wipeable_string s = epee::to_hex::wipeable_string(ptx.tx_key); for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) - s += epee::string_tools::pod_to_hex(additional_tx_key); - fill(tx_key, s); + s += epee::to_hex::wipeable_string(additional_tx_key); + fill(tx_key, std::string(s.data(), s.size())); } // Compute amount leaving wallet in tx. By convention dests does not include change outputs fill(amount, total_amount(ptx)); @@ -1564,11 +1570,13 @@ namespace tools } else if(req.key_type.compare("view_key") == 0) { - res.key = string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key); + epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_view_secret_key); + res.key = std::string(key.data(), key.size()); } else if(req.key_type.compare("spend_key") == 0) { - res.key = string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_spend_secret_key); + epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key); + res.key = std::string(key.data(), key.size()); } else { @@ -1794,11 +1802,11 @@ namespace tools return false; } - std::ostringstream oss; - oss << epee::string_tools::pod_to_hex(tx_key); + epee::wipeable_string s; + s += epee::to_hex::wipeable_string(tx_key); for (size_t i = 0; i < additional_tx_keys.size(); ++i) - oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); - res.tx_key = oss.str(); + s += epee::to_hex::wipeable_string(additional_tx_keys[i]); + res.tx_key = std::string(s.data(), s.size()); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1814,26 +1822,33 @@ namespace tools return false; } - std::string tx_key_str = req.tx_key; + epee::wipeable_string tx_key_str = req.tx_key; + if (tx_key_str.size() < 64 || tx_key_str.size() % 64) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; + er.message = "Tx key has invalid format"; + return false; + } + const char *data = tx_key_str.data(); crypto::secret_key tx_key; - if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) + if (!epee::wipeable_string(data, 64).hex_to_pod(unwrap(unwrap(tx_key)))) { er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; er.message = "Tx key has invalid format"; return false; } - tx_key_str = tx_key_str.substr(64); + size_t offset = 64; std::vector<crypto::secret_key> additional_tx_keys; - while (!tx_key_str.empty()) + while (offset < tx_key_str.size()) { additional_tx_keys.resize(additional_tx_keys.size() + 1); - if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) + if (!epee::wipeable_string(data + offset, 64).hex_to_pod(unwrap(unwrap(additional_tx_keys.back())))) { er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; er.message = "Tx key has invalid format"; return false; } - tx_key_str = tx_key_str.substr(64); + offset += 64; } cryptonote::address_parse_info info; @@ -3260,12 +3275,172 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ } +class t_daemon +{ +private: + const boost::program_options::variables_map& vm; + +public: + t_daemon(boost::program_options::variables_map const & _vm) + : vm(_vm) + { + } + + bool run() + { + std::unique_ptr<tools::wallet2> wal; + try + { + const bool testnet = tools::wallet2::has_testnet_option(vm); + const bool stagenet = tools::wallet2::has_stagenet_option(vm); + if (testnet && stagenet) + { + MERROR(tools::wallet_rpc_server::tr("Can't specify more than one of --testnet and --stagenet")); + return false; + } + + const auto arg_wallet_file = wallet_args::arg_wallet_file(); + const auto arg_from_json = wallet_args::arg_generate_from_json(); + + const auto wallet_file = command_line::get_arg(vm, arg_wallet_file); + const auto from_json = command_line::get_arg(vm, arg_from_json); + const auto wallet_dir = command_line::get_arg(vm, arg_wallet_dir); + const auto prompt_for_password = command_line::get_arg(vm, arg_prompt_for_password); + const auto password_prompt = prompt_for_password ? password_prompter : nullptr; + + if(!wallet_file.empty() && !from_json.empty()) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Can't specify more than one of --wallet-file and --generate-from-json")); + return false; + } + + if (!wallet_dir.empty()) + { + wal = NULL; + goto just_dir; + } + + if (wallet_file.empty() && from_json.empty()) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Must specify --wallet-file or --generate-from-json or --wallet-dir")); + return false; + } + + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet...")); + if(!wallet_file.empty()) + { + wal = tools::wallet2::make_from_file(vm, true, wallet_file, password_prompt).first; + } + else + { + try + { + wal = tools::wallet2::make_from_json(vm, true, from_json, password_prompt); + } + catch (const std::exception &e) + { + MERROR("Error creating wallet: " << e.what()); + return false; + } + } + if (!wal) + { + return false; + } + + bool quit = false; + tools::signal_handler::install([&wal, &quit](int) { + assert(wal); + quit = true; + wal->stop(); + }); + + wal->refresh(wal->is_trusted_daemon()); + // if we ^C during potentially length load/refresh, there's no server loop yet + if (quit) + { + MINFO(tools::wallet_rpc_server::tr("Saving wallet...")); + wal->store(); + MINFO(tools::wallet_rpc_server::tr("Successfully saved")); + return false; + } + MINFO(tools::wallet_rpc_server::tr("Successfully loaded")); + } + catch (const std::exception& e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Wallet initialization failed: ") << e.what()); + return false; + } + just_dir: + tools::wallet_rpc_server wrpc; + if (wal) wrpc.set_wallet(wal.release()); + bool r = wrpc.init(&vm); + CHECK_AND_ASSERT_MES(r, false, tools::wallet_rpc_server::tr("Failed to initialize wallet RPC server")); + tools::signal_handler::install([&wrpc](int) { + wrpc.send_stop_signal(); + }); + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet RPC server")); + try + { + wrpc.run(); + } + catch (const std::exception &e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Failed to run wallet: ") << e.what()); + return false; + } + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Stopped wallet RPC server")); + try + { + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Saving wallet...")); + wrpc.stop(); + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Successfully saved")); + } + catch (const std::exception& e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Failed to save wallet: ") << e.what()); + return false; + } + return true; + } +}; + +class t_executor final +{ +public: + static std::string const NAME; + + std::string const & name() + { + return NAME; + } + + t_daemon create_daemon(boost::program_options::variables_map const & vm) + { + return t_daemon(vm); + } + + bool run_non_interactive(boost::program_options::variables_map const & vm) + { + return t_daemon(vm).run(); + } + + bool run_interactive(boost::program_options::variables_map const & vm) + { + return t_daemon(vm).run(); + } +}; + +std::string const t_executor::NAME = "Wallet RPC Daemon"; + int main(int argc, char** argv) { namespace po = boost::program_options; const auto arg_wallet_file = wallet_args::arg_wallet_file(); const auto arg_from_json = wallet_args::arg_generate_from_json(); + po::options_description hidden_options("Hidden"); + po::options_description desc_params(wallet_args::tr("Wallet options")); tools::wallet2::init_options(desc_params); command_line::add_arg(desc_params, arg_rpc_bind_port); @@ -3277,6 +3452,8 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); + daemonizer::init_options(hidden_options, desc_params); + boost::optional<po::variables_map> vm; bool should_terminate = false; std::tie(vm, should_terminate) = wallet_args::main( @@ -3298,115 +3475,5 @@ int main(int argc, char** argv) { return 0; } - std::unique_ptr<tools::wallet2> wal; - try - { - const bool testnet = tools::wallet2::has_testnet_option(*vm); - const bool stagenet = tools::wallet2::has_stagenet_option(*vm); - if (testnet && stagenet) - { - MERROR(tools::wallet_rpc_server::tr("Can't specify more than one of --testnet and --stagenet")); - return 1; - } - - const auto wallet_file = command_line::get_arg(*vm, arg_wallet_file); - const auto from_json = command_line::get_arg(*vm, arg_from_json); - const auto wallet_dir = command_line::get_arg(*vm, arg_wallet_dir); - const auto prompt_for_password = command_line::get_arg(*vm, arg_prompt_for_password); - const auto password_prompt = prompt_for_password ? password_prompter : nullptr; - - if(!wallet_file.empty() && !from_json.empty()) - { - LOG_ERROR(tools::wallet_rpc_server::tr("Can't specify more than one of --wallet-file and --generate-from-json")); - return 1; - } - - if (!wallet_dir.empty()) - { - wal = NULL; - goto just_dir; - } - - if (wallet_file.empty() && from_json.empty()) - { - LOG_ERROR(tools::wallet_rpc_server::tr("Must specify --wallet-file or --generate-from-json or --wallet-dir")); - return 1; - } - - LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet...")); - if(!wallet_file.empty()) - { - wal = tools::wallet2::make_from_file(*vm, true, wallet_file, password_prompt).first; - } - else - { - try - { - wal = tools::wallet2::make_from_json(*vm, true, from_json, password_prompt); - } - catch (const std::exception &e) - { - MERROR("Error creating wallet: " << e.what()); - return 1; - } - } - if (!wal) - { - return 1; - } - - bool quit = false; - tools::signal_handler::install([&wal, &quit](int) { - assert(wal); - quit = true; - wal->stop(); - }); - - wal->refresh(wal->is_trusted_daemon()); - // if we ^C during potentially length load/refresh, there's no server loop yet - if (quit) - { - MINFO(tools::wallet_rpc_server::tr("Saving wallet...")); - wal->store(); - MINFO(tools::wallet_rpc_server::tr("Successfully saved")); - return 1; - } - MINFO(tools::wallet_rpc_server::tr("Successfully loaded")); - } - catch (const std::exception& e) - { - LOG_ERROR(tools::wallet_rpc_server::tr("Wallet initialization failed: ") << e.what()); - return 1; - } -just_dir: - tools::wallet_rpc_server wrpc; - if (wal) wrpc.set_wallet(wal.release()); - bool r = wrpc.init(&(vm.get())); - CHECK_AND_ASSERT_MES(r, 1, tools::wallet_rpc_server::tr("Failed to initialize wallet RPC server")); - tools::signal_handler::install([&wrpc](int) { - wrpc.send_stop_signal(); - }); - LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet RPC server")); - try - { - wrpc.run(); - } - catch (const std::exception &e) - { - LOG_ERROR(tools::wallet_rpc_server::tr("Failed to run wallet: ") << e.what()); - return 1; - } - LOG_PRINT_L0(tools::wallet_rpc_server::tr("Stopped wallet RPC server")); - try - { - LOG_PRINT_L0(tools::wallet_rpc_server::tr("Saving wallet...")); - wrpc.stop(); - LOG_PRINT_L0(tools::wallet_rpc_server::tr("Successfully saved")); - } - catch (const std::exception& e) - { - LOG_ERROR(tools::wallet_rpc_server::tr("Failed to save wallet: ") << e.what()); - return 1; - } - return 0; + return daemonizer::daemonize(argc, const_cast<const char**>(argv), t_executor{}, *vm) ? 0 : 1; } diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index ce10e2917..4501cf575 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -766,15 +766,9 @@ namespace wallet_rpc struct response { std::string tx_hash; - std::string tx_key; - uint64_t fee; - std::string tx_blob; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) - KV_SERIALIZE(tx_key) - KV_SERIALIZE(fee) - KV_SERIALIZE(tx_blob) END_KV_SERIALIZE_MAP() }; }; |