2018-06-21 14:28:34 +00:00
|
|
|
# This file is part of the Trezor project.
|
2016-11-25 21:53:55 +00:00
|
|
|
#
|
2018-06-21 14:28:34 +00:00
|
|
|
# Copyright (C) 2012-2018 SatoshiLabs and contributors
|
2016-11-25 21:53:55 +00:00
|
|
|
#
|
|
|
|
# This library is free software: you can redistribute it and/or modify
|
2018-06-21 14:28:34 +00:00
|
|
|
# it under the terms of the GNU Lesser General Public License version 3
|
|
|
|
# as published by the Free Software Foundation.
|
2016-11-25 21:53:55 +00:00
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Lesser General Public License for more details.
|
|
|
|
#
|
2018-06-21 14:28:34 +00:00
|
|
|
# You should have received a copy of the License along with this library.
|
|
|
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
2016-11-25 21:53:55 +00:00
|
|
|
|
2018-05-11 13:24:24 +00:00
|
|
|
import functools
|
|
|
|
import logging
|
2014-06-12 11:26:24 +00:00
|
|
|
import sys
|
2017-12-29 18:18:04 +00:00
|
|
|
import warnings
|
2013-09-13 03:31:24 +00:00
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
from . import (
|
|
|
|
btc,
|
|
|
|
cosi,
|
|
|
|
device,
|
|
|
|
ethereum,
|
2018-09-13 16:47:19 +00:00
|
|
|
exceptions,
|
2018-08-13 16:21:24 +00:00
|
|
|
firmware,
|
|
|
|
lisk,
|
|
|
|
mapping,
|
|
|
|
messages as proto,
|
|
|
|
misc,
|
|
|
|
nem,
|
|
|
|
stellar,
|
|
|
|
tools,
|
|
|
|
)
|
2017-06-23 19:31:42 +00:00
|
|
|
|
2017-12-29 18:18:04 +00:00
|
|
|
if sys.version_info.major < 3:
|
2018-02-27 15:30:32 +00:00
|
|
|
raise Exception("Trezorlib does not support Python 2 anymore.")
|
2017-12-29 18:18:04 +00:00
|
|
|
|
2014-11-07 00:58:20 +00:00
|
|
|
|
|
|
|
SCREENSHOT = False
|
2018-05-11 13:24:24 +00:00
|
|
|
LOG = logging.getLogger(__name__)
|
2016-06-29 20:40:37 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
PinException = exceptions.PinException
|
2016-06-29 20:40:37 +00:00
|
|
|
|
2017-06-23 19:31:42 +00:00
|
|
|
|
2014-02-13 15:46:21 +00:00
|
|
|
def get_buttonrequest_value(code):
|
|
|
|
# Converts integer code to its string representation of ButtonRequestType
|
2018-08-13 16:21:24 +00:00
|
|
|
return [
|
|
|
|
k
|
|
|
|
for k in dir(proto.ButtonRequestType)
|
|
|
|
if getattr(proto.ButtonRequestType, k) == code
|
|
|
|
][0]
|
2017-06-23 19:31:42 +00:00
|
|
|
|
2014-02-02 17:27:44 +00:00
|
|
|
|
2018-05-21 12:28:53 +00:00
|
|
|
class MovedTo:
|
|
|
|
"""Deprecation redirector for methods that were formerly part of TrezorClient"""
|
2018-08-13 16:21:24 +00:00
|
|
|
|
2018-05-21 12:28:53 +00:00
|
|
|
def __init__(self, where):
|
|
|
|
self.where = where
|
2018-08-13 16:21:24 +00:00
|
|
|
self.name = where.__module__ + "." + where.__name__
|
2013-09-13 03:31:24 +00:00
|
|
|
|
2018-05-21 12:28:53 +00:00
|
|
|
def _deprecated_redirect(self, client, *args, **kwargs):
|
|
|
|
"""Redirector for a deprecated method on TrezorClient"""
|
2018-08-13 16:21:24 +00:00
|
|
|
warnings.warn(
|
|
|
|
"Function has been moved to %s" % self.name,
|
|
|
|
DeprecationWarning,
|
|
|
|
stacklevel=2,
|
|
|
|
)
|
2018-05-21 12:28:53 +00:00
|
|
|
return self.where(client, *args, **kwargs)
|
2017-06-23 19:31:42 +00:00
|
|
|
|
2018-05-21 12:28:53 +00:00
|
|
|
def __get__(self, instance, cls):
|
|
|
|
if instance is None:
|
|
|
|
return self._deprecated_redirect
|
|
|
|
else:
|
|
|
|
return functools.partial(self._deprecated_redirect, instance)
|
2014-02-21 01:30:10 +00:00
|
|
|
|
2017-06-23 19:31:42 +00:00
|
|
|
|
2014-02-13 15:46:21 +00:00
|
|
|
class BaseClient(object):
|
|
|
|
# Implements very basic layer of sending raw protobuf
|
|
|
|
# messages to device and getting its response back.
|
2018-09-13 16:47:19 +00:00
|
|
|
def __init__(self, transport, ui, **kwargs):
|
2018-05-24 17:14:05 +00:00
|
|
|
LOG.info("creating client instance for device: {}".format(transport.get_path()))
|
2017-09-04 11:36:08 +00:00
|
|
|
self.transport = transport
|
2018-09-13 16:47:19 +00:00
|
|
|
self.ui = ui
|
2014-02-13 16:20:40 +00:00
|
|
|
super(BaseClient, self).__init__() # *args, **kwargs)
|
2013-12-30 22:35:20 +00:00
|
|
|
|
2017-09-04 09:30:34 +00:00
|
|
|
def close(self):
|
|
|
|
pass
|
|
|
|
|
2016-02-10 15:46:58 +00:00
|
|
|
def cancel(self):
|
2018-10-02 15:18:13 +00:00
|
|
|
self._raw_write(proto.Cancel())
|
2016-02-10 15:46:58 +00:00
|
|
|
|
2018-05-21 12:28:53 +00:00
|
|
|
@tools.session
|
2014-02-15 19:30:39 +00:00
|
|
|
def call_raw(self, msg):
|
2018-10-02 15:18:13 +00:00
|
|
|
__tracebackhide__ = True # for pytest # pylint: disable=W0612
|
|
|
|
self._raw_write(msg)
|
|
|
|
return self._raw_read()
|
|
|
|
|
|
|
|
def _raw_write(self, msg):
|
2018-08-13 16:21:24 +00:00
|
|
|
__tracebackhide__ = True # for pytest # pylint: disable=W0612
|
2017-09-04 11:36:08 +00:00
|
|
|
self.transport.write(msg)
|
2018-10-02 15:18:13 +00:00
|
|
|
|
|
|
|
def _raw_read(self):
|
|
|
|
__tracebackhide__ = True # for pytest # pylint: disable=W0612
|
2017-09-04 11:36:08 +00:00
|
|
|
return self.transport.read()
|
2014-02-15 19:30:39 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
def callback_PinMatrixRequest(self, msg):
|
|
|
|
pin = self.ui.get_pin(msg.type)
|
|
|
|
if not pin.isdigit():
|
|
|
|
raise ValueError("Non-numeric PIN provided")
|
2014-02-13 15:46:21 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
resp = self.call_raw(proto.PinMatrixAck(pin=pin))
|
|
|
|
if isinstance(resp, proto.Failure) and resp.code in (
|
2018-08-13 16:21:24 +00:00
|
|
|
proto.FailureType.PinInvalid,
|
|
|
|
proto.FailureType.PinCancelled,
|
|
|
|
proto.FailureType.PinExpected,
|
|
|
|
):
|
2018-10-02 15:18:13 +00:00
|
|
|
raise exceptions.PinException(resp.code, resp.message)
|
2014-03-28 15:26:48 +00:00
|
|
|
else:
|
2018-09-13 16:47:19 +00:00
|
|
|
return resp
|
2014-02-13 15:46:21 +00:00
|
|
|
|
|
|
|
def callback_PassphraseRequest(self, msg):
|
2018-09-13 16:47:19 +00:00
|
|
|
if msg.on_device:
|
|
|
|
passphrase = None
|
2014-11-07 00:09:53 +00:00
|
|
|
else:
|
2018-09-13 16:47:19 +00:00
|
|
|
passphrase = self.ui.get_passphrase()
|
2014-02-13 15:46:21 +00:00
|
|
|
|
2018-10-26 12:09:32 +00:00
|
|
|
state_request = self.call_raw(proto.PassphraseAck(passphrase=passphrase))
|
|
|
|
if not isinstance(state_request, proto.PassphraseStateRequest):
|
|
|
|
raise exceptions.TrezorException("Passphrase state missing")
|
|
|
|
self.state = state_request.state
|
2018-09-13 16:47:19 +00:00
|
|
|
return self.call_raw(proto.PassphraseStateAck())
|
2016-01-12 23:17:38 +00:00
|
|
|
|
2014-02-15 19:30:39 +00:00
|
|
|
def callback_ButtonRequest(self, msg):
|
2018-10-02 15:18:13 +00:00
|
|
|
__tracebackhide__ = True # for pytest # pylint: disable=W0612
|
2018-09-13 16:47:19 +00:00
|
|
|
# do this raw - send ButtonAck first, notify UI later
|
2018-10-02 15:18:13 +00:00
|
|
|
self._raw_write(proto.ButtonAck())
|
2018-09-13 16:47:19 +00:00
|
|
|
self.ui.button_request(msg.code)
|
2018-10-02 15:18:13 +00:00
|
|
|
return self._raw_read()
|
2014-02-13 15:46:21 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
@tools.session
|
|
|
|
def call(self, msg):
|
|
|
|
resp = self.call_raw(msg)
|
|
|
|
while True:
|
|
|
|
handler_name = "callback_{}".format(resp.__class__.__name__)
|
|
|
|
handler = getattr(self, handler_name, None)
|
|
|
|
if handler is None:
|
|
|
|
break
|
|
|
|
resp = handler(resp) # pylint: disable=E1102
|
2014-01-13 03:44:57 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
if isinstance(resp, proto.Failure):
|
|
|
|
if resp.code == proto.FailureType.ActionCancelled:
|
|
|
|
raise exceptions.Cancelled
|
2018-10-12 13:49:17 +00:00
|
|
|
raise exceptions.TrezorFailure(resp)
|
2018-02-28 22:12:55 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
return resp
|
2014-02-21 20:00:56 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
def register_message(self, msg):
|
|
|
|
"""Allow application to register custom protobuf message type"""
|
|
|
|
mapping.register_message(msg)
|
2014-02-13 15:46:21 +00:00
|
|
|
|
2017-06-23 19:31:42 +00:00
|
|
|
|
2014-02-13 15:46:21 +00:00
|
|
|
class ProtocolMixin(object):
|
2018-08-13 16:21:24 +00:00
|
|
|
VENDORS = ("bitcointrezor.com", "trezor.io")
|
2014-02-13 15:46:21 +00:00
|
|
|
|
2018-03-27 12:52:32 +00:00
|
|
|
def __init__(self, state=None, *args, **kwargs):
|
2014-02-13 16:20:40 +00:00
|
|
|
super(ProtocolMixin, self).__init__(*args, **kwargs)
|
2018-03-27 12:52:32 +00:00
|
|
|
self.state = state
|
2013-09-13 03:31:24 +00:00
|
|
|
self.init_device()
|
2014-03-28 18:47:53 +00:00
|
|
|
self.tx_api = None
|
2014-02-13 15:46:21 +00:00
|
|
|
|
2014-03-28 18:47:53 +00:00
|
|
|
def set_tx_api(self, tx_api):
|
|
|
|
self.tx_api = tx_api
|
2014-02-13 15:46:21 +00:00
|
|
|
|
|
|
|
def init_device(self):
|
2018-09-13 16:47:19 +00:00
|
|
|
resp = self.call(proto.Initialize(state=self.state))
|
|
|
|
if not isinstance(resp, proto.Features):
|
|
|
|
raise exceptions.TrezorException("Unexpected initial response")
|
|
|
|
else:
|
|
|
|
self.features = resp
|
2015-01-28 04:31:30 +00:00
|
|
|
if str(self.features.vendor) not in self.VENDORS:
|
2017-11-06 10:09:54 +00:00
|
|
|
raise RuntimeError("Unsupported device")
|
2014-01-27 10:25:27 +00:00
|
|
|
|
2016-02-10 14:53:14 +00:00
|
|
|
@staticmethod
|
|
|
|
def expand_path(n):
|
2018-08-13 16:21:24 +00:00
|
|
|
warnings.warn(
|
|
|
|
"expand_path is deprecated, use tools.parse_path",
|
|
|
|
DeprecationWarning,
|
|
|
|
stacklevel=2,
|
|
|
|
)
|
2018-04-18 13:00:59 +00:00
|
|
|
return tools.parse_path(n)
|
2014-01-09 16:34:29 +00:00
|
|
|
|
2018-06-25 15:51:09 +00:00
|
|
|
@tools.expect(proto.Success, field="message")
|
2018-08-13 16:21:24 +00:00
|
|
|
def ping(
|
|
|
|
self,
|
|
|
|
msg,
|
|
|
|
button_protection=False,
|
|
|
|
pin_protection=False,
|
|
|
|
passphrase_protection=False,
|
|
|
|
):
|
|
|
|
msg = proto.Ping(
|
|
|
|
message=msg,
|
|
|
|
button_protection=button_protection,
|
|
|
|
pin_protection=pin_protection,
|
|
|
|
passphrase_protection=passphrase_protection,
|
|
|
|
)
|
2014-02-13 15:46:21 +00:00
|
|
|
return self.call(msg)
|
2013-09-13 03:31:24 +00:00
|
|
|
|
2013-10-08 18:33:39 +00:00
|
|
|
def get_device_id(self):
|
|
|
|
return self.features.device_id
|
2013-09-13 03:31:24 +00:00
|
|
|
|
2018-03-20 12:10:08 +00:00
|
|
|
def _prepare_sign_tx(self, inputs, outputs):
|
2017-12-12 15:40:11 +00:00
|
|
|
tx = proto.TransactionType()
|
2018-03-20 12:10:08 +00:00
|
|
|
tx.inputs = inputs
|
|
|
|
tx.outputs = outputs
|
2014-04-09 19:42:30 +00:00
|
|
|
|
2018-03-20 12:29:33 +00:00
|
|
|
txes = {None: tx}
|
2014-04-09 19:42:30 +00:00
|
|
|
|
|
|
|
for inp in inputs:
|
2018-03-20 12:10:08 +00:00
|
|
|
if inp.prev_hash in txes:
|
2014-04-09 19:42:30 +00:00
|
|
|
continue
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
if inp.script_type in (
|
|
|
|
proto.InputScriptType.SPENDP2SHWITNESS,
|
|
|
|
proto.InputScriptType.SPENDWITNESS,
|
|
|
|
):
|
2018-03-20 12:10:08 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
if not self.tx_api:
|
2018-08-13 16:21:24 +00:00
|
|
|
raise RuntimeError("TX_API not defined")
|
2018-03-20 12:10:08 +00:00
|
|
|
|
2018-09-12 22:44:08 +00:00
|
|
|
prev_tx = self.tx_api.get_tx(inp.prev_hash.hex())
|
2018-03-20 12:10:08 +00:00
|
|
|
txes[inp.prev_hash] = prev_tx
|
2014-04-09 19:42:30 +00:00
|
|
|
|
|
|
|
return txes
|
2014-02-13 15:46:21 +00:00
|
|
|
|
2018-06-25 15:51:09 +00:00
|
|
|
@tools.expect(proto.Success, field="message")
|
2018-06-13 17:04:18 +00:00
|
|
|
def clear_session(self):
|
|
|
|
return self.call(proto.ClearSession())
|
2018-04-20 16:33:56 +00:00
|
|
|
|
2018-06-13 17:04:18 +00:00
|
|
|
# Device functionality
|
2018-08-10 11:32:50 +00:00
|
|
|
wipe_device = MovedTo(device.wipe)
|
|
|
|
recovery_device = MovedTo(device.recover)
|
|
|
|
reset_device = MovedTo(device.reset)
|
|
|
|
backup_device = MovedTo(device.backup)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
set_u2f_counter = MovedTo(device.set_u2f_counter)
|
|
|
|
|
|
|
|
apply_settings = MovedTo(device.apply_settings)
|
|
|
|
apply_flags = MovedTo(device.apply_flags)
|
|
|
|
change_pin = MovedTo(device.change_pin)
|
|
|
|
|
|
|
|
# Firmware functionality
|
|
|
|
firmware_update = MovedTo(firmware.update)
|
|
|
|
|
|
|
|
# BTC-like functionality
|
|
|
|
get_public_node = MovedTo(btc.get_public_node)
|
|
|
|
get_address = MovedTo(btc.get_address)
|
|
|
|
sign_tx = MovedTo(btc.sign_tx)
|
|
|
|
sign_message = MovedTo(btc.sign_message)
|
|
|
|
verify_message = MovedTo(btc.verify_message)
|
|
|
|
|
|
|
|
# CoSi functionality
|
|
|
|
cosi_commit = MovedTo(cosi.commit)
|
|
|
|
cosi_sign = MovedTo(cosi.sign)
|
|
|
|
|
|
|
|
# Ethereum functionality
|
|
|
|
ethereum_get_address = MovedTo(ethereum.get_address)
|
|
|
|
ethereum_sign_tx = MovedTo(ethereum.sign_tx)
|
|
|
|
ethereum_sign_message = MovedTo(ethereum.sign_message)
|
|
|
|
ethereum_verify_message = MovedTo(ethereum.verify_message)
|
|
|
|
|
|
|
|
# Lisk functionality
|
|
|
|
lisk_get_address = MovedTo(lisk.get_address)
|
|
|
|
lisk_get_public_key = MovedTo(lisk.get_public_key)
|
|
|
|
lisk_sign_message = MovedTo(lisk.sign_message)
|
|
|
|
lisk_verify_message = MovedTo(lisk.verify_message)
|
|
|
|
lisk_sign_tx = MovedTo(lisk.sign_tx)
|
|
|
|
|
|
|
|
# NEM functionality
|
|
|
|
nem_get_address = MovedTo(nem.get_address)
|
|
|
|
nem_sign_tx = MovedTo(nem.sign_tx)
|
|
|
|
|
|
|
|
# Stellar functionality
|
|
|
|
stellar_get_address = MovedTo(stellar.get_address)
|
|
|
|
stellar_sign_transaction = MovedTo(stellar.sign_tx)
|
|
|
|
|
2018-06-13 17:35:01 +00:00
|
|
|
# Miscellaneous cryptographic functionality
|
2018-06-13 17:04:18 +00:00
|
|
|
get_entropy = MovedTo(misc.get_entropy)
|
|
|
|
sign_identity = MovedTo(misc.sign_identity)
|
|
|
|
get_ecdh_session_key = MovedTo(misc.get_ecdh_session_key)
|
|
|
|
encrypt_keyvalue = MovedTo(misc.encrypt_keyvalue)
|
|
|
|
decrypt_keyvalue = MovedTo(misc.decrypt_keyvalue)
|
2018-04-04 01:50:22 +00:00
|
|
|
|
2017-06-23 19:31:42 +00:00
|
|
|
|
2018-09-13 16:47:19 +00:00
|
|
|
class TrezorClient(ProtocolMixin, BaseClient):
|
2018-03-28 13:57:50 +00:00
|
|
|
def __init__(self, transport, *args, **kwargs):
|
|
|
|
super().__init__(transport=transport, *args, **kwargs)
|