aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r--src/wallet/wallet2.cpp592
1 files changed, 443 insertions, 149 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 37b60c5d7..bbb801716 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -67,10 +67,14 @@ using namespace epee;
#include "common/json_util.h"
#include "memwipe.h"
#include "common/base58.h"
+#include "common/combinator.h"
#include "common/dns_utils.h"
#include "common/notify.h"
+#include "common/perf_timer.h"
#include "ringct/rctSigs.h"
#include "ringdb.h"
+#include "device/device_cold.hpp"
+#include "device_trezor/device_trezor.hpp"
extern "C"
{
@@ -110,11 +114,11 @@ using namespace cryptonote;
#define SUBADDRESS_LOOKAHEAD_MAJOR 50
#define SUBADDRESS_LOOKAHEAD_MINOR 200
-#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
+#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\003"
#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
-#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
+#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\004"
#define SEGREGATION_FORK_HEIGHT 99999999
#define TESTNET_SEGREGATION_FORK_HEIGHT 99999999
@@ -174,6 +178,20 @@ namespace
return public_keys;
}
+
+ bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2)
+ {
+ if (s1.empty() || s2.empty())
+ return false;
+
+ for (const auto& e: s1)
+ {
+ if (s2.find(e) != s2.end())
+ return true;
+ }
+
+ return false;
+ }
}
namespace
@@ -768,6 +786,11 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra)
return idx + extra;
}
+static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet)
+{
+ shim->get_tx_pub_key_from_received_outs = boost::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, _1);
+}
+
//-----------------------------------------------------------------
} //namespace
@@ -807,7 +830,12 @@ wallet_keys_unlocker::~wallet_keys_unlocker()
{
if (!locked)
return;
- w.encrypt_keys(key);
+ try { w.encrypt_keys(key); }
+ catch (...)
+ {
+ MERROR("Failed to re-encrypt wallet keys");
+ // do not propagate through dtor, we'd crash
+ }
}
wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
@@ -1060,8 +1088,9 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl
bool wallet2::reconnect_device()
{
bool r = true;
- hw::device &hwdev = hw::get_device(m_device_name);
+ hw::device &hwdev = lookup_device(m_device_name);
hwdev.set_name(m_device_name);
+ hwdev.set_network_type(m_nettype);
r = hwdev.init();
if (!r){
LOG_PRINT_L2("Could not init device");
@@ -1347,14 +1376,12 @@ void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::publi
//----------------------------------------------------------------------------------------------------
void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const
{
- const cryptonote::account_keys& keys = m_account.get_keys();
-
if(!parse_tx_extra(tx.extra, tx_cache_data.tx_extra_fields))
{
// Extra may only be partially parsed, it's OK if tx_extra_fields contains public key
LOG_PRINT_L0("Transaction extra has unsupported format: " << txid);
- tx_cache_data.tx_extra_fields.clear();
- return;
+ if (tx_cache_data.tx_extra_fields.empty())
+ return;
}
// Don't try to extract tx public key if tx has no ouputs
@@ -1597,6 +1624,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_txid = txid;
td.m_key_image = tx_scan_info[o].ki;
td.m_key_image_known = !m_watch_only && !m_multisig;
+ td.m_key_image_requested = false;
td.m_key_image_partial = m_multisig;
td.m_amount = amount;
td.m_pk_index = pk_index - 1;
@@ -1919,6 +1947,7 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans
entry.first->second.m_subaddr_indices = subaddr_indices;
}
+ entry.first->second.m_rings.clear();
for (const auto &in: tx.vin)
{
if (in.type() != typeid(cryptonote::txin_to_key))
@@ -2138,7 +2167,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
{
THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range");
const size_t n_vouts = m_refresh_type == RefreshType::RefreshOptimizeCoinbase ? 1 : parsed_blocks[i].block.miner_tx.vout.size();
- tpool.submit(&waiter, [&, i, txidx](){ geniod(parsed_blocks[i].block.miner_tx, n_vouts, txidx); }, true);
+ tpool.submit(&waiter, [&, i, n_vouts, txidx](){ geniod(parsed_blocks[i].block.miner_tx, n_vouts, txidx); }, true);
}
++txidx;
for (size_t j = 0; j < parsed_blocks[i].txes.size(); ++j)
@@ -2290,7 +2319,7 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe
//----------------------------------------------------------------------------------------------------
void wallet2::update_pool_state(bool refreshed)
{
- MDEBUG("update_pool_state start");
+ MTRACE("update_pool_state start");
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
if (m_encrypt_keys_after_refresh)
@@ -2309,7 +2338,7 @@ void wallet2::update_pool_state(bool refreshed)
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error);
- MDEBUG("update_pool_state got pool");
+ MTRACE("update_pool_state got pool");
// remove any pending tx that's not in the pool
std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin();
@@ -2366,7 +2395,7 @@ void wallet2::update_pool_state(bool refreshed)
}
}
}
- MDEBUG("update_pool_state done first loop");
+ MTRACE("update_pool_state done first loop");
// remove pool txes to us that aren't in the pool anymore
// but only if we just refreshed, so that the tx can go in
@@ -2375,7 +2404,7 @@ void wallet2::update_pool_state(bool refreshed)
if (refreshed)
remove_obsolete_pool_txs(res.tx_hashes);
- MDEBUG("update_pool_state done second loop");
+ MTRACE("update_pool_state done second loop");
// gather txids of new pool txes to us
std::vector<std::pair<crypto::hash, bool>> txids;
@@ -2512,7 +2541,7 @@ void wallet2::update_pool_state(bool refreshed)
LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << res.status);
}
}
- MDEBUG("update_pool_state end");
+ MTRACE("update_pool_state end");
}
//----------------------------------------------------------------------------------------------------
void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force)
@@ -2720,7 +2749,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
short_chain_history.clear();
get_short_chain_history(short_chain_history);
fast_refresh(stop_height, blocks_start_height, short_chain_history, true);
- THROW_WALLET_EXCEPTION_IF(m_blockchain.size() != stop_height, error::wallet_internal_error, "Unexpected hashchain size");
+ THROW_WALLET_EXCEPTION_IF((m_blockchain.size() == stop_height || (m_blockchain.size() == 1 && stop_height == 0) ? false : true), error::wallet_internal_error, "Unexpected hashchain size");
THROW_WALLET_EXCEPTION_IF(m_blockchain.offset() != 0, error::wallet_internal_error, "Unexpected hashchain offset");
for (const auto &h: tip)
m_blockchain.push_back(h);
@@ -2841,10 +2870,11 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t>
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res);
req.amounts.push_back(0);
req.from_height = 0;
- req.cumulative = true;
+ req.cumulative = false;
req.binary = true;
+ req.compress = true;
m_daemon_rpc_mutex.lock();
- bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req, res, m_http_client, rpc_timeout);
+ bool r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
if (!r)
{
@@ -2871,8 +2901,10 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t>
MWARNING("Failed to request output distribution: results are not for amount 0");
return false;
}
- start_height = res.distributions[0].start_height;
- distribution = std::move(res.distributions[0].distribution);
+ for (size_t i = 1; i < res.distributions[0].data.distribution.size(); ++i)
+ res.distributions[0].data.distribution[i] += res.distributions[0].data.distribution[i-1];
+ start_height = res.distributions[0].data.start_height;
+ distribution = std::move(res.distributions[0].data.distribution);
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -2944,6 +2976,7 @@ bool wallet2::deinit()
{
m_is_initialized=false;
unlock_keys_file();
+ m_account.deinit();
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -3133,20 +3166,28 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
account_data = buffer.GetString();
// Encrypt the entire JSON object.
- 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>();
crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]);
keys_file_data.account_data = cipher;
- unlock_keys_file();
+ std::string tmp_file_name = keys_file_name + ".new";
std::string buf;
r = ::serialization::dump_binary(keys_file_data, buf);
- r = r && epee::file_io_utils::save_string_to_file(keys_file_name, buf); //and never touch wallet_keys_file again, only read
- CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << keys_file_name);
+ r = r && epee::file_io_utils::save_string_to_file(tmp_file_name, buf);
+ CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name);
+
+ unlock_keys_file();
+ std::error_code e = tools::replace_file(tmp_file_name, keys_file_name);
lock_keys_file();
+ if (e) {
+ boost::filesystem::remove(tmp_file_name);
+ LOG_ERROR("failed to update wallet keys file " << keys_file_name);
+ return false;
+ }
+
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -3413,13 +3454,20 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
r = epee::serialization::load_t_from_binary(m_account, account_data);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
- if (m_key_device_type == hw::device::device_type::LEDGER) {
+ if (m_key_device_type == hw::device::device_type::LEDGER || m_key_device_type == hw::device::device_type::TREZOR) {
LOG_PRINT_L0("Account on device. Initing device...");
- hw::device &hwdev = hw::get_device(m_device_name);
- hwdev.set_name(m_device_name);
- hwdev.init();
- hwdev.connect();
+ hw::device &hwdev = lookup_device(m_device_name);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.set_name(m_device_name), error::wallet_internal_error, "Could not set device name " + m_device_name);
+ hwdev.set_network_type(m_nettype);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.init(), error::wallet_internal_error, "Could not initialize the device " + m_device_name);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.connect(), error::wallet_internal_error, "Could not connect to the device " + m_device_name);
m_account.set_device(hwdev);
+
+ account_public_address device_account_public_address;
+ THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address");
+ THROW_WALLET_EXCEPTION_IF(device_account_public_address != m_account.get_keys().m_account_address, error::wallet_internal_error, "Device wallet does not match wallet address. "
+ "Device address: " + cryptonote::get_account_address_as_str(m_nettype, false, device_account_public_address) +
+ ", wallet address: " + m_account.get_public_address_str(m_nettype));
LOG_PRINT_L0("Device inited...");
} else if (key_on_device()) {
THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported");
@@ -3450,7 +3498,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
const cryptonote::account_keys& keys = m_account.get_keys();
hw::device &hwdev = m_account.get_device();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
- if(!m_watch_only && !m_multisig)
+ if(!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
@@ -3474,7 +3522,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(), m_kdf_rounds);
+ bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds);
lock_keys_file();
return r;
}
@@ -3914,8 +3962,9 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
}
- auto &hwdev = hw::get_device(device_name);
+ auto &hwdev = lookup_device(device_name);
hwdev.set_name(device_name);
+ hwdev.set_network_type(m_nettype);
m_account.create_from_device(hwdev);
m_key_device_type = m_account.get_device().get_type();
@@ -4263,7 +4312,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
return make_multisig(password, secret_keys, public_keys, threshold);
}
-bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers)
+bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers)
{
exchange_multisig_keys(password, pkeys, signers);
return true;
@@ -5077,11 +5126,27 @@ void wallet2::rescan_spent()
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::rescan_blockchain(bool refresh)
+void wallet2::rescan_blockchain(bool hard, bool refresh)
{
- clear();
+ if(hard)
+ {
+ clear();
+ setup_new_blockchain();
+ }
+ else
+ {
+ m_blockchain.clear();
+ m_transfers.clear();
+ m_key_images.clear();
+ m_pub_keys.clear();
+ m_scanned_pool_txs[0].clear();
+ m_scanned_pool_txs[1].clear();
- setup_new_blockchain();
+ cryptonote::block b;
+ generate_genesis(b);
+ m_blockchain.push_back(get_block_hash(b));
+ m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
+ }
if (refresh)
this->refresh(false);
@@ -5448,7 +5513,7 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c
txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
}
- txs.transfers = m_transfers;
+ txs.transfers = export_outputs();
// save as binary
std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
@@ -5781,22 +5846,8 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<too
}
// import key images
- if (signed_txs.key_images.size() > m_transfers.size())
- {
- LOG_PRINT_L1("More key images returned that we know outputs for");
- return false;
- }
- for (size_t i = 0; i < signed_txs.key_images.size(); ++i)
- {
- transfer_details &td = m_transfers[i];
- if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != signed_txs.key_images[i])
- LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
- td.m_key_image = signed_txs.key_images[i];
- m_key_images[m_transfers[i].m_key_image] = i;
- td.m_key_image_known = true;
- td.m_key_image_partial = false;
- m_pub_keys[m_transfers[i].get_public_key()] = i;
- }
+ bool r = import_key_images(signed_txs.key_images);
+ if (!r) return false;
ptx = signed_txs.ptx;
@@ -5997,10 +6048,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
if (sd.use_bulletproofs)
{
- range_proof_type = rct::RangeProofBulletproof;
- for (const rct::Bulletproof &proof: ptx.tx.rct_signatures.p.bulletproofs)
- if (proof.V.size() > 1)
- range_proof_type = rct::RangeProofPaddedBulletproof;
+ range_proof_type = rct::RangeProofPaddedBulletproof;
}
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, range_proof_type, &msout, false);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
@@ -6015,7 +6063,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
for (auto &sig: ptx.multisig_sigs)
{
- if (sig.ignore != local_signer)
+ if (sig.ignore.find(local_signer) == sig.ignore.end())
{
ptx.tx.rct_signatures = sig.sigs;
@@ -6049,7 +6097,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
bool found = false;
for (const auto &sig: ptx.multisig_sigs)
{
- if (sig.ignore != local_signer && exported_txs.m_signers.find(sig.ignore) == exported_txs.m_signers.end())
+ if (sig.ignore.find(local_signer) == sig.ignore.end() && !keys_intersect(sig.ignore, exported_txs.m_signers))
{
THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final");
ptx.tx.rct_signatures = sig.sigs;
@@ -6346,6 +6394,19 @@ crypto::chacha_key wallet2::get_ringdb_key()
return *m_ringdb_key;
}
+void wallet2::register_devices(){
+ hw::trezor::register_all();
+}
+
+hw::device& wallet2::lookup_device(const std::string & device_descriptor){
+ if (!m_devices_registered){
+ m_devices_registered = true;
+ register_devices();
+ }
+
+ return hw::get_device(device_descriptor);
+}
+
bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx)
{
if (!m_ringdb)
@@ -6693,15 +6754,21 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
uint64_t rct_start_height;
std::vector<uint64_t> rct_offsets;
bool has_rct = false;
+ uint64_t max_rct_index = 0;
for (size_t idx: selected_transfers)
if (m_transfers[idx].is_rct())
- { has_rct = true; break; }
+ {
+ has_rct = true;
+ max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index);
+ }
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");
+ THROW_WALLET_EXCEPTION_IF(rct_offsets.back() <= max_rct_index,
+ error::get_output_distribution, "Daemon reports suspicious number of rct outputs");
}
// get histogram for the amounts we need
@@ -6757,13 +6824,13 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
if (d.amount == amount)
{
- THROW_WALLET_EXCEPTION_IF(d.start_height > segregation_fork_height, error::get_output_distribution, "Distribution start_height too high");
- THROW_WALLET_EXCEPTION_IF(segregation_fork_height - d.start_height >= d.distribution.size(), error::get_output_distribution, "Distribution size too small");
- THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.start_height >= d.distribution.size(), error::get_output_distribution, "Distribution size too small");
+ THROW_WALLET_EXCEPTION_IF(d.data.start_height > segregation_fork_height, error::get_output_distribution, "Distribution start_height too high");
+ THROW_WALLET_EXCEPTION_IF(segregation_fork_height - d.data.start_height >= d.data.distribution.size(), error::get_output_distribution, "Distribution size too small");
+ THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.data.start_height >= d.data.distribution.size(), error::get_output_distribution, "Distribution size too small");
THROW_WALLET_EXCEPTION_IF(segregation_fork_height <= RECENT_OUTPUT_BLOCKS, error::wallet_internal_error, "Fork height too low");
- THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS < d.start_height, error::get_output_distribution, "Bad start height");
- uint64_t till_fork = d.distribution[segregation_fork_height - d.start_height];
- uint64_t recent = till_fork - d.distribution[segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.start_height];
+ THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS < d.data.start_height, error::get_output_distribution, "Bad start height");
+ uint64_t till_fork = d.data.distribution[segregation_fork_height - d.data.start_height];
+ uint64_t recent = till_fork - d.data.distribution[segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.data.start_height];
segregation_limit[amount] = std::make_pair(till_fork, recent);
found = true;
break;
@@ -6792,21 +6859,23 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
//static const double shape = m_testnet ? 17.02 : 17.28;
static const double scale = 1/1.61;
std::gamma_distribution<double> gamma(shape, scale);
+ THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, error::wallet_internal_error, "Bad offset calculation");
+ uint64_t last_usable_block = rct_offsets.size() - 1;
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)
+ if (block_offset > last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)
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],
+ block_offset = last_usable_block - block_offset;
+ THROW_WALLET_EXCEPTION_IF(block_offset > last_usable_block, error::wallet_internal_error, "Bad offset calculation");
+ THROW_WALLET_EXCEPTION_IF(block_offset > 0 && rct_offsets[block_offset] < rct_offsets[block_offset - 1],
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]));
+ std::to_string(block_offset - 1) + ": " + std::to_string(rct_offsets[block_offset - 1]) + ", " +
+ std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]));
uint64_t first_block_offset = block_offset, last_block_offset = block_offset;
- for (size_t half_window = 0; half_window < GAMMA_PICK_HALF_WINDOW; ++half_window)
+ for (size_t half_window = 0; half_window <= GAMMA_PICK_HALF_WINDOW; ++half_window)
{
// end when we have a non empty block
uint64_t cum0 = first_block_offset > 0 ? rct_offsets[first_block_offset] - rct_offsets[first_block_offset - 1] : rct_offsets[0];
@@ -6815,19 +6884,24 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
uint64_t cum1 = last_block_offset > 0 ? rct_offsets[last_block_offset] - rct_offsets[last_block_offset - 1] : rct_offsets[0];
if (cum1 > 1)
break;
- if (first_block_offset == 0 && last_block_offset >= rct_offsets.size() - 2)
+ if (first_block_offset == 0 && last_block_offset >= last_usable_block)
break;
// expand up to bounds
if (first_block_offset > 0)
--first_block_offset;
- if (last_block_offset < rct_offsets.size() - 1)
+ else
+ return std::numeric_limits<uint64_t>::max(); // bad pick
+ if (last_block_offset < last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)
++last_block_offset;
+ else
+ return std::numeric_limits<uint64_t>::max(); // bad pick
}
- const uint64_t n_rct = rct_offsets[last_block_offset] - (first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1]);
+ const uint64_t first_rct = first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1];
+ const uint64_t n_rct = rct_offsets[last_block_offset] - first_rct;
if (n_rct == 0)
return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0;
- MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset);
- return rct_offsets[first_block_offset] + crypto::rand<uint64_t>() % n_rct;
+ MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset + rct_start_height);
+ return first_rct + crypto::rand<uint64_t>() % n_rct;
};
size_t num_selected_transfers = 0;
@@ -7132,6 +7206,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
}
// get the keys for those
+ req.get_txid = false;
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -7166,6 +7241,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
break;
}
}
+ bool use_histogram = amount != 0 || !has_rct_distribution;
+ if (!use_histogram)
+ num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE];
// make sure the real outputs we asked for are really included, along
// with the correct key and mask: this guards against an active attack
@@ -7258,6 +7336,20 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
outs.push_back(v);
}
}
+
+ // save those outs in the ringdb for reuse
+ for (size_t i = 0; i < selected_transfers.size(); ++i)
+ {
+ const size_t idx = selected_transfers[i];
+ THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "selected_transfers entry out of range");
+ const transfer_details &td = m_transfers[idx];
+ std::vector<uint64_t> ring;
+ ring.reserve(outs[i].size());
+ for (const auto &e: outs[i])
+ ring.push_back(std::get<0>(e));
+ if (!set_ring(td.m_key_image, ring, false))
+ MERROR("Failed to set ring for " << td.m_key_image);
+ }
}
template<typename T>
@@ -7446,30 +7538,56 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
// if this is a multisig wallet, create a list of multisig signers we can use
std::deque<crypto::public_key> multisig_signers;
size_t n_multisig_txes = 0;
+ std::vector<std::unordered_set<crypto::public_key>> ignore_sets;
if (m_multisig && !m_transfers.empty())
{
const crypto::public_key local_signer = get_multisig_signer_public_key();
size_t n_available_signers = 1;
+
+ // At this step we need to define set of participants available for signature,
+ // i.e. those of them who exchanged with multisig info's
for (const crypto::public_key &signer: m_multisig_signers)
{
if (signer == local_signer)
continue;
- multisig_signers.push_front(signer);
for (const auto &i: m_transfers[0].m_multisig_info)
{
if (i.m_signer == signer)
{
- multisig_signers.pop_front();
multisig_signers.push_back(signer);
++n_available_signers;
break;
}
}
}
- multisig_signers.push_back(local_signer);
+ // n_available_signers includes the transaction creator, but multisig_signers doesn't
MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers");
- THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed);
- n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1;
+ THROW_WALLET_EXCEPTION_IF(n_available_signers < m_multisig_threshold, error::multisig_import_needed);
+ if (n_available_signers > m_multisig_threshold)
+ {
+ // If there more potential signers (those who exchanged with multisig info)
+ // than threshold needed some of them should be skipped since we don't know
+ // who will sign tx and who won't. Hence we don't contribute their LR pairs to the signature.
+
+ // We create as many transactions as many combinations of excluded signers may be.
+ // For example, if we have 2/4 wallet and wallets are: A, B, C and D. Let A be
+ // transaction creator, so we need just 1 signature from set of B, C, D.
+ // Using "excluding" logic here we have to exclude 2-of-3 wallets. Combinations go as follows:
+ // BC, BD, and CD. We save these sets to use later and counting the number of required txs.
+ tools::Combinator<crypto::public_key> c(std::vector<crypto::public_key>(multisig_signers.begin(), multisig_signers.end()));
+ auto ignore_combinations = c.combine(multisig_signers.size() + 1 - m_multisig_threshold);
+ for (const auto& combination: ignore_combinations)
+ {
+ ignore_sets.push_back(std::unordered_set<crypto::public_key>(combination.begin(), combination.end()));
+ }
+
+ n_multisig_txes = ignore_sets.size();
+ }
+ else
+ {
+ // If we have exact count of signers just to fit in threshold we don't exclude anyone and create 1 transaction
+ n_multisig_txes = 1;
+ }
MDEBUG("We will create " << n_multisig_txes << " txes");
}
@@ -7537,8 +7655,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.mask = td.m_mask;
if (m_multisig)
{
- crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front();
- src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore, used_L, used_L);
+ auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
+ src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L);
}
else
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
@@ -7600,7 +7718,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
std::vector<tools::wallet2::multisig_sig> multisig_sigs;
if (m_multisig)
{
- crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front();
+ auto ignore = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout});
if (m_multisig_threshold < m_multisig_signers.size())
@@ -7608,7 +7726,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx);
// create the other versions, one for every other participant (the first one's already done above)
- for (size_t signer_index = 1; signer_index < n_multisig_txes; ++signer_index)
+ for (size_t ignore_index = 1; ignore_index < ignore_sets.size(); ++ignore_index)
{
std::unordered_set<rct::key> new_used_L;
size_t src_idx = 0;
@@ -7616,7 +7734,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
for(size_t idx: selected_transfers)
{
cryptonote::tx_source_entry& src = sources_copy[src_idx];
- src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L);
+ src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_sets[ignore_index], used_L, new_used_L);
++src_idx;
}
@@ -7628,7 +7746,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix");
- multisig_sigs.push_back({ms_tx.rct_signatures, multisig_signers[signer_index], new_used_L, std::unordered_set<crypto::public_key>(), msout});
+ multisig_sigs.push_back({ms_tx.rct_signatures, ignore_sets[ignore_index], new_used_L, std::unordered_set<crypto::public_key>(), msout});
ms_tx.rct_signatures = tx.rct_signatures;
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures");
@@ -7929,6 +8047,7 @@ void wallet2::light_wallet_get_unspent_outs()
td.m_key_image = unspent_key_image;
td.m_key_image_known = !m_watch_only && !m_multisig;
+ td.m_key_image_requested = false;
td.m_key_image_partial = m_multisig;
td.m_amount = o.amount;
td.m_pk_index = 0;
@@ -8033,7 +8152,6 @@ void wallet2::light_wallet_get_address_txs()
// for balance calculation
uint64_t wallet_total_sent = 0;
- uint64_t wallet_total_unlocked_sent = 0;
// txs in pool
std::vector<crypto::hash> pool_txs;
@@ -8365,7 +8483,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 * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof)) / 1024;
+ const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof));
uint64_t balance_subtotal = 0;
uint64_t unlocked_balance_subtotal = 0;
for (uint32_t index_minor : subaddr_indices)
@@ -8433,12 +8551,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
}
- // shuffle & sort output indices
+ // sort output indices
{
- std::random_device rd;
- std::mt19937 g(rd());
- std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g);
- std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g);
auto sort_predicate = [&unlocked_balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y)
{
return unlocked_balance_per_subaddr[x.first] > unlocked_balance_per_subaddr[y.first];
@@ -8630,7 +8744,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
cryptonote::transaction test_tx;
pending_tx test_ptx;
- needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_multiplier);
+ needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
uint64_t inputs = 0, outputs = needed_fee;
for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount();
@@ -9084,6 +9198,62 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
return ptx_vector;
}
//----------------------------------------------------------------------------------------------------
+void wallet2::cold_tx_aux_import(const std::vector<pending_tx> & ptx, const std::vector<std::string> & tx_device_aux)
+{
+ CHECK_AND_ASSERT_THROW_MES(ptx.size() == tx_device_aux.size(), "TX aux has invalid size");
+ for (size_t i = 0; i < ptx.size(); ++i){
+ crypto::hash txid;
+ txid = get_transaction_hash(ptx[i].tx);
+ set_tx_device_aux(txid, tx_device_aux[i]);
+ }
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux)
+{
+ auto & hwdev = get_account().get_device();
+ if (!hwdev.has_tx_cold_sign()){
+ throw std::invalid_argument("Device does not support cold sign protocol");
+ }
+
+ unsigned_tx_set txs;
+ for (auto &tx: ptx_vector)
+ {
+ txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
+ }
+ txs.transfers = std::make_pair(0, m_transfers);
+
+ auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
+ CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
+
+ hw::tx_aux_data aux_data;
+ hw::wallet_shim wallet_shim;
+ setup_shim(&wallet_shim, this);
+ aux_data.tx_recipients = dsts_info;
+ dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data);
+ tx_device_aux = aux_data.tx_device_aux;
+
+ MDEBUG("Signed tx data from hw: " << exported_txs.ptx.size() << " transactions");
+ for (auto &c_ptx: exported_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(c_ptx.tx));
+}
+//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
+ auto & hwdev = get_account().get_device();
+ if (!hwdev.has_ki_cold_sync()){
+ throw std::invalid_argument("Device does not support cold ki sync protocol");
+ }
+
+ auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
+ CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
+
+ std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
+ hw::wallet_shim wallet_shim;
+ setup_shim(&wallet_shim, this);
+
+ dev_cold->ki_sync(&wallet_shim, m_transfers, ski);
+
+ return import_key_images(ski, 0, spent, unspent);
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const
{
boost::optional<std::string> result = m_node_rpc_proxy.get_earliest_height(version, earliest_height);
@@ -10240,6 +10410,19 @@ std::string wallet2::get_tx_note(const crypto::hash &txid) const
return i->second;
}
+void wallet2::set_tx_device_aux(const crypto::hash &txid, const std::string &aux)
+{
+ m_tx_device[txid] = aux;
+}
+
+std::string wallet2::get_tx_device_aux(const crypto::hash &txid) const
+{
+ std::unordered_map<crypto::hash, std::string>::const_iterator i = m_tx_device.find(txid);
+ if (i == m_tx_device.end())
+ return std::string();
+ return i->second;
+}
+
void wallet2::set_attribute(const std::string &key, const std::string &value)
{
m_attributes[key] = value;
@@ -10283,7 +10466,7 @@ const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& w
return m_account_tags;
}
-void wallet2::set_account_tag(const std::set<uint32_t> account_indices, const std::string& tag)
+void wallet2::set_account_tag(const std::set<uint32_t> &account_indices, const std::string& tag)
{
for (uint32_t account_index : account_indices)
{
@@ -10430,31 +10613,45 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
bool wallet2::export_key_images(const std::string &filename) const
{
- std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images();
+ PERF_TIMER(export_key_images);
+ std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images();
std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ const uint32_t offset = ski.first;
std::string data;
+ data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key));
+ data.resize(4);
+ data[0] = offset & 0xff;
+ data[1] = (offset >> 8) & 0xff;
+ data[2] = (offset >> 16) & 0xff;
+ data[3] = (offset >> 24) & 0xff;
data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
- for (const auto &i: ski)
+ for (const auto &i: ski.second)
{
data += std::string((const char *)&i.first, sizeof(crypto::key_image));
data += std::string((const char *)&i.second, sizeof(crypto::signature));
}
// encrypt data, keep magic plaintext
+ PERF_TIMER(export_key_images_encrypt);
std::string ciphertext = encrypt_with_view_secret_key(data);
return epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
}
//----------------------------------------------------------------------------------------------------
-std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key_images() const
+std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> wallet2::export_key_images() const
{
+ PERF_TIMER(export_key_images_raw);
std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
- ski.reserve(m_transfers.size());
- for (size_t n = 0; n < m_transfers.size(); ++n)
+ size_t offset = 0;
+ while (offset < m_transfers.size() && !m_transfers[offset].m_key_image_requested)
+ ++offset;
+
+ ski.reserve(m_transfers.size() - offset);
+ for (size_t n = offset; n < m_transfers.size(); ++n)
{
const transfer_details &td = m_transfers[n];
@@ -10498,11 +10695,12 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
ski.push_back(std::make_pair(td.m_key_image, signature));
}
- return ski;
+ return std::make_pair(offset, ski);
}
uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent)
{
+ PERF_TIMER(import_key_images_fsu);
std::string data;
bool r = epee::file_io_utils::load_file_to_string(filename, data);
@@ -10516,6 +10714,7 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
try
{
+ PERF_TIMER(import_key_images_decrypt);
data = decrypt_with_view_secret_key(std::string(data, magiclen));
}
catch (const std::exception &e)
@@ -10523,15 +10722,17 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what());
}
- const size_t headerlen = 2 * sizeof(crypto::public_key);
+ const size_t headerlen = 4 + 2 * sizeof(crypto::public_key);
THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename);
- const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
- const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
+ const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24);
+ const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4];
+ const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)];
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
{
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account");
}
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs");
const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature);
THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size,
@@ -10548,28 +10749,33 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
ski.push_back(std::make_pair(key_image, signature));
}
- return import_key_images(ski, spent, unspent);
+ return import_key_images(ski, offset, spent, unspent);
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent)
+uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent)
{
+ PERF_TIMER(import_key_images_lots);
COMMAND_RPC_IS_KEY_IMAGE_SPENT::request req = AUTO_VAL_INIT(req);
COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
- THROW_WALLET_EXCEPTION_IF(signed_key_images.size() > m_transfers.size(), error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs");
+ THROW_WALLET_EXCEPTION_IF(signed_key_images.size() > m_transfers.size() - offset, error::wallet_internal_error,
"The blockchain is out of date compared to the signed key images");
- if (signed_key_images.empty())
+ if (signed_key_images.empty() && offset == 0)
{
spent = 0;
unspent = 0;
return 0;
}
+ req.key_images.reserve(signed_key_images.size());
+
+ PERF_TIMER_START(import_key_images_A);
for (size_t n = 0; n < signed_key_images.size(); ++n)
{
- const transfer_details &td = m_transfers[n];
+ const transfer_details &td = m_transfers[n + offset];
const crypto::key_image &key_image = signed_key_images[n].first;
const crypto::signature &signature = signed_key_images[n].second;
@@ -10580,30 +10786,37 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
const cryptonote::txout_to_key &o = boost::get<cryptonote::txout_to_key>(out.target);
const crypto::public_key pkey = o.key;
- std::vector<const crypto::public_key*> pkeys;
- pkeys.push_back(&pkey);
- THROW_WALLET_EXCEPTION_IF(!(rct::scalarmultKey(rct::ki2rct(key_image), rct::curveOrder()) == rct::identity()),
- error::wallet_internal_error, "Key image out of validity domain: input " + boost::lexical_cast<std::string>(n) + "/"
- + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image));
-
- THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature),
- error::signature_check_failed, boost::lexical_cast<std::string>(n) + "/"
- + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)
- + ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
+ if (!td.m_key_image_known || !(key_image == td.m_key_image))
+ {
+ std::vector<const crypto::public_key*> pkeys;
+ pkeys.push_back(&pkey);
+ THROW_WALLET_EXCEPTION_IF(!(rct::scalarmultKey(rct::ki2rct(key_image), rct::curveOrder()) == rct::identity()),
+ error::wallet_internal_error, "Key image out of validity domain: input " + boost::lexical_cast<std::string>(n + offset) + "/"
+ + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image));
+ THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature),
+ error::signature_check_failed, boost::lexical_cast<std::string>(n + offset) + "/"
+ + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)
+ + ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
+ }
req.key_images.push_back(epee::string_tools::pod_to_hex(key_image));
}
+ PERF_TIMER_STOP(import_key_images_A);
+ PERF_TIMER_START(import_key_images_B);
for (size_t n = 0; n < signed_key_images.size(); ++n)
{
- m_transfers[n].m_key_image = signed_key_images[n].first;
- m_key_images[m_transfers[n].m_key_image] = n;
- m_transfers[n].m_key_image_known = true;
- m_transfers[n].m_key_image_partial = false;
+ m_transfers[n + offset].m_key_image = signed_key_images[n].first;
+ m_key_images[m_transfers[n + offset].m_key_image] = n + offset;
+ m_transfers[n + offset].m_key_image_known = true;
+ m_transfers[n + offset].m_key_image_requested = false;
+ m_transfers[n + offset].m_key_image_partial = false;
}
+ PERF_TIMER_STOP(import_key_images_B);
if(check_spent)
{
+ PERF_TIMER(import_key_images_RPC);
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -10615,7 +10828,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size()));
for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n)
{
- transfer_details &td = m_transfers[n];
+ transfer_details &td = m_transfers[n + offset];
td.m_spent = daemon_resp.spent_status[n] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT;
}
}
@@ -10626,6 +10839,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
// 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;
+ PERF_TIMER_START(import_key_images_C);
for (const transfer_details &td: m_transfers)
{
for (const cryptonote::txin_v& in : td.m_tx.vin)
@@ -10634,10 +10848,12 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
spent_key_images.insert(std::make_pair(boost::get<cryptonote::txin_to_key>(in).k_image, td.m_txid));
}
}
+ PERF_TIMER_STOP(import_key_images_C);
+ PERF_TIMER_START(import_key_images_D);
for(size_t i = 0; i < signed_key_images.size(); ++i)
{
- transfer_details &td = m_transfers[i];
+ const transfer_details &td = m_transfers[i + offset];
uint64_t amount = td.amount();
if (td.m_spent)
spent += amount;
@@ -10655,6 +10871,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
spent_txids.insert(skii->second);
}
}
+ PERF_TIMER_STOP(import_key_images_D);
+
MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent");
if (check_spent)
@@ -10664,8 +10882,12 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res;
gettxs_req.decode_as_json = false;
gettxs_req.prune = false;
+ gettxs_req.txs_hashes.reserve(spent_txids.size());
for (const crypto::hash& spent_txid : spent_txids)
gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid));
+
+
+ PERF_TIMER_START(import_key_images_E);
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -10673,8 +10895,10 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size()));
+ PERF_TIMER_STOP(import_key_images_E);
// process each outgoing tx
+ PERF_TIMER_START(import_key_images_F);
auto spent_txid = spent_txids.begin();
hw::device &hwdev = m_account.get_device();
for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs)
@@ -10770,7 +10994,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
++spent_txid;
}
+ PERF_TIMER_STOP(import_key_images_F);
+ PERF_TIMER_START(import_key_images_G);
for (size_t n : swept_transfers)
{
const transfer_details& td = m_transfers[n];
@@ -10781,10 +11007,35 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
const crypto::hash &spent_txid = crypto::null_hash; // spent txid is unknown
m_confirmed_txs.insert(std::make_pair(spent_txid, pd));
}
+ PERF_TIMER_STOP(import_key_images_G);
}
return m_transfers[signed_key_images.size() - 1].m_block_height;
}
+
+bool wallet2::import_key_images(std::vector<crypto::key_image> key_images)
+{
+ if (key_images.size() > m_transfers.size())
+ {
+ LOG_PRINT_L1("More key images returned that we know outputs for");
+ return false;
+ }
+ for (size_t i = 0; i < key_images.size(); ++i)
+ {
+ transfer_details &td = m_transfers[i];
+ if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != key_images[i])
+ LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
+ td.m_key_image = key_images[i];
+ m_key_images[m_transfers[i].m_key_image] = i;
+ td.m_key_image_known = true;
+ td.m_key_image_requested = false;
+ td.m_key_image_partial = false;
+ m_pub_keys[m_transfers[i].get_public_key()] = i;
+ }
+
+ return true;
+}
+
wallet2::payment_container wallet2::export_payments() const
{
payment_container payments;
@@ -10843,50 +11094,87 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect
m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
}
//----------------------------------------------------------------------------------------------------
-std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const
+std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs() const
{
+ PERF_TIMER(export_outputs);
std::vector<tools::wallet2::transfer_details> outs;
- outs.reserve(m_transfers.size());
- for (size_t n = 0; n < m_transfers.size(); ++n)
+ size_t offset = 0;
+ while (offset < m_transfers.size() && m_transfers[offset].m_key_image_known)
+ ++offset;
+
+ outs.reserve(m_transfers.size() - offset);
+ for (size_t n = offset; n < m_transfers.size(); ++n)
{
const transfer_details &td = m_transfers[n];
outs.push_back(td);
}
- return outs;
+ return std::make_pair(offset, outs);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::export_outputs_to_str() const
{
- std::vector<tools::wallet2::transfer_details> outs = export_outputs();
+ PERF_TIMER(export_outputs_to_str);
std::stringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
- ar << outs;
+ const auto& outputs = export_outputs();
+ ar << outputs;
std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
std::string header;
header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
+ PERF_TIMER(export_outputs_encryption);
std::string ciphertext = encrypt_with_view_secret_key(header + oss.str());
return magic + ciphertext;
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs)
+size_t wallet2::import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs)
{
- m_transfers.clear();
- m_transfers.reserve(outputs.size());
- for (size_t i = 0; i < outputs.size(); ++i)
+ PERF_TIMER(import_outputs);
+
+ THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error,
+ "Imported outputs omit more outputs that we know of");
+
+ const size_t offset = outputs.first;
+ const size_t original_size = m_transfers.size();
+ m_transfers.resize(offset + outputs.second.size());
+ for (size_t i = 0; i < offset; ++i)
+ m_transfers[i].m_key_image_requested = false;
+ for (size_t i = 0; i < outputs.second.size(); ++i)
{
- transfer_details td = outputs[i];
+ transfer_details td = outputs.second[i];
+
+ // skip those we've already imported, or which have different data
+ if (i + offset < original_size)
+ {
+ // compare the data used to create the key image below
+ const transfer_details &org_td = m_transfers[i + offset];
+ if (!org_td.m_key_image_known)
+ goto process;
+#define CMPF(f) if (!(td.f == org_td.f)) goto process
+ CMPF(m_txid);
+ CMPF(m_key_image);
+ CMPF(m_internal_output_index);
+#undef CMPF
+ if (!(get_transaction_prefix_hash(td.m_tx) == get_transaction_prefix_hash(org_td.m_tx)))
+ goto process;
+
+ // copy anyway, since the comparison does not include ancillary fields which may have changed
+ m_transfers[i + offset] = std::move(td);
+ continue;
+ }
+
+process:
// the hot wallet wouldn't have known about key images (except if we already exported them)
cryptonote::keypair in_ephemeral;
- THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i));
+ THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i + offset));
crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td);
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
@@ -10897,13 +11185,14 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
expand_subaddresses(td.m_subaddr_index);
td.m_key_image_known = true;
+ td.m_key_image_requested = true;
td.m_key_image_partial = false;
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != out_key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i));
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i + offset));
- m_key_images[td.m_key_image] = m_transfers.size();
- m_pub_keys[td.get_public_key()] = m_transfers.size();
- m_transfers.push_back(std::move(td));
+ m_key_images[td.m_key_image] = i + offset;
+ m_pub_keys[td.get_public_key()] = i + offset;
+ m_transfers[i + offset] = std::move(td);
}
return m_transfers.size();
@@ -10911,6 +11200,7 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
//----------------------------------------------------------------------------------------------------
size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
{
+ PERF_TIMER(import_outputs_from_str);
std::string data = outputs_st;
const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen))
@@ -10920,6 +11210,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
try
{
+ PERF_TIMER(import_outputs_decrypt);
data = decrypt_with_view_secret_key(std::string(data, magiclen));
}
catch (const std::exception &e)
@@ -10946,7 +11237,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
std::string body(data, headerlen);
std::stringstream iss;
iss << body;
- std::vector<tools::wallet2::transfer_details> outputs;
+ std::pair<size_t, std::vector<tools::wallet2::transfer_details>> outputs;
try
{
boost::archive::portable_binary_iarchive ar(iss);
@@ -11025,7 +11316,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con
return kLRki;
}
//----------------------------------------------------------------------------------------------------
-rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const
+rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const
{
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index");
@@ -11036,8 +11327,9 @@ rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto
size_t n_signers_used = 1;
for (const auto &p: m_transfers[n].m_multisig_info)
{
- if (p.m_signer == ignore)
+ if (ignore_set.find(p.m_signer) != ignore_set.end())
continue;
+
for (const auto &lr: p.m_LR)
{
if (used_L.find(lr.m_L) != used_L.end())
@@ -11082,7 +11374,6 @@ cryptonote::blobdata wallet2::export_multisig()
for (size_t n = 0; n < m_transfers.size(); ++n)
{
transfer_details &td = m_transfers[n];
- const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
crypto::key_image ki;
td.m_multisig_k.clear();
info[n].m_LR.clear();
@@ -11096,7 +11387,10 @@ cryptonote::blobdata wallet2::export_multisig()
info[n].m_partial_key_images.push_back(ki);
}
- size_t nlr = m_multisig_threshold < m_multisig_signers.size() ? m_multisig_threshold - 1 : 1;
+ // Wallet tries to create as many transactions as many signers combinations. We calculate the maximum number here as follows:
+ // if we have 2/4 wallet with signers: A, B, C, D and A is a transaction creator it will need to pick up 1 signer from 3 wallets left.
+ // That means counting combinations for excluding 2-of-3 wallets (k = total signers count - threshold, n = total signers count - 1).
+ size_t nlr = tools::combinations_count(m_multisig_signers.size() - m_multisig_threshold, m_multisig_signers.size() - 1);
for (size_t m = 0; m < nlr; ++m)
{
td.m_multisig_k.push_back(rct::skGen());
@@ -11111,7 +11405,6 @@ cryptonote::blobdata wallet2::export_multisig()
boost::archive::portable_binary_oarchive ar(oss);
ar << info;
- std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
std::string header;
header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
@@ -11138,6 +11431,7 @@ void wallet2::update_multisig_rescan_info(const std::vector<std::vector<rct::key
m_key_images.erase(td.m_key_image);
td.m_key_image = get_multisig_composite_key_image(n);
td.m_key_image_known = true;
+ td.m_key_image_requested = false;
td.m_key_image_partial = false;
td.m_multisig_k = multisig_k[n];
m_key_images[td.m_key_image] = n;