python/trezorctl: implement common client and exception handling (fixes #226)

pull/919/head
matejcik 4 years ago
parent b440ca1ec5
commit f52c087cb6

@ -114,7 +114,7 @@ def verify_message(client, coin_name, address, signature, message):
coin_name=coin_name, coin_name=coin_name,
) )
) )
except exceptions.TrezorFailure as e: except exceptions.TrezorFailure:
return False return False
return isinstance(resp, messages.Success) return isinstance(resp, messages.Success)

@ -14,14 +14,72 @@
# You should have received a copy of the License along with this library. # 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>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import functools
import sys
import click import click
from .. import exceptions
from ..client import TrezorClient
from ..transport import get_transport
from ..ui import ClickUI
class ChoiceType(click.Choice): class ChoiceType(click.Choice):
def __init__(self, typemap): def __init__(self, typemap):
super(ChoiceType, self).__init__(typemap.keys()) super().__init__(typemap.keys())
self.typemap = typemap self.typemap = typemap
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
value = super(ChoiceType, self).convert(value, param, ctx) value = super().convert(value, param, ctx)
return self.typemap[value] return self.typemap[value]
class TrezorConnection:
def __init__(self, path, session_id, passphrase_on_host):
self.path = path
self.session_id = session_id
self.passphrase_on_host = passphrase_on_host
def get_transport(self):
try:
# look for transport without prefix search
return get_transport(self.path, prefix_search=False)
except Exception:
# most likely not found. try again below.
pass
# look for transport with prefix search
# if this fails, we want the exception to bubble up to the caller
return get_transport(self.path, prefix_search=True)
def get_ui(self):
return ClickUI(passphrase_on_host=self.passphrase_on_host)
def get_client(self):
transport = self.get_transport()
ui = self.get_ui()
return TrezorClient(transport, ui=ui, session_id=self.session_id)
def with_client(func):
@click.pass_obj
@functools.wraps(func)
def trezorctl_command_with_client(obj, *args, **kwargs):
try:
client = obj.get_client()
except Exception:
click.echo("Failed to find a Trezor device.")
if obj.path is not None:
click.echo("Using path: {}".format(obj.path))
sys.exit(1)
try:
return func(client, *args, **kwargs)
except exceptions.Cancelled:
click.echo("Action was cancelled.")
sys.exit(1)
except exceptions.TrezorException as e:
raise click.ClickException(str(e)) from e
return trezorctl_command_with_client

@ -19,6 +19,7 @@ import json
import click import click
from .. import binance, tools from .. import binance, tools
from . import with_client
PATH_HELP = "BIP-32 path to key, e.g. m/44'/714'/0'/0/0" PATH_HELP = "BIP-32 path to key, e.g. m/44'/714'/0'/0/0"
@ -31,24 +32,20 @@ def cli():
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, show_display): def get_address(client, address, show_display):
"""Get Binance address for specified path.""" """Get Binance address for specified path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return binance.get_address(client, address_n, show_display) return binance.get_address(client, address_n, show_display)
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_public_key(connect, address, show_display): def get_public_key(client, address, show_display):
"""Get Binance public key.""" """Get Binance public key."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return binance.get_public_key(client, address_n, show_display).hex() return binance.get_public_key(client, address_n, show_display).hex()
@ -61,10 +58,8 @@ def get_public_key(connect, address, show_display):
required=True, required=True,
help="Transaction in JSON format", help="Transaction in JSON format",
) )
@click.pass_obj @with_client
def sign_tx(connect, address, file): def sign_tx(client, address, file):
"""Sign Binance transaction""" """Sign Binance transaction"""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return binance.sign_tx(client, address_n, json.load(file)) return binance.sign_tx(client, address_n, json.load(file))

