// 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 constuint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag)); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) { balance_subtotal += balance_per_subaddr[index_minor]; unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor].first; } 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 + min_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money, unlocked_balance_subtotal, needed_money, 0);
for (uint32_t i : subaddr_indices) LOG_PRINT_L2("Candidate subaddress index for spending: " << i);
// for rct, since we don't see the amounts, we will try to make all transactions // look the same, with 1 or 2 inputs, and 2 outputs. One input is preferable, as // this prevents linking to another by provenance analysis, but two is ok if we // try to pick outputs not from the same block. We will get two outputs, one for // the destination, and one for change. LOG_PRINT_L2("checking preferred"); std::vector<size_t> preferred_inputs; uint64_t rct_outs_needed = 2 * (fake_outs_count + 1); rct_outs_needed += 100; // some fudge factor since we don't know how many are locked if (use_rct) { // 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 = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); 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()) + ") "; LOG_PRINT_L1("Found preferred rct inputs for rct tx: " << s);
// bring the list of available outputs stored by the same subaddress index to the front of the list uint32_t index_minor = m_transfers[preferred_inputs[0]].m_subaddr_index.minor; for (size_t i = 1; i < unused_transfers_indices_per_subaddr.size(); ++i) { if (unused_transfers_indices_per_subaddr[i].first == index_minor) { std::swap(unused_transfers_indices_per_subaddr[0], unused_transfers_indices_per_subaddr[i]); break; } } for (size_t i = 1; i < unused_dust_indices_per_subaddr.size(); ++i) { if (unused_dust_indices_per_subaddr[i].first == index_minor) { std::swap(unused_dust_indices_per_subaddr[0], unused_dust_indices_per_subaddr[i]); break; } } } } LOG_PRINT_L2("done checking preferred");
// while: // - we have something to send // - or we need to gather more fee // - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2) unsignedint 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;
// make sure the real outputs we asked for are really included, along // with the correct key and mask: this guards against an active attack // where the node sends dummy data for all outputs, and we then send // the real one, which the node can then tell from the fake outputs, // as it has different data than the dummy data it had sent earlier bool real_out_found = false; for (size_t n = 0; n < requested_outputs_count; ++n) { size_t i = base + n; if (req.outputs[i].index == td.m_global_output_index) if (daemon_resp.outs[i].key == boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key) if (daemon_resp.outs[i].mask == mask) if (daemon_resp.outs[i].unlocked) real_out_found = true; } THROW_WALLET_EXCEPTION_IF(!real_out_found, error::wallet_internal_error, "Daemon response did not include the requested real output");
// pick real out first (it will be sorted when done) outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask));
// then pick outs from an existing ring, if any if (td.m_key_image_known && !td.m_key_image_partial) { std::vector<uint64_t> ring; if (get_ring(get_ringdb_key(), td.m_key_image, ring)) { for (uint64_t out: ring) { if (out < num_outs) { if (out != td.m_global_output_index) { bool found = false; for (size_t o = 0; o < requested_outputs_count; ++o) { size_t i = base + o; if (req.outputs[i].index == out) { LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key << " (from existing ring)"); tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked); found = true; break; } } THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, "Falied to find existing ring output in daemon out data"); } } } } }
// then pick others in random order till we reach the required number // since we use an equiprobable pick here, we don't upset the triangular distribution std::vector<size_t> order; order.resize(requested_outputs_count); for (size_t n = 0; n < order.size(); ++n) order[n] = n; std::shuffle(order.begin(), order.end(), crypto::random_device{});
LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(td.is_rct() ? 0 : td.amount())); for (size_t o = 0; o < requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o) { size_t i = base + order[o]; LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked); } if (outs.back().size() < fake_outputs_count + 1) { scanty_outs[td.is_rct() ? 0 : td.amount()] = outs.back().size(); } else { // sort the subsection, so any spares are reset in order std::sort(outs.back().begin(), outs.back().end(), [](const get_outs_entry &a, const get_outs_entry &b) { return std::get<0>(a) < std::get<0>(b); }); } base += requested_outputs_count;