From 318cc78457136ec778d6dff43b36f9447d95a43d Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Sun, 11 Nov 2018 20:07:25 +0100 Subject: [PATCH 1/5] device/trezor: passphrase entry on host - simple device callback object added. Device can request passphrase/PIN entry via the callback or notify user some action is required - callback is routed to wallet2, which routes the callback to i_wallet_callback so CLI or GUI wallets can support passphrase entry for HW tokens - wallet: device open needs wallet callback first - passphrase protected device needs wallet callback so user can enter passphrase --- src/device/device.hpp | 9 ++++ src/device_trezor/device_trezor_base.cpp | 6 +-- src/device_trezor/device_trezor_base.hpp | 19 +++------ src/simplewallet/simplewallet.cpp | 37 ++++++++++++++++- src/simplewallet/simplewallet.h | 3 ++ src/wallet/wallet2.cpp | 52 ++++++++++++++++++++++-- src/wallet/wallet2.h | 22 ++++++++++ src/wallet/wallet_errors.h | 8 ++++ 8 files changed, 134 insertions(+), 22 deletions(-) diff --git a/src/device/device.hpp b/src/device/device.hpp index dd9ad4332..3e782d21a 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -80,6 +80,14 @@ namespace hw { return false; } + class i_device_callback { + public: + virtual void on_button_request() {} + virtual void on_pin_request(epee::wipeable_string & pin) {} + virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {} + virtual ~i_device_callback() = default; + }; + class device { protected: std::string name; @@ -129,6 +137,7 @@ namespace hw { virtual device_type get_type() const = 0; virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; }; + virtual void set_callback(i_device_callback * callback) {}; /* ======================================================================= */ /* LOCKER */ diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index 38c20c30b..dcfc9cb67 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -39,7 +39,7 @@ namespace trezor { const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080, 0x80000000}; - device_trezor_base::device_trezor_base() { + device_trezor_base::device_trezor_base(): m_callback(nullptr) { } @@ -332,10 +332,6 @@ namespace trezor { MDEBUG("on_passhprase_state_request"); CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); - if (m_callback){ - m_callback->on_passphrase_state_request(msg->state()); - } - messages::common::PassphraseStateAck m; resp = call_raw(&m); } diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 88d419494..a4e92bf78 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -57,17 +57,6 @@ namespace trezor { #ifdef WITH_DEVICE_TREZOR class device_trezor_base; - /** - * Trezor device callbacks - */ - class trezor_callback { - public: - virtual void on_button_request() {}; - virtual void on_pin_request(epee::wipeable_string & pin) {}; - virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {}; - virtual void on_passphrase_state_request(const std::string & state) {}; - }; - /** * TREZOR device template with basic functions */ @@ -79,7 +68,7 @@ namespace trezor { mutable boost::mutex command_locker; std::shared_ptr m_transport; - std::shared_ptr m_callback; + i_device_callback * m_callback; std::string full_name; @@ -218,7 +207,11 @@ namespace trezor { return m_transport; } - std::shared_ptr getCallback(){ + void set_callback(i_device_callback * callback) override { + m_callback = callback; + } + + i_device_callback * get_callback(){ return m_callback; } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d9fd0c13e..6d19235c5 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3786,6 +3786,7 @@ boost::optional simple_wallet::new_wallet(const boost::pr { auto rc = tools::wallet2::make_new(vm, false, password_prompter); m_wallet = std::move(rc.first); + m_wallet->callback(this); if (!m_wallet) { return {}; @@ -3893,7 +3894,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) epee::wipeable_string password; try { - auto rc = tools::wallet2::make_from_file(vm, false, m_wallet_file, password_prompter); + auto rc = tools::wallet2::make_from_file(vm, false, "", password_prompter); m_wallet = std::move(rc.first); password = std::move(std::move(rc.second).password()); if (!m_wallet) @@ -3901,6 +3902,8 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) return false; } + m_wallet->callback(this); + m_wallet->load(m_wallet_file, password); std::string prefix; bool ready; uint32_t threshold, total; @@ -4304,6 +4307,38 @@ boost::optional simple_wallet::on_get_password(const char return pwd_container->password(); } //---------------------------------------------------------------------------------------------------- +void simple_wallet::on_button_request() +{ + message_writer(console_color_white, false) << tr("Device requires attention"); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_pin_request(epee::wipeable_string & pin) +{ +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + std::string msg = tr("Enter device PIN"); + auto pwd_container = tools::password_container::prompt(false, msg.c_str()); + THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device PIN")); + pin = pwd_container->password(); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) +{ + if (on_device){ + message_writer(console_color_white, true) << tr("Please enter the device passphrase on the device"); + return; + } + +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + std::string msg = tr("Enter device passphrase"); + auto pwd_container = tools::password_container::prompt(false, msg.c_str()); + THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device passphrase")); + passphrase = pwd_container->password(); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bool is_init) { if (!try_connect_to_daemon(is_init)) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 421afbeda..7e2edba7f 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -287,6 +287,9 @@ namespace cryptonote virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index); virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); virtual boost::optional on_get_password(const char *reason); + virtual void on_button_request(); + virtual void on_pin_request(epee::wipeable_string & pin); + virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase); //---------------------------------------------------------- friend class refresh_progress_reporter_t; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 49aef4350..ae6ed0937 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -818,6 +818,24 @@ wallet_keys_unlocker::~wallet_keys_unlocker() w.encrypt_keys(key); } +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): m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), @@ -929,7 +947,7 @@ std::pair, 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 +1089,16 @@ 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_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; } @@ -3441,6 +3460,7 @@ 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_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 +3967,7 @@ 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_callback(get_device_callback()); m_account.create_from_device(hwdev); m_key_device_type = m_account.get_device().get_type(); @@ -11940,4 +11961,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); +} } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index eb0763131..c07054177 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -98,11 +98,26 @@ namespace tools virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {} virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {} virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {} + // Device callbacks + virtual void on_button_request() {} + virtual void on_pin_request(epee::wipeable_string & pin) {} + virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {} // Common callbacks virtual void on_pool_tx_removed(const crypto::hash &txid) {} virtual ~i_wallet2_callback() {} }; + class wallet_device_callback : public hw::i_device_callback + { + public: + wallet_device_callback(wallet2 * wallet): wallet(wallet) {}; + void on_button_request() override; + void on_pin_request(epee::wipeable_string & pin) override; + void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) override; + private: + wallet2 * wallet; + }; + struct tx_dust_policy { uint64_t dust_threshold; @@ -154,6 +169,7 @@ namespace tools { friend class ::Serialization_portability_wallet_Test; friend class wallet_keys_unlocker; + friend class wallet_device_callback; public: static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); @@ -1285,6 +1301,11 @@ namespace tools void setup_new_blockchain(); void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file); + wallet_device_callback * get_device_callback(); + void on_button_request(); + void on_pin_request(epee::wipeable_string & pin); + void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase); + cryptonote::account_base m_account; boost::optional m_daemon_login; std::string m_daemon_address; @@ -1396,6 +1417,7 @@ namespace tools bool m_devices_registered; std::shared_ptr m_tx_notify; + std::unique_ptr m_device_callback; }; } BOOST_CLASS_VERSION(tools::wallet2, 26) diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index b3141985d..e2caee5d2 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -219,6 +219,14 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct password_entry_failed : public wallet_runtime_error + { + explicit password_entry_failed(std::string&& loc, const std::string &msg = "Password entry failed") + : wallet_runtime_error(std::move(loc), msg) + { + } + }; + //---------------------------------------------------------------------------------------------------- const char* const file_error_messages[] = { "file already exists", "file not found", From d21dad70ddda771bf33a19f9a53c48cc91d8464f Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Mon, 12 Nov 2018 00:07:25 +0100 Subject: [PATCH 2/5] device: enable to use multiple independent device wallets - adds a new option `--hw-device-deriv-path` to the simple wallet. Enables to specify wallet derivation path / wallet code (path avoided so it can be misinterpreted as a file path). - devices can use different derivation mechanisms. Trezor uses standard SLIP-10 mechanism with fixed SLIP-44 prefix for Monero - Trezor: when empty, the default derivation mechanism is used with 44'/128'/0'. When entered the derivation path is 44'/128'/PATH. - Trezor: the path is always taken as elements are hardened (1<<31 bit turned on) --- src/device/device.hpp | 1 + src/device_trezor/device_trezor_base.cpp | 39 +++++++++++++++++++++++- src/device_trezor/device_trezor_base.hpp | 10 +++++- src/simplewallet/simplewallet.cpp | 2 ++ src/wallet/wallet2.cpp | 19 ++++++++++++ src/wallet/wallet2.h | 4 +++ 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/device/device.hpp b/src/device/device.hpp index 3e782d21a..399648f01 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -138,6 +138,7 @@ namespace hw { virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; }; virtual void set_callback(i_device_callback * callback) {}; + virtual void set_derivation_path(const std::string &derivation_path) {}; /* ======================================================================= */ /* LOCKER */ diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index dcfc9cb67..fddc6082c 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -28,6 +28,9 @@ // #include "device_trezor_base.hpp" +#include +#include +#include namespace hw { namespace trezor { @@ -36,8 +39,9 @@ namespace trezor { #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "device.trezor" +#define TREZOR_BIP44_HARDENED_ZERO 0x80000000 - const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080, 0x80000000}; + const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080}; device_trezor_base::device_trezor_base(): m_callback(nullptr) { @@ -252,6 +256,39 @@ namespace trezor { } } + void device_trezor_base::ensure_derivation_path() noexcept { + if (m_wallet_deriv_path.empty()){ + m_wallet_deriv_path.push_back(TREZOR_BIP44_HARDENED_ZERO); // default 0' + } + } + + void device_trezor_base::set_derivation_path(const std::string &deriv_path){ + this->m_wallet_deriv_path.clear(); + + if (deriv_path.empty() || deriv_path == "-"){ + ensure_derivation_path(); + return; + } + + CHECK_AND_ASSERT_THROW_MES(deriv_path.size() <= 255, "Derivation path is too long"); + + std::vector fields; + boost::split(fields, deriv_path, boost::is_any_of("/")); + CHECK_AND_ASSERT_THROW_MES(fields.size() <= 10, "Derivation path is too long"); + + boost::regex rgx("^([0-9]+)'?$"); + boost::cmatch match; + + this->m_wallet_deriv_path.reserve(fields.size()); + for(const std::string & cur : fields){ + const bool ok = boost::regex_match(cur.c_str(), match, rgx); + CHECK_AND_ASSERT_THROW_MES(ok, "Invalid wallet code: " << deriv_path << ". Invalid path element: " << cur); + CHECK_AND_ASSERT_THROW_MES(match[0].length() > 0, "Invalid wallet code: " << deriv_path << ". Invalid path element: " << cur); + + const unsigned long cidx = std::stoul(match[0].str()) | TREZOR_BIP44_HARDENED_ZERO; + this->m_wallet_deriv_path.push_back((unsigned int)cidx); + } + } /* ======================================================================= */ /* TREZOR PROTOCOL */ diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index a4e92bf78..4d205ad7a 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -71,6 +71,7 @@ namespace trezor { i_device_callback * m_callback; std::string full_name; + std::vector m_wallet_deriv_path; cryptonote::network_type network_type; @@ -81,6 +82,7 @@ namespace trezor { void require_connected(); void call_ping_unsafe(); void test_ping(); + void ensure_derivation_path() noexcept; // Communication methods @@ -176,9 +178,13 @@ namespace trezor { msg->add_address_n(x); } } else { + ensure_derivation_path(); for (unsigned int i : DEFAULT_BIP44_PATH) { msg->add_address_n(i); } + for (unsigned int i : m_wallet_deriv_path) { + msg->add_address_n(i); + } } if (network_type){ @@ -201,7 +207,7 @@ namespace trezor { bool reset(); // Default derivation path for Monero - static const uint32_t DEFAULT_BIP44_PATH[3]; + static const uint32_t DEFAULT_BIP44_PATH[2]; std::shared_ptr getTransport(){ return m_transport; @@ -215,6 +221,8 @@ namespace trezor { return m_callback; } + void set_derivation_path(const std::string &deriv_path) override; + /* ======================================================================= */ /* SETUP/TEARDOWN */ /* ======================================================================= */ diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 6d19235c5..07084d15b 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3804,9 +3804,11 @@ boost::optional simple_wallet::new_wallet(const boost::pr m_wallet->set_refresh_from_block_height(m_restore_height); auto device_desc = tools::wallet2::device_name_option(vm); + auto device_derivation_path = tools::wallet2::device_derivation_path_option(vm); try { bool create_address_file = command_line::get_arg(vm, arg_create_address_file); + m_wallet->device_derivation_path(device_derivation_path); 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()); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ae6ed0937..83aae6de3 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -207,6 +207,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"), ""}; + const command_line::arg_descriptor 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 tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" }; }; @@ -259,6 +260,7 @@ std::unique_ptr 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 +316,7 @@ std::unique_ptr 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 { @@ -912,6 +915,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{}; @@ -928,6 +936,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); } @@ -1089,6 +1098,7 @@ 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){ @@ -3160,6 +3170,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 writer(buffer); @@ -3279,6 +3292,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; } @@ -3446,6 +3460,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 { @@ -3460,6 +3477,7 @@ 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); @@ -3967,6 +3985,7 @@ 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); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c07054177..a588e8003 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -191,6 +191,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 std::string device_derivation_path_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. @@ -978,6 +979,8 @@ namespace tools 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; } + const std::string & device_derivation_path() const { return m_device_derivation_path; } + void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; } 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); @@ -1384,6 +1387,7 @@ namespace tools std::unordered_set m_scanned_pool_txs[2]; size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; std::string m_device_name; + std::string m_device_derivation_path; // Aux transaction data from device std::unordered_map m_tx_device; From 9cf636af696edfa5ad5d0679c95a67f9a15e0635 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Mon, 12 Nov 2018 04:13:54 +0100 Subject: [PATCH 3/5] device/trezor: ask for KI sync on first refresh When doing a first refresh on HW-token based wallet KI sync is required if money were received. Received money may indicate wallet was already used before the restore I.e., some transaction could have been already sent from the wallet. The spent UTXO would not be detected as spent which could lead to double spending errors on submitting a new transaction. Thus if the wallet is HW-token based with the cold signing protocol and the first refresh detected received money the user is asked to perform the key image sync. --- src/simplewallet/simplewallet.cpp | 53 ++++++++++++++++++++++++------- src/simplewallet/simplewallet.h | 2 ++ src/wallet/wallet2.cpp | 14 +++++--- src/wallet/wallet2.h | 7 +++- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 07084d15b..c85cbfb8a 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -4341,6 +4341,29 @@ void simple_wallet::on_passphrase_request(bool on_device, epee::wipeable_string passphrase = pwd_container->password(); } //---------------------------------------------------------------------------------------------------- +void simple_wallet::on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money) +{ + // Key image sync after the first refresh + if (!m_wallet->get_account().get_device().has_tx_cold_sign()) { + return; + } + + if (!received_money || m_wallet->get_device_last_key_image_sync() != 0) { + return; + } + + // Finished first refresh for HW device and money received -> KI sync + message_writer() << "\n" << tr("The first refresh has finished for the HW-based wallet with received money. hw_key_images_sync is needed. "); + + std::string accepted = input_line(tr("Do you want to do it now? (Y/Yes/N/No): ")); + if (std::cin.eof() || !command_line::is_yes(accepted)) { + message_writer(console_color_red, false) << tr("hw_key_images_sync skipped. Run command manually before a transfer."); + return; + } + + key_images_sync_intern(); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bool is_init) { if (!try_connect_to_daemon(is_init)) @@ -4358,13 +4381,14 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo message_writer() << tr("Starting refresh..."); uint64_t fetched_blocks = 0; + bool received_money = false; bool ok = false; std::ostringstream ss; try { m_in_manual_refresh.store(true, std::memory_order_relaxed); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); - m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks); + m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks, received_money); ok = true; // Clear line "Height xxx of xxx" std::cout << "\r \r"; @@ -4372,6 +4396,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo if (is_init) print_accounts(); show_balance_unlocked(); + on_refresh_finished(start_height, fetched_blocks, is_init, received_money); } catch (const tools::error::daemon_busy&) { @@ -8134,13 +8159,13 @@ bool simple_wallet::hw_key_images_sync(const std::vector &args) fail_msg_writer() << tr("hw wallet does not support cold KI sync"); return true; } - if (!m_wallet->is_trusted_daemon()) - { - fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); - return true; - } LOCK_IDLE_SCOPE(); + key_images_sync_intern(); + return true; +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::key_images_sync_intern(){ try { message_writer(console_color_white, false) << tr("Please confirm the key image sync on the device"); @@ -8149,19 +8174,23 @@ bool simple_wallet::hw_key_images_sync(const std::vector &args) uint64_t height = m_wallet->cold_key_image_sync(spent, unspent); if (height > 0) { - success_msg_writer() << tr("Signed key images imported to height ") << height << ", " - << print_money(spent) << tr(" spent, ") << print_money(unspent) << tr(" unspent"); - } else { + success_msg_writer() << tr("Key images synchronized to height ") << height; + if (!m_wallet->is_trusted_daemon()) + { + message_writer() << tr("Running untrusted daemon, cannot determine which transaction output is spent. Use a trusted daemon with --trusted-daemon and run rescan_spent"); + } else + { + success_msg_writer() << print_money(spent) << tr(" spent, ") << print_money(unspent) << tr(" unspent"); + } + } + else { fail_msg_writer() << tr("Failed to import key images"); } } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to import key images: ") << e.what(); - return true; } - - return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::hw_reconnect(const std::vector &args) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 7e2edba7f..665879cac 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -241,6 +241,8 @@ namespace cryptonote bool print_ring_members(const std::vector& ptx_vector, std::ostream& ostr); std::string get_prompt() const; bool print_seed(bool encrypted); + void key_images_sync_intern(); + void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money); struct transfer_view { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 83aae6de3..46bd47353 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -892,7 +892,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) { } @@ -3009,6 +3010,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; } @@ -9235,9 +9237,7 @@ void wallet2::cold_sign_tx(const std::vector& 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"); @@ -9248,7 +9248,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 diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index a588e8003..4d121598b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -810,6 +810,7 @@ namespace tools bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const; uint64_t get_last_block_reward() const { return m_last_block_reward; } + uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; } template inline void serialize(t_archive &a, const unsigned int ver) @@ -918,6 +919,9 @@ namespace tools if(ver < 26) return; a & m_tx_device; + if(ver < 27) + return; + a & m_device_last_key_image_sync; } /*! @@ -1388,6 +1392,7 @@ namespace tools size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; std::string m_device_name; std::string m_device_derivation_path; + uint64_t m_device_last_key_image_sync; // Aux transaction data from device std::unordered_map m_tx_device; @@ -1424,7 +1429,7 @@ namespace tools std::unique_ptr m_device_callback; }; } -BOOST_CLASS_VERSION(tools::wallet2, 26) +BOOST_CLASS_VERSION(tools::wallet2, 27) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 10) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) From 65b9bca70e3aa0aabc135f952da3695fdab00918 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Thu, 29 Nov 2018 04:22:52 +0100 Subject: [PATCH 4/5] device/trezor: python2 compatibility - bundle dependencies --- src/device_trezor/trezor/tools/README.md | 22 ++- src/device_trezor/trezor/tools/pb2cpp.py | 14 +- .../trezor/tools/py2backports/__init__.py | 0 .../trezor/tools/py2backports/tempfile.py | 72 +++++++++ .../trezor/tools/py2backports/weakref.py | 148 ++++++++++++++++++ 5 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 src/device_trezor/trezor/tools/py2backports/__init__.py create mode 100644 src/device_trezor/trezor/tools/py2backports/tempfile.py create mode 100644 src/device_trezor/trezor/tools/py2backports/weakref.py diff --git a/src/device_trezor/trezor/tools/README.md b/src/device_trezor/trezor/tools/README.md index b176017ac..0802e734a 100644 --- a/src/device_trezor/trezor/tools/README.md +++ b/src/device_trezor/trezor/tools/README.md @@ -10,14 +10,28 @@ Install `protoc` for your distribution. Requirements: Soft requirement: Python 3, can be easily installed with [pyenv]. +If Python 3 is used there are no additional python dependencies. -### Python 2 +Since Cmake 3.12 the `FindPython` module is used to locate the Python +interpreter in your system. It preferably searches for Python 3, if none +is found, it searches for Python 2. -Workaround if there is no Python3 available: +Lower version of the cmake uses another module which does not guarantee +ordering. If you want to override the selected python you can do it in +the following way: ```bash -pip install backports.tempfile -``` +export TREZOR_PYTHON=`which python3` +``` + + +### Python 2.7+ + +Python 3 has `tempfile.TemporaryDirectory` available but Python 2 lacks +this class so the message generation code uses `backports.tempfile` package +bundled in the repository. + +The minimal Python versions are 2.7 and 3.4 ### Regenerate messages diff --git a/src/device_trezor/trezor/tools/pb2cpp.py b/src/device_trezor/trezor/tools/pb2cpp.py index 4d7cc775f..3e0318ea5 100644 --- a/src/device_trezor/trezor/tools/pb2cpp.py +++ b/src/device_trezor/trezor/tools/pb2cpp.py @@ -14,12 +14,18 @@ import hashlib try: from tempfile import TemporaryDirectory except: - # Py2 backward compatibility, optionally installed by user - # pip install backports.tempfile + # Py2 backward compatibility, using bundled sources. + # Original source: pip install backports.tempfile try: - from backports.tempfile import TemporaryDirectory + # Try bundled python version + import sys + sys.path.append(os.path.dirname(__file__)) + from py2backports.tempfile import TemporaryDirectory + except: - raise EnvironmentError('TemporaryDirectory could not be imported. Try: pip install backports.tempfile') + raise EnvironmentError('Python 2.7+ or 3.4+ is required. ' + 'TemporaryDirectory is not available in Python 2.' + 'Try to specify python to use, e.g.: "export TREZOR_PYTHON=`which python3`"') AUTO_HEADER = "# Automatically generated by pb2cpp\n" diff --git a/src/device_trezor/trezor/tools/py2backports/__init__.py b/src/device_trezor/trezor/tools/py2backports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/device_trezor/trezor/tools/py2backports/tempfile.py b/src/device_trezor/trezor/tools/py2backports/tempfile.py new file mode 100644 index 000000000..e259dea3f --- /dev/null +++ b/src/device_trezor/trezor/tools/py2backports/tempfile.py @@ -0,0 +1,72 @@ +""" +https://github.com/pjdelport/backports.tempfile/blob/master/src/backports/tempfile.py +Partial backport of Python 3.5's tempfile module: + TemporaryDirectory +Backport modifications are marked with marked with "XXX backport". +""" +from __future__ import absolute_import + +import sys +import warnings as _warnings +from shutil import rmtree as _rmtree + +from py2backports.weakref import finalize + + +# XXX backport: Rather than backporting all of mkdtemp(), we just create a +# thin wrapper implementing its Python 3.5 signature. +if sys.version_info < (3, 5): + from tempfile import mkdtemp as old_mkdtemp + + def mkdtemp(suffix=None, prefix=None, dir=None): + """ + Wrap `tempfile.mkdtemp()` to make the suffix and prefix optional (like Python 3.5). + """ + kwargs = {k: v for (k, v) in + dict(suffix=suffix, prefix=prefix, dir=dir).items() + if v is not None} + return old_mkdtemp(**kwargs) + +else: + from tempfile import mkdtemp + + +# XXX backport: ResourceWarning was added in Python 3.2. +# For earlier versions, fall back to RuntimeWarning instead. +_ResourceWarning = RuntimeWarning if sys.version_info < (3, 2) else ResourceWarning + + +class TemporaryDirectory(object): + """Create and return a temporary directory. This has the same + behavior as mkdtemp but can be used as a context manager. For + example: + with TemporaryDirectory() as tmpdir: + ... + Upon exiting the context, the directory and everything contained + in it are removed. + """ + + def __init__(self, suffix=None, prefix=None, dir=None): + self.name = mkdtemp(suffix, prefix, dir) + self._finalizer = finalize( + self, self._cleanup, self.name, + warn_message="Implicitly cleaning up {!r}".format(self)) + + @classmethod + def _cleanup(cls, name, warn_message): + _rmtree(name) + _warnings.warn(warn_message, _ResourceWarning) + + + def __repr__(self): + return "<{} {!r}>".format(self.__class__.__name__, self.name) + + def __enter__(self): + return self.name + + def __exit__(self, exc, value, tb): + self.cleanup() + + def cleanup(self): + if self._finalizer.detach(): + _rmtree(self.name) diff --git a/src/device_trezor/trezor/tools/py2backports/weakref.py b/src/device_trezor/trezor/tools/py2backports/weakref.py new file mode 100644 index 000000000..eb646812b --- /dev/null +++ b/src/device_trezor/trezor/tools/py2backports/weakref.py @@ -0,0 +1,148 @@ +""" +https://github.com/pjdelport/backports.weakref/blob/master/src/backports/weakref.py +Partial backport of Python 3.6's weakref module: + finalize (new in Python 3.4) +Backport modifications are marked with "XXX backport". +""" +from __future__ import absolute_import + +import itertools +import sys +from weakref import ref + +__all__ = ['finalize'] + + +class finalize(object): + """Class for finalization of weakrefable objects + finalize(obj, func, *args, **kwargs) returns a callable finalizer + object which will be called when obj is garbage collected. The + first time the finalizer is called it evaluates func(*arg, **kwargs) + and returns the result. After this the finalizer is dead, and + calling it just returns None. + When the program exits any remaining finalizers for which the + atexit attribute is true will be run in reverse order of creation. + By default atexit is true. + """ + + # Finalizer objects don't have any state of their own. They are + # just used as keys to lookup _Info objects in the registry. This + # ensures that they cannot be part of a ref-cycle. + + __slots__ = () + _registry = {} + _shutdown = False + _index_iter = itertools.count() + _dirty = False + _registered_with_atexit = False + + class _Info(object): + __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index") + + def __init__(self, obj, func, *args, **kwargs): + if not self._registered_with_atexit: + # We may register the exit function more than once because + # of a thread race, but that is harmless + import atexit + atexit.register(self._exitfunc) + finalize._registered_with_atexit = True + info = self._Info() + info.weakref = ref(obj, self) + info.func = func + info.args = args + info.kwargs = kwargs or None + info.atexit = True + info.index = next(self._index_iter) + self._registry[self] = info + finalize._dirty = True + + def __call__(self, _=None): + """If alive then mark as dead and return func(*args, **kwargs); + otherwise return None""" + info = self._registry.pop(self, None) + if info and not self._shutdown: + return info.func(*info.args, **(info.kwargs or {})) + + def detach(self): + """If alive then mark as dead and return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None and self._registry.pop(self, None): + return (obj, info.func, info.args, info.kwargs or {}) + + def peek(self): + """If alive then return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None: + return (obj, info.func, info.args, info.kwargs or {}) + + @property + def alive(self): + """Whether finalizer is alive""" + return self in self._registry + + @property + def atexit(self): + """Whether finalizer should be called at exit""" + info = self._registry.get(self) + return bool(info) and info.atexit + + @atexit.setter + def atexit(self, value): + info = self._registry.get(self) + if info: + info.atexit = bool(value) + + def __repr__(self): + info = self._registry.get(self) + obj = info and info.weakref() + if obj is None: + return '<%s object at %#x; dead>' % (type(self).__name__, id(self)) + else: + return '<%s object at %#x; for %r at %#x>' % \ + (type(self).__name__, id(self), type(obj).__name__, id(obj)) + + @classmethod + def _select_for_exit(cls): + # Return live finalizers marked for exit, oldest first + L = [(f,i) for (f,i) in cls._registry.items() if i.atexit] + L.sort(key=lambda item:item[1].index) + return [f for (f,i) in L] + + @classmethod + def _exitfunc(cls): + # At shutdown invoke finalizers for which atexit is true. + # This is called once all other non-daemonic threads have been + # joined. + reenable_gc = False + try: + if cls._registry: + import gc + if gc.isenabled(): + reenable_gc = True + gc.disable() + pending = None + while True: + if pending is None or finalize._dirty: + pending = cls._select_for_exit() + finalize._dirty = False + if not pending: + break + f = pending.pop() + try: + # gc is disabled, so (assuming no daemonic + # threads) the following is the only line in + # this function which might trigger creation + # of a new finalizer + f() + except Exception: + sys.excepthook(*sys.exc_info()) + assert f not in cls._registry + finally: + # prevent any more finalizers from executing during shutdown + finalize._shutdown = True + if reenable_gc: + gc.enable() From d71f89e2a26720021f7509f8c1eee87645f48529 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Wed, 28 Nov 2018 22:22:11 +0100 Subject: [PATCH 5/5] device/trezor: device/trezor: correct device initialization, status check - checks if the device is in the correct usable state - implements check for the v2.0.9 firmware which does not support payment IDs - simple transacttion check, payment id fmt consistency - minor fixes, refactoring, webusb session counting fix --- cmake/CheckTrezor.cmake | 2 +- src/device_trezor/device_trezor.cpp | 77 +++++++++++++++++++++--- src/device_trezor/device_trezor.hpp | 5 +- src/device_trezor/device_trezor_base.cpp | 58 ++++++++++++++++-- src/device_trezor/device_trezor_base.hpp | 19 +++++- src/device_trezor/trezor/transport.cpp | 2 +- 6 files changed, 140 insertions(+), 23 deletions(-) diff --git a/cmake/CheckTrezor.cmake b/cmake/CheckTrezor.cmake index ea21237fd..bcd3cfc2c 100644 --- a/cmake/CheckTrezor.cmake +++ b/cmake/CheckTrezor.cmake @@ -56,7 +56,7 @@ if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON) endif() if(USE_DEVICE_TREZOR_UDP_RELEASE) - add_definitions(-DWITH_DEVICE_TREZOR_UDP_RELEASE=1) + add_definitions(-DUSE_DEVICE_TREZOR_UDP_RELEASE=1) endif() if (Protobuf_INCLUDE_DIR) diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index 5096fcea8..8868fb995 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -121,7 +121,8 @@ namespace trezor { const boost::optional & network_type){ AUTO_LOCK_CMD(); require_connected(); - test_ping(); + device_state_reset_unsafe(); + require_initialized(); auto req = std::make_shared(); this->set_msg_addr(req.get(), path, network_type); @@ -136,7 +137,8 @@ namespace trezor { const boost::optional & network_type){ AUTO_LOCK_CMD(); require_connected(); - test_ping(); + device_state_reset_unsafe(); + require_initialized(); auto req = std::make_shared(); this->set_msg_addr(req.get(), path, network_type); @@ -152,7 +154,8 @@ namespace trezor { { AUTO_LOCK_CMD(); require_connected(); - test_ping(); + device_state_reset_unsafe(); + require_initialized(); std::shared_ptr req; @@ -238,12 +241,11 @@ namespace trezor { cpend.construction_data = cdata.tx_data; // Transaction check - cryptonote::blobdata tx_blob; - cryptonote::transaction tx_deserialized; - bool r = cryptonote::t_serializable_object_to_blob(cpend.tx, tx_blob); - CHECK_AND_ASSERT_THROW_MES(r, "Transaction serialization failed"); - r = cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx_deserialized); - CHECK_AND_ASSERT_THROW_MES(r, "Transaction deserialization failed"); + try { + transaction_check(cdata, aux_data); + } catch(const std::exception &e){ + throw exc::ProtocolException(std::string("Transaction verification failed: ") + e.what()); + } std::string key_images; bool all_are_txin_to_key = std::all_of(cdata.tx.vin.begin(), cdata.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool @@ -283,7 +285,8 @@ namespace trezor { { AUTO_LOCK_CMD(); require_connected(); - test_ping(); + device_state_reset_unsafe(); + require_initialized(); CHECK_AND_ASSERT_THROW_MES(idx < unsigned_tx.txes.size(), "Invalid transaction index"); signer = std::make_shared(wallet, &unsigned_tx, idx, &aux_data); @@ -294,6 +297,7 @@ namespace trezor { // Step: Init auto init_msg = signer->step_init(); this->set_msg_addr(init_msg.get()); + transaction_pre_check(init_msg); auto response = this->client_exchange(init_msg); signer->step_init_ack(response); @@ -351,6 +355,59 @@ namespace trezor { signer->step_final_ack(ack_final); } + void device_trezor::transaction_pre_check(std::shared_ptr init_msg) + { + CHECK_AND_ASSERT_THROW_MES(init_msg, "TransactionInitRequest is empty"); + CHECK_AND_ASSERT_THROW_MES(init_msg->has_tsx_data(), "TransactionInitRequest has no transaction data"); + CHECK_AND_ASSERT_THROW_MES(m_features, "Device state not initialized"); // make sure the caller did not reset features + const bool nonce_required = init_msg->tsx_data().has_payment_id() && init_msg->tsx_data().payment_id().size() > 0; + + if (nonce_required){ + // Versions 2.0.9 and lower do not support payment ID + CHECK_AND_ASSERT_THROW_MES(m_features->has_major_version() && m_features->has_minor_version() && m_features->has_patch_version(), "Invalid Trezor firmware version information"); + const uint32_t vma = m_features->major_version(); + const uint32_t vmi = m_features->minor_version(); + const uint32_t vpa = m_features->patch_version(); + if (vma < 2 || (vma == 2 && vmi == 0 && vpa <= 9)) { + throw exc::TrezorException("Trezor firmware 2.0.9 and lower does not support transactions with short payment IDs or integrated addresses. Please update."); + } + } + } + + void device_trezor::transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data) + { + // Simple serialization check + cryptonote::blobdata tx_blob; + cryptonote::transaction tx_deserialized; + bool r = cryptonote::t_serializable_object_to_blob(tdata.tx, tx_blob); + CHECK_AND_ASSERT_THROW_MES(r, "Transaction serialization failed"); + r = cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx_deserialized); + CHECK_AND_ASSERT_THROW_MES(r, "Transaction deserialization failed"); + + // Extras check + std::vector tx_extra_fields; + cryptonote::tx_extra_nonce nonce; + + r = cryptonote::parse_tx_extra(tdata.tx.extra, tx_extra_fields); + CHECK_AND_ASSERT_THROW_MES(r, "tx.extra parsing failed"); + + const bool nonce_required = tdata.tsx_data.has_payment_id() && tdata.tsx_data.payment_id().size() > 0; + const bool has_nonce = cryptonote::find_tx_extra_field_by_type(tx_extra_fields, nonce); + CHECK_AND_ASSERT_THROW_MES(has_nonce == nonce_required, "Transaction nonce presence inconsistent"); + + if (nonce_required){ + const std::string & payment_id = tdata.tsx_data.payment_id(); + if (payment_id.size() == 32){ + crypto::hash payment_id_long{}; + CHECK_AND_ASSERT_THROW_MES(cryptonote::get_payment_id_from_tx_extra_nonce(nonce.nonce, payment_id_long), "Long payment ID not present"); + + } else if (payment_id.size() == 8){ + crypto::hash8 payment_id_short{}; + CHECK_AND_ASSERT_THROW_MES(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(nonce.nonce, payment_id_short), "Short payment ID not present"); + } + } + } + #else //WITH_DEVICE_TREZOR void register_all(std::map> ®istry) { diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp index a2134574c..1f08be887 100644 --- a/src/device_trezor/device_trezor.hpp +++ b/src/device_trezor/device_trezor.hpp @@ -57,9 +57,8 @@ namespace trezor { */ class device_trezor : public hw::trezor::device_trezor_base, public hw::device_cold { protected: - // To speed up blockchain parsing the view key maybe handle here. - crypto::secret_key viewkey; - bool has_view_key; + void transaction_pre_check(std::shared_ptr init_msg); + void transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data); public: device_trezor(); diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index fddc6082c..5071932ee 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -65,7 +65,7 @@ namespace trezor { } bool device_trezor_base::set_name(const std::string & name) { - this->full_name = name; + this->m_full_name = name; this->name = ""; auto delim = name.find(':'); @@ -77,10 +77,10 @@ namespace trezor { } const std::string device_trezor_base::get_name() const { - if (this->full_name.empty()) { + if (this->m_full_name.empty()) { return std::string("name).append(">"); } - return this->full_name; + return this->m_full_name; } bool device_trezor_base::init() { @@ -139,6 +139,9 @@ namespace trezor { } bool device_trezor_base::disconnect() { + m_device_state.clear(); + m_features.reset(); + if (m_transport){ try { m_transport->close(); @@ -193,6 +196,25 @@ namespace trezor { } } + void device_trezor_base::require_initialized(){ + if (!m_features){ + throw exc::TrezorException("Device state not initialized"); + } + + if (m_features->has_bootloader_mode() && m_features->bootloader_mode()){ + throw exc::TrezorException("Device is in the bootloader mode"); + } + + if (m_features->has_firmware_present() && !m_features->firmware_present()){ + throw exc::TrezorException("Device has no firmware loaded"); + } + + // Hard requirement on initialized field, has to be there. + if (!m_features->has_initialized() || !m_features->initialized()){ + throw exc::TrezorException("Device is not initialized"); + } + } + void device_trezor_base::call_ping_unsafe(){ auto pingMsg = std::make_shared(); pingMsg->set_message("PING"); @@ -217,7 +239,7 @@ namespace trezor { void device_trezor_base::write_raw(const google::protobuf::Message * msg){ require_connected(); CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); - this->getTransport()->write(*msg); + this->get_transport()->write(*msg); } GenericMessage device_trezor_base::read_raw(){ @@ -225,7 +247,7 @@ namespace trezor { std::shared_ptr msg_resp; hw::trezor::messages::MessageType msg_resp_type; - this->getTransport()->read(msg_resp, &msg_resp_type); + this->get_transport()->read(msg_resp, &msg_resp_type); return GenericMessage(msg_resp_type, msg_resp); } @@ -314,6 +336,25 @@ namespace trezor { return false; } + void device_trezor_base::device_state_reset_unsafe() + { + require_connected(); + auto initMsg = std::make_shared(); + + if(!m_device_state.empty()) { + initMsg->set_allocated_state(&m_device_state); + } + + m_features = this->client_exchange(initMsg); + initMsg->release_state(); + } + + void device_trezor_base::device_state_reset() + { + AUTO_LOCK_CMD(); + device_state_reset_unsafe(); + } + void device_trezor_base::on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg) { CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); @@ -361,7 +402,13 @@ namespace trezor { // TODO: remove passphrase from memory m.set_passphrase(passphrase.data(), passphrase.size()); } + + if (!m_device_state.empty()){ + m.set_allocated_state(&m_device_state); + } + resp = call_raw(&m); + m.release_state(); } void device_trezor_base::on_passphrase_state_request(GenericMessage & resp, const messages::common::PassphraseStateRequest * msg) @@ -369,6 +416,7 @@ namespace trezor { MDEBUG("on_passhprase_state_request"); CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); + m_device_state = msg->state(); messages::common::PassphraseStateAck m; resp = call_raw(&m); } diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 4d205ad7a..3c35e8aca 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -70,8 +70,10 @@ namespace trezor { std::shared_ptr m_transport; i_device_callback * m_callback; - std::string full_name; + std::string m_full_name; std::vector m_wallet_deriv_path; + std::string m_device_state; // returned after passphrase entry, session + std::shared_ptr m_features; // features from the last device reset cryptonote::network_type network_type; @@ -80,8 +82,10 @@ namespace trezor { // void require_connected(); + void require_initialized(); void call_ping_unsafe(); void test_ping(); + void device_state_reset_unsafe(); void ensure_derivation_path() noexcept; // Communication methods @@ -130,7 +134,7 @@ namespace trezor { // Scoped session closer BOOST_SCOPE_EXIT_ALL(&, this) { if (open_session){ - this->getTransport()->close(); + this->get_transport()->close(); } }; @@ -209,7 +213,7 @@ namespace trezor { // Default derivation path for Monero static const uint32_t DEFAULT_BIP44_PATH[2]; - std::shared_ptr getTransport(){ + std::shared_ptr get_transport(){ return m_transport; } @@ -221,6 +225,10 @@ namespace trezor { return m_callback; } + std::shared_ptr & get_features() { + return m_features; + } + void set_derivation_path(const std::string &deriv_path) override; /* ======================================================================= */ @@ -250,6 +258,11 @@ namespace trezor { */ bool ping(); + /** + * Performs Initialize call to the Trezor, resets to known state. + */ + void device_state_reset(); + // Protocol callbacks void on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg); void on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg); diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index 814537eb6..cd66e59e8 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -840,7 +840,7 @@ namespace trezor{ throw exc::DeviceAcquireException("Unable to claim libusb device"); } - m_conn_count += 1; + m_conn_count = 1; m_proto->session_begin(*this); #undef TREZOR_DESTROY_SESSION