@ -20,7 +20,7 @@ import json
import click import click
from .. import btc, messages, protobuf, tools from .. import btc, messages, protobuf, tools
from . import ChoiceType from . import ChoiceType, with_client
INPUT_SCRIPTS = { INPUT_SCRIPTS = {
"address": messages.InputScriptType.SPENDADDRESS, "address": messages.InputScriptType.SPENDADDRESS,
@ -52,13 +52,13 @@ def cli():
@click.option("-n", "--address", required=True, help="BIP-32 path") @click.option("-n", "--address", required=True, help="BIP-32 path")
@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address") @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address")
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, coin, address, script_type, show_display): def get_address(client, coin, address, script_type, show_display):
"""Get address for specified path.""" """Get address for specified path."""
coin = coin or DEFAULT_COIN coin = coin or DEFAULT_COIN
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return btc.get_address( return btc.get_address(
connect(), coin, address_n, show_display, script_type=script_type client, coin, address_n, show_display, script_type=script_type
) )
@ -68,13 +68,13 @@ def get_address(connect, coin, address, script_type, show_display):
@click.option("-e", "--curve") @click.option("-e", "--curve")
@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address") @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address")
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_public_node(connect, coin, address, curve, script_type, show_display): def get_public_node(client, coin, address, curve, script_type, show_display):
"""Get public node of given path.""" """Get public node of given path."""
coin = coin or DEFAULT_COIN coin = coin or DEFAULT_COIN
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
result = btc.get_public_node( result = btc.get_public_node(
connect(), client,
address_n, address_n,
ecdsa_curve_name=curve, ecdsa_curve_name=curve,
show_display=show_display, show_display=show_display,
@ -101,8 +101,8 @@ def get_public_node(connect, coin, address, curve, script_type, show_display):
@cli.command() @cli.command()
@click.option("-c", "--coin", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option("-c", "--coin", "_ignore", is_flag=True, hidden=True, expose_value=False)
@click.argument("json_file", type=click.File()) @click.argument("json_file", type=click.File())
@click.pass_obj @with_client
def sign_tx(connect, json_file): def sign_tx(client, json_file):
"""Sign transaction. """Sign transaction.
Transaction data must be provided in a JSON file. See `transaction-format.md` for Transaction data must be provided in a JSON file. See `transaction-format.md` for
@ -111,8 +111,6 @@ def sign_tx(connect, json_file):
$ python3 tools/build_tx.py | trezorctl btc sign-tx - $ python3 tools/build_tx.py | trezorctl btc sign-tx -
""" """
client = connect()
data = json.load(json_file) data = json.load(json_file)
coin = data.get("coin_name", DEFAULT_COIN) coin = data.get("coin_name", DEFAULT_COIN)
details = protobuf.dict_to_proto(messages.SignTx, data.get("details", {})) details = protobuf.dict_to_proto(messages.SignTx, data.get("details", {}))
@ -145,12 +143,12 @@ def sign_tx(connect, json_file):
@click.option("-n", "--address", required=True, help="BIP-32 path") @click.option("-n", "--address", required=True, help="BIP-32 path")
@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address") @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address")
@click.argument("message") @click.argument("message")
@click.pass_obj @with_client
def sign_message(connect, coin, address, message, script_type): def sign_message(client, coin, address, message, script_type):
"""Sign message using address of given path.""" """Sign message using address of given path."""
coin = coin or DEFAULT_COIN coin = coin or DEFAULT_COIN
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
res = btc.sign_message(connect(), coin, address_n, message, script_type) res = btc.sign_message(client, coin, address_n, message, script_type)
return { return {
"message": message, "message": message,
"address": res.address, "address": res.address,
@ -163,12 +161,12 @@ def sign_message(connect, coin, address, message, script_type):
@click.argument("address") @click.argument("address")
@click.argument("signature") @click.argument("signature")
@click.argument("message") @click.argument("message")
@click.pass_obj @with_client
def verify_message(connect, coin, address, signature, message): def verify_message(client, coin, address, signature, message):
"""Verify message.""" """Verify message."""
signature = base64.b64decode(signature) signature = base64.b64decode(signature)
coin = coin or DEFAULT_COIN coin = coin or DEFAULT_COIN
return btc.verify_message(connect(), coin, address, signature, message) return btc.verify_message(client, coin, address, signature, message)
# #

@ -19,6 +19,7 @@ import json
import click import click
from .. import cardano, tools from .. import cardano, tools
from . import with_client
PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0" PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
@ -37,11 +38,9 @@ def cli():
help="Transaction in JSON format", help="Transaction in JSON format",
) )
@click.option("-N", "--network", type=int, default=1) @click.option("-N", "--network", type=int, default=1)
@click.pass_obj @with_client
def sign_tx(connect, file, network): def sign_tx(client, file, network):
"""Sign Cardano transaction.""" """Sign Cardano transaction."""
client = connect()
transaction = json.load(file) transaction = json.load(file)
inputs = [cardano.create_input(input) for input in transaction["inputs"]] inputs = [cardano.create_input(input) for input in transaction["inputs"]]
@ -59,21 +58,17 @@ def sign_tx(connect, file, network):
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, show_display): def get_address(client, address, show_display):
"""Get Cardano address.""" """Get Cardano address."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return cardano.get_address(client, address_n, show_display) return cardano.get_address(client, address_n, show_display)
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.pass_obj @with_client
def get_public_key(connect, address): def get_public_key(client, address):
"""Get Cardano public key.""" """Get Cardano public key."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return cardano.get_public_key(client, address_n) return cardano.get_public_key(client, address_n)

@ -17,6 +17,7 @@
import click import click
from .. import cosi, tools from .. import cosi, tools
from . import with_client
PATH_HELP = "BIP-32 path, e.g. m/44'/0'/0'/0/0" PATH_HELP = "BIP-32 path, e.g. m/44'/0'/0'/0/0"
@ -29,10 +30,9 @@ def cli():
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.argument("data") @click.argument("data")
@click.pass_obj @with_client
def commit(connect, address, data): def commit(client, address, data):
"""Ask device to commit to CoSi signing.""" """Ask device to commit to CoSi signing."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return cosi.commit(client, address_n, bytes.fromhex(data)) return cosi.commit(client, address_n, bytes.fromhex(data))
@ -42,10 +42,9 @@ def commit(connect, address, data):
@click.argument("data") @click.argument("data")
@click.argument("global_commitment") @click.argument("global_commitment")
@click.argument("global_pubkey") @click.argument("global_pubkey")
@click.pass_obj @with_client
def sign(connect, address, data, global_commitment, global_pubkey): def sign(client, address, data, global_commitment, global_pubkey):
"""Ask device to sign using CoSi.""" """Ask device to sign using CoSi."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return cosi.sign( return cosi.sign(
client, client,

@ -17,6 +17,7 @@
import click import click
from .. import misc, tools from .. import misc, tools
from . import with_client
@click.group(name="crypto") @click.group(name="crypto")
@ -26,32 +27,29 @@ def cli():
@cli.command() @cli.command()
@click.argument("size", type=int) @click.argument("size", type=int)
@click.pass_obj @with_client
def get_entropy(connect, size): def get_entropy(client, size):
"""Get random bytes from device.""" """Get random bytes from device."""
return misc.get_entropy(connect(), size).hex() return misc.get_entropy(client, size).hex()
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/10016'/0") @click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/10016'/0")
@click.argument("key") @click.argument("key")
@click.argument("value") @click.argument("value")
@click.pass_obj @with_client
def encrypt_keyvalue(connect, address, key, value): def encrypt_keyvalue(client, address, key, value):
"""Encrypt value by given key and path.""" """Encrypt value by given key and path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
res = misc.encrypt_keyvalue(client, address_n, key, value.encode()) return misc.encrypt_keyvalue(client, address_n, key, value.encode()).hex()
return res.hex()
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/10016'/0") @click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/10016'/0")
@click.argument("key") @click.argument("key")
@click.argument("value") @click.argument("value")
@click.pass_obj @with_client
def decrypt_keyvalue(connect, address, key, value): def decrypt_keyvalue(client, address, key, value):
"""Decrypt value by given key and path.""" """Decrypt value by given key and path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return misc.decrypt_keyvalue(client, address_n, key, bytes.fromhex(value)) return misc.decrypt_keyvalue(client, address_n, key, bytes.fromhex(value))

@ -18,6 +18,7 @@ import click
from .. import debuglink, mapping, messages, protobuf from .. import debuglink, mapping, messages, protobuf
from ..messages import DebugLinkShowTextStyle as S from ..messages import DebugLinkShowTextStyle as S
from . import with_client
@click.group(name="debug") @click.group(name="debug")
@ -40,8 +41,8 @@ STYLES = {
@click.option("-c", "--color", help="Header icon color") @click.option("-c", "--color", help="Header icon color")
@click.option("-h", "--header", help="Header text", default="Showing text") @click.option("-h", "--header", help="Header text", default="Showing text")
@click.argument("body") @click.argument("body")
@click.pass_obj @with_client
def show_text(connect, icon, color, header, body): def show_text(client, icon, color, header, body):
"""Show text on Trezor display. """Show text on Trezor display.
For usage instructions, see: For usage instructions, see:
@ -68,16 +69,14 @@ def show_text(connect, icon, color, header, body):
_flush() _flush()
return debuglink.show_text( return debuglink.show_text(client, header, body_text, icon=icon, icon_color=color)
connect(), header, body_text, icon=icon, icon_color=color
)
@cli.command() @cli.command()
@click.argument("message_name_or_type") @click.argument("message_name_or_type")
@click.argument("hex_data") @click.argument("hex_data")
@click.pass_obj @click.pass_obj
def send_bytes(connect, message_name_or_type, hex_data): def send_bytes(obj, message_name_or_type, hex_data):
"""Send raw bytes to Trezor. """Send raw bytes to Trezor.
Message type and message data must be specified separately, due to how message Message type and message data must be specified separately, due to how message
@ -100,7 +99,7 @@ def send_bytes(connect, message_name_or_type, hex_data):
except Exception as e: except Exception as e:
raise click.ClickException("Invalid hex data.") from e raise click.ClickException("Invalid hex data.") from e
transport = connect(return_transport=True) transport = obj.get_transport()
transport.begin_session() transport.begin_session()
transport.write(message_type, message_data) transport.write(message_type, message_data)

@ -19,7 +19,7 @@ import sys
import click import click
from .. import debuglink, device, exceptions, messages, ui from .. import debuglink, device, exceptions, messages, ui
from . import ChoiceType from . import ChoiceType, with_client
RECOVERY_TYPE = { RECOVERY_TYPE = {
"scrambled": messages.RecoveryDeviceType.ScrambledWords, "scrambled": messages.RecoveryDeviceType.ScrambledWords,
@ -45,10 +45,10 @@ def cli():
@cli.command() @cli.command()
@click.pass_obj @with_client
def self_test(connect): def self_test(client):
"""Perform a self-test.""" """Perform a self-test."""
return debuglink.self_test(connect()) return debuglink.self_test(client)
@cli.command() @cli.command()
@ -58,10 +58,9 @@ def self_test(connect):
help="Wipe device in bootloader mode. This also erases the firmware.", help="Wipe device in bootloader mode. This also erases the firmware.",
is_flag=True, is_flag=True,
) )
@click.pass_obj @with_client
def wipe(connect, bootloader): def wipe(client, bootloader):
"""Reset device to factory defaults and remove all private data.""" """Reset device to factory defaults and remove all private data."""
client = connect()
if bootloader: if bootloader:
if not client.features.bootloader_mode: if not client.features.bootloader_mode:
click.echo("Please switch your device to bootloader mode.") click.echo("Please switch your device to bootloader mode.")
@ -82,7 +81,7 @@ def wipe(connect, bootloader):
click.echo("Wiping user data!") click.echo("Wiping user data!")
try: try:
return device.wipe(connect()) return device.wipe(client)
except exceptions.TrezorFailure as e: except exceptions.TrezorFailure as e:
click.echo("Action failed: {} {}".format(*e.args)) click.echo("Action failed: {} {}".format(*e.args))
sys.exit(3) sys.exit(3)
@ -97,9 +96,9 @@ def wipe(connect, bootloader):
@click.option("-s", "--slip0014", is_flag=True) @click.option("-s", "--slip0014", is_flag=True)
@click.option("-b", "--needs-backup", is_flag=True) @click.option("-b", "--needs-backup", is_flag=True)
@click.option("-n", "--no-backup", is_flag=True) @click.option("-n", "--no-backup", is_flag=True)
@click.pass_obj @with_client
def load( def load(
connect, client,
mnemonic, mnemonic,
pin, pin,
passphrase_protection, passphrase_protection,
@ -116,8 +115,6 @@ def load(
if slip0014 and mnemonic: if slip0014 and mnemonic:
raise click.ClickException("Cannot use -s and -m together.") raise click.ClickException("Cannot use -s and -m together.")
client = connect()
if slip0014: if slip0014:
mnemonic = [" ".join(["all"] * 12)] mnemonic = [" ".join(["all"] * 12)]
if not label: if not label:
@ -147,9 +144,9 @@ def load(
"-t", "--type", "rec_type", type=ChoiceType(RECOVERY_TYPE), default="scrambled" "-t", "--type", "rec_type", type=ChoiceType(RECOVERY_TYPE), default="scrambled"
) )
@click.option("-d", "--dry-run", is_flag=True) @click.option("-d", "--dry-run", is_flag=True)
@click.pass_obj @with_client
def recover( def recover(
connect, client,
words, words,
expand, expand,
pin_protection, pin_protection,
@ -167,7 +164,7 @@ def recover(
click.echo(ui.RECOVERY_MATRIX_DESCRIPTION) click.echo(ui.RECOVERY_MATRIX_DESCRIPTION)
return device.recover( return device.recover(
connect(), client,
word_count=int(words), word_count=int(words),
passphrase_protection=passphrase_protection, passphrase_protection=passphrase_protection,
pin_protection=pin_protection, pin_protection=pin_protection,
@ -190,9 +187,9 @@ def recover(
@click.option("-s", "--skip-backup", is_flag=True) @click.option("-s", "--skip-backup", is_flag=True)
@click.option("-n", "--no-backup", is_flag=True) @click.option("-n", "--no-backup", is_flag=True)
@click.option("-b", "--backup-type", type=ChoiceType(BACKUP_TYPE), default="single") @click.option("-b", "--backup-type", type=ChoiceType(BACKUP_TYPE), default="single")
@click.pass_obj @with_client
def setup( def setup(
connect, client,
show_entropy, show_entropy,
strength, strength,
passphrase_protection, passphrase_protection,
@ -207,7 +204,6 @@ def setup(
if strength: if strength:
strength = int(strength) strength = int(strength)
client = connect()
if ( if (
backup_type == messages.BackupType.Slip39_Basic backup_type == messages.BackupType.Slip39_Basic
and messages.Capability.Shamir not in client.features.capabilities and messages.Capability.Shamir not in client.features.capabilities
@ -236,16 +232,16 @@ def setup(
@cli.command() @cli.command()
@click.pass_obj @with_client
def backup(connect): def backup(client):
"""Perform device seed backup.""" """Perform device seed backup."""
return device.backup(connect()) return device.backup(client)
@cli.command() @cli.command()
@click.argument("operation", type=ChoiceType(SD_PROTECT_OPERATIONS)) @click.argument("operation", type=ChoiceType(SD_PROTECT_OPERATIONS))
@click.pass_obj @with_client
def sd_protect(connect, operation): def sd_protect(client, operation):
"""Secure the device with SD card protection. """Secure the device with SD card protection.
When SD card protection is enabled, a randomly generated secret is stored When SD card protection is enabled, a randomly generated secret is stored
@ -259,7 +255,6 @@ def sd_protect(connect, operation):
disable - Remove SD card secret protection. disable - Remove SD card secret protection.
refresh - Replace the current SD card secret with a new one. refresh - Replace the current SD card secret with a new one.
""" """
client = connect()
if client.features.model == "1": if client.features.model == "1":
raise click.BadUsage("Trezor One does not support SD card protection.") raise click.BadUsage("Trezor One does not support SD card protection.")
return device.sd_protect(client, operation) return device.sd_protect(client, operation)

@ -19,6 +19,7 @@ import json
import click import click
from .. import eos, tools from .. import eos, tools
from . import with_client
PATH_HELP = "BIP-32 path, e.g. m/44'/194'/0'/0/0" PATH_HELP = "BIP-32 path, e.g. m/44'/194'/0'/0/0"
@ -31,10 +32,9 @@ def cli():
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_public_key(connect, address, show_display): def get_public_key(client, address, show_display):
"""Get Eos public key in base58 encoding.""" """Get Eos public key in base58 encoding."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
res = eos.get_public_key(client, address_n, show_display) res = eos.get_public_key(client, address_n, show_display)
return "WIF: {}\nRaw: {}".format(res.wif_public_key, res.raw_public_key.hex()) return "WIF: {}\nRaw: {}".format(res.wif_public_key, res.raw_public_key.hex())
@ -49,11 +49,9 @@ def get_public_key(connect, address, show_display):
required=True, required=True,
help="Transaction in JSON format", help="Transaction in JSON format",
) )
@click.pass_obj @with_client
def sign_transaction(connect, address, file): def sign_transaction(client, address, file):
"""Sign EOS transaction.""" """Sign EOS transaction."""
client = connect()
tx_json = json.load(file) tx_json = json.load(file)
address_n = tools.parse_path(address) address_n = tools.parse_path(address)

@ -21,6 +21,7 @@ from decimal import Decimal
import click import click
from .. import ethereum, tools from .. import ethereum, tools
from . import with_client
try: try:
import rlp import rlp
@ -119,10 +120,9 @@ def cli():
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, show_display): def get_address(client, address, show_display):
"""Get Ethereum address in hex encoding.""" """Get Ethereum address in hex encoding."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return ethereum.get_address(client, address_n, show_display) return ethereum.get_address(client, address_n, show_display)
@ -130,10 +130,9 @@ def get_address(connect, address, show_display):
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_public_node(connect, address, show_display): def get_public_node(client, address, show_display):
"""Get Ethereum public node of given path.""" """Get Ethereum public node of given path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
result = ethereum.get_public_node(client, address_n, show_display=show_display) result = ethereum.get_public_node(client, address_n, show_display=show_display)
return { return {
@ -179,9 +178,9 @@ def get_public_node(connect, address, show_display):
) )
@click.argument("to_address") @click.argument("to_address")
@click.argument("amount", callback=_amount_to_int) @click.argument("amount", callback=_amount_to_int)
@click.pass_obj @with_client
def sign_tx( def sign_tx(
connect, client,
chain_id, chain_id,
address, address,
amount, amount,
@ -234,7 +233,6 @@ def sign_tx(
click.echo("Can't send tokens and custom data at the same time") click.echo("Can't send tokens and custom data at the same time")
sys.exit(1) sys.exit(1)
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
from_address = ethereum.get_address(client, address_n) from_address = ethereum.get_address(client, address_n)
@ -296,10 +294,9 @@ def sign_tx(
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.argument("message") @click.argument("message")
@click.pass_obj @with_client
def sign_message(connect, address, message): def sign_message(client, address, message):
"""Sign message with Ethereum address.""" """Sign message with Ethereum address."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
ret = ethereum.sign_message(client, address_n, message) ret = ethereum.sign_message(client, address_n, message)
output = { output = {
@ -314,8 +311,8 @@ def sign_message(connect, address, message):
@click.argument("address") @click.argument("address")
@click.argument("signature") @click.argument("signature")
@click.argument("message") @click.argument("message")
@click.pass_obj @with_client
def verify_message(connect, address, signature, message): def verify_message(client, address, signature, message):
"""Verify message signed with Ethereum address.""" """Verify message signed with Ethereum address."""
signature = _decode_hex(signature) signature = _decode_hex(signature)
return ethereum.verify_message(connect(), address, signature, message) return ethereum.verify_message(client, address, signature, message)

@ -17,6 +17,7 @@
import click import click
from .. import fido from .. import fido
from . import with_client
ALGORITHM_NAME = {-7: "ES256 (ECDSA w/ SHA-256)", -8: "EdDSA"} ALGORITHM_NAME = {-7: "ES256 (ECDSA w/ SHA-256)", -8: "EdDSA"}
@ -34,11 +35,10 @@ def credentials():
@credentials.command(name="list") @credentials.command(name="list")
@click.pass_obj @with_client
def credentials_list(connect): def credentials_list(client):
"""List all resident credentials on the device.""" """List all resident credentials on the device."""
creds = fido.list_credentials(client)
creds = fido.list_credentials(connect())
for cred in creds: for cred in creds:
click.echo("") click.echo("")
click.echo("WebAuthn credential at index {}:".format(cred.index)) click.echo("WebAuthn credential at index {}:".format(cred.index))
@ -72,23 +72,23 @@ def credentials_list(connect):
@credentials.command(name="add") @credentials.command(name="add")
@click.argument("hex_credential_id") @click.argument("hex_credential_id")
@click.pass_obj @with_client
def credentials_add(connect, hex_credential_id): def credentials_add(client, hex_credential_id):
"""Add the credential with the given ID as a resident credential. """Add the credential with the given ID as a resident credential.
HEX_CREDENTIAL_ID is the credential ID as a hexadecimal string. HEX_CREDENTIAL_ID is the credential ID as a hexadecimal string.
""" """
return fido.add_credential(connect(), bytes.fromhex(hex_credential_id)) return fido.add_credential(client, bytes.fromhex(hex_credential_id))
@credentials.command(name="remove") @credentials.command(name="remove")
@click.option( @click.option(
"-i", "--index", required=True, type=click.IntRange(0, 99), help="Credential index." "-i", "--index", required=True, type=click.IntRange(0, 99), help="Credential index."
) )
@click.pass_obj @with_client
def credentials_remove(connect, index): def credentials_remove(client, index):
"""Remove the resident credential at the given index.""" """Remove the resident credential at the given index."""
return fido.remove_credential(connect(), index) return fido.remove_credential(client, index)
# #
@ -103,19 +103,19 @@ def counter():
@counter.command(name="set") @counter.command(name="set")
@click.argument("counter", type=int) @click.argument("counter", type=int)
@click.pass_obj @with_client
def counter_set(connect, counter): def counter_set(client, counter):
"""Set FIDO/U2F counter value.""" """Set FIDO/U2F counter value."""
return fido.set_counter(connect(), counter) return fido.set_counter(client, counter)
@counter.command(name="get-next") @counter.command(name="get-next")
@click.pass_obj @with_client
def counter_get_next(connect): def counter_get_next(client):
"""Get-and-increase value of FIDO/U2F counter. """Get-and-increase value of FIDO/U2F counter.
FIDO counter value cannot be read directly. On each U2F exchange, the counter value FIDO counter value cannot be read directly. On each U2F exchange, the counter value
is returned and atomically increased. This command performs the same operation is returned and atomically increased. This command performs the same operation
and returns the counter value. and returns the counter value.
""" """
return fido.get_next_counter(connect()) return fido.get_next_counter(client)

@ -20,6 +20,7 @@ import click
import requests import requests
from .. import exceptions, firmware from .. import exceptions, firmware
from . import with_client
ALLOWED_FIRMWARE_FORMATS = { ALLOWED_FIRMWARE_FORMATS = {
1: (firmware.FirmwareFormat.TREZOR_ONE, firmware.FirmwareFormat.TREZOR_ONE_V2), 1: (firmware.FirmwareFormat.TREZOR_ONE, firmware.FirmwareFormat.TREZOR_ONE_V2),
@ -173,9 +174,9 @@ def find_best_firmware_version(
@click.option("--fingerprint", help="Expected firmware fingerprint in hex") @click.option("--fingerprint", help="Expected firmware fingerprint in hex")
@click.option("--skip-vendor-header", help="Skip vendor header validation on Trezor T") @click.option("--skip-vendor-header", help="Skip vendor header validation on Trezor T")
# fmt: on # fmt: on
@click.pass_obj @with_client
def firmware_update( def firmware_update(
connect, client,
filename, filename,
url, url,
version, version,
@ -207,7 +208,6 @@ def firmware_update(
click.echo("You can use only one of: filename, url, version.") click.echo("You can use only one of: filename, url, version.")
sys.exit(1) sys.exit(1)
client = connect()
if not dry_run and not client.features.bootloader_mode: if not dry_run and not client.features.bootloader_mode:
click.echo("Please switch your device to bootloader mode.") click.echo("Please switch your device to bootloader mode.")
sys.exit(1) sys.exit(1)

@ -19,6 +19,7 @@ import json
import click import click
from .. import lisk, tools from .. import lisk, tools
from . import with_client
PATH_HELP = "BIP-32 path, e.g. m/44'/134'/0'/0'" PATH_HELP = "BIP-32 path, e.g. m/44'/134'/0'/0'"
@ -31,10 +32,9 @@ def cli():
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, show_display): def get_address(client, address, show_display):
"""Get Lisk address for specified path.""" """Get Lisk address for specified path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return lisk.get_address(client, address_n, show_display) return lisk.get_address(client, address_n, show_display)
@ -42,10 +42,9 @@ def get_address(connect, address, show_display):
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_public_key(connect, address, show_display): def get_public_key(client, address, show_display):
"""Get Lisk public key for specified path.""" """Get Lisk public key for specified path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
res = lisk.get_public_key(client, address_n, show_display) res = lisk.get_public_key(client, address_n, show_display)
output = {"public_key": res.public_key.hex()} output = {"public_key": res.public_key.hex()}
@ -58,10 +57,9 @@ def get_public_key(connect, address, show_display):
"-f", "--file", type=click.File("r"), default="-", help="Transaction in JSON format" "-f", "--file", type=click.File("r"), default="-", help="Transaction in JSON format"
) )
# @click.option('-b', '--broadcast', help='Broadcast Lisk transaction') # @click.option('-b', '--broadcast', help='Broadcast Lisk transaction')
@click.pass_obj @with_client
def sign_tx(connect, address, file): def sign_tx(client, address, file):
"""Sign Lisk transaction.""" """Sign Lisk transaction."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
transaction = lisk.sign_tx(client, address_n, json.load(file)) transaction = lisk.sign_tx(client, address_n, json.load(file))
@ -73,10 +71,9 @@ def sign_tx(connect, address, file):
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.argument("message") @click.argument("message")
@click.pass_obj @with_client
def sign_message(connect, address, message): def sign_message(client, address, message):
"""Sign message with Lisk address.""" """Sign message with Lisk address."""
client = connect()
address_n = client.expand_path(address) address_n = client.expand_path(address)
res = lisk.sign_message(client, address_n, message) res = lisk.sign_message(client, address_n, message)
output = { output = {
@ -91,9 +88,9 @@ def sign_message(connect, address, message):
@click.argument("pubkey") @click.argument("pubkey")
@click.argument("signature") @click.argument("signature")
@click.argument("message") @click.argument("message")
@click.pass_obj @with_client
def verify_message(connect, pubkey, signature, message): def verify_message(client, pubkey, signature, message):
"""Verify message signed with Lisk address.""" """Verify message signed with Lisk address."""
signature = bytes.fromhex(signature) signature = bytes.fromhex(signature)
pubkey = bytes.fromhex(pubkey) pubkey = bytes.fromhex(pubkey)
return lisk.verify_message(connect(), pubkey, signature, message) return lisk.verify_message(client, pubkey, signature, message)

@ -17,6 +17,7 @@
import click import click
from .. import monero, tools from .. import monero, tools
from . import with_client
PATH_HELP = "BIP-32 path, e.g. m/44'/128'/0'" PATH_HELP = "BIP-32 path, e.g. m/44'/128'/0'"
@ -32,10 +33,9 @@ def cli():
@click.option( @click.option(
"-t", "--network-type", type=click.Choice(["0", "1", "2", "3"]), default="0" "-t", "--network-type", type=click.Choice(["0", "1", "2", "3"]), default="0"
) )
@click.pass_obj @with_client
def get_address(connect, address, show_display, network_type): def get_address(client, address, show_display, network_type):
"""Get Monero address for specified path.""" """Get Monero address for specified path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
network_type = int(network_type) network_type = int(network_type)
return monero.get_address(client, address_n, show_display, network_type) return monero.get_address(client, address_n, show_display, network_type)
@ -46,10 +46,9 @@ def get_address(connect, address, show_display, network_type):
@click.option( @click.option(
"-t", "--network-type", type=click.Choice(["0", "1", "2", "3"]), default="0" "-t", "--network-type", type=click.Choice(["0", "1", "2", "3"]), default="0"
) )
@click.pass_obj @with_client
def get_watch_key(connect, address, network_type): def get_watch_key(client, address, network_type):
"""Get Monero watch key for specified path.""" """Get Monero watch key for specified path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
network_type = int(network_type) network_type = int(network_type)
res = monero.get_watch_key(client, address_n, network_type) res = monero.get_watch_key(client, address_n, network_type)

@ -20,6 +20,7 @@ import click
import requests import requests
from .. import nem, tools from .. import nem, tools
from . import with_client
PATH_HELP = "BIP-32 path, e.g. m/44'/134'/0'/0'" PATH_HELP = "BIP-32 path, e.g. m/44'/134'/0'/0'"
@ -33,10 +34,9 @@ def cli():
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-N", "--network", type=int, default=0x68) @click.option("-N", "--network", type=int, default=0x68)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, network, show_display): def get_address(client, address, network, show_display):
"""Get NEM address for specified path.""" """Get NEM address for specified path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return nem.get_address(client, address_n, network, show_display) return nem.get_address(client, address_n, network, show_display)
@ -51,10 +51,9 @@ def get_address(connect, address, network, show_display):
help="Transaction in NIS (RequestPrepareAnnounce) format", help="Transaction in NIS (RequestPrepareAnnounce) format",
) )
@click.option("-b", "--broadcast", help="NIS to announce transaction to") @click.option("-b", "--broadcast", help="NIS to announce transaction to")
@click.pass_obj @with_client
def sign_tx(connect, address, file, broadcast): def sign_tx(client, address, file, broadcast):
"""Sign (and optionally broadcast) NEM transaction.""" """Sign (and optionally broadcast) NEM transaction."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
transaction = nem.sign_tx(client, address_n, json.load(file)) transaction = nem.sign_tx(client, address_n, json.load(file))

@ -19,6 +19,7 @@ import json
import click import click
from .. import ripple, tools from .. import ripple, tools
from . import with_client
PATH_HELP = "BIP-32 path to key, e.g. m/44'/144'/0'/0/0" PATH_HELP = "BIP-32 path to key, e.g. m/44'/144'/0'/0/0"
@ -31,10 +32,9 @@ def cli():
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, show_display): def get_address(client, address, show_display):
"""Get Ripple address""" """Get Ripple address"""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return ripple.get_address(client, address_n, show_display) return ripple.get_address(client, address_n, show_display)
@ -44,10 +44,9 @@ def get_address(connect, address, show_display):
@click.option( @click.option(
"-f", "--file", type=click.File("r"), default="-", help="Transaction in JSON format" "-f", "--file", type=click.File("r"), default="-", help="Transaction in JSON format"
) )
@click.pass_obj @with_client
def sign_tx(connect, address, file): def sign_tx(client, address, file):
"""Sign Ripple transaction""" """Sign Ripple transaction"""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
msg = ripple.create_sign_tx_msg(json.load(file)) msg = ripple.create_sign_tx_msg(json.load(file))

@ -17,7 +17,7 @@
import click import click
from .. import device from .. import device
from . import ChoiceType from . import ChoiceType, with_client
ROTATION = {"north": 0, "east": 90, "south": 180, "west": 270} ROTATION = {"north": 0, "east": 90, "south": 180, "west": 270}
@ -29,51 +29,51 @@ def cli():
@cli.command() @cli.command()
@click.option("-r", "--remove", is_flag=True) @click.option("-r", "--remove", is_flag=True)
@click.pass_obj @with_client
def pin(connect, remove): def pin(client, remove):
"""Set, change or remove PIN.""" """Set, change or remove PIN."""
return device.change_pin(connect(), remove) return device.change_pin(client, remove)
@cli.command() @cli.command()
@click.option("-r", "--remove", is_flag=True) @click.option("-r", "--remove", is_flag=True)
@click.pass_obj @with_client
def wipe_code(connect, remove): def wipe_code(client, remove):
"""Set or remove the wipe code. """Set or remove the wipe code.
The wipe code functions as a "self-destruct PIN". If the wipe code is ever The wipe code functions as a "self-destruct PIN". If the wipe code is ever
entered into any PIN entry dialog, then all private data will be immediately entered into any PIN entry dialog, then all private data will be immediately
removed and the device will be reset to factory defaults. removed and the device will be reset to factory defaults.
""" """
return device.change_wipe_code(connect(), remove) return device.change_wipe_code(client, remove)
@cli.command() @cli.command()
# keep the deprecated -l/--label option, make it do nothing # keep the deprecated -l/--label option, make it do nothing
@click.option("-l", "--label", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option("-l", "--label", "_ignore", is_flag=True, hidden=True, expose_value=False)
@click.argument("label") @click.argument("label")
@click.pass_obj @with_client
def label(connect, label): def label(client, label):
"""Set new device label.""" """Set new device label."""
return device.apply_settings(connect(), label=label) return device.apply_settings(client, label=label)
@cli.command() @cli.command()
@click.argument("rotation", type=ChoiceType(ROTATION)) @click.argument("rotation", type=ChoiceType(ROTATION))
@click.pass_obj @with_client
def display_rotation(connect, rotation): def display_rotation(client, rotation):
"""Set display rotation. """Set display rotation.
Configure display rotation for Trezor Model T. The options are Configure display rotation for Trezor Model T. The options are
north, east, south or west. north, east, south or west.
""" """
return device.apply_settings(connect(), display_rotation=rotation) return device.apply_settings(client, display_rotation=rotation)
@cli.command() @cli.command()
@click.argument("delay", type=str) @click.argument("delay", type=str)
@click.pass_obj @with_client
def auto_lock_delay(connect, delay): def auto_lock_delay(client, delay):
"""Set auto-lock delay (in seconds).""" """Set auto-lock delay (in seconds)."""
value, unit = delay[:-1], delay[-1:] value, unit = delay[:-1], delay[-1:]
units = {"s": 1, "m": 60, "h": 3600} units = {"s": 1, "m": 60, "h": 3600}
@ -81,13 +81,13 @@ def auto_lock_delay(connect, delay):
seconds = float(value) * units[unit] seconds = float(value) * units[unit]
else: else:
seconds = float(delay) # assume seconds if no unit is specified seconds = float(delay) # assume seconds if no unit is specified
return device.apply_settings(connect(), auto_lock_delay_ms=int(seconds * 1000)) return device.apply_settings(client, auto_lock_delay_ms=int(seconds * 1000))
@cli.command() @cli.command()
@click.argument("flags") @click.argument("flags")
@click.pass_obj @with_client
def flags(connect, flags): def flags(client, flags):
"""Set device flags.""" """Set device flags."""
flags = flags.lower() flags = flags.lower()
if flags.startswith("0b"): if flags.startswith("0b"):
@ -96,13 +96,13 @@ def flags(connect, flags):
flags = int(flags, 16) flags = int(flags, 16)
else: else:
flags = int(flags) flags = int(flags)
return device.apply_flags(connect(), flags=flags) return device.apply_flags(client, flags=flags)
@cli.command() @cli.command()
@click.option("-f", "--filename", default=None) @click.option("-f", "--filename", default=None)
@click.pass_obj @with_client
def homescreen(connect, filename): def homescreen(client, filename):
"""Set new homescreen.""" """Set new homescreen."""
if filename is None: if filename is None:
img = b"\x00" img = b"\x00"
@ -125,7 +125,7 @@ def homescreen(connect, filename):
o = i + j * 128 o = i + j * 128
img[o // 8] |= 1 << (7 - o % 8) img[o // 8] |= 1 << (7 - o % 8)
img = bytes(img) img = bytes(img)
return device.apply_settings(connect(), homescreen=img) return device.apply_settings(client, homescreen=img)
# #
@ -140,16 +140,16 @@ def passphrase():
@passphrase.command(name="enabled") @passphrase.command(name="enabled")
@click.option("-f/-F", "--force-on-device/--no-force-on-device", default=None) @click.option("-f/-F", "--force-on-device/--no-force-on-device", default=None)
@click.pass_obj @with_client
def passphrase_enable(connect, force_on_device: bool): def passphrase_enable(client, force_on_device: bool):
"""Enable passphrase.""" """Enable passphrase."""
return device.apply_settings( return device.apply_settings(
connect(), use_passphrase=True, passphrase_always_on_device=force_on_device client, use_passphrase=True, passphrase_always_on_device=force_on_device
) )
@passphrase.command(name="disabled") @passphrase.command(name="disabled")
@click.pass_obj @with_client
def passphrase_disable(connect): def passphrase_disable(client):
"""Disable passphrase.""" """Disable passphrase."""
return device.apply_settings(connect(), use_passphrase=False) return device.apply_settings(client, use_passphrase=False)

@ -19,6 +19,7 @@ import base64
import click import click
from .. import stellar, tools from .. import stellar, tools
from . import with_client
PATH_HELP = "BIP32 path. Always use hardened paths and the m/44'/148'/ prefix" PATH_HELP = "BIP32 path. Always use hardened paths and the m/44'/148'/ prefix"
@ -37,10 +38,9 @@ def cli():
default=stellar.DEFAULT_BIP32_PATH, default=stellar.DEFAULT_BIP32_PATH,
) )
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, show_display): def get_address(client, address, show_display):
"""Get Stellar public address.""" """Get Stellar public address."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return stellar.get_address(client, address_n, show_display) return stellar.get_address(client, address_n, show_display)
@ -61,14 +61,13 @@ def get_address(connect, address, show_display):
help="Network passphrase (blank for public network).", help="Network passphrase (blank for public network).",
) )
@click.argument("b64envelope") @click.argument("b64envelope")
@click.pass_obj @with_client
def sign_transaction(connect, b64envelope, address, network_passphrase): def sign_transaction(client, b64envelope, address, network_passphrase):
"""Sign a base64-encoded transaction envelope. """Sign a base64-encoded transaction envelope.
For testnet transactions, use the following network passphrase: For testnet transactions, use the following network passphrase:
'Test SDF Network ; September 2015' 'Test SDF Network ; September 2015'
""" """
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
tx, operations = stellar.parse_transaction_bytes(base64.b64decode(b64envelope)) tx, operations = stellar.parse_transaction_bytes(base64.b64decode(b64envelope))
resp = stellar.sign_tx(client, tx, operations, address_n, network_passphrase) resp = stellar.sign_tx(client, tx, operations, address_n, network_passphrase)

@ -19,6 +19,7 @@ import json
import click import click
from .. import messages, protobuf, tezos, tools from .. import messages, protobuf, tezos, tools
from . import with_client
PATH_HELP = "BIP-32 path, e.g. m/44'/1729'/0'" PATH_HELP = "BIP-32 path, e.g. m/44'/1729'/0'"
@ -31,10 +32,9 @@ def cli():
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_address(connect, address, show_display): def get_address(client, address, show_display):
"""Get Tezos address for specified path.""" """Get Tezos address for specified path."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return tezos.get_address(client, address_n, show_display) return tezos.get_address(client, address_n, show_display)
@ -42,10 +42,9 @@ def get_address(connect, address, show_display):
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.pass_obj @with_client
def get_public_key(connect, address, show_display): def get_public_key(client, address, show_display):
"""Get Tezos public key.""" """Get Tezos public key."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
return tezos.get_public_key(client, address_n, show_display) return tezos.get_public_key(client, address_n, show_display)
@ -59,10 +58,9 @@ def get_public_key(connect, address, show_display):
default="-", default="-",
help="Transaction in JSON format (byte fields should be hexlified)", help="Transaction in JSON format (byte fields should be hexlified)",
) )
@click.pass_obj @with_client
def sign_tx(connect, address, file): def sign_tx(client, address, file):
"""Sign Tezos transaction.""" """Sign Tezos transaction."""
client = connect()
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
msg = protobuf.dict_to_proto(messages.TezosSignTx, json.load(file)) msg = protobuf.dict_to_proto(messages.TezosSignTx, json.load(file))
return tezos.sign_tx(client, address_n, msg) return tezos.sign_tx(client, address_n, msg)

@ -17,17 +17,18 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import json import json
import logging
import os import os
import sys
import time import time
import click import click
from .. import log, messages, protobuf, ui from .. import log, messages, protobuf, ui
from ..client import TrezorClient from ..client import TrezorClient
from ..transport import enumerate_devices, get_transport from ..transport import enumerate_devices
from ..transport.udp import UdpTransport from ..transport.udp import UdpTransport
from . import ( from . import (
TrezorConnection,
binance, binance,
btc, btc,
cardano, cardano,
@ -46,8 +47,11 @@ from . import (
settings, settings,
stellar, stellar,
tezos, tezos,
with_client,
) )
LOG = logging.getLogger(__name__)
COMMAND_ALIASES = { COMMAND_ALIASES = {
"change-pin": settings.pin, "change-pin": settings.pin,
"enable-passphrase": settings.passphrase_enable, "enable-passphrase": settings.passphrase_enable,
@ -157,26 +161,7 @@ def cli(ctx, path, verbose, is_json, passphrase_on_host, session_id):
except ValueError: except ValueError:
raise click.ClickException("Not a valid session id: {}".format(session_id)) raise click.ClickException("Not a valid session id: {}".format(session_id))
def get_device(return_transport=False): ctx.obj = TrezorConnection(path, session_id, passphrase_on_host)
try:
transport = get_transport(path, prefix_search=False)
except Exception:
try:
transport = get_transport(path, prefix_search=True)
except Exception:
click.echo("Failed to find a Trezor device.")
if path is not None:
click.echo("Using path: {}".format(path))
sys.exit(1)
if return_transport:
return transport
return TrezorClient(
transport=transport,
ui=ui.ClickUI(passphrase_on_host=passphrase_on_host),
session_id=session_id,
)
ctx.obj = get_device
@cli.resultcallback() @cli.resultcallback()
@ -211,6 +196,7 @@ def format_device_name(features):
label = features.label or "(unnamed)" label = features.label or "(unnamed)"
return "{} [Trezor {}, {}]".format(label, model, features.device_id) return "{} [Trezor {}, {}]".format(label, model, features.device_id)
# #
# Common functions # Common functions
# #
@ -244,15 +230,15 @@ def version():
@cli.command() @cli.command()
@click.argument("message") @click.argument("message")
@click.option("-b", "--button-protection", is_flag=True) @click.option("-b", "--button-protection", is_flag=True)
@click.pass_obj @with_client
def ping(connect, message, button_protection): def ping(client, message, button_protection):
"""Send ping message.""" """Send ping message."""
return connect().ping(message, button_protection=button_protection) return client.ping(message, button_protection=button_protection)
@cli.command() @cli.command()
@click.pass_obj @with_client
def get_session(connect): def get_session(client):
"""Get a session ID for subsequent commands. """Get a session ID for subsequent commands.
Unlocks Trezor with a passphrase and returns a session ID. Use this session ID with Unlocks Trezor with a passphrase and returns a session ID. Use this session ID with
@ -265,7 +251,6 @@ def get_session(connect):
from ..btc import get_address from ..btc import get_address
from ..client import PASSPHRASE_TEST_PATH from ..client import PASSPHRASE_TEST_PATH
client = connect()
if client.features.model == "1" and client.version < (1, 9, 0): if client.features.model == "1" and client.version < (1, 9, 0):
raise click.ClickException("Upgrade your firmware to enable session support.") raise click.ClickException("Upgrade your firmware to enable session support.")
@ -277,17 +262,17 @@ def get_session(connect):
@cli.command() @cli.command()
@click.pass_obj @with_client
def clear_session(connect): def clear_session(client):
"""Clear session (remove cached PIN, passphrase, etc.).""" """Clear session (remove cached PIN, passphrase, etc.)."""
return connect().clear_session() return client.clear_session()
@cli.command() @cli.command()
@click.pass_obj @with_client
def get_features(connect): def get_features(client):
"""Retrieve device features and settings.""" """Retrieve device features and settings."""
return connect().features return client.features
@cli.command() @cli.command()
@ -304,13 +289,13 @@ def usb_reset():
@cli.command() @cli.command()
@click.option("-t", "--timeout", type=float, default=10, help="Timeout in seconds") @click.option("-t", "--timeout", type=float, default=10, help="Timeout in seconds")
@click.pass_context @click.pass_obj
def wait_for_emulator(ctx, timeout): def wait_for_emulator(obj, timeout):
"""Wait until Trezor Emulator comes up. """Wait until Trezor Emulator comes up.
Tries to connect to emulator and returns when it succeeds. Tries to connect to emulator and returns when it succeeds.
""" """
path = ctx.parent.params.get("path") path = obj.path
if path: if path:
if not path.startswith("udp:"): if not path.startswith("udp:"):
raise click.ClickException("You must use UDP path, not {}".format(path)) raise click.ClickException("You must use UDP path, not {}".format(path))
@ -320,8 +305,7 @@ def wait_for_emulator(ctx, timeout):
UdpTransport(path).wait_until_ready(timeout) UdpTransport(path).wait_until_ready(timeout)
end = time.monotonic() end = time.monotonic()
if ctx.parent.params.get("verbose"): LOG.info("Waited for {:.3f} seconds".format(end - start))
click.echo("Waited for {:.3f} seconds".format(end - start))
# #

@ -21,8 +21,6 @@ import struct
import unicodedata import unicodedata
from typing import List, NewType from typing import List, NewType
from .exceptions import TrezorFailure
HARDENED_FLAG = 1 << 31 HARDENED_FLAG = 1 << 31
Address = NewType("Address", List[int]) Address = NewType("Address", List[int])

Loading…
Cancel
Save