Merge pull request #4839

d71f89e2 device/trezor: device/trezor: correct device initialization, status check (Dusan Klinec)
65b9bca7 device/trezor: python2 compatibility - bundle dependencies (Dusan Klinec)
9cf636af device/trezor: ask for KI sync on first refresh (Dusan Klinec)
d21dad70 device: enable to use multiple independent device wallets (Dusan Klinec)
318cc784 device/trezor: passphrase entry on host (Dusan Klinec)
This commit is contained in:
Riccardo Spagni 2018-12-12 11:53:42 +02:00
commit 0b31baf6e4
No known key found for this signature in database
GPG Key ID: 55432DF31CCD4FCD
17 changed files with 653 additions and 73 deletions

View File

@ -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)

View File

@ -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,8 @@ 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) {};
virtual void set_derivation_path(const std::string &derivation_path) {};
/* ======================================================================= */
/* LOCKER */

View File

@ -121,7 +121,8 @@ namespace trezor {
const boost::optional<cryptonote::network_type> & network_type){
AUTO_LOCK_CMD();
require_connected();
test_ping();
device_state_reset_unsafe();
require_initialized();
auto req = std::make_shared<messages::monero::MoneroGetAddress>();
this->set_msg_addr<messages::monero::MoneroGetAddress>(req.get(), path, network_type);
@ -136,7 +137,8 @@ namespace trezor {
const boost::optional<cryptonote::network_type> & network_type){
AUTO_LOCK_CMD();
require_connected();
test_ping();
device_state_reset_unsafe();
require_initialized();
auto req = std::make_shared<messages::monero::MoneroGetWatchKey>();
this->set_msg_addr<messages::monero::MoneroGetWatchKey>(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<messages::monero::MoneroKeyImageExportInitRequest> 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<protocol::tx::Signer>(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<messages::monero::MoneroTransactionInitAck>(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<messages::monero::MoneroTransactionInitRequest> 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<cryptonote::tx_extra_field> 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<std::string, std::unique_ptr<device>> &registry) {

View File

@ -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<messages::monero::MoneroTransactionInitRequest> init_msg);
void transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data);
public:
device_trezor();

View File

@ -28,6 +28,9 @@
//
#include "device_trezor_base.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/regex.hpp>
namespace hw {
namespace trezor {
@ -36,10 +39,11 @@ 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() {
device_trezor_base::device_trezor_base(): m_callback(nullptr) {
}
@ -61,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(':');
@ -73,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("<disconnected:").append(this->name).append(">");
}
return this->full_name;
return this->m_full_name;
}
bool device_trezor_base::init() {
@ -135,6 +139,9 @@ namespace trezor {
}
bool device_trezor_base::disconnect() {
m_device_state.clear();
m_features.reset();
if (m_transport){
try {
m_transport->close();
@ -189,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<messages::management::Ping>();
pingMsg->set_message("PING");
@ -213,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(){
@ -221,7 +247,7 @@ namespace trezor {
std::shared_ptr<google::protobuf::Message> 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);
}
@ -252,6 +278,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 */
@ -277,6 +336,25 @@ namespace trezor {
return false;
}
void device_trezor_base::device_state_reset_unsafe()
{
require_connected();
auto initMsg = std::make_shared<messages::management::Initialize>();
if(!m_device_state.empty()) {
initMsg->set_allocated_state(&m_device_state);
}
m_features = this->client_exchange<messages::management::Features>(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");
@ -324,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)
@ -332,10 +416,7 @@ 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());
}
m_device_state = msg->state();
messages::common::PassphraseStateAck m;
resp = call_raw(&m);
}

View File

@ -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,9 +68,12 @@ namespace trezor {
mutable boost::mutex command_locker;
std::shared_ptr<Transport> m_transport;
std::shared_ptr<trezor_callback> m_callback;
i_device_callback * m_callback;
std::string full_name;
std::string m_full_name;
std::vector<unsigned int> m_wallet_deriv_path;
std::string m_device_state; // returned after passphrase entry, session
std::shared_ptr<messages::management::Features> m_features; // features from the last device reset
cryptonote::network_type network_type;
@ -90,8 +82,11 @@ 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
@ -139,7 +134,7 @@ namespace trezor {
// Scoped session closer
BOOST_SCOPE_EXIT_ALL(&, this) {
if (open_session){
this->getTransport()->close();
this->get_transport()->close();
}
};
@ -187,9 +182,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){
@ -212,16 +211,26 @@ 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<Transport> getTransport(){
std::shared_ptr<Transport> get_transport(){
return m_transport;
}
std::shared_ptr<trezor_callback> getCallback(){
void set_callback(i_device_callback * callback) override {
m_callback = callback;
}
i_device_callback * get_callback(){
return m_callback;
}
std::shared_ptr<messages::management::Features> & get_features() {
return m_features;
}
void set_derivation_path(const std::string &deriv_path) override;
/* ======================================================================= */
/* SETUP/TEARDOWN */
/* ======================================================================= */
@ -249,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);

View File

@ -10,15 +10,29 @@ 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
```bash

View File

@ -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"

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -3790,6 +3790,7 @@ boost::optional<epee::wipeable_string> 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 {};
@ -3807,9 +3808,11 @@ boost::optional<epee::wipeable_string> 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());
@ -3897,7 +3900,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)
@ -3905,6 +3908,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;
@ -4308,6 +4313,61 @@ boost::optional<epee::wipeable_string> 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();
}
//----------------------------------------------------------------------------------------------------
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))
@ -4325,13 +4385,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";
@ -4339,6 +4400,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&)
{
@ -8096,13 +8158,13 @@ bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &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");
@ -8111,19 +8173,23 @@ bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &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<std::string> &args)

View File

@ -241,6 +241,8 @@ namespace cryptonote
bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& 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
{
@ -287,6 +289,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<epee::wipeable_string> 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;

View File

@ -222,6 +222,7 @@ struct options {
};
const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1};
const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""};
const command_line::arg_descriptor<std::string> hw_device_derivation_path = {"hw-device-deriv-path", tools::wallet2::tr("HW device wallet derivation path (e.g., SLIP-10)"), ""};
const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" };
};
@ -274,6 +275,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
auto daemon_host = command_line::get_arg(vm, opts.daemon_host);
auto daemon_port = command_line::get_arg(vm, opts.daemon_port);
auto device_name = command_line::get_arg(vm, opts.hw_device);
auto device_derivation_path = command_line::get_arg(vm, opts.hw_device_derivation_path);
THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port,
tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once"));
@ -329,6 +331,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
wallet->set_ring_database(ringdb_path.string());
wallet->device_name(device_name);
wallet->device_derivation_path(device_derivation_path);
try
{
@ -838,6 +841,24 @@ wallet_keys_unlocker::~wallet_keys_unlocker()
}
}
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),
@ -891,7 +912,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_ringdb(),
m_last_block_reward(0),
m_encrypt_keys_after_refresh(boost::none),
m_unattended(unattended)
m_unattended(unattended),
m_device_last_key_image_sync(0)
{
}
@ -914,6 +936,11 @@ std::string wallet2::device_name_option(const boost::program_options::variables_
return command_line::get_arg(vm, options().hw_device);
}
std::string wallet2::device_derivation_path_option(const boost::program_options::variables_map &vm)
{
return command_line::get_arg(vm, options().hw_device_derivation_path);
}
void wallet2::init_options(boost::program_options::options_description& desc_params)
{
const options opts{};
@ -930,6 +957,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
command_line::add_arg(desc_params, opts.shared_ringdb_dir);
command_line::add_arg(desc_params, opts.kdf_rounds);
command_line::add_arg(desc_params, opts.hw_device);
command_line::add_arg(desc_params, opts.hw_device_derivation_path);
command_line::add_arg(desc_params, opts.tx_notify);
}
@ -949,7 +977,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file(
return {nullptr, password_container{}};
}
auto wallet = make_basic(vm, unattended, opts, password_prompter);
if (wallet)
if (wallet && !wallet_file.empty())
{
wallet->load(wallet_file, pwd->password());
}
@ -1091,15 +1119,17 @@ bool wallet2::reconnect_device()
hw::device &hwdev = lookup_device(m_device_name);
hwdev.set_name(m_device_name);
hwdev.set_network_type(m_nettype);
hwdev.set_derivation_path(m_device_derivation_path);
hwdev.set_callback(get_device_callback());
r = hwdev.init();
if (!r){
LOG_PRINT_L2("Could not init device");
MERROR("Could not init device");
return false;
}
r = hwdev.connect();
if (!r){
LOG_PRINT_L2("Could not connect to the device");
MERROR("Could not connect to the device");
return false;
}
@ -2998,6 +3028,7 @@ bool wallet2::clear()
m_subaddresses.clear();
m_subaddress_labels.clear();
m_multisig_rounds_passed = 0;
m_device_last_key_image_sync = 0;
return true;
}
@ -3159,6 +3190,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
value.SetString(m_device_name.c_str(), m_device_name.size());
json.AddMember("device_name", value, json.GetAllocator());
value.SetString(m_device_derivation_path.c_str(), m_device_derivation_path.size());
json.AddMember("device_derivation_path", value, json.GetAllocator());
// Serialize the JSON object
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
@ -3278,6 +3312,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
m_device_name = "";
m_device_derivation_path = "";
m_key_device_type = hw::device::device_type::SOFTWARE;
encrypted_secret_keys = false;
}
@ -3445,6 +3480,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default";
}
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_derivation_path, std::string, String, false, std::string());
m_device_derivation_path = field_device_derivation_path;
}
else
{
@ -3459,6 +3497,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
hw::device &hwdev = lookup_device(m_device_name);
THROW_WALLET_EXCEPTION_IF(!hwdev.set_name(m_device_name), error::wallet_internal_error, "Could not set device name " + m_device_name);
hwdev.set_network_type(m_nettype);
hwdev.set_derivation_path(m_device_derivation_path);
hwdev.set_callback(get_device_callback());
THROW_WALLET_EXCEPTION_IF(!hwdev.init(), error::wallet_internal_error, "Could not initialize the device " + m_device_name);
THROW_WALLET_EXCEPTION_IF(!hwdev.connect(), error::wallet_internal_error, "Could not connect to the device " + m_device_name);
m_account.set_device(hwdev);
@ -3965,6 +4005,8 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
auto &hwdev = lookup_device(device_name);
hwdev.set_name(device_name);
hwdev.set_network_type(m_nettype);
hwdev.set_derivation_path(m_device_derivation_path);
hwdev.set_callback(get_device_callback());
m_account.create_from_device(hwdev);
m_key_device_type = m_account.get_device().get_type();
@ -9238,9 +9280,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
auto & hwdev = get_account().get_device();
if (!hwdev.has_ki_cold_sync()){
throw std::invalid_argument("Device does not support cold ki sync protocol");
}
CHECK_AND_ASSERT_THROW_MES(hwdev.has_ki_cold_sync(), "Device does not support cold ki sync protocol");
auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
@ -9251,7 +9291,11 @@ uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
dev_cold->ki_sync(&wallet_shim, m_transfers, ski);
return import_key_images(ski, 0, spent, unspent);
// Call COMMAND_RPC_IS_KEY_IMAGE_SPENT only if daemon is trusted.
uint64_t import_res = import_key_images(ski, 0, spent, unspent, is_trusted_daemon());
m_device_last_key_image_sync = time(NULL);
return import_res;
}
//----------------------------------------------------------------------------------------------------
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const
@ -11985,4 +12029,29 @@ uint64_t wallet2::get_segregation_fork_height() const
void wallet2::generate_genesis(cryptonote::block& b) const {
cryptonote::generate_genesis_block(b, get_config(m_nettype).GENESIS_TX, get_config(m_nettype).GENESIS_NONCE);
}
//----------------------------------------------------------------------------------------------------
wallet_device_callback * wallet2::get_device_callback()
{
if (!m_device_callback){
m_device_callback.reset(new wallet_device_callback(this));
}
return m_device_callback.get();
}//----------------------------------------------------------------------------------------------------
void wallet2::on_button_request()
{
if (0 != m_callback)
m_callback->on_button_request();
}
//----------------------------------------------------------------------------------------------------
void wallet2::on_pin_request(epee::wipeable_string & pin)
{
if (0 != m_callback)
m_callback->on_pin_request(pin);
}
//----------------------------------------------------------------------------------------------------
void wallet2::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
{
if (0 != m_callback)
m_callback->on_passphrase_request(on_device, passphrase);
}
}

View File

@ -100,11 +100,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;
@ -156,6 +171,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);
@ -177,6 +193,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.
@ -795,6 +812,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 <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
@ -903,6 +921,9 @@ namespace tools
if(ver < 26)
return;
a & m_tx_device;
if(ver < 27)
return;
a & m_device_last_key_image_sync;
}
/*!
@ -964,6 +985,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<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);
@ -1287,6 +1310,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<epee::net_utils::http::login> m_daemon_login;
std::string m_daemon_address;
@ -1365,6 +1393,8 @@ namespace tools
std::unordered_set<crypto::hash> 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;
uint64_t m_device_last_key_image_sync;
// Aux transaction data from device
std::unordered_map<crypto::hash, std::string> m_tx_device;
@ -1398,9 +1428,10 @@ namespace tools
bool m_devices_registered;
std::shared_ptr<tools::Notify> m_tx_notify;
std::unique_ptr<wallet_device_callback> 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)

View File

@ -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",