mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-30 17:01:34 +00:00
trezor: update to trezor 0.11.0
This commit is contained in:
parent
5411ad9633
commit
c33c907330
5 changed files with 339 additions and 474 deletions
|
@ -1,11 +0,0 @@
|
||||||
from trezorlib.client import proto, BaseClient, ProtocolMixin
|
|
||||||
from .clientbase import TrezorClientBase
|
|
||||||
|
|
||||||
class TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient):
|
|
||||||
def __init__(self, transport, handler, plugin):
|
|
||||||
BaseClient.__init__(self, transport=transport)
|
|
||||||
ProtocolMixin.__init__(self, transport=transport)
|
|
||||||
TrezorClientBase.__init__(self, handler, plugin, proto)
|
|
||||||
|
|
||||||
|
|
||||||
TrezorClientBase.wrap_methods(TrezorClient)
|
|
|
@ -4,118 +4,76 @@ from struct import pack
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import PrintError, UserCancelled
|
from electrum.util import PrintError, UserCancelled
|
||||||
from electrum.keystore import bip39_normalize_passphrase
|
from electrum.keystore import bip39_normalize_passphrase
|
||||||
from electrum.bip32 import serialize_xpub, convert_bip32_path_to_list_of_uint32
|
from electrum.bip32 import serialize_xpub, convert_bip32_path_to_list_of_uint32 as parse_path
|
||||||
|
|
||||||
|
from trezorlib.client import TrezorClient
|
||||||
|
from trezorlib.exceptions import TrezorFailure, Cancelled
|
||||||
|
from trezorlib.messages import WordRequestType, FailureType, RecoveryDeviceType
|
||||||
|
import trezorlib.btc
|
||||||
|
import trezorlib.device
|
||||||
|
|
||||||
class GuiMixin(object):
|
MESSAGES = {
|
||||||
# Requires: self.proto, self.device
|
|
||||||
|
|
||||||
# ref: https://github.com/trezor/trezor-common/blob/44dfb07cfaafffada4b2ce0d15ba1d90d17cf35e/protob/types.proto#L89
|
|
||||||
messages = {
|
|
||||||
3: _("Confirm the transaction output on your {} device"),
|
3: _("Confirm the transaction output on your {} device"),
|
||||||
4: _("Confirm internal entropy on your {} device to begin"),
|
4: _("Confirm internal entropy on your {} device to begin"),
|
||||||
5: _("Write down the seed word shown on your {}"),
|
5: _("Write down the seed word shown on your {}"),
|
||||||
6: _("Confirm on your {} that you want to wipe it clean"),
|
6: _("Confirm on your {} that you want to wipe it clean"),
|
||||||
7: _("Confirm on your {} device the message to sign"),
|
7: _("Confirm on your {} device the message to sign"),
|
||||||
8: _("Confirm the total amount spent and the transaction fee on your "
|
8: _("Confirm the total amount spent and the transaction fee on your {} device"),
|
||||||
"{} device"),
|
|
||||||
10: _("Confirm wallet address on your {} device"),
|
10: _("Confirm wallet address on your {} device"),
|
||||||
14: _("Choose on your {} device where to enter your passphrase"),
|
14: _("Choose on your {} device where to enter your passphrase"),
|
||||||
'default': _("Check your {} device to continue"),
|
'default': _("Check your {} device to continue"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def callback_Failure(self, msg):
|
|
||||||
# BaseClient's unfortunate call() implementation forces us to
|
|
||||||
# raise exceptions on failure in order to unwind the stack.
|
|
||||||
# However, making the user acknowledge they cancelled
|
|
||||||
# gets old very quickly, so we suppress those. The NotInitialized
|
|
||||||
# one is misnamed and indicates a passphrase request was cancelled.
|
|
||||||
if msg.code in (self.types.FailureType.PinCancelled,
|
|
||||||
self.types.FailureType.ActionCancelled,
|
|
||||||
self.types.FailureType.NotInitialized):
|
|
||||||
raise UserCancelled()
|
|
||||||
raise RuntimeError(msg.message)
|
|
||||||
|
|
||||||
def callback_ButtonRequest(self, msg):
|
class TrezorClientBase(PrintError):
|
||||||
message = self.msg
|
def __init__(self, transport, handler, plugin):
|
||||||
if not message:
|
self.client = TrezorClient(transport, ui=self)
|
||||||
message = self.messages.get(msg.code, self.messages['default'])
|
self.plugin = plugin
|
||||||
self.handler.show_message(message.format(self.device), self.cancel)
|
|
||||||
return self.proto.ButtonAck()
|
|
||||||
|
|
||||||
def callback_PinMatrixRequest(self, msg):
|
|
||||||
if msg.type == 2:
|
|
||||||
msg = _("Enter a new PIN for your {}:")
|
|
||||||
elif msg.type == 3:
|
|
||||||
msg = (_("Re-enter the new PIN for your {}.\n\n"
|
|
||||||
"NOTE: the positions of the numbers have changed!"))
|
|
||||||
else:
|
|
||||||
msg = _("Enter your current {} PIN:")
|
|
||||||
pin = self.handler.get_pin(msg.format(self.device))
|
|
||||||
if len(pin) > 9:
|
|
||||||
self.handler.show_error(_('The PIN cannot be longer than 9 characters.'))
|
|
||||||
pin = '' # to cancel below
|
|
||||||
if not pin:
|
|
||||||
return self.proto.Cancel()
|
|
||||||
return self.proto.PinMatrixAck(pin=pin)
|
|
||||||
|
|
||||||
def callback_PassphraseRequest(self, req):
|
|
||||||
if req and hasattr(req, 'on_device') and req.on_device is True:
|
|
||||||
return self.proto.PassphraseAck()
|
|
||||||
|
|
||||||
if self.creating_wallet:
|
|
||||||
msg = _("Enter a passphrase to generate this wallet. Each time "
|
|
||||||
"you use this wallet your {} will prompt you for the "
|
|
||||||
"passphrase. If you forget the passphrase you cannot "
|
|
||||||
"access the bitcoins in the wallet.").format(self.device)
|
|
||||||
else:
|
|
||||||
msg = _("Enter the passphrase to unlock this wallet:")
|
|
||||||
passphrase = self.handler.get_passphrase(msg, self.creating_wallet)
|
|
||||||
if passphrase is None:
|
|
||||||
return self.proto.Cancel()
|
|
||||||
passphrase = bip39_normalize_passphrase(passphrase)
|
|
||||||
|
|
||||||
ack = self.proto.PassphraseAck(passphrase=passphrase)
|
|
||||||
length = len(ack.passphrase)
|
|
||||||
if length > 50:
|
|
||||||
self.handler.show_error(_("Too long passphrase ({} > 50 chars).").format(length))
|
|
||||||
return self.proto.Cancel()
|
|
||||||
return ack
|
|
||||||
|
|
||||||
def callback_PassphraseStateRequest(self, msg):
|
|
||||||
return self.proto.PassphraseStateAck()
|
|
||||||
|
|
||||||
def callback_WordRequest(self, msg):
|
|
||||||
if (msg.type is not None
|
|
||||||
and msg.type in (self.types.WordRequestType.Matrix9,
|
|
||||||
self.types.WordRequestType.Matrix6)):
|
|
||||||
num = 9 if msg.type == self.types.WordRequestType.Matrix9 else 6
|
|
||||||
char = self.handler.get_matrix(num)
|
|
||||||
if char == 'x':
|
|
||||||
return self.proto.Cancel()
|
|
||||||
return self.proto.WordAck(word=char)
|
|
||||||
|
|
||||||
self.step += 1
|
|
||||||
msg = _("Step {}/24. Enter seed word as explained on "
|
|
||||||
"your {}:").format(self.step, self.device)
|
|
||||||
word = self.handler.get_word(msg)
|
|
||||||
# Unfortunately the device can't handle self.proto.Cancel()
|
|
||||||
return self.proto.WordAck(word=word)
|
|
||||||
|
|
||||||
|
|
||||||
class TrezorClientBase(GuiMixin, PrintError):
|
|
||||||
|
|
||||||
def __init__(self, handler, plugin, proto):
|
|
||||||
assert hasattr(self, 'tx_api') # ProtocolMixin already constructed?
|
|
||||||
self.proto = proto
|
|
||||||
self.device = plugin.device
|
self.device = plugin.device
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
self.tx_api = plugin
|
|
||||||
self.types = plugin.types
|
|
||||||
self.msg = None
|
self.msg = None
|
||||||
self.creating_wallet = False
|
self.creating_wallet = False
|
||||||
|
|
||||||
|
self.in_flow = False
|
||||||
|
|
||||||
self.used()
|
self.used()
|
||||||
|
|
||||||
|
def run_flow(self, message=None, creating_wallet=False):
|
||||||
|
if self.in_flow:
|
||||||
|
raise RuntimeError("Overlapping call to run_flow")
|
||||||
|
|
||||||
|
self.in_flow = True
|
||||||
|
self.msg = message
|
||||||
|
self.creating_wallet = creating_wallet
|
||||||
|
self.prevent_timeouts()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def end_flow(self):
|
||||||
|
self.in_flow = False
|
||||||
|
self.msg = None
|
||||||
|
self.creating_wallet = False
|
||||||
|
self.handler.finished()
|
||||||
|
self.used()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.end_flow()
|
||||||
|
if exc_value is not None:
|
||||||
|
if issubclass(exc_type, Cancelled):
|
||||||
|
raise UserCancelled from exc_value
|
||||||
|
elif issubclass(exc_type, TrezorFailure):
|
||||||
|
raise RuntimeError(exc_value.message) from exc_value
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def features(self):
|
||||||
|
return self.client.features
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s/%s" % (self.label(), self.features.device_id)
|
return "%s/%s" % (self.label(), self.features.device_id)
|
||||||
|
|
||||||
|
@ -131,8 +89,11 @@ class TrezorClientBase(GuiMixin, PrintError):
|
||||||
return not self.features.bootloader_mode
|
return not self.features.bootloader_mode
|
||||||
|
|
||||||
def has_usable_connection_with_device(self):
|
def has_usable_connection_with_device(self):
|
||||||
|
if self.in_flow:
|
||||||
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = self.ping("electrum pinging device")
|
res = self.client.ping("electrum pinging device")
|
||||||
assert res == "electrum pinging device"
|
assert res == "electrum pinging device"
|
||||||
except BaseException:
|
except BaseException:
|
||||||
return False
|
return False
|
||||||
|
@ -150,47 +111,41 @@ class TrezorClientBase(GuiMixin, PrintError):
|
||||||
self.print_error("timed out")
|
self.print_error("timed out")
|
||||||
self.clear_session()
|
self.clear_session()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def expand_path(n):
|
|
||||||
return convert_bip32_path_to_list_of_uint32(n)
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
'''Provided here as in keepkeylib but not trezorlib.'''
|
|
||||||
self.transport.write(self.proto.Cancel())
|
|
||||||
|
|
||||||
def i4b(self, x):
|
def i4b(self, x):
|
||||||
return pack('>I', x)
|
return pack('>I', x)
|
||||||
|
|
||||||
def get_xpub(self, bip32_path, xtype):
|
def get_xpub(self, bip32_path, xtype, creating=False):
|
||||||
address_n = self.expand_path(bip32_path)
|
address_n = parse_path(bip32_path)
|
||||||
creating = False
|
with self.run_flow(creating_wallet=creating):
|
||||||
node = self.get_public_node(address_n, creating).node
|
node = trezorlib.btc.get_public_node(self.client, address_n).node
|
||||||
return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
|
return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
|
||||||
|
|
||||||
def toggle_passphrase(self):
|
def toggle_passphrase(self):
|
||||||
if self.features.passphrase_protection:
|
if self.features.passphrase_protection:
|
||||||
self.msg = _("Confirm on your {} device to disable passphrases")
|
msg = _("Confirm on your {} device to disable passphrases")
|
||||||
else:
|
else:
|
||||||
self.msg = _("Confirm on your {} device to enable passphrases")
|
msg = _("Confirm on your {} device to enable passphrases")
|
||||||
enabled = not self.features.passphrase_protection
|
enabled = not self.features.passphrase_protection
|
||||||
self.apply_settings(use_passphrase=enabled)
|
with self.run_flow(msg):
|
||||||
|
trezorlib.device.apply_settings(self.client, use_passphrase=enabled)
|
||||||
|
|
||||||
def change_label(self, label):
|
def change_label(self, label):
|
||||||
self.msg = _("Confirm the new label on your {} device")
|
with self.run_flow(_("Confirm the new label on your {} device")):
|
||||||
self.apply_settings(label=label)
|
trezorlib.device.apply_settings(self.client, label=label)
|
||||||
|
|
||||||
def change_homescreen(self, homescreen):
|
def change_homescreen(self, homescreen):
|
||||||
self.msg = _("Confirm on your {} device to change your home screen")
|
with self.run_flow(_("Confirm on your {} device to change your home screen")):
|
||||||
self.apply_settings(homescreen=homescreen)
|
trezorlib.device.apply_settings(self.client, homescreen=homescreen)
|
||||||
|
|
||||||
def set_pin(self, remove):
|
def set_pin(self, remove):
|
||||||
if remove:
|
if remove:
|
||||||
self.msg = _("Confirm on your {} device to disable PIN protection")
|
msg = _("Confirm on your {} device to disable PIN protection")
|
||||||
elif self.features.pin_protection:
|
elif self.features.pin_protection:
|
||||||
self.msg = _("Confirm on your {} device to change your PIN")
|
msg = _("Confirm on your {} device to change your PIN")
|
||||||
else:
|
else:
|
||||||
self.msg = _("Confirm on your {} device to set a PIN")
|
msg = _("Confirm on your {} device to set a PIN")
|
||||||
self.change_pin(remove)
|
with self.run_flow(msg):
|
||||||
|
trezorlib.device.change_pin(remove)
|
||||||
|
|
||||||
def clear_session(self):
|
def clear_session(self):
|
||||||
'''Clear the session to force pin (and passphrase if enabled)
|
'''Clear the session to force pin (and passphrase if enabled)
|
||||||
|
@ -198,21 +153,15 @@ class TrezorClientBase(GuiMixin, PrintError):
|
||||||
self.print_error("clear session:", self)
|
self.print_error("clear session:", self)
|
||||||
self.prevent_timeouts()
|
self.prevent_timeouts()
|
||||||
try:
|
try:
|
||||||
super(TrezorClientBase, self).clear_session()
|
self.client.clear_session()
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
# If the device was removed it has the same effect...
|
# If the device was removed it has the same effect...
|
||||||
self.print_error("clear_session: ignoring error", str(e))
|
self.print_error("clear_session: ignoring error", str(e))
|
||||||
|
|
||||||
def get_public_node(self, address_n, creating):
|
|
||||||
self.creating_wallet = creating
|
|
||||||
return super(TrezorClientBase, self).get_public_node(address_n)
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
'''Called when Our wallet was closed or the device removed.'''
|
'''Called when Our wallet was closed or the device removed.'''
|
||||||
self.print_error("closing client")
|
self.print_error("closing client")
|
||||||
self.clear_session()
|
self.clear_session()
|
||||||
# Release the device
|
|
||||||
self.transport.close()
|
|
||||||
|
|
||||||
def firmware_version(self):
|
def firmware_version(self):
|
||||||
f = self.features
|
f = self.features
|
||||||
|
@ -225,27 +174,112 @@ class TrezorClientBase(GuiMixin, PrintError):
|
||||||
"""Returns '1' for Trezor One, 'T' for Trezor T."""
|
"""Returns '1' for Trezor One, 'T' for Trezor T."""
|
||||||
return self.features.model
|
return self.features.model
|
||||||
|
|
||||||
@staticmethod
|
def show_address(self, address_str, script_type, multisig=None):
|
||||||
def wrapper(func):
|
coin_name = self.plugin.get_coin_name()
|
||||||
'''Wrap methods to clear any message box they opened.'''
|
address_n = parse_path(address_str)
|
||||||
|
with self.run_flow():
|
||||||
|
return trezorlib.btc.get_address(
|
||||||
|
self.client,
|
||||||
|
coin_name,
|
||||||
|
address_n,
|
||||||
|
show_display=True,
|
||||||
|
script_type=script_type,
|
||||||
|
multisig=multisig)
|
||||||
|
|
||||||
def wrapped(self, *args, **kwargs):
|
def sign_message(self, address_str, message):
|
||||||
try:
|
coin_name = self.plugin.get_coin_name()
|
||||||
self.prevent_timeouts()
|
address_n = parse_path(address_str)
|
||||||
return func(self, *args, **kwargs)
|
with self.run_flow():
|
||||||
finally:
|
return trezorlib.btc.sign_message(
|
||||||
self.used()
|
self.client,
|
||||||
self.handler.finished()
|
coin_name,
|
||||||
self.creating_wallet = False
|
address_n,
|
||||||
self.msg = None
|
message)
|
||||||
|
|
||||||
return wrapped
|
def recover_device(self, recovery_type, *args, **kwargs):
|
||||||
|
input_callback = self.mnemonic_callback(recovery_type)
|
||||||
|
with self.run_flow():
|
||||||
|
return trezorlib.device.recover(
|
||||||
|
self.client,
|
||||||
|
*args,
|
||||||
|
input_callback=input_callback,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
@staticmethod
|
# ========= Unmodified trezorlib methods =========
|
||||||
def wrap_methods(cls):
|
|
||||||
for method in ['apply_settings', 'change_pin',
|
def sign_tx(self, *args, **kwargs):
|
||||||
'get_address', 'get_public_node',
|
with self.run_flow():
|
||||||
'load_device_by_mnemonic', 'load_device_by_xprv',
|
return trezorlib.btc.sign_tx(self.client, *args, **kwargs)
|
||||||
'recovery_device', 'reset_device', 'sign_message',
|
|
||||||
'sign_tx', 'wipe_device']:
|
def reset_device(self, *args, **kwargs):
|
||||||
setattr(cls, method, cls.wrapper(getattr(cls, method)))
|
with self.run_flow():
|
||||||
|
return trezorlib.device.reset(self.client, *args, **kwargs)
|
||||||
|
|
||||||
|
def wipe_device(self, *args, **kwargs):
|
||||||
|
with self.run_flow():
|
||||||
|
return trezorlib.device.wipe(self.client, *args, **kwargs)
|
||||||
|
|
||||||
|
# ========= UI methods ==========
|
||||||
|
|
||||||
|
def button_request(self, code):
|
||||||
|
message = self.msg or MESSAGES.get(code) or MESSAGES['default']
|
||||||
|
self.handler.show_message(message.format(self.device), self.client.cancel)
|
||||||
|
|
||||||
|
def get_pin(self, code=None):
|
||||||
|
if code == 2:
|
||||||
|
msg = _("Enter a new PIN for your {}:")
|
||||||
|
elif code == 3:
|
||||||
|
msg = (_("Re-enter the new PIN for your {}.\n\n"
|
||||||
|
"NOTE: the positions of the numbers have changed!"))
|
||||||
|
else:
|
||||||
|
msg = _("Enter your current {} PIN:")
|
||||||
|
pin = self.handler.get_pin(msg.format(self.device))
|
||||||
|
if not pin:
|
||||||
|
raise Cancelled
|
||||||
|
if len(pin) > 9:
|
||||||
|
self.handler.show_error(_('The PIN cannot be longer than 9 characters.'))
|
||||||
|
raise Cancelled
|
||||||
|
return pin
|
||||||
|
|
||||||
|
def get_passphrase(self):
|
||||||
|
if self.creating_wallet:
|
||||||
|
msg = _("Enter a passphrase to generate this wallet. Each time "
|
||||||
|
"you use this wallet your {} will prompt you for the "
|
||||||
|
"passphrase. If you forget the passphrase you cannot "
|
||||||
|
"access the bitcoins in the wallet.").format(self.device)
|
||||||
|
else:
|
||||||
|
msg = _("Enter the passphrase to unlock this wallet:")
|
||||||
|
passphrase = self.handler.get_passphrase(msg, self.creating_wallet)
|
||||||
|
if passphrase is None:
|
||||||
|
raise Cancelled
|
||||||
|
passphrase = bip39_normalize_passphrase(passphrase)
|
||||||
|
length = len(passphrase)
|
||||||
|
if length > 50:
|
||||||
|
self.handler.show_error(_("Too long passphrase ({} > 50 chars).").format(length))
|
||||||
|
raise Cancelled
|
||||||
|
return passphrase
|
||||||
|
|
||||||
|
def _matrix_char(self, matrix_type):
|
||||||
|
num = 9 if matrix_type == WordRequestType.Matrix9 else 6
|
||||||
|
char = self.handler.get_matrix(num)
|
||||||
|
if char == 'x':
|
||||||
|
raise Cancelled
|
||||||
|
return char
|
||||||
|
|
||||||
|
def mnemonic_callback(self, recovery_type):
|
||||||
|
if recovery_type is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if recovery_type == RecoveryDeviceType.Matrix:
|
||||||
|
return self._matrix_char
|
||||||
|
|
||||||
|
step = 0
|
||||||
|
def word_callback(_ignored):
|
||||||
|
nonlocal step
|
||||||
|
step += 1
|
||||||
|
msg = _("Step {}/24. Enter seed word as explained on your {}:").format(step, self.device)
|
||||||
|
word = self.handler.get_word(msg)
|
||||||
|
if not word:
|
||||||
|
raise Cancelled
|
||||||
|
return word
|
||||||
|
return word_callback
|
||||||
|
|
|
@ -12,7 +12,7 @@ from electrum.util import bh2u
|
||||||
|
|
||||||
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
||||||
from ..hw_wallet.plugin import only_hook_if_libraries_available
|
from ..hw_wallet.plugin import only_hook_if_libraries_available
|
||||||
from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC,
|
from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER,
|
||||||
RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX)
|
RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX)
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,7 +197,6 @@ class QtPlugin(QtPluginBase):
|
||||||
text = widget.toPlainText().strip()
|
text = widget.toPlainText().strip()
|
||||||
return ' '.join(text.split())
|
return ' '.join(text.split())
|
||||||
|
|
||||||
if method in [TIM_NEW, TIM_RECOVER]:
|
|
||||||
gb = QGroupBox()
|
gb = QGroupBox()
|
||||||
hbox1 = QHBoxLayout()
|
hbox1 = QHBoxLayout()
|
||||||
gb.setLayout(hbox1)
|
gb.setLayout(hbox1)
|
||||||
|
@ -213,34 +212,9 @@ class QtPlugin(QtPluginBase):
|
||||||
rb.setChecked(True)
|
rb.setChecked(True)
|
||||||
cb_pin = QCheckBox(_('Enable PIN protection'))
|
cb_pin = QCheckBox(_('Enable PIN protection'))
|
||||||
cb_pin.setChecked(True)
|
cb_pin.setChecked(True)
|
||||||
else:
|
|
||||||
text = QTextEdit()
|
|
||||||
text.setMaximumHeight(60)
|
|
||||||
if method == TIM_MNEMONIC:
|
|
||||||
msg = _("Enter your BIP39 mnemonic:")
|
|
||||||
else:
|
|
||||||
msg = _("Enter the master private key beginning with xprv:")
|
|
||||||
def set_enabled():
|
|
||||||
from electrum.bip32 import is_xprv
|
|
||||||
wizard.next_button.setEnabled(is_xprv(clean_text(text)))
|
|
||||||
text.textChanged.connect(set_enabled)
|
|
||||||
next_enabled = False
|
|
||||||
|
|
||||||
vbox.addWidget(QLabel(msg))
|
|
||||||
vbox.addWidget(text)
|
|
||||||
pin = QLineEdit()
|
|
||||||
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
|
|
||||||
pin.setMaximumWidth(100)
|
|
||||||
hbox_pin = QHBoxLayout()
|
|
||||||
hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
|
|
||||||
hbox_pin.addWidget(pin)
|
|
||||||
hbox_pin.addStretch(1)
|
|
||||||
|
|
||||||
if method in [TIM_NEW, TIM_RECOVER]:
|
|
||||||
vbox.addWidget(WWLabel(RECOMMEND_PIN))
|
vbox.addWidget(WWLabel(RECOMMEND_PIN))
|
||||||
vbox.addWidget(cb_pin)
|
vbox.addWidget(cb_pin)
|
||||||
else:
|
|
||||||
vbox.addLayout(hbox_pin)
|
|
||||||
|
|
||||||
passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
|
passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
|
||||||
passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
|
passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
|
||||||
|
@ -277,14 +251,9 @@ class QtPlugin(QtPluginBase):
|
||||||
|
|
||||||
wizard.exec_layout(vbox, next_enabled=next_enabled)
|
wizard.exec_layout(vbox, next_enabled=next_enabled)
|
||||||
|
|
||||||
if method in [TIM_NEW, TIM_RECOVER]:
|
|
||||||
item = bg_numwords.checkedId()
|
item = bg_numwords.checkedId()
|
||||||
pin = cb_pin.isChecked()
|
pin = cb_pin.isChecked()
|
||||||
recovery_type = bg_rectype.checkedId() if bg_rectype else None
|
recovery_type = bg_rectype.checkedId() if bg_rectype else None
|
||||||
else:
|
|
||||||
item = ' '.join(str(clean_text(text)).split())
|
|
||||||
pin = str(pin.text())
|
|
||||||
recovery_type = None
|
|
||||||
|
|
||||||
return (item, name.text(), pin, cb_phrase.isChecked(), recovery_type)
|
return (item, name.text(), pin, cb_phrase.isChecked(), recovery_type)
|
||||||
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
from electrum.util import PrintError
|
|
||||||
|
|
||||||
|
|
||||||
class TrezorTransport(PrintError):
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def all_transports():
|
|
||||||
"""Reimplemented trezorlib.transport.all_transports so that we can
|
|
||||||
enable/disable specific transports.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# only to detect trezorlib version
|
|
||||||
from trezorlib.transport import all_transports
|
|
||||||
except ImportError:
|
|
||||||
# old trezorlib. compat for trezorlib < 0.9.2
|
|
||||||
transports = []
|
|
||||||
try:
|
|
||||||
from trezorlib.transport_bridge import BridgeTransport
|
|
||||||
transports.append(BridgeTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from trezorlib.transport_hid import HidTransport
|
|
||||||
transports.append(HidTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from trezorlib.transport_udp import UdpTransport
|
|
||||||
transports.append(UdpTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from trezorlib.transport_webusb import WebUsbTransport
|
|
||||||
transports.append(WebUsbTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# new trezorlib.
|
|
||||||
transports = []
|
|
||||||
try:
|
|
||||||
from trezorlib.transport.bridge import BridgeTransport
|
|
||||||
transports.append(BridgeTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from trezorlib.transport.hid import HidTransport
|
|
||||||
transports.append(HidTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from trezorlib.transport.udp import UdpTransport
|
|
||||||
transports.append(UdpTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from trezorlib.transport.webusb import WebUsbTransport
|
|
||||||
transports.append(WebUsbTransport)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
return transports
|
|
||||||
return transports
|
|
||||||
|
|
||||||
def enumerate_devices(self):
|
|
||||||
"""Just like trezorlib.transport.enumerate_devices,
|
|
||||||
but with exception catching, so that transports can fail separately.
|
|
||||||
"""
|
|
||||||
devices = []
|
|
||||||
for transport in self.all_transports():
|
|
||||||
try:
|
|
||||||
new_devices = transport.enumerate()
|
|
||||||
except BaseException as e:
|
|
||||||
self.print_error('enumerate failed for {}. error {}'
|
|
||||||
.format(transport.__name__, str(e)))
|
|
||||||
else:
|
|
||||||
devices.extend(new_devices)
|
|
||||||
return devices
|
|
||||||
|
|
||||||
def get_transport(self, path=None):
|
|
||||||
"""Reimplemented trezorlib.transport.get_transport,
|
|
||||||
(1) for old trezorlib
|
|
||||||
(2) to be able to disable specific transports
|
|
||||||
(3) to call our own enumerate_devices that catches exceptions
|
|
||||||
"""
|
|
||||||
if path is None:
|
|
||||||
try:
|
|
||||||
return self.enumerate_devices()[0]
|
|
||||||
except IndexError:
|
|
||||||
raise Exception("No TREZOR device found") from None
|
|
||||||
|
|
||||||
def match_prefix(a, b):
|
|
||||||
return a.startswith(b) or b.startswith(a)
|
|
||||||
transports = [t for t in self.all_transports() if match_prefix(path, t.PATH_PREFIX)]
|
|
||||||
if transports:
|
|
||||||
return transports[0].find_by_path(path)
|
|
||||||
raise Exception("Unknown path prefix '%s'" % path)
|
|
|
@ -1,10 +1,9 @@
|
||||||
from binascii import hexlify, unhexlify
|
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
|
from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
|
||||||
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
||||||
from electrum.bip32 import deserialize_xpub
|
from electrum.bip32 import deserialize_xpub, convert_bip32_path_to_list_of_uint32 as parse_path
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import Device
|
from electrum.plugin import Device
|
||||||
|
@ -15,10 +14,31 @@ from electrum.base_wizard import ScriptTypeNotSupported
|
||||||
from ..hw_wallet import HW_PluginBase
|
from ..hw_wallet import HW_PluginBase
|
||||||
from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data
|
from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data
|
||||||
|
|
||||||
|
try:
|
||||||
|
import trezorlib
|
||||||
|
import trezorlib.transport
|
||||||
|
|
||||||
|
from .clientbase import TrezorClientBase
|
||||||
|
|
||||||
|
from trezorlib.messages import (
|
||||||
|
RecoveryDeviceType, HDNodeType, HDNodePathType,
|
||||||
|
InputScriptType, OutputScriptType, MultisigRedeemScriptType,
|
||||||
|
TxInputType, TxOutputType, TxOutputBinType, TransactionType, SignTx)
|
||||||
|
|
||||||
|
RECOVERY_TYPE_SCRAMBLED_WORDS = RecoveryDeviceType.ScrambledWords
|
||||||
|
RECOVERY_TYPE_MATRIX = RecoveryDeviceType.Matrix
|
||||||
|
|
||||||
|
TREZORLIB = True
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
TREZORLIB = False
|
||||||
|
|
||||||
|
RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX = range(2)
|
||||||
|
|
||||||
|
|
||||||
# TREZOR initialization methods
|
# TREZOR initialization methods
|
||||||
TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)
|
TIM_NEW, TIM_RECOVER = range(2)
|
||||||
RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX = range(0, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class TrezorKeyStore(Hardware_KeyStore):
|
class TrezorKeyStore(Hardware_KeyStore):
|
||||||
|
@ -37,8 +57,7 @@ class TrezorKeyStore(Hardware_KeyStore):
|
||||||
def sign_message(self, sequence, message, password):
|
def sign_message(self, sequence, message, password):
|
||||||
client = self.get_client()
|
client = self.get_client()
|
||||||
address_path = self.get_derivation() + "/%d/%d"%sequence
|
address_path = self.get_derivation() + "/%d/%d"%sequence
|
||||||
address_n = client.expand_path(address_path)
|
msg_sig = client.sign_message(address_path, message)
|
||||||
msg_sig = client.sign_message(self.plugin.get_coin_name(), address_n, message)
|
|
||||||
return msg_sig.signature
|
return msg_sig.signature
|
||||||
|
|
||||||
def sign_transaction(self, tx, password):
|
def sign_transaction(self, tx, password):
|
||||||
|
@ -75,37 +94,31 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
libraries_URL = 'https://github.com/trezor/python-trezor'
|
libraries_URL = 'https://github.com/trezor/python-trezor'
|
||||||
minimum_firmware = (1, 5, 2)
|
minimum_firmware = (1, 5, 2)
|
||||||
keystore_class = TrezorKeyStore
|
keystore_class = TrezorKeyStore
|
||||||
minimum_library = (0, 9, 0)
|
minimum_library = (0, 11, 0)
|
||||||
|
maximum_library = (0, 12)
|
||||||
SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
|
SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
|
||||||
|
DEVICE_IDS = ('TREZOR',)
|
||||||
|
|
||||||
MAX_LABEL_LEN = 32
|
MAX_LABEL_LEN = 32
|
||||||
|
|
||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
HW_PluginBase.__init__(self, parent, config, name)
|
super().__init__(parent, config, name)
|
||||||
|
|
||||||
self.libraries_available = self.check_libraries_available()
|
self.libraries_available = self.check_libraries_available()
|
||||||
if not self.libraries_available:
|
if not self.libraries_available:
|
||||||
return
|
return
|
||||||
|
|
||||||
from . import client
|
|
||||||
from . import transport
|
|
||||||
import trezorlib.messages
|
|
||||||
self.client_class = client.TrezorClient
|
|
||||||
self.types = trezorlib.messages
|
|
||||||
self.DEVICE_IDS = ('TREZOR',)
|
|
||||||
|
|
||||||
self.transport_handler = transport.TrezorTransport()
|
|
||||||
self.device_manager().register_enumerate_func(self.enumerate)
|
self.device_manager().register_enumerate_func(self.enumerate)
|
||||||
|
|
||||||
def get_library_version(self):
|
def get_library_version(self):
|
||||||
import trezorlib
|
if not TREZORLIB:
|
||||||
|
raise ImportError
|
||||||
try:
|
try:
|
||||||
return trezorlib.__version__
|
return trezorlib.__version__
|
||||||
except AttributeError:
|
except Exception:
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
def enumerate(self):
|
def enumerate(self):
|
||||||
devices = self.transport_handler.enumerate_devices()
|
devices = trezorlib.transport.enumerate_devices()
|
||||||
return [Device(path=d.get_path(),
|
return [Device(path=d.get_path(),
|
||||||
interface_number=-1,
|
interface_number=-1,
|
||||||
id_=d.get_path(),
|
id_=d.get_path(),
|
||||||
|
@ -117,7 +130,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
def create_client(self, device, handler):
|
def create_client(self, device, handler):
|
||||||
try:
|
try:
|
||||||
self.print_error("connecting to device at", device.path)
|
self.print_error("connecting to device at", device.path)
|
||||||
transport = self.transport_handler.get_transport(device.path)
|
transport = trezorlib.transport.get_transport(device.path)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.print_error("cannot connect at", device.path, str(e))
|
self.print_error("cannot connect at", device.path, str(e))
|
||||||
return None
|
return None
|
||||||
|
@ -128,14 +141,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
|
|
||||||
self.print_error("connected to device at", device.path)
|
self.print_error("connected to device at", device.path)
|
||||||
# note that this call can still raise!
|
# note that this call can still raise!
|
||||||
client = self.client_class(transport, handler, self)
|
client = TrezorClientBase(transport, handler, self)
|
||||||
|
|
||||||
# Try a ping for device sanity
|
|
||||||
try:
|
|
||||||
client.ping('t')
|
|
||||||
except BaseException as e:
|
|
||||||
self.print_error("ping failed", str(e))
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not client.atleast_version(*self.minimum_firmware):
|
if not client.atleast_version(*self.minimum_firmware):
|
||||||
msg = (_('Outdated {} firmware for device labelled {}. Please '
|
msg = (_('Outdated {} firmware for device labelled {}. Please '
|
||||||
|
@ -177,8 +183,6 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
# Must be short as QT doesn't word-wrap radio button text
|
# Must be short as QT doesn't word-wrap radio button text
|
||||||
(TIM_NEW, _("Let the device generate a completely new seed randomly")),
|
(TIM_NEW, _("Let the device generate a completely new seed randomly")),
|
||||||
(TIM_RECOVER, _("Recover from a seed you have previously written down")),
|
(TIM_RECOVER, _("Recover from a seed you have previously written down")),
|
||||||
(TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")),
|
|
||||||
(TIM_PRIVKEY, _("Upload a master private key"))
|
|
||||||
]
|
]
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
client = devmgr.client_by_id(device_id)
|
client = devmgr.client_by_id(device_id)
|
||||||
|
@ -222,49 +226,37 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
"the words carefully!"),
|
"the words carefully!"),
|
||||||
blocking=True)
|
blocking=True)
|
||||||
|
|
||||||
language = 'english'
|
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
client = devmgr.client_by_id(device_id)
|
client = devmgr.client_by_id(device_id)
|
||||||
|
|
||||||
if method == TIM_NEW:
|
if method == TIM_NEW:
|
||||||
strength = 64 * (item + 2) # 128, 192 or 256
|
client.reset_device(
|
||||||
u2f_counter = 0
|
strength=64 * (item + 2), # 128, 192 or 256
|
||||||
skip_backup = False
|
passphrase_protection=passphrase_protection,
|
||||||
client.reset_device(True, strength, passphrase_protection,
|
pin_protection=pin_protection,
|
||||||
pin_protection, label, language,
|
label=label)
|
||||||
u2f_counter, skip_backup)
|
|
||||||
elif method == TIM_RECOVER:
|
elif method == TIM_RECOVER:
|
||||||
word_count = 6 * (item + 2) # 12, 18 or 24
|
client.recover_device(
|
||||||
client.step = 0
|
recovery_type=recovery_type,
|
||||||
if recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS:
|
word_count=6 * (item + 2), # 12, 18 or 24
|
||||||
recovery_type_trezor = self.types.RecoveryDeviceType.ScrambledWords
|
passphrase_protection=passphrase_protection,
|
||||||
else:
|
pin_protection=pin_protection,
|
||||||
recovery_type_trezor = self.types.RecoveryDeviceType.Matrix
|
label=label)
|
||||||
client.recovery_device(word_count, passphrase_protection,
|
|
||||||
pin_protection, label, language,
|
|
||||||
type=recovery_type_trezor)
|
|
||||||
if recovery_type == RECOVERY_TYPE_MATRIX:
|
if recovery_type == RECOVERY_TYPE_MATRIX:
|
||||||
handler.close_matrix_dialog()
|
handler.close_matrix_dialog()
|
||||||
elif method == TIM_MNEMONIC:
|
|
||||||
pin = pin_protection # It's the pin, not a boolean
|
|
||||||
client.load_device_by_mnemonic(str(item), pin,
|
|
||||||
passphrase_protection,
|
|
||||||
label, language)
|
|
||||||
else:
|
else:
|
||||||
pin = pin_protection # It's the pin, not a boolean
|
raise RuntimeError("Unsupported recovery method")
|
||||||
client.load_device_by_xprv(item, pin, passphrase_protection,
|
|
||||||
label, language)
|
|
||||||
|
|
||||||
def _make_node_path(self, xpub, address_n):
|
def _make_node_path(self, xpub, address_n):
|
||||||
_, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub)
|
_, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub)
|
||||||
node = self.types.HDNodeType(
|
node = HDNodeType(
|
||||||
depth=depth,
|
depth=depth,
|
||||||
fingerprint=int.from_bytes(fingerprint, 'big'),
|
fingerprint=int.from_bytes(fingerprint, 'big'),
|
||||||
child_num=int.from_bytes(child_num, 'big'),
|
child_num=int.from_bytes(child_num, 'big'),
|
||||||
chain_code=chain_code,
|
chain_code=chain_code,
|
||||||
public_key=key,
|
public_key=key,
|
||||||
)
|
)
|
||||||
return self.types.HDNodePathType(node=node, address_n=address_n)
|
return HDNodePathType(node=node, address_n=address_n)
|
||||||
|
|
||||||
def setup_device(self, device_info, wizard, purpose):
|
def setup_device(self, device_info, wizard, purpose):
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
|
@ -275,9 +267,10 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
_('Make sure it is in the correct state.'))
|
_('Make sure it is in the correct state.'))
|
||||||
# fixme: we should use: client.handler = wizard
|
# fixme: we should use: client.handler = wizard
|
||||||
client.handler = self.create_handler(wizard)
|
client.handler = self.create_handler(wizard)
|
||||||
if not device_info.initialized:
|
creating = not device_info.initialized
|
||||||
|
if creating:
|
||||||
self.initialize_device(device_id, wizard, client.handler)
|
self.initialize_device(device_id, wizard, client.handler)
|
||||||
client.get_xpub('m', 'standard')
|
client.get_xpub('m', 'standard', creating)
|
||||||
client.used()
|
client.used()
|
||||||
|
|
||||||
def get_xpub(self, device_id, derivation, xtype, wizard):
|
def get_xpub(self, device_id, derivation, xtype, wizard):
|
||||||
|
@ -292,33 +285,33 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
|
|
||||||
def get_trezor_input_script_type(self, electrum_txin_type: str):
|
def get_trezor_input_script_type(self, electrum_txin_type: str):
|
||||||
if electrum_txin_type in ('p2wpkh', 'p2wsh'):
|
if electrum_txin_type in ('p2wpkh', 'p2wsh'):
|
||||||
return self.types.InputScriptType.SPENDWITNESS
|
return InputScriptType.SPENDWITNESS
|
||||||
if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
|
if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
|
||||||
return self.types.InputScriptType.SPENDP2SHWITNESS
|
return InputScriptType.SPENDP2SHWITNESS
|
||||||
if electrum_txin_type in ('p2pkh', ):
|
if electrum_txin_type in ('p2pkh', ):
|
||||||
return self.types.InputScriptType.SPENDADDRESS
|
return InputScriptType.SPENDADDRESS
|
||||||
if electrum_txin_type in ('p2sh', ):
|
if electrum_txin_type in ('p2sh', ):
|
||||||
return self.types.InputScriptType.SPENDMULTISIG
|
return InputScriptType.SPENDMULTISIG
|
||||||
raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
|
raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
|
||||||
|
|
||||||
def get_trezor_output_script_type(self, electrum_txin_type: str):
|
def get_trezor_output_script_type(self, electrum_txin_type: str):
|
||||||
if electrum_txin_type in ('p2wpkh', 'p2wsh'):
|
if electrum_txin_type in ('p2wpkh', 'p2wsh'):
|
||||||
return self.types.OutputScriptType.PAYTOWITNESS
|
return OutputScriptType.PAYTOWITNESS
|
||||||
if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
|
if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
|
||||||
return self.types.OutputScriptType.PAYTOP2SHWITNESS
|
return OutputScriptType.PAYTOP2SHWITNESS
|
||||||
if electrum_txin_type in ('p2pkh', ):
|
if electrum_txin_type in ('p2pkh', ):
|
||||||
return self.types.OutputScriptType.PAYTOADDRESS
|
return OutputScriptType.PAYTOADDRESS
|
||||||
if electrum_txin_type in ('p2sh', ):
|
if electrum_txin_type in ('p2sh', ):
|
||||||
return self.types.OutputScriptType.PAYTOMULTISIG
|
return OutputScriptType.PAYTOMULTISIG
|
||||||
raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
|
raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
|
||||||
|
|
||||||
def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
|
def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
|
||||||
self.prev_tx = prev_tx
|
prev_tx = { txhash: self.electrum_tx_to_txtype(tx, xpub_path) for txhash, tx in prev_tx.items() }
|
||||||
self.xpub_path = xpub_path
|
|
||||||
client = self.get_client(keystore)
|
client = self.get_client(keystore)
|
||||||
inputs = self.tx_inputs(tx, True)
|
inputs = self.tx_inputs(tx, xpub_path, True)
|
||||||
outputs = self.tx_outputs(keystore.get_derivation(), tx)
|
outputs = self.tx_outputs(keystore.get_derivation(), tx)
|
||||||
signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0]
|
details = SignTx(lock_time=tx.locktime)
|
||||||
|
signatures, _ = client.sign_tx(self.get_coin_name(), inputs, outputs, details=details, prev_txes=prev_tx)
|
||||||
signatures = [(bh2u(x) + '01') for x in signatures]
|
signatures = [(bh2u(x) + '01') for x in signatures]
|
||||||
tx.update_signatures(signatures)
|
tx.update_signatures(signatures)
|
||||||
|
|
||||||
|
@ -327,74 +320,50 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
keystore = wallet.get_keystore()
|
keystore = wallet.get_keystore()
|
||||||
if not self.show_address_helper(wallet, address, keystore):
|
if not self.show_address_helper(wallet, address, keystore):
|
||||||
return
|
return
|
||||||
client = self.get_client(keystore)
|
deriv_suffix = wallet.get_address_index(address)
|
||||||
if not client.atleast_version(1, 3):
|
|
||||||
keystore.handler.show_error(_("Your device firmware is too old"))
|
|
||||||
return
|
|
||||||
change, index = wallet.get_address_index(address)
|
|
||||||
derivation = keystore.derivation
|
derivation = keystore.derivation
|
||||||
address_path = "%s/%d/%d"%(derivation, change, index)
|
address_path = "%s/%d/%d"%(derivation, *deriv_suffix)
|
||||||
address_n = client.expand_path(address_path)
|
|
||||||
xpubs = wallet.get_master_public_keys()
|
|
||||||
if len(xpubs) == 1:
|
|
||||||
script_type = self.get_trezor_input_script_type(wallet.txin_type)
|
script_type = self.get_trezor_input_script_type(wallet.txin_type)
|
||||||
client.get_address(self.get_coin_name(), address_n, True, script_type=script_type)
|
|
||||||
else:
|
# prepare multisig, if available:
|
||||||
def f(xpub):
|
xpubs = wallet.get_master_public_keys()
|
||||||
return self._make_node_path(xpub, [change, index])
|
if len(xpubs) > 1:
|
||||||
pubkeys = wallet.get_public_keys(address)
|
pubkeys = wallet.get_public_keys(address)
|
||||||
# sort xpubs using the order of pubkeys
|
# sort xpubs using the order of pubkeys
|
||||||
sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs)))
|
sorted_pairs = sorted(zip(pubkeys, xpubs))
|
||||||
pubkeys = list(map(f, sorted_xpubs))
|
multisig = self._make_multisig(
|
||||||
multisig = self.types.MultisigRedeemScriptType(
|
wallet.m,
|
||||||
pubkeys=pubkeys,
|
[(xpub, deriv_suffix) for _, xpub in sorted_pairs])
|
||||||
signatures=[b''] * wallet.n,
|
else:
|
||||||
m=wallet.m,
|
multisig = None
|
||||||
)
|
|
||||||
script_type = self.get_trezor_input_script_type(wallet.txin_type)
|
|
||||||
client.get_address(self.get_coin_name(), address_n, True, multisig=multisig, script_type=script_type)
|
|
||||||
|
|
||||||
def tx_inputs(self, tx, for_sig=False):
|
client = self.get_client(keystore)
|
||||||
|
client.show_address(address_path, script_type, multisig)
|
||||||
|
|
||||||
|
def tx_inputs(self, tx, xpub_path, for_sig=False):
|
||||||
inputs = []
|
inputs = []
|
||||||
for txin in tx.inputs():
|
for txin in tx.inputs():
|
||||||
txinputtype = self.types.TxInputType()
|
txinputtype = TxInputType()
|
||||||
if txin['type'] == 'coinbase':
|
if txin['type'] == 'coinbase':
|
||||||
prev_hash = b"\x00"*32
|
prev_hash = b"\x00"*32
|
||||||
prev_index = 0xffffffff # signed int -1
|
prev_index = 0xffffffff # signed int -1
|
||||||
else:
|
else:
|
||||||
if for_sig:
|
if for_sig:
|
||||||
x_pubkeys = txin['x_pubkeys']
|
x_pubkeys = txin['x_pubkeys']
|
||||||
if len(x_pubkeys) == 1:
|
xpubs = [parse_xpubkey(x) for x in x_pubkeys]
|
||||||
x_pubkey = x_pubkeys[0]
|
multisig = self._make_multisig(txin.get('num_sig'), xpubs, txin.get('signatures'))
|
||||||
xpub, s = parse_xpubkey(x_pubkey)
|
|
||||||
xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
|
|
||||||
txinputtype._extend_address_n(xpub_n + s)
|
|
||||||
txinputtype.script_type = self.get_trezor_input_script_type(txin['type'])
|
|
||||||
else:
|
|
||||||
def f(x_pubkey):
|
|
||||||
xpub, s = parse_xpubkey(x_pubkey)
|
|
||||||
return self._make_node_path(xpub, s)
|
|
||||||
pubkeys = list(map(f, x_pubkeys))
|
|
||||||
multisig = self.types.MultisigRedeemScriptType(
|
|
||||||
pubkeys=pubkeys,
|
|
||||||
signatures=list(map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures'))),
|
|
||||||
m=txin.get('num_sig'),
|
|
||||||
)
|
|
||||||
script_type = self.get_trezor_input_script_type(txin['type'])
|
script_type = self.get_trezor_input_script_type(txin['type'])
|
||||||
txinputtype = self.types.TxInputType(
|
txinputtype = TxInputType(
|
||||||
script_type=script_type,
|
script_type=script_type,
|
||||||
multisig=multisig
|
multisig=multisig)
|
||||||
)
|
|
||||||
# find which key is mine
|
# find which key is mine
|
||||||
for x_pubkey in x_pubkeys:
|
for xpub, deriv in xpubs:
|
||||||
if is_xpubkey(x_pubkey):
|
if xpub in xpub_path:
|
||||||
xpub, s = parse_xpubkey(x_pubkey)
|
xpub_n = parse_path(xpub_path[xpub])
|
||||||
if xpub in self.xpub_path:
|
txinputtype.address_n = xpub_n + deriv
|
||||||
xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
|
|
||||||
txinputtype._extend_address_n(xpub_n + s)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
prev_hash = unhexlify(txin['prevout_hash'])
|
prev_hash = bfh(txin['prevout_hash'])
|
||||||
prev_index = txin['prevout_n']
|
prev_index = txin['prevout_n']
|
||||||
|
|
||||||
if 'value' in txin:
|
if 'value' in txin:
|
||||||
|
@ -412,39 +381,44 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
|
|
||||||
return inputs
|
return inputs
|
||||||
|
|
||||||
|
def _make_multisig(self, m, xpubs, signatures=None):
|
||||||
|
if len(xpubs) == 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs]
|
||||||
|
if signatures is None:
|
||||||
|
signatures = [b''] * len(pubkeys)
|
||||||
|
elif len(signatures) != len(pubkeys):
|
||||||
|
raise RuntimeError('Mismatched number of signatures')
|
||||||
|
else:
|
||||||
|
signatures = [bfh(x)[:-1] if x else b'' for x in signatures]
|
||||||
|
|
||||||
|
return MultisigRedeemScriptType(
|
||||||
|
pubkeys=pubkeys,
|
||||||
|
signatures=signatures,
|
||||||
|
m=m)
|
||||||
|
|
||||||
def tx_outputs(self, derivation, tx):
|
def tx_outputs(self, derivation, tx):
|
||||||
|
|
||||||
def create_output_by_derivation():
|
def create_output_by_derivation():
|
||||||
script_type = self.get_trezor_output_script_type(info.script_type)
|
script_type = self.get_trezor_output_script_type(info.script_type)
|
||||||
if len(xpubs) == 1:
|
deriv = parse_path("/%d/%d" % index)
|
||||||
address_n = self.client_class.expand_path(derivation + "/%d/%d" % index)
|
multisig = self._make_multisig(m, [(xpub, deriv) for xpub in xpubs])
|
||||||
txoutputtype = self.types.TxOutputType(
|
txoutputtype = TxOutputType(
|
||||||
amount=amount,
|
|
||||||
script_type=script_type,
|
|
||||||
address_n=address_n,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
address_n = self.client_class.expand_path("/%d/%d" % index)
|
|
||||||
pubkeys = [self._make_node_path(xpub, address_n) for xpub in xpubs]
|
|
||||||
multisig = self.types.MultisigRedeemScriptType(
|
|
||||||
pubkeys=pubkeys,
|
|
||||||
signatures=[b''] * len(pubkeys),
|
|
||||||
m=m)
|
|
||||||
txoutputtype = self.types.TxOutputType(
|
|
||||||
multisig=multisig,
|
multisig=multisig,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
address_n=self.client_class.expand_path(derivation + "/%d/%d" % index),
|
address_n=parse_path(derivation + "/%d/%d" % index),
|
||||||
script_type=script_type)
|
script_type=script_type)
|
||||||
return txoutputtype
|
return txoutputtype
|
||||||
|
|
||||||
def create_output_by_address():
|
def create_output_by_address():
|
||||||
txoutputtype = self.types.TxOutputType()
|
txoutputtype = TxOutputType()
|
||||||
txoutputtype.amount = amount
|
txoutputtype.amount = amount
|
||||||
if _type == TYPE_SCRIPT:
|
if _type == TYPE_SCRIPT:
|
||||||
txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN
|
txoutputtype.script_type = OutputScriptType.PAYTOOPRETURN
|
||||||
txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
|
txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
|
||||||
elif _type == TYPE_ADDRESS:
|
elif _type == TYPE_ADDRESS:
|
||||||
txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
|
txoutputtype.script_type = OutputScriptType.PAYTOADDRESS
|
||||||
txoutputtype.address = address
|
txoutputtype.address = address
|
||||||
return txoutputtype
|
return txoutputtype
|
||||||
|
|
||||||
|
@ -476,23 +450,17 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
|
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
def electrum_tx_to_txtype(self, tx):
|
def electrum_tx_to_txtype(self, tx, xpub_path):
|
||||||
t = self.types.TransactionType()
|
t = TransactionType()
|
||||||
if tx is None:
|
if tx is None:
|
||||||
# probably for segwit input and we don't need this prev txn
|
# probably for segwit input and we don't need this prev txn
|
||||||
return t
|
return t
|
||||||
d = deserialize(tx.raw)
|
d = deserialize(tx.raw)
|
||||||
t.version = d['version']
|
t.version = d['version']
|
||||||
t.lock_time = d['lockTime']
|
t.lock_time = d['lockTime']
|
||||||
inputs = self.tx_inputs(tx)
|
t.inputs = self.tx_inputs(tx, xpub_path)
|
||||||
t._extend_inputs(inputs)
|
t.bin_outputs = [
|
||||||
for vout in d['outputs']:
|
TxOutputBinType(amount=vout['value'], script_pubkey=bfh(vout['scriptPubKey']))
|
||||||
o = t._add_bin_outputs()
|
for vout in d['outputs']
|
||||||
o.amount = vout['value']
|
]
|
||||||
o.script_pubkey = bfh(vout['scriptPubKey'])
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
# This function is called from the TREZOR libraries (via tx_api)
|
|
||||||
def get_tx(self, tx_hash):
|
|
||||||
tx = self.prev_tx[tx_hash]
|
|
||||||
return self.electrum_tx_to_txtype(tx)
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue