From d21dad70ddda771bf33a19f9a53c48cc91d8464f Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Mon, 12 Nov 2018 00:07:25 +0100 Subject: [PATCH] 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;