From f9b22a7b01994f444185f38fe1f645593590c943 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Fri, 24 Aug 2018 00:50:31 +0200 Subject: [PATCH] hw_device: support for multiple devices added [for review] - device name is a new wallet property - full device name is now a bit more structured so we can address particular device vendor + device path. Example: 'Ledger', 'Trezor:udp', 'Trezor:udp:127.0.0.1:21324', 'Trezor:bridge:usb01'. The part before ':' identifies HW device implementation, the optional part after ':' is device path to look for. - new --hw-device parameter added to the wallet, can name the hardware device - device reconnect added --- src/cryptonote_basic/account.cpp | 8 +++- src/cryptonote_basic/account.h | 3 +- src/device/device.cpp | 70 +++++++++++++++++++++---------- src/device/device.hpp | 13 +++++- src/simplewallet/simplewallet.cpp | 39 +++++++++++++++-- src/simplewallet/simplewallet.h | 3 +- src/wallet/wallet2.cpp | 51 +++++++++++++++++++++- src/wallet/wallet2.h | 5 +++ 8 files changed, 160 insertions(+), 32 deletions(-) diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index 4cbfa8142..e891a748d 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -197,10 +197,14 @@ DISABLE_VS_WARNINGS(4244 4345) //----------------------------------------------------------------- void account_base::create_from_device(const std::string &device_name) { - hw::device &hwdev = hw::get_device(device_name); - m_keys.set_device(hwdev); hwdev.set_name(device_name); + create_from_device(hwdev); + } + + void account_base::create_from_device(hw::device &hwdev) + { + m_keys.set_device(hwdev); MCDEBUG("ledger", "device type: "< &multisig_keys); diff --git a/src/device/device.cpp b/src/device/device.cpp index 983f59b60..8a8b40061 100644 --- a/src/device/device.cpp +++ b/src/device/device.cpp @@ -39,32 +39,60 @@ namespace hw { /* ======================================================================= */ /* SETUP */ - /* ======================================================================= */ - device& get_device(const std::string device_descriptor) { - - struct s_devices { - std::map> registry; - s_devices() : registry() { - hw::core::register_all(registry); - #ifdef HAVE_PCSC - hw::ledger::register_all(registry); - #endif - }; - }; - - static const s_devices devices; + /* ======================================================================= */ - auto device = devices.registry.find(device_descriptor); - if (device == devices.registry.end()) { - MERROR("device not found in registry: '" << device_descriptor << "'\n" << - "known devices:"); - - for( const auto& sm_pair : devices.registry ) { + static std::unique_ptr registry; + + device_registry::device_registry(){ + hw::core::register_all(registry); + #ifdef HAVE_PCSC + hw::ledger::register_all(registry); + #endif + } + + bool device_registry::register_device(const std::string & device_name, device * hw_device){ + auto search = registry.find(device_name); + if (search != registry.end()){ + return false; + } + + registry.insert(std::make_pair(device_name, std::unique_ptr(hw_device))); + return true; + } + + device& device_registry::get_device(const std::string & device_descriptor){ + // Device descriptor can contain further specs after first : + auto delim = device_descriptor.find(':'); + auto device_descriptor_lookup = device_descriptor; + if (delim != std::string::npos) { + device_descriptor_lookup = device_descriptor.substr(0, delim); + } + + auto device = registry.find(device_descriptor_lookup); + if (device == registry.end()) { + MERROR("Device not found in registry: '" << device_descriptor << "'. Known devices: "); + for( const auto& sm_pair : registry ) { MERROR(" - " << sm_pair.first); } - throw std::runtime_error("device not found: "+ device_descriptor); + throw std::runtime_error("device not found: " + device_descriptor); } return *device->second; } + device& get_device(const std::string & device_descriptor) { + if (!registry){ + registry.reset(new device_registry()); + } + + return registry->get_device(device_descriptor); + } + + bool register_device(const std::string & device_name, device * hw_device){ + if (!registry){ + registry.reset(new device_registry()); + } + + return registry->register_device(device_name, hw_device); + } + } diff --git a/src/device/device.hpp b/src/device/device.hpp index c21456daf..d14b8848c 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -202,6 +202,17 @@ namespace hw { ~reset_mode() { hwref.set_mode(hw::device::NONE);} }; - device& get_device(const std::string device_descriptor) ; + class device_registry { + private: + std::map> registry; + + public: + device_registry(); + bool register_device(const std::string & device_name, device * hw_device); + device& get_device(const std::string & device_descriptor); + }; + + device& get_device(const std::string & device_descriptor); + bool register_device(const std::string & device_name, device * hw_device); } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index e535b6d27..ad67b108e 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -2523,6 +2523,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::import_key_images, this, _1), tr("import_key_images "), tr("Import a signed key images list and verify their spent status.")); + m_cmd_binder.set_handler("hw_reconnect", + boost::bind(&simple_wallet::hw_reconnect, this, _1), + tr("hw_reconnect"), + tr("Attempts to reconnect HW wallet.")); m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), tr("export_outputs "), @@ -2650,6 +2654,7 @@ bool simple_wallet::set_variable(const std::vector &args) success_msg_writer() << "subaddress-lookahead = " << lookahead.first << ":" << lookahead.second; success_msg_writer() << "segregation-height = " << m_wallet->segregation_height(); success_msg_writer() << "ignore-fractional-outputs = " << m_wallet->ignore_fractional_outputs(); + success_msg_writer() << "device_name = " << m_wallet->device_name(); return true; } else @@ -3295,7 +3300,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { m_wallet_file = m_generate_from_device; // create wallet - auto r = new_wallet(vm, "Ledger"); + auto r = new_wallet(vm); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; // if no block_height is specified, assume its a new account and start it "now" @@ -3703,8 +3708,8 @@ boost::optional simple_wallet::new_wallet(const boost::pr } //---------------------------------------------------------------------------------------------------- -boost::optional simple_wallet::new_wallet(const boost::program_options::variables_map& vm, - const std::string &device_name) { +boost::optional simple_wallet::new_wallet(const boost::program_options::variables_map& vm) +{ auto rc = tools::wallet2::make_new(vm, false, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) @@ -3723,10 +3728,11 @@ boost::optional simple_wallet::new_wallet(const boost::pr if (m_restore_height) m_wallet->set_refresh_from_block_height(m_restore_height); + auto device_desc = tools::wallet2::device_name_option(vm); try { bool create_address_file = command_line::get_arg(vm, arg_create_address_file); - m_wallet->restore(m_wallet_file, std::move(rc.second).password(), device_name, create_address_file); + m_wallet->restore(m_wallet_file, std::move(rc.second).password(), device_desc.empty() ? "Ledger" : device_desc, create_address_file); message_writer(console_color_white, true) << tr("Generated new wallet on hw device: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); } @@ -7745,6 +7751,31 @@ bool simple_wallet::import_key_images(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::hw_reconnect(const std::vector &args) +{ + if (!m_wallet->key_on_device()) + { + fail_msg_writer() << tr("command only supported by HW wallet"); + return true; + } + + LOCK_IDLE_SCOPE(); + try + { + bool r = m_wallet->reconnect_device(); + if (!r){ + fail_msg_writer() << tr("Failed to reconnect device"); + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to reconnect device: ") << tr(e.what()); + return true; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::export_outputs(const std::vector &args) { if (m_wallet->key_on_device()) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index bfbe633ac..d50e4ce04 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -98,7 +98,7 @@ namespace cryptonote const boost::optional& spendkey, const crypto::secret_key& viewkey); boost::optional new_wallet(const boost::program_options::variables_map& vm, const epee::wipeable_string &multisig_keys, const std::string &old_language); - boost::optional new_wallet(const boost::program_options::variables_map& vm, const std::string& device_name); + boost::optional new_wallet(const boost::program_options::variables_map& vm); bool open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); @@ -201,6 +201,7 @@ namespace cryptonote bool verify(const std::vector &args); bool export_key_images(const std::vector &args); bool import_key_images(const std::vector &args); + bool hw_reconnect(const std::vector &args); bool export_outputs(const std::vector &args); bool import_outputs(const std::vector &args); bool show_transfer(const std::vector &args); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b266821d6..dc7ab668f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -161,6 +161,7 @@ struct options { } }; const command_line::arg_descriptor kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1}; + const command_line::arg_descriptor hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) @@ -211,6 +212,7 @@ std::unique_ptr make_basic(const boost::program_options::variabl auto daemon_address = command_line::get_arg(vm, opts.daemon_address); 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); 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")); @@ -265,6 +267,7 @@ std::unique_ptr make_basic(const boost::program_options::variabl wallet->init(unattended, std::move(daemon_address), std::move(login), 0, false, *trusted_daemon); 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); return wallet; } @@ -814,6 +817,11 @@ bool wallet2::has_stagenet_option(const boost::program_options::variables_map& v return command_line::get_arg(vm, options().stagenet); } +std::string wallet2::device_name_option(const boost::program_options::variables_map& vm) +{ + return command_line::get_arg(vm, options().hw_device); +} + void wallet2::init_options(boost::program_options::options_description& desc_params) { const options opts{}; @@ -829,6 +837,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.stagenet); command_line::add_arg(desc_params, opts.shared_ringdb_dir); command_line::add_arg(desc_params, opts.kdf_rounds); + command_line::add_arg(desc_params, opts.hw_device); } std::unique_ptr wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function(const char *, bool)> &password_prompter) @@ -984,6 +993,27 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl return true; } //---------------------------------------------------------------------------------------------------- +bool wallet2::reconnect_device() +{ + bool r = true; + hw::device &hwdev = hw::get_device(m_device_name); + hwdev.set_name(m_device_name); + r = hwdev.init(); + if (!r){ + LOG_PRINT_L2("Could not init device"); + return false; + } + + r = hwdev.connect(); + if (!r){ + LOG_PRINT_L2("Could not connect to the device"); + return false; + } + + m_account.set_device(hwdev); + return true; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Gets the seed language */ @@ -2980,6 +3010,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(1); json.AddMember("encrypted_secret_keys", value2, json.GetAllocator()); + value.SetString(m_device_name.c_str(), m_device_name.size()); + json.AddMember("device_name", value, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -3088,6 +3121,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_ignore_fractional_outputs = true; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; + m_device_name = ""; m_key_on_device = false; encrypted_secret_keys = false; } @@ -3219,8 +3253,15 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_subaddress_lookahead_major = field_subaddress_lookahead_major; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); m_subaddress_lookahead_minor = field_subaddress_lookahead_minor; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false); encrypted_secret_keys = field_encrypted_secret_keys; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_name, std::string, String, false, std::string()); + if (m_device_name.empty() && field_device_name_found) + { + m_device_name = field_device_name; + } } else { @@ -3231,7 +3272,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ r = epee::serialization::load_t_from_binary(m_account, account_data); if (r && m_key_on_device) { LOG_PRINT_L0("Account on device. Initing device..."); - hw::device &hwdev = hw::get_device("Ledger"); + hw::device &hwdev = hw::get_device(m_device_name); + hwdev.set_name(m_device_name); hwdev.init(); hwdev.connect(); m_account.set_device(hwdev); @@ -3673,13 +3715,18 @@ 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); } m_key_on_device = true; - m_account.create_from_device(device_name); + + auto &hwdev = hw::get_device(device_name); + hwdev.set_name(device_name); + + m_account.create_from_device(hwdev); m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); setup_keys(password); + m_device_name = device_name; create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); if (m_subaddress_lookahead_major == SUBADDRESS_LOOKAHEAD_MAJOR && m_subaddress_lookahead_minor == SUBADDRESS_LOOKAHEAD_MINOR) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 402066b50..2f833c679 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -172,6 +172,7 @@ namespace tools static bool has_testnet_option(const boost::program_options::variables_map& vm); static bool has_stagenet_option(const boost::program_options::variables_map& vm); + static std::string device_name_option(const boost::program_options::variables_map& vm); static void init_options(boost::program_options::options_description& desc_params); //! Uses stdin and stdout. Returns a wallet2 if no errors. @@ -709,6 +710,7 @@ namespace tools bool has_unknown_key_images() const; bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; bool key_on_device() const { return m_key_on_device; } + bool reconnect_device(); // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; @@ -938,6 +940,8 @@ namespace tools void ignore_fractional_outputs(bool value) { m_ignore_fractional_outputs = value; } bool confirm_non_default_ring_size() const { return m_confirm_non_default_ring_size; } void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } + const std::string & device_name() const { return m_device_name; } + void device_name(const std::string & device_name) { m_device_name = device_name; } bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys); @@ -1319,6 +1323,7 @@ namespace tools NodeRPCProxy m_node_rpc_proxy; std::unordered_set m_scanned_pool_txs[2]; size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; + std::string m_device_name; // Light wallet bool m_light_wallet; /* sends view key to daemon for scanning */