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)
This commit is contained in:
Dusan Klinec 2018-11-12 00:07:25 +01:00
parent 318cc78457
commit d21dad70dd
No known key found for this signature in database
GPG key ID: 6337E118CCBCE103
6 changed files with 73 additions and 2 deletions

View file

@ -138,6 +138,7 @@ namespace hw {
virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; }; virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; };
virtual void set_callback(i_device_callback * callback) {}; virtual void set_callback(i_device_callback * callback) {};
virtual void set_derivation_path(const std::string &derivation_path) {};
/* ======================================================================= */ /* ======================================================================= */
/* LOCKER */ /* LOCKER */

View file

@ -28,6 +28,9 @@
// //
#include "device_trezor_base.hpp" #include "device_trezor_base.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/regex.hpp>
namespace hw { namespace hw {
namespace trezor { namespace trezor {
@ -36,8 +39,9 @@ namespace trezor {
#undef MONERO_DEFAULT_LOG_CATEGORY #undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "device.trezor" #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) { 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<std::string> 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 */ /* TREZOR PROTOCOL */

View file

@ -71,6 +71,7 @@ namespace trezor {
i_device_callback * m_callback; i_device_callback * m_callback;
std::string full_name; std::string full_name;
std::vector<unsigned int> m_wallet_deriv_path;
cryptonote::network_type network_type; cryptonote::network_type network_type;
@ -81,6 +82,7 @@ namespace trezor {
void require_connected(); void require_connected();
void call_ping_unsafe(); void call_ping_unsafe();
void test_ping(); void test_ping();
void ensure_derivation_path() noexcept;
// Communication methods // Communication methods
@ -176,9 +178,13 @@ namespace trezor {
msg->add_address_n(x); msg->add_address_n(x);
} }
} else { } else {
ensure_derivation_path();
for (unsigned int i : DEFAULT_BIP44_PATH) { for (unsigned int i : DEFAULT_BIP44_PATH) {
msg->add_address_n(i); msg->add_address_n(i);
} }
for (unsigned int i : m_wallet_deriv_path) {
msg->add_address_n(i);
}
} }
if (network_type){ if (network_type){
@ -201,7 +207,7 @@ namespace trezor {
bool reset(); bool reset();
// Default derivation path for Monero // Default derivation path for Monero
static const uint32_t DEFAULT_BIP44_PATH[3]; static const uint32_t DEFAULT_BIP44_PATH[2];
std::shared_ptr<Transport> getTransport(){ std::shared_ptr<Transport> getTransport(){
return m_transport; return m_transport;
@ -215,6 +221,8 @@ namespace trezor {
return m_callback; return m_callback;
} }
void set_derivation_path(const std::string &deriv_path) override;
/* ======================================================================= */ /* ======================================================================= */
/* SETUP/TEARDOWN */ /* SETUP/TEARDOWN */
/* ======================================================================= */ /* ======================================================================= */

View file

@ -3804,9 +3804,11 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
m_wallet->set_refresh_from_block_height(m_restore_height); m_wallet->set_refresh_from_block_height(m_restore_height);
auto device_desc = tools::wallet2::device_name_option(vm); auto device_desc = tools::wallet2::device_name_option(vm);
auto device_derivation_path = tools::wallet2::device_derivation_path_option(vm);
try try
{ {
bool create_address_file = command_line::get_arg(vm, arg_create_address_file); 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); 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: ") message_writer(console_color_white, true) << tr("Generated new wallet on hw device: ")
<< m_wallet->get_account().get_public_address_str(m_wallet->nettype()); << m_wallet->get_account().get_public_address_str(m_wallet->nettype());

View file

@ -207,6 +207,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<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 = {"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" , "" }; 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 +260,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_host = command_line::get_arg(vm, opts.daemon_host);
auto daemon_port = command_line::get_arg(vm, opts.daemon_port); auto daemon_port = command_line::get_arg(vm, opts.daemon_port);
auto device_name = command_line::get_arg(vm, opts.hw_device); 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, 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")); 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<tools::wallet2> make_basic(const boost::program_options::variabl
boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
wallet->set_ring_database(ringdb_path.string()); wallet->set_ring_database(ringdb_path.string());
wallet->device_name(device_name); wallet->device_name(device_name);
wallet->device_derivation_path(device_derivation_path);
try 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); 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) void wallet2::init_options(boost::program_options::options_description& desc_params)
{ {
const options opts{}; 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.shared_ringdb_dir);
command_line::add_arg(desc_params, opts.kdf_rounds); 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);
command_line::add_arg(desc_params, opts.hw_device_derivation_path);
command_line::add_arg(desc_params, opts.tx_notify); 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); hw::device &hwdev = lookup_device(m_device_name);
hwdev.set_name(m_device_name); hwdev.set_name(m_device_name);
hwdev.set_network_type(m_nettype); hwdev.set_network_type(m_nettype);
hwdev.set_derivation_path(m_device_derivation_path);
hwdev.set_callback(get_device_callback()); hwdev.set_callback(get_device_callback());
r = hwdev.init(); r = hwdev.init();
if (!r){ 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()); value.SetString(m_device_name.c_str(), m_device_name.size());
json.AddMember("device_name", value, json.GetAllocator()); 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 // Serialize the JSON object
rapidjson::StringBuffer buffer; rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); rapidjson::Writer<rapidjson::StringBuffer> 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_major = SUBADDRESS_LOOKAHEAD_MAJOR;
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
m_device_name = ""; m_device_name = "";
m_device_derivation_path = "";
m_key_device_type = hw::device::device_type::SOFTWARE; m_key_device_type = hw::device::device_type::SOFTWARE;
encrypted_secret_keys = false; 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"; 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 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); 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); 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_network_type(m_nettype);
hwdev.set_derivation_path(m_device_derivation_path);
hwdev.set_callback(get_device_callback()); 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.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); 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); auto &hwdev = lookup_device(device_name);
hwdev.set_name(device_name); hwdev.set_name(device_name);
hwdev.set_network_type(m_nettype); hwdev.set_network_type(m_nettype);
hwdev.set_derivation_path(m_device_derivation_path);
hwdev.set_callback(get_device_callback()); hwdev.set_callback(get_device_callback());
m_account.create_from_device(hwdev); m_account.create_from_device(hwdev);

View file

@ -191,6 +191,7 @@ namespace tools
static bool has_testnet_option(const boost::program_options::variables_map& vm); 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 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_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); static void init_options(boost::program_options::options_description& desc_params);
//! Uses stdin and stdout. Returns a wallet2 if no errors. //! 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; } 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; } const std::string & device_name() const { return m_device_name; }
void device_name(const std::string & device_name) { m_device_name = 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<crypto::secret_key> &additional_tx_keys) const; bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys); void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
@ -1384,6 +1387,7 @@ namespace tools
std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor;
std::string m_device_name; std::string m_device_name;
std::string m_device_derivation_path;
// Aux transaction data from device // Aux transaction data from device
std::unordered_map<crypto::hash, std::string> m_tx_device; std::unordered_map<crypto::hash, std::string> m_tx_device;