diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 495 |
1 files changed, 360 insertions, 135 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c4124a677..425fb746a 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -39,7 +39,6 @@ using namespace epee; #include "cryptonote_config.h" #include "wallet2.h" -#include "wallet2_api.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "rpc/core_rpc_server_commands_defs.h" #include "misc_language.h" @@ -435,7 +434,7 @@ static void emplace_or_replace(std::unordered_multimap<crypto::hash, tools::wall auto range = container.equal_range(key); for (auto i = range.first; i != range.second; ++i) { - if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash) + if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash && i->second.m_pd.m_subaddr_index == pd.m_pd.m_subaddr_index) { i->second = pd; return; @@ -457,6 +456,59 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_ } } +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size) +{ + size_t size = 0; + + // tx prefix + + // first few bytes + size += 1 + 6; + + // vin + size += n_inputs * (1+6+(mixin+1)*2+32); + + // vout + size += n_outputs * (6+32); + + // extra + size += extra_size; + + // rct signatures + + // type + size += 1; + + // rangeSigs + size += (2*64*32+32+64*32) * n_outputs; + + // MGs + size += n_inputs * (64 * (mixin+1) + 32); + + // mixRing - not serialized, can be reconstructed + /* size += 2 * 32 * (mixin+1) * n_inputs; */ + + // pseudoOuts + size += 32 * n_inputs; + // ecdhInfo + size += 2 * 32 * n_outputs; + // outPk - only commitment is saved + size += 32 * n_outputs; + // txnFee + size += 4; + + LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " with ring size " << (mixin+1) << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); + return size; +} + +size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size) +{ + if (use_rct) + return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1, extra_size); + else + return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; +} + } //namespace namespace tools @@ -646,8 +698,7 @@ void wallet2::add_subaddress_account(const std::string& label) //---------------------------------------------------------------------------------------------------- void wallet2::add_subaddress(uint32_t index_major, const std::string& label) { - if (index_major >= m_subaddress_labels.size()) - throw std::runtime_error("index_major is out of bound"); + THROW_WALLET_EXCEPTION_IF(index_major >= m_subaddress_labels.size(), error::account_index_outofbound); uint32_t index_minor = (uint32_t)get_num_subaddresses(index_major); expand_subaddresses({index_major, index_minor}); m_subaddress_labels[index_major][index_minor] = label; @@ -703,10 +754,9 @@ std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& in //---------------------------------------------------------------------------------------------------- void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, const std::string &label) { - if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size()) - MERROR("Subaddress index is out of bounds. Failed to set subaddress label."); - else - m_subaddress_labels[index.major][index.minor] = label; + THROW_WALLET_EXCEPTION_IF(index.major >= m_subaddress_labels.size(), error::account_index_outofbound); + THROW_WALLET_EXCEPTION_IF(index.minor >= m_subaddress_labels[index.major].size(), error::address_index_outofbound); + m_subaddress_labels[index.major][index.minor] = label; } //---------------------------------------------------------------------------------------------------- /*! @@ -836,7 +886,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote tools::threadpool::waiter waiter; const cryptonote::account_keys& keys = m_account.get_keys(); crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + if (!generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation)) + { + MWARNING("Failed to generate key derivation from tx pubkey, skipping"); + static_assert(sizeof(derivation) == sizeof(rct::key), "Mismatched sizes of key_derivation and rct::key"); + memcpy(&derivation, rct::identity().bytes, sizeof(derivation)); + } // additional tx pubkeys and derivations for multi-destination transfers involving one or more subaddresses std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); @@ -844,7 +899,11 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) { additional_derivations.push_back({}); - generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + if (!generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back())) + { + MWARNING("Failed to generate key derivation from tx pubkey, skipping"); + additional_derivations.pop_back(); + } } if (miner_tx && m_refresh_type == RefreshNoCoinbase) @@ -1027,7 +1086,9 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } uint64_t tx_money_spent_in_ins = 0; - boost::optional<uint32_t> subaddr_account; + // The line below is equivalent to "boost::optional<uint32_t> subaddr_account;", but avoids the GCC warning: ‘*((void*)& subaddr_account +4)’ may be used uninitialized in this function + // It's a GCC bug with boost::optional, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47679 + auto subaddr_account ([]()->boost::optional<uint32_t> {return boost::none;}()); std::set<uint32_t> subaddr_indices; // check all outputs for spending (compare key images) for(auto& in: tx.vin) @@ -3992,7 +4053,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto pending_tx ptx; // loop until fee is met without increasing tx size to next KB boundary. - uint64_t needed_fee = 0; + const size_t estimated_tx_size = estimate_tx_size(false, unused_transfers_indices.size(), fake_outs_count, dst_vector.size(), extra.size()); + uint64_t needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); do { transfer(dst_vector, fake_outs_count, unused_transfers_indices, unlock_time, needed_fee, extra, tx, ptx, trusted_daemon); @@ -4582,9 +4644,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit(); uint64_t needed_money = fee; LOG_PRINT_L2("transfer_selected_rct: starting with fee " << print_money (needed_money)); - LOG_PRINT_L0("selected transfers: "); - for (auto t: selected_transfers) - LOG_PRINT_L2(" " << t); + LOG_PRINT_L2("selected transfers: " << strjoin(selected_transfers, " ")); // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t @@ -4668,21 +4728,25 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry change_dts.amount = found_money - needed_money; if (change_dts.amount == 0) { - // If the change is 0, send it to a random address, to avoid confusing - // the sender with a 0 amount output. We send a 0 amount in order to avoid - // letting the destination be able to work out which of the inputs is the - // real one in our rings - LOG_PRINT_L2("generating dummy address for 0 change"); - cryptonote::account_base dummy; - dummy.generate(); - change_dts.addr = dummy.get_keys().m_account_address; - LOG_PRINT_L2("generated dummy address for 0 change"); + if (splitted_dsts.size() == 1) + { + // If the change is 0, send it to a random address, to avoid confusing + // the sender with a 0 amount output. We send a 0 amount in order to avoid + // letting the destination be able to work out which of the inputs is the + // real one in our rings + LOG_PRINT_L2("generating dummy address for 0 change"); + cryptonote::account_base dummy; + dummy.generate(); + change_dts.addr = dummy.get_keys().m_account_address; + LOG_PRINT_L2("generated dummy address for 0 change"); + splitted_dsts.push_back(change_dts); + } } else { change_dts.addr = get_subaddress({subaddr_account, 0}); + splitted_dsts.push_back(change_dts); } - splitted_dsts.push_back(change_dts); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; @@ -4729,59 +4793,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry LOG_PRINT_L2("transfer_selected_rct done"); } -static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) -{ - size_t size = 0; - - // tx prefix - - // first few bytes - size += 1 + 6; - - // vin - size += n_inputs * (1+6+(mixin+1)*2+32); - - // vout - size += n_outputs * (6+32); - - // extra - size += 40; - - // rct signatures - - // type - size += 1; - - // rangeSigs - size += (2*64*32+32+64*32) * n_outputs; - - // MGs - size += n_inputs * (64 * (mixin+1) + 32); - - // mixRing - not serialized, can be reconstructed - /* size += 2 * 32 * (mixin+1) * n_inputs; */ - - // pseudoOuts - size += 32 * n_inputs; - // ecdhInfo - size += 2 * 32 * n_outputs; - // outPk - only commitment is saved - size += 32 * n_outputs; - // txnFee - size += 4; - - LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " with ring size " << (mixin+1) << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); - return size; -} - -static size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs) -{ - if (use_rct) - return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1); - else - return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES; -} - std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const { std::vector<size_t> picks; @@ -4789,19 +4800,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui LOG_PRINT_L2("pick_preferred_rct_inputs: needed_money " << print_money(needed_money)); - // try to find a rct input of enough size - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; - if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) - { - LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); - picks.push_back(i); - return picks; - } - } - - // then try to find two outputs + // try to find two outputs // this could be made better by picking one of the outputs to be a small one, since those // are less useful since often below the needed money, so if one can be used in a pair, // it gets rid of it for the future @@ -5555,12 +5554,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. - uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier); + uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count, 2, extra.size()), fee_multiplier); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { string s; - for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") "; + for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + " (" + print_money(m_transfers[i].amount()) + ") "; LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s); // bring the list of available outputs stored by the same subaddress index to the front of the list @@ -5592,14 +5591,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp unsigned int original_output_index = 0; std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second; std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second; - while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) { + while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || !preferred_inputs.empty() || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) { TX &tx = txes.back(); LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size()); - LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size()); LOG_PRINT_L2("unused_transfers_indices: " << strjoin(*unused_transfers_indices, " ")); - LOG_PRINT_L2("unused_dust_indices:" << strjoin(*unused_dust_indices, " ")); - LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? -1 : dsts[0].amount)); + LOG_PRINT_L2("unused_dust_indices: " << strjoin(*unused_dust_indices, " ")); + LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? "-" : cryptonote::print_money(dsts[0].amount))); LOG_PRINT_L2("adding_fee " << adding_fee << ", use_rct " << use_rct); // if we need to spend money and don't have any left, we fail @@ -5611,7 +5609,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) // This could be more clever, but maybe at the cost of making probabilistic inferences easier size_t idx; - if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) { + if (!preferred_inputs.empty()) { + idx = pop_back(preferred_inputs); + pop_if_present(*unused_transfers_indices, idx); + pop_if_present(*unused_dust_indices, idx); + } else if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) { // the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too std::vector<size_t> indices = get_only_rct(*unused_dust_indices, *unused_transfers_indices); idx = pop_best_value(indices, tx.selected_transfers, true); @@ -5634,10 +5636,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } pop_if_present(*unused_transfers_indices, idx); pop_if_present(*unused_dust_indices, idx); - } else if (!preferred_inputs.empty()) { - idx = pop_back(preferred_inputs); - pop_if_present(*unused_transfers_indices, idx); - pop_if_present(*unused_dust_indices, idx); } else idx = pop_best_value(unused_transfers_indices->empty() ? *unused_dust_indices : *unused_transfers_indices, tx.selected_transfers); @@ -5659,7 +5657,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) << @@ -5671,7 +5669,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp ++original_output_index; } - if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { + if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); @@ -5684,26 +5682,31 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_size_limit); - bool try_tx; - if (adding_fee) - { - /* might not actually be enough if adding this output bumps size to next kB, but we need to try */ - try_tx = available_for_fee >= needed_fee; - } - else + bool try_tx = false; + // if we have preferred picks, but haven't yet used all of them, continue + if (preferred_inputs.empty()) { - const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()); - try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); + if (adding_fee) + { + /* might not actually be enough if adding this output bumps size to next kB, but we need to try */ + try_tx = available_for_fee >= needed_fee; + } + else + { + const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()); + try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); + } } if (try_tx) { cryptonote::transaction test_tx; pending_tx test_ptx; - needed_fee = 0; + const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()); + needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); - LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << - tx.selected_transfers.size() << " outputs"); + LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " << + tx.selected_transfers.size() << " inputs"); if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); @@ -5745,8 +5748,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - do { + LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); + while (needed_fee > test_ptx.fee) { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); @@ -5757,7 +5760,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } while (needed_fee > test_ptx.fee); + } LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -5939,14 +5942,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_size_limit); - const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); + const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1, extra.size()); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); if (try_tx) { cryptonote::transaction test_tx; pending_tx test_ptx; - needed_fee = 0; + const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()); + needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress)); @@ -5966,7 +5970,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); - do { + while (needed_fee > test_ptx.fee) { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); tx.dsts[0].amount = available_for_fee - needed_fee; if (use_rct) @@ -5979,7 +5983,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } while (needed_fee > test_ptx.fee); + } LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -6209,6 +6213,236 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s additional_tx_keys = j->second; return true; } +//---------------------------------------------------------------------------------------------------- +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, + "get_spend_proof requires spend secret key and is not available for a watch-only wallet"); + + // fetch tx from daemon + 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; + 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<std::vector<crypto::signature>> signatures; + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // check if the key image belongs to us + const auto found = m_key_images.find(in_key->k_image); + if(found == m_key_images.end()) + { + THROW_WALLET_EXCEPTION_IF(i > 0, error::wallet_internal_error, "subset of key images belong to us, very weird!"); + THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, "This tx wasn't generated by this wallet!"); + } + + // derive the real output keypair + const transfer_details& in_td = m_transfers[found->second]; + const txout_to_key* const in_tx_out_pkey = boost::get<txout_to_key>(std::addressof(in_td.m_tx.vout[in_td.m_internal_output_index].target)); + THROW_WALLET_EXCEPTION_IF(in_tx_out_pkey == nullptr, error::wallet_internal_error, "Output is not txout_to_key"); + const crypto::public_key in_tx_pub_key = get_tx_pub_key_from_extra(in_td.m_tx, in_td.m_pk_index); + const std::vector<crypto::public_key> in_additionakl_tx_pub_keys = get_additional_tx_pub_keys_from_extra(in_td.m_tx); + keypair in_ephemeral; + crypto::key_image in_img; + THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey->key, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img), + error::wallet_internal_error, "failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(in_key->k_image != in_img, error::wallet_internal_error, "key image mismatch"); + + // get output pubkeys in the ring + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + const size_t ring_size = in_key->key_offsets.size(); + THROW_WALLET_EXCEPTION_IF(absolute_offsets.size() != ring_size, error::wallet_internal_error, "absolute offsets size is wrong"); + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + req.outputs.resize(ring_size); + for (size_t j = 0; j < ring_size; ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::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_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); + + // copy pubkey pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // figure out real output index and secret key + size_t sec_index = -1; + for (size_t j = 0; j < ring_size; ++j) + { + if (res.outs[j].key == in_ephemeral.pub) + { + sec_index = j; + break; + } + } + THROW_WALLET_EXCEPTION_IF(sec_index >= ring_size, error::wallet_internal_error, "secret index not found"); + + // generate ring sig for this input + signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = signatures.back(); + sigs.resize(in_key->key_offsets.size()); + crypto::generate_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, in_ephemeral.sec, sec_index, sigs.data()); + } + + std::string sig_str = "SpendProofV1"; + for (const std::vector<crypto::signature>& ring_sig : signatures) + for (const crypto::signature& sig : ring_sig) + sig_str += tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); + return sig_str; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str) +{ + const std::string header = "SpendProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + + // fetch tx from daemon + 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; + 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"); + + // check signature size + size_t num_sigs = 0; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key != nullptr) + num_sigs += in_key->key_offsets.size(); + } + std::vector<std::vector<crypto::signature>> signatures = { std::vector<crypto::signature>(1) }; + const size_t sig_len = tools::base58::encode(std::string((const char *)&signatures[0][0], sizeof(crypto::signature))).size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * sig_len, + error::wallet_internal_error, "incorrect signature size"); + + // decode base58 + signatures.clear(); + size_t offset = header_len; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + signatures.resize(signatures.size() + 1); + signatures.back().resize(in_key->key_offsets.size()); + for (size_t j = 0; j < in_key->key_offsets.size(); ++j) + { + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, sig_len), sig_decoded), error::wallet_internal_error, "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, "Signature decoding error"); + memcpy(&signatures.back()[j], sig_decoded.data(), sizeof(crypto::signature)); + offset += sig_len; + } + } + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + std::vector<std::vector<crypto::signature>>::const_iterator sig_iter = signatures.cbegin(); + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // get output pubkeys in the ring + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + req.outputs.resize(absolute_offsets.size()); + for (size_t j = 0; j < absolute_offsets.size(); ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::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_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + + // copy pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // check this ring + if (!crypto::check_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, sig_iter->data())) + return false; + ++sig_iter; + } + THROW_WALLET_EXCEPTION_IF(sig_iter != signatures.cend(), error::wallet_internal_error, "Signature iterator didn't reach the end"); + return true; +} +//---------------------------------------------------------------------------------------------------- void wallet2::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) { @@ -6306,9 +6540,8 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de } } -std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, std::string &error_str) +std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) { - error_str = ""; // determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound) const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0; @@ -6324,11 +6557,7 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac { crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - if (!get_tx_key(txid, tx_key, additional_tx_keys)) - { - error_str = tr("Tx secret key wasn't found in the wallet file."); - return {}; - } + THROW_WALLET_EXCEPTION_IF(!get_tx_key(txid, tx_key, additional_tx_keys), error::wallet_internal_error, "Tx secret key wasn't found in the wallet file."); const size_t num_sigs = 1 + additional_tx_keys.size(); shared_secret.resize(num_sigs); @@ -6431,11 +6660,7 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac bool in_pool; uint64_t confirmations; check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); - if (!received) - { - error_str = tr("No funds received in this tx."); - return {}; - } + THROW_WALLET_EXCEPTION_IF(!received, error::wallet_internal_error, tr("No funds received in this tx.")); // concatenate all signature strings for (size_t i = 0; i < num_sigs; ++i) |