diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/utils.cpp | 3 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 4 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 2 | ||||
-rw-r--r-- | src/wallet/api/wallet2_api.h | 20 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.cpp | 27 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.h | 15 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 267 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 21 | ||||
-rw-r--r-- | src/wallet/wallet_args.cpp | 3 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 71 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.h | 2 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 25 |
12 files changed, 359 insertions, 101 deletions
diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index aebe41e59..86fe56564 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -51,6 +51,9 @@ bool isAddressLocal(const std::string &address) void onStartup() { tools::on_startup(); +#ifdef NDEBUG + tools::disable_core_dumps(); +#endif } } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 680da26ce..f7c074b5a 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -366,7 +366,7 @@ void Wallet::error(const std::string &category, const std::string &str) { } ///////////////////////// WalletImpl implementation //////////////////////// -WalletImpl::WalletImpl(NetworkType nettype) +WalletImpl::WalletImpl(NetworkType nettype, bool restricted, uint64_t kdf_rounds) :m_wallet(nullptr) , m_status(Wallet::Status_Ok) , m_trustedDaemon(false) @@ -377,7 +377,7 @@ WalletImpl::WalletImpl(NetworkType nettype) , m_rebuildWalletCache(false) , m_is_connected(false) { - m_wallet = new tools::wallet2(static_cast<cryptonote::network_type>(nettype)); + m_wallet = new tools::wallet2(static_cast<cryptonote::network_type>(nettype), restricted, kdf_rounds); m_history = new TransactionHistoryImpl(this); m_wallet2Callback = new Wallet2CallbackImpl(this); m_wallet->callback(m_wallet2Callback); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 58be686fc..28b73423d 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -52,7 +52,7 @@ struct Wallet2CallbackImpl; class WalletImpl : public Wallet { public: - WalletImpl(NetworkType nettype = MAINNET); + WalletImpl(NetworkType nettype = MAINNET, bool restricted = false, uint64_t kdf_rounds = 1); ~WalletImpl(); bool create(const std::string &path, const std::string &password, const std::string &language); diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 0cd0ff5cf..5a52c6b17 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -920,9 +920,10 @@ struct WalletManager * \param password Password of wallet file * \param language Language to be used to generate electrum seed mnemonic * \param nettype Network type + * \param kdf_rounds Number of rounds for key derivation function * \return Wallet instance (Wallet::status() needs to be called to check if created successfully) */ - virtual Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype) = 0; + virtual Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1) = 0; Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, bool testnet = false) // deprecated { return createWallet(path, password, language, testnet ? TESTNET : MAINNET); @@ -933,9 +934,10 @@ struct WalletManager * \param path Name of wallet file * \param password Password of wallet file * \param nettype Network type + * \param kdf_rounds Number of rounds for key derivation function * \return Wallet instance (Wallet::status() needs to be called to check if opened successfully) */ - virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype) = 0; + virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1) = 0; Wallet * openWallet(const std::string &path, const std::string &password, bool testnet = false) // deprecated { return openWallet(path, password, testnet ? TESTNET : MAINNET); @@ -948,10 +950,11 @@ struct WalletManager * \param mnemonic mnemonic (25 words electrum seed) * \param nettype Network type * \param restoreHeight restore from start height + * \param kdf_rounds Number of rounds for key derivation function * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, - NetworkType nettype = MAINNET, uint64_t restoreHeight = 0) = 0; + NetworkType nettype = MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1) = 0; Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, bool testnet = false, uint64_t restoreHeight = 0) // deprecated { @@ -983,6 +986,7 @@ struct WalletManager * \param addressString public address * \param viewKeyString view key * \param spendKeyString spend key (optional) + * \param kdf_rounds Number of rounds for key derivation function * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * createWalletFromKeys(const std::string &path, @@ -992,7 +996,8 @@ struct WalletManager uint64_t restoreHeight, const std::string &addressString, const std::string &viewKeyString, - const std::string &spendKeyString = "") = 0; + const std::string &spendKeyString = "", + uint64_t kdf_rounds = 1) = 0; Wallet * createWalletFromKeys(const std::string &path, const std::string &password, const std::string &language, @@ -1043,6 +1048,7 @@ struct WalletManager * \param deviceName Device name * \param restoreHeight restore from start height (0 sets to current height) * \param subaddressLookahead Size of subaddress lookahead (empty sets to some default low value) + * \param kdf_rounds Number of rounds for key derivation function * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * createWalletFromDevice(const std::string &path, @@ -1050,7 +1056,8 @@ struct WalletManager NetworkType nettype, const std::string &deviceName, uint64_t restoreHeight = 0, - const std::string &subaddressLookahead = "") = 0; + const std::string &subaddressLookahead = "", + uint64_t kdf_rounds = 1) = 0; /*! * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted @@ -1075,13 +1082,14 @@ struct WalletManager * @param keys_file_name - location of keys file * @param password - password to verify * @param no_spend_key - verify only view keys? + * @param kdf_rounds - number of rounds for key derivation function * @return - true if password is correct * * @note * This function will fail when the wallet keys file is opened because the wallet program locks the keys file. * In this case, Wallet::unlockKeysFile() and Wallet::lockKeysFile() need to be called before and after the call to this function, respectively. */ - virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0; + 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 findWallets - searches for the wallet files by given path name recursively diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 99eadc82f..5daf11ec0 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -50,16 +50,16 @@ namespace epee { namespace Monero { Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password, - const std::string &language, NetworkType nettype) + const std::string &language, NetworkType nettype, uint64_t kdf_rounds) { - WalletImpl * wallet = new WalletImpl(nettype); + WalletImpl * wallet = new WalletImpl(nettype, false, kdf_rounds); wallet->create(path, password, language); return wallet; } -Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype) +Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds) { - WalletImpl * wallet = new WalletImpl(nettype); + WalletImpl * wallet = new WalletImpl(nettype, false, kdf_rounds); wallet->open(path, password); //Refresh addressBook wallet->addressBook()->refresh(); @@ -87,9 +87,10 @@ Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, NetworkType nettype, - uint64_t restoreHeight) + uint64_t restoreHeight, + uint64_t kdf_rounds) { - WalletImpl * wallet = new WalletImpl(nettype); + WalletImpl * wallet = new WalletImpl(nettype, false, kdf_rounds); if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } @@ -104,9 +105,10 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, uint64_t restoreHeight, const std::string &addressString, const std::string &viewKeyString, - const std::string &spendKeyString) + const std::string &spendKeyString, + uint64_t kdf_rounds) { - WalletImpl * wallet = new WalletImpl(nettype); + WalletImpl * wallet = new WalletImpl(nettype, false, kdf_rounds); if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } @@ -119,9 +121,10 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, NetworkType nettype, const std::string &deviceName, uint64_t restoreHeight, - const std::string &subaddressLookahead) + const std::string &subaddressLookahead, + uint64_t kdf_rounds) { - WalletImpl * wallet = new WalletImpl(nettype); + WalletImpl * wallet = new WalletImpl(nettype, false, kdf_rounds); if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } @@ -159,9 +162,9 @@ bool WalletManagerImpl::walletExists(const std::string &path) return false; } -bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const +bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds) const { - return tools::wallet2::verify_password(keys_file_name, password, no_spend_key, hw::get_device("default")); + return tools::wallet2::verify_password(keys_file_name, password, no_spend_key, hw::get_device("default"), kdf_rounds); } std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path) diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 656a7142c..8b1c8be7f 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -39,13 +39,14 @@ class WalletManagerImpl : public WalletManager { public: Wallet * createWallet(const std::string &path, const std::string &password, - const std::string &language, NetworkType nettype) override; - Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype) override; + const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1) override; + Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1) override; virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, NetworkType nettype, - uint64_t restoreHeight) override; + uint64_t restoreHeight, + uint64_t kdf_rounds = 1) override; virtual Wallet * createWalletFromKeys(const std::string &path, const std::string &password, const std::string &language, @@ -53,7 +54,8 @@ public: uint64_t restoreHeight, const std::string &addressString, const std::string &viewKeyString, - const std::string &spendKeyString = "") override; + const std::string &spendKeyString = "", + uint64_t kdf_rounds = 1) override; // next two methods are deprecated - use the above version which allow setting of a password virtual Wallet * recoveryWallet(const std::string &path, const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight) override; // deprecated: use createWalletFromKeys(..., password, ...) instead @@ -69,10 +71,11 @@ public: NetworkType nettype, const std::string &deviceName, uint64_t restoreHeight = 0, - const std::string &subaddressLookahead = "") override; + const std::string &subaddressLookahead = "", + uint64_t kdf_rounds = 1) override; 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) const override; + bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; 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/wallet2.cpp b/src/wallet/wallet2.cpp index b4b86057f..4559ebd84 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -160,6 +160,7 @@ struct options { return val; } }; + const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) @@ -203,6 +204,8 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl const bool stagenet = command_line::get_arg(vm, opts.stagenet); const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; const bool restricted = command_line::get_arg(vm, opts.restricted); + const uint64_t kdf_rounds = command_line::get_arg(vm, opts.kdf_rounds); + THROW_WALLET_EXCEPTION_IF(kdf_rounds == 0, tools::error::wallet_internal_error, "KDF rounds must not be 0"); auto daemon_address = command_line::get_arg(vm, opts.daemon_address); auto daemon_host = command_line::get_arg(vm, opts.daemon_host); @@ -236,7 +239,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); - std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, restricted)); + std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, restricted, kdf_rounds)); wallet->init(std::move(daemon_address), std::move(login)); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); @@ -647,7 +650,7 @@ const size_t MAX_SPLIT_ATTEMPTS = 30; constexpr const std::chrono::seconds wallet2::rpc_timeout; const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); } -wallet2::wallet2(network_type nettype, bool restricted): +wallet2::wallet2(network_type nettype, bool restricted, uint64_t kdf_rounds): m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_run(true), @@ -679,6 +682,7 @@ wallet2::wallet2(network_type nettype, bool restricted): m_ignore_fractional_outputs(true), m_is_initialized(false), m_restricted(restricted), + m_kdf_rounds(kdf_rounds), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), @@ -723,6 +727,7 @@ 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.restricted); command_line::add_arg(desc_params, opts.shared_ringdb_dir); + command_line::add_arg(desc_params, opts.kdf_rounds); } std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -899,6 +904,14 @@ cryptonote::account_public_address wallet2::get_subaddress(const cryptonote::sub return hwdev.get_subaddress(m_account.get_keys(), index); } //---------------------------------------------------------------------------------------------------- +boost::optional<cryptonote::subaddress_index> wallet2::get_subaddress_index(const cryptonote::account_public_address& address) const +{ + auto index = m_subaddresses.find(address.m_spend_public_key); + if (index == m_subaddresses.end()) + return boost::none; + return index->second; +} +//---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const { hw::device &hwdev = m_account.get_device(); @@ -1594,6 +1607,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote payment.m_block_height = height; payment.m_unlock_time = tx.unlock_time; payment.m_timestamp = ts; + payment.m_coinbase = miner_tx; payment.m_subaddr_index = i.first; if (pool) { emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, double_spend_seen}); @@ -2547,7 +2561,7 @@ bool wallet2::refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& rece return ok; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_output_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution) +bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution) { uint32_t rpc_version; boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); @@ -2844,7 +2858,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable // Encrypt the entire JSON object. crypto::chacha_key key; - crypto::generate_chacha_key(password.data(), password.size(), key); + crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); std::string cipher; cipher.resize(account_data.size()); keys_file_data.iv = crypto::rand<crypto::chacha_iv>(); @@ -2878,7 +2892,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ 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); + crypto::generate_chacha_key(password.data(), password.size(), key, m_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]); @@ -3084,7 +3098,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) { // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). unlock_keys_file(); - bool r = verify_password(m_keys_file, password, m_watch_only || m_multisig, m_account.get_device()); + bool r = verify_password(m_keys_file, password, m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds); lock_keys_file(); return r; } @@ -3102,7 +3116,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password * */ -bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev) +bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds) { rapidjson::Document json; wallet2::keys_file_data keys_file_data; @@ -3114,7 +3128,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip 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); + 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]); @@ -3982,7 +3996,7 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout) bool wallet2::generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const { hw::device &hwdev = m_account.get_device(); - return hwdev.generate_chacha_key(m_account.get_keys(), key); + return hwdev.generate_chacha_key(m_account.get_keys(), key, m_kdf_rounds); } //---------------------------------------------------------------------------------------------------- void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password) @@ -6206,22 +6220,42 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> bool is_shortly_after_segregation_fork = height >= segregation_fork_height && height < segregation_fork_height + SEGREGATION_FORK_VICINITY; bool is_after_segregation_fork = height >= segregation_fork_height; + // if we have at least one rct out, get the distribution, or fall back to the previous system + uint64_t rct_start_height; + std::vector<uint64_t> rct_offsets; + bool has_rct = false; + for (size_t idx: selected_transfers) + if (m_transfers[idx].is_rct()) + { has_rct = true; break; } + const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets); + if (has_rct_distribution) + { + // check we're clear enough of rct start, to avoid corner cases below + THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, + error::get_output_distribution, "Not enough rct outputs"); + } + // get histogram for the amounts we need cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); - m_daemon_rpc_mutex.lock(); + // request histogram for all outputs, except 0 if we have the rct distribution for(size_t idx: selected_transfers) - req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); - std::sort(req_t.amounts.begin(), req_t.amounts.end()); - auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end()); - req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); - req_t.unlocked = true; - req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); + if (!m_transfers[idx].is_rct() || !has_rct_distribution) + req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); + if (!req_t.amounts.empty()) + { + std::sort(req_t.amounts.begin(), req_t.amounts.end()); + auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end()); + req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); + req_t.unlocked = true; + req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); + THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); + } // if we want to segregate fake outs pre or post fork, get distribution std::unordered_map<uint64_t, std::pair<uint64_t, uint64_t>> segregation_limit; @@ -6277,6 +6311,36 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + struct gamma_engine + { + typedef uint64_t result_type; + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return std::numeric_limits<result_type>::max(); } + result_type operator()() { return crypto::rand<result_type>(); } + } engine; + static const double shape = 19.28/*16.94*/; + //static const double shape = m_testnet ? 17.02 : 17.28; + static const double scale = 1/1.61; + std::gamma_distribution<double> gamma(shape, scale); + auto pick_gamma = [&]() + { + double x = gamma(engine); + x = exp(x); + uint64_t block_offset = x / DIFFICULTY_TARGET_V2; // this assumes constant target over the whole rct range + if (block_offset >= rct_offsets.size() - 1) + return std::numeric_limits<uint64_t>::max(); // bad pick + block_offset = rct_offsets.size() - 2 - block_offset; + THROW_WALLET_EXCEPTION_IF(block_offset >= rct_offsets.size() - 1, error::wallet_internal_error, "Bad offset calculation"); + THROW_WALLET_EXCEPTION_IF(rct_offsets[block_offset + 1] < rct_offsets[block_offset], + error::get_output_distribution, "Decreasing offsets in rct distribution: " + + std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]) + ", " + + std::to_string(block_offset + 1) + ": " + std::to_string(rct_offsets[block_offset + 1])); + uint64_t n_rct = rct_offsets[block_offset + 1] - rct_offsets[block_offset]; + if (n_rct == 0) + return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0; + return rct_offsets[block_offset] + crypto::rand<uint64_t>() % n_rct; + }; + size_t num_selected_transfers = 0; for(size_t idx: selected_transfers) { @@ -6287,6 +6351,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); + bool use_histogram = amount != 0 || !has_rct_distribution; const bool output_is_pre_fork = td.m_block_height < segregation_fork_height; uint64_t num_outs = 0, num_recent_outs = 0; @@ -6342,26 +6407,41 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> num_post_fork_outs = num_outs - segregation_limit[amount].first; } - LOG_PRINT_L1("" << num_outs << " unlocked outputs of size " << print_money(amount)); - THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, - "histogram reports no unlocked outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours"); - THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, - "histogram reports more recent outs than outs for " + boost::lexical_cast<std::string>(amount)); + if (use_histogram) + { + LOG_PRINT_L1("" << num_outs << " unlocked outputs of size " << print_money(amount)); + THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, + "histogram reports no unlocked outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours"); + THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, + "histogram reports more recent outs than outs for " + boost::lexical_cast<std::string>(amount)); + } + else + { + // the base offset of the first rct output in the first unlocked block (or the one to be if there's none) + num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; + LOG_PRINT_L1("" << num_outs << " unlocked rct outputs"); + THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, + "histogram reports no unlocked rct outputs, not even ours"); + } - // how many fake outs to draw on a pre-fork triangular distribution + // how many fake outs to draw on a pre-fork distribution size_t pre_fork_outputs_count = requested_outputs_count * pre_fork_num_out_ratio; size_t post_fork_outputs_count = requested_outputs_count * post_fork_num_out_ratio; // how many fake outs to draw otherwise size_t normal_output_count = requested_outputs_count - pre_fork_outputs_count - post_fork_outputs_count; - // X% of those outs are to be taken from recent outputs - size_t recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO; - if (recent_outputs_count == 0) - recent_outputs_count = 1; // ensure we have at least one, if possible - if (recent_outputs_count > num_recent_outs) - recent_outputs_count = num_recent_outs; - if (td.m_global_output_index >= num_outs - num_recent_outs && recent_outputs_count > 0) - --recent_outputs_count; // if the real out is recent, pick one less recent fake out + size_t recent_outputs_count = 0; + if (use_histogram) + { + // X% of those outs are to be taken from recent outputs + recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO; + if (recent_outputs_count == 0) + recent_outputs_count = 1; // ensure we have at least one, if possible + if (recent_outputs_count > num_recent_outs) + recent_outputs_count = num_recent_outs; + if (td.m_global_output_index >= num_outs - num_recent_outs && recent_outputs_count > 0) + --recent_outputs_count; // if the real out is recent, pick one less recent fake out + } LOG_PRINT_L1("Fake output makeup: " << requested_outputs_count << " requested: " << recent_outputs_count << " recent, " << pre_fork_outputs_count << " pre-fork, " << post_fork_outputs_count << " post-fork, " << (requested_outputs_count - recent_outputs_count - pre_fork_outputs_count - post_fork_outputs_count) << " full-chain"); @@ -6441,7 +6521,26 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> uint64_t i; const char *type = ""; - if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with + if (amount == 0 && has_rct_distribution) + { + // gamma distribution + if (num_found -1 < recent_outputs_count + pre_fork_outputs_count) + { + do i = pick_gamma(); while (i >= segregation_limit[amount].first); + type = "pre-fork gamma"; + } + else if (num_found -1 < recent_outputs_count + pre_fork_outputs_count + post_fork_outputs_count) + { + do i = pick_gamma(); while (i < segregation_limit[amount].first || i >= num_outs); + type = "post-fork gamma"; + } + else + { + do i = pick_gamma(); while (i >= num_outs); + type = "gamma"; + } + } + else if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with { // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); @@ -6506,7 +6605,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // get the keys for those m_daemon_rpc_mutex.lock(); - r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_bin("/get_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, "get_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); @@ -7466,6 +7565,7 @@ void wallet2::light_wallet_get_address_txs() payment.m_block_height = t.height; payment.m_unlock_time = t.unlock_time; payment.m_timestamp = t.timestamp; + payment.m_coinbase = t.coinbase; if (t.mempool) { if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) { @@ -7734,6 +7834,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // early out if we know we can't make it anyway // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through + const uint64_t min_fee = (fee_multiplier * fee_per_kb * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof)) / 1024; uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -7741,10 +7842,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp balance_subtotal += balance_per_subaddr[index_minor]; unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor]; } - THROW_WALLET_EXCEPTION_IF(needed_money > balance_subtotal, error::not_enough_money, + THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > balance_subtotal, error::not_enough_money, balance_subtotal, needed_money, 0); // first check overall balance is enough, then unlocked one, so we throw distinct exceptions - THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance_subtotal, error::not_enough_unlocked_money, + THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money, unlocked_balance_subtotal, needed_money, 0); for (uint32_t i : subaddr_indices) @@ -8612,6 +8713,54 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s return true; } //---------------------------------------------------------------------------------------------------- +void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys) +{ + // fetch tx from daemon and check if secret keys agree with corresponding public keys + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + req.prune = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + std::vector<tx_extra_field> tx_extra_fields; + THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(tx.extra, tx_extra_fields), error::wallet_internal_error, "Transaction extra has unsupported format"); + tx_extra_pub_key pub_key_field; + bool found = false; + size_t index = 0; + while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, index++)) + { + crypto::public_key calculated_pub_key; + crypto::secret_key_to_public_key(tx_key, calculated_pub_key); + if (calculated_pub_key == pub_key_field.pub_key) + { + found = true; + break; + } + } + THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, "Given tx secret key doesn't agree with the tx public key in the blockchain"); + tx_extra_additional_pub_keys additional_tx_pub_keys; + find_tx_extra_field_by_type(tx_extra_fields, additional_tx_pub_keys); + THROW_WALLET_EXCEPTION_IF(additional_tx_keys.size() != additional_tx_pub_keys.data.size(), error::wallet_internal_error, "The number of additional tx secret keys doesn't agree with the number of additional tx public keys in the blockchain" ); + m_tx_keys.insert(std::make_pair(txid, tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, additional_tx_keys)); +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string &message) { THROW_WALLET_EXCEPTION_IF(m_watch_only, error::wallet_internal_error, @@ -9899,6 +10048,17 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag std::unordered_set<crypto::hash> spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input. std::vector<size_t> swept_transfers; // If such a spending tx wasn't found in m_transfers, this means the spending tx // was created by sweep_all, so we can't know the spent height and other detailed info. + std::unordered_map<crypto::key_image, crypto::hash> spent_key_images; + + for (const transfer_details &td: m_transfers) + { + for (const cryptonote::txin_v& in : td.m_tx.vin) + { + if (in.type() == typeid(cryptonote::txin_to_key)) + spent_key_images.insert(std::make_pair(boost::get<cryptonote::txin_to_key>(in).k_image, td.m_txid)); + } + } + for(size_t i = 0; i < signed_key_images.size(); ++i) { transfer_details &td = m_transfers[i]; @@ -9912,28 +10072,11 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag if (i < daemon_resp.spent_status.size() && daemon_resp.spent_status[i] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN) { - bool is_spent_tx_found = false; - for (auto it = m_transfers.rbegin(); &(*it) != &td; ++it) - { - bool is_spent_tx = false; - for(const cryptonote::txin_v& in : it->m_tx.vin) - { - if(in.type() == typeid(cryptonote::txin_to_key) && td.m_key_image == boost::get<cryptonote::txin_to_key>(in).k_image) - { - is_spent_tx = true; - break; - } - } - if (is_spent_tx) - { - is_spent_tx_found = true; - spent_txids.insert(it->m_txid); - break; - } - } - - if (!is_spent_tx_found) + const std::unordered_map<crypto::key_image, crypto::hash>::const_iterator skii = spent_key_images.find(td.m_key_image); + if (skii == spent_key_images.end()) swept_transfers.push_back(i); + else + spent_txids.insert(skii->second); } } MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); @@ -10536,7 +10679,7 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const { crypto::chacha_key key; - crypto::generate_chacha_key(&skey, sizeof(skey), key); + crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds); std::string ciphertext; crypto::chacha_iv iv = crypto::rand<crypto::chacha_iv>(); ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); @@ -10566,7 +10709,7 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret error::wallet_internal_error, "Unexpected ciphertext size"); crypto::chacha_key key; - crypto::generate_chacha_key(&skey, sizeof(skey), key); + crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds); const crypto::chacha_iv &iv = *(const crypto::chacha_iv*)&ciphertext[0]; std::string plaintext; plaintext.resize(ciphertext.size() - prefix_size); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 220eca8a8..6e6c1a6ee 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -165,9 +165,9 @@ namespace tools //! Just parses variables. static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, 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); + 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); - wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, bool restricted = false); + wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, bool restricted = false, uint64_t kdf_rounds = 1); ~wallet2(); struct multisig_info @@ -260,12 +260,12 @@ namespace tools uint64_t m_block_height; uint64_t m_unlock_time; uint64_t m_timestamp; + bool m_coinbase; cryptonote::subaddress_index m_subaddr_index; }; struct address_tx : payment_details { - bool m_coinbase; bool m_mempool; bool m_incoming; }; @@ -659,6 +659,7 @@ namespace tools // Subaddress scheme cryptonote::account_public_address get_subaddress(const cryptonote::subaddress_index& index) const; cryptonote::account_public_address get_address() const { return get_subaddress({0,0}); } + boost::optional<cryptonote::subaddress_index> get_subaddress_index(const cryptonote::account_public_address& address) const; crypto::public_key get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const; std::vector<crypto::public_key> get_subaddress_spend_public_keys(uint32_t account, uint32_t begin, uint32_t end) const; std::string get_subaddress_as_str(const cryptonote::subaddress_index& index) const; @@ -931,6 +932,7 @@ namespace tools void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } 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); void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message); @@ -1215,7 +1217,7 @@ namespace tools void cache_ringdb_key(); void clear_ringdb_key(); - bool get_output_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution); + bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution); uint64_t get_segregation_fork_height() const; @@ -1258,6 +1260,7 @@ namespace tools bool m_key_on_device; cryptonote::network_type m_nettype; bool m_restricted; + uint64_t m_kdf_rounds; std::string seed_language; /*!< Language of the mnemonics (seed). */ bool is_old_file_format; /*!< Whether the wallet file is of an old file format */ bool m_watch_only; /*!< no spend key */ @@ -1323,7 +1326,7 @@ BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) -BOOST_CLASS_VERSION(tools::wallet2::payment_details, 3) +BOOST_CLASS_VERSION(tools::wallet2::payment_details, 4) BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) @@ -1590,16 +1593,24 @@ namespace boost a & x.m_timestamp; if (ver < 2) { + x.m_coinbase = false; x.m_subaddr_index = {}; return; } a & x.m_subaddr_index; if (ver < 3) { + x.m_coinbase = false; x.m_fee = 0; return; } a & x.m_fee; + if (ver < 4) + { + x.m_coinbase = false; + return; + } + a & x.m_coinbase; } template <class Archive> diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index a629eb506..95a4e0ad6 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -109,6 +109,9 @@ namespace wallet_args std::string lang = i18n_get_language(); tools::on_startup(); +#ifdef NDEBUG + tools::disable_core_dumps(); +#endif tools::set_strict_default_file_permissions(true); epee::string_tools::set_module_name_and_folder(argv[0]); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index b9cf99635..c6a81d886 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -270,7 +270,7 @@ namespace tools entry.unlock_time = pd.m_unlock_time; entry.fee = pd.m_fee; entry.note = m_wallet->get_tx_note(pd.m_tx_hash); - entry.type = "in"; + entry.type = pd.m_coinbase ? "block" : "in"; entry.subaddr_index = pd.m_subaddr_index; entry.address = m_wallet->get_subaddress_as_str(pd.m_subaddr_index); set_confirmations(entry, m_wallet->get_blockchain_current_height(), m_wallet->get_last_block_reward()); @@ -355,14 +355,20 @@ namespace tools std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index); std::vector<tools::wallet2::transfer_details> transfers; m_wallet->get_transfers(transfers); - for (const auto& i : balance_per_subaddress) + std::set<uint32_t> address_indices = req.address_indices; + if (address_indices.empty()) + { + for (const auto& i : balance_per_subaddress) + address_indices.insert(i.first); + } + for (uint32_t i : address_indices) { wallet_rpc::COMMAND_RPC_GET_BALANCE::per_subaddress_info info; - info.address_index = i.first; + info.address_index = i; cryptonote::subaddress_index index = {req.account_index, info.address_index}; info.address = m_wallet->get_subaddress_as_str(index); - info.balance = i.second; - info.unlocked_balance = unlocked_balance_per_subaddress[i.first]; + info.balance = balance_per_subaddress[i]; + info.unlocked_balance = unlocked_balance_per_subaddress[i]; info.label = m_wallet->get_subaddress_label(index); info.num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == index; }); res.per_subaddress.push_back(info); @@ -416,6 +422,27 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_getaddress_index(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->nettype(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + auto index = m_wallet->get_subaddress_index(info.address); + if (!index) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Address doesn't belong to the wallet"; + return false; + } + res.index = *index; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -1255,7 +1282,39 @@ namespace tools } } - res.integrated_address = m_wallet->get_integrated_address_as_str(payment_id); + if (req.standard_address.empty()) + { + res.integrated_address = m_wallet->get_integrated_address_as_str(payment_id); + } + else + { + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->nettype(), req.standard_address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + if (info.is_subaddress) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Subaddress shouldn't be used"; + return false; + } + if (info.has_payment_id) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Already integrated address"; + return false; + } + if (req.payment_id.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Payment ID shouldn't be left unspecified"; + return false; + } + res.integrated_address = get_account_integrated_address_as_str(m_wallet->nettype(), info.address, payment_id); + } res.payment_id = epee::string_tools::pod_to_hex(payment_id); return true; } diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 9cb67c593..b7e545c53 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -69,6 +69,7 @@ namespace tools BEGIN_JSON_RPC_MAP("/json_rpc") MAP_JON_RPC_WE("get_balance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) MAP_JON_RPC_WE("get_address", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) + MAP_JON_RPC_WE("get_address_index", on_getaddress_index, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX) MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) MAP_JON_RPC_WE("create_address", on_create_address, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS) @@ -146,6 +147,7 @@ namespace tools //json_rpc bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er); bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er); + bool on_getaddress_index(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::response& res, epee::json_rpc::error& er); bool on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er); bool on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er); bool on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 1bd572add..48d881c4c 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 1 +#define WALLET_RPC_VERSION_MINOR 2 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -62,8 +62,10 @@ namespace wallet_rpc struct request { uint32_t account_index; + std::set<uint32_t> address_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) + KV_SERIALIZE(address_indices) END_KV_SERIALIZE_MAP() }; @@ -141,6 +143,25 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_ADDRESS_INDEX + { + struct request + { + std::string address; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + cryptonote::subaddress_index index; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(index) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_CREATE_ADDRESS { struct request @@ -914,9 +935,11 @@ namespace wallet_rpc { struct request { + std::string standard_address; std::string payment_id; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(standard_address) KV_SERIALIZE(payment_id) END_KV_SERIALIZE_MAP() }; |