diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 184 |
1 files changed, 149 insertions, 35 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 49aef4350..097278961 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -67,6 +67,7 @@ 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" @@ -177,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 @@ -207,6 +222,7 @@ struct options { }; const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1}; const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""}; + const command_line::arg_descriptor<std::string> hw_device_derivation_path = {"hw-device-deriv-path", tools::wallet2::tr("HW device wallet derivation path (e.g., SLIP-10)"), ""}; const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" }; }; @@ -259,6 +275,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl auto daemon_host = command_line::get_arg(vm, opts.daemon_host); auto daemon_port = command_line::get_arg(vm, opts.daemon_port); auto device_name = command_line::get_arg(vm, opts.hw_device); + auto device_derivation_path = command_line::get_arg(vm, opts.hw_device_derivation_path); THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port, tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once")); @@ -314,6 +331,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); wallet->device_name(device_name); + wallet->device_derivation_path(device_derivation_path); try { @@ -815,7 +833,30 @@ 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 + } +} + +void wallet_device_callback::on_button_request() +{ + if (wallet) + wallet->on_button_request(); +} + +void wallet_device_callback::on_pin_request(epee::wipeable_string & pin) +{ + if (wallet) + wallet->on_pin_request(pin); +} + +void wallet_device_callback::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) +{ + if (wallet) + wallet->on_passphrase_request(on_device, passphrase); } wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): @@ -871,7 +912,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_ringdb(), m_last_block_reward(0), m_encrypt_keys_after_refresh(boost::none), - m_unattended(unattended) + m_unattended(unattended), + m_device_last_key_image_sync(0) { } @@ -894,6 +936,11 @@ std::string wallet2::device_name_option(const boost::program_options::variables_ return command_line::get_arg(vm, options().hw_device); } +std::string wallet2::device_derivation_path_option(const boost::program_options::variables_map &vm) +{ + return command_line::get_arg(vm, options().hw_device_derivation_path); +} + void wallet2::init_options(boost::program_options::options_description& desc_params) { const options opts{}; @@ -910,6 +957,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.shared_ringdb_dir); command_line::add_arg(desc_params, opts.kdf_rounds); command_line::add_arg(desc_params, opts.hw_device); + command_line::add_arg(desc_params, opts.hw_device_derivation_path); command_line::add_arg(desc_params, opts.tx_notify); } @@ -929,7 +977,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( return {nullptr, password_container{}}; } auto wallet = make_basic(vm, unattended, opts, password_prompter); - if (wallet) + if (wallet && !wallet_file.empty()) { wallet->load(wallet_file, pwd->password()); } @@ -1071,15 +1119,17 @@ bool wallet2::reconnect_device() hw::device &hwdev = lookup_device(m_device_name); hwdev.set_name(m_device_name); hwdev.set_network_type(m_nettype); + hwdev.set_derivation_path(m_device_derivation_path); + hwdev.set_callback(get_device_callback()); r = hwdev.init(); if (!r){ - LOG_PRINT_L2("Could not init device"); + MERROR("Could not init device"); return false; } r = hwdev.connect(); if (!r){ - LOG_PRINT_L2("Could not connect to the device"); + MERROR("Could not connect to the device"); return false; } @@ -1356,14 +1406,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 @@ -2149,7 +2197,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) @@ -2980,6 +3028,7 @@ bool wallet2::clear() m_subaddresses.clear(); m_subaddress_labels.clear(); m_multisig_rounds_passed = 0; + m_device_last_key_image_sync = 0; return true; } @@ -3141,6 +3190,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value.SetString(m_device_name.c_str(), m_device_name.size()); json.AddMember("device_name", value, json.GetAllocator()); + value.SetString(m_device_derivation_path.c_str(), m_device_derivation_path.size()); + json.AddMember("device_derivation_path", value, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -3260,6 +3312,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_device_name = ""; + m_device_derivation_path = ""; m_key_device_type = hw::device::device_type::SOFTWARE; encrypted_secret_keys = false; } @@ -3427,6 +3480,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default"; } } + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_derivation_path, std::string, String, false, std::string()); + m_device_derivation_path = field_device_derivation_path; } else { @@ -3441,6 +3497,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ 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); + hwdev.set_derivation_path(m_device_derivation_path); + hwdev.set_callback(get_device_callback()); 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); @@ -3947,6 +4005,8 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p auto &hwdev = lookup_device(device_name); hwdev.set_name(device_name); hwdev.set_network_type(m_nettype); + hwdev.set_derivation_path(m_device_derivation_path); + hwdev.set_callback(get_device_callback()); m_account.create_from_device(hwdev); m_key_device_type = m_account.get_device().get_type(); @@ -4294,7 +4354,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; @@ -6045,7 +6105,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; @@ -6079,7 +6139,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; @@ -7520,30 +7580,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"); } @@ -7611,8 +7697,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()}); @@ -7674,7 +7760,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()) @@ -7682,7 +7768,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; @@ -7690,7 +7776,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; } @@ -7702,7 +7788,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"); @@ -8108,7 +8194,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; @@ -9195,9 +9280,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_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"); - } + CHECK_AND_ASSERT_THROW_MES(hwdev.has_ki_cold_sync(), "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"); @@ -9208,7 +9291,11 @@ uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) { dev_cold->ki_sync(&wallet_shim, m_transfers, ski); - return import_key_images(ski, 0, spent, unspent); + // Call COMMAND_RPC_IS_KEY_IMAGE_SPENT only if daemon is trusted. + uint64_t import_res = import_key_images(ski, 0, spent, unspent, is_trusted_daemon()); + m_device_last_key_image_sync = time(NULL); + + return import_res; } //---------------------------------------------------------------------------------------------------- void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const @@ -10423,7 +10510,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) { @@ -11273,7 +11360,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"); @@ -11284,8 +11371,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()) @@ -11330,7 +11418,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(); @@ -11344,7 +11431,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()); @@ -11359,7 +11449,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)); @@ -11940,4 +12029,29 @@ uint64_t wallet2::get_segregation_fork_height() const void wallet2::generate_genesis(cryptonote::block& b) const { cryptonote::generate_genesis_block(b, get_config(m_nettype).GENESIS_TX, get_config(m_nettype).GENESIS_NONCE); } +//---------------------------------------------------------------------------------------------------- +wallet_device_callback * wallet2::get_device_callback() +{ + if (!m_device_callback){ + m_device_callback.reset(new wallet_device_callback(this)); + } + return m_device_callback.get(); +}//---------------------------------------------------------------------------------------------------- +void wallet2::on_button_request() +{ + if (0 != m_callback) + m_callback->on_button_request(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::on_pin_request(epee::wipeable_string & pin) +{ + if (0 != m_callback) + m_callback->on_pin_request(pin); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) +{ + if (0 != m_callback) + m_callback->on_passphrase_request(on_device, passphrase); +} } |