From e1cf2cbfea8c319c5b9baea6d55a26d38008b444 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 3 Jan 2020 15:09:35 -0500 Subject: [PATCH 1/5] Split part of get_client into get_client_class get_client_class gets the callable class for the given client name --- hwilib/commands.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/hwilib/commands.py b/hwilib/commands.py index 54b41150f..ca7175dde 100644 --- a/hwilib/commands.py +++ b/hwilib/commands.py @@ -43,21 +43,29 @@ class AddressType(Enum): WPKH = 2 SH_WPKH = 3 -# Get the client for the device -def get_client(device_type, device_path, password='', expert=False): +def get_client_class(device_type): device_type = device_type.split('_')[0] class_name = device_type.capitalize() module = device_type.lower() - client = None try: imported_dev = importlib.import_module('.devices.' + module, __package__) client_constructor = getattr(imported_dev, class_name + 'Client') - client = client_constructor(device_path, password, expert) except ImportError: + raise UnknownDeviceError('Unknown device type specified') + + return client_constructor + +# Get the client for the device +def get_client(device_type, device_path, password='', expert=False): + client = None + try: + client_constructor = get_client_class(device_type) + client = client_constructor(device_path, password, expert) + except: if client: client.close() - raise UnknownDeviceError('Unknown device type specified') + raise return client From c2be19562ecd4a6212890bcf8b7593bf28ad1d44 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 3 Jan 2020 15:59:32 -0500 Subject: [PATCH 2/5] Add device model clients and use them --- hwilib/commands.py | 15 +++++++++------ hwilib/devices/digitalbitbox.py | 5 +++++ hwilib/devices/ledger.py | 11 +++++++++++ hwilib/devices/trezor.py | 10 ++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/hwilib/commands.py b/hwilib/commands.py index ca7175dde..c3baf2c8e 100644 --- a/hwilib/commands.py +++ b/hwilib/commands.py @@ -44,14 +44,17 @@ class AddressType(Enum): SH_WPKH = 3 def get_client_class(device_type): - device_type = device_type.split('_')[0] - class_name = device_type.capitalize() - module = device_type.lower() + device_type_split = device_type.split('_') + if device_type_split[-1].lower() == 'simulator': + del device_type_split[-1] + device_type_split = [x.capitalize() for x in device_type_split] + device_model = ''.join(device_type_split) + module = device_type_split[0].lower() try: imported_dev = importlib.import_module('.devices.' + module, __package__) - client_constructor = getattr(imported_dev, class_name + 'Client') - except ImportError: + client_constructor = getattr(imported_dev, device_model + 'Client') + except (ImportError, AttributeError): raise UnknownDeviceError('Unknown device type specified') return client_constructor @@ -89,7 +92,7 @@ def find_device(password='', device_type=None, fingerprint=None, expert=False): continue client = None try: - client = get_client(d['type'], d['path'], password, expert) + client = get_client(d['model'], d['path'], password, expert) if fingerprint: master_fpr = d.get('fingerprint', None) diff --git a/hwilib/devices/digitalbitbox.py b/hwilib/devices/digitalbitbox.py index 466430d45..cef877347 100644 --- a/hwilib/devices/digitalbitbox.py +++ b/hwilib/devices/digitalbitbox.py @@ -617,6 +617,11 @@ def send_pin(self, pin): def toggle_passphrase(self): raise UnavailableActionError('The Digital Bitbox does not support toggling passphrase from the host') +class Digitalbitbox01Client(DigitalbitboxClient): + def __init__(self, path, password='', expert=False): + super(Digitalbitbox01Client, self).__init__(path, password, expert) + self.type = 'Digital BitBox01' + def enumerate(password=''): results = [] devices = hid.enumerate(DBB_VENDOR_ID, DBB_DEVICE_ID) diff --git a/hwilib/devices/ledger.py b/hwilib/devices/ledger.py index 3880ccee9..3b8025ae7 100644 --- a/hwilib/devices/ledger.py +++ b/hwilib/devices/ledger.py @@ -102,6 +102,7 @@ class LedgerClient(HardwareWalletClient): def __init__(self, path, password='', expert=False): super(LedgerClient, self).__init__(path, password, expert) + self.type = 'Ledger Nano S and X' if path.startswith('tcp'): split_path = path.split(':') @@ -386,6 +387,16 @@ def send_pin(self, pin): def toggle_passphrase(self): raise UnavailableActionError('The Ledger Nano S and X do not support toggling passphrase from the host') +class LedgerNanoSClient(LedgerClient): + def __init__(self, path, password='', expert=False): + super(LedgerNanoSClient, self).__init__(path, password, expert) + self.type = 'Ledger Nano S' + +class LedgerNanoXClient(LedgerClient): + def __init__(self, path, password='', expert=False): + super(LedgerNanoXClient, self).__init__(path, password, expert) + self.type = 'Ledger Nano X' + def enumerate(password=''): results = [] devices = [] diff --git a/hwilib/devices/trezor.py b/hwilib/devices/trezor.py index 6c73aab57..5abe98706 100644 --- a/hwilib/devices/trezor.py +++ b/hwilib/devices/trezor.py @@ -551,6 +551,16 @@ def toggle_passphrase(self): print(PIN_MATRIX_DESCRIPTION, file=sys.stderr) return {'success': True} +class Trezor1Client(TrezorClient): + def __init__(self, path, password='', expert=False): + super(Trezor1Client, self).__init__(path, password, expert) + self.type = 'Trezor 1' + +class TrezorTClient(TrezorClient): + def __init__(self, path, password='', expert=False): + super(TrezorTClient, self).__init__(path, password, expert) + self.type = 'Trezor T' + def enumerate(password=''): results = [] for dev in enumerate_devices(): From 3c1a4da81d605531056d5165bafc1b013f9e5e72 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 3 Jan 2020 16:42:52 -0500 Subject: [PATCH 3/5] Ledger device model specific error messages Instead of saying "Ledger Nano S and X", give the correct name depending on whether the device model client is being used. --- hwilib/devices/ledger.py | 12 ++++++------ test/test_ledger.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/hwilib/devices/ledger.py b/hwilib/devices/ledger.py index 3b8025ae7..51d955197 100644 --- a/hwilib/devices/ledger.py +++ b/hwilib/devices/ledger.py @@ -357,19 +357,19 @@ def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, desc # Setup a new device def setup_device(self, label='', passphrase=''): - raise UnavailableActionError('The Ledger Nano S and X do not support software setup') + raise UnavailableActionError('The {} does not support software setup'.format(self.type)) # Wipe this device def wipe_device(self): - raise UnavailableActionError('The Ledger Nano S and X do not support wiping via software') + raise UnavailableActionError('The {} does not support wiping via software'.format(self.type)) # Restore device from mnemonic or xprv def restore_device(self, label='', word_count=24): - raise UnavailableActionError('The Ledger Nano S and X do not support restoring via software') + raise UnavailableActionError('The {} does not support restoring via software'.format(self.type)) # Begin backup process def backup_device(self, label='', passphrase=''): - raise UnavailableActionError('The Ledger Nano S and X do not support creating a backup via software') + raise UnavailableActionError('The {} does not support creating a backup via software'.format(self.type)) # Close the device def close(self): @@ -377,11 +377,11 @@ def close(self): # Prompt pin def prompt_pin(self): - raise UnavailableActionError('The Ledger Nano S and X do not need a PIN sent from the host') + raise UnavailableActionError('The {} does not need a PIN sent from the host'.format(self.type)) # Send pin def send_pin(self, pin): - raise UnavailableActionError('The Ledger Nano S and X do not need a PIN sent from the host') + raise UnavailableActionError('The {} does not need a PIN sent from the host'.format(self.type)) # Toggle passphrase def toggle_passphrase(self): diff --git a/test/test_ledger.py b/test/test_ledger.py index 815952123..ee94b8105 100755 --- a/test/test_ledger.py +++ b/test/test_ledger.py @@ -63,41 +63,41 @@ def test_pin(self): result = self.do_command(self.dev_args + ['promptpin']) self.assertIn('error', result) self.assertIn('code', result) - self.assertEqual(result['error'], 'The Ledger Nano S and X do not need a PIN sent from the host') + self.assertEqual(result['error'], 'The Ledger Nano S does not need a PIN sent from the host') self.assertEqual(result['code'], -9) result = self.do_command(self.dev_args + ['sendpin', '1234']) self.assertIn('error', result) self.assertIn('code', result) - self.assertEqual(result['error'], 'The Ledger Nano S and X do not need a PIN sent from the host') + self.assertEqual(result['error'], 'The Ledger Nano S does not need a PIN sent from the host') self.assertEqual(result['code'], -9) def test_setup(self): result = self.do_command(self.dev_args + ['-i', 'setup']) self.assertIn('error', result) self.assertIn('code', result) - self.assertEqual(result['error'], 'The Ledger Nano S and X do not support software setup') + self.assertEqual(result['error'], 'The Ledger Nano S does not support software setup') self.assertEqual(result['code'], -9) def test_wipe(self): result = self.do_command(self.dev_args + ['wipe']) self.assertIn('error', result) self.assertIn('code', result) - self.assertEqual(result['error'], 'The Ledger Nano S and X do not support wiping via software') + self.assertEqual(result['error'], 'The Ledger Nano S does not support wiping via software') self.assertEqual(result['code'], -9) def test_restore(self): result = self.do_command(self.dev_args + ['-i', 'restore']) self.assertIn('error', result) self.assertIn('code', result) - self.assertEqual(result['error'], 'The Ledger Nano S and X do not support restoring via software') + self.assertEqual(result['error'], 'The Ledger Nano S does not support restoring via software') self.assertEqual(result['code'], -9) def test_backup(self): result = self.do_command(self.dev_args + ['backup']) self.assertIn('error', result) self.assertIn('code', result) - self.assertEqual(result['error'], 'The Ledger Nano S and X do not support creating a backup via software') + self.assertEqual(result['error'], 'The Ledger Nano S does not support creating a backup via software') self.assertEqual(result['code'], -9) class TestLedgerGetXpub(DeviceTestCase): From 40142735429847ba151ec6d8caf5f5e401116572 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 3 Jan 2020 15:10:33 -0500 Subject: [PATCH 4/5] Add getfeatures command and dummy hwwclient.get_features --- hwilib/cli.py | 17 +++++++++ hwilib/devices/coldcard.py | 5 +++ hwilib/devices/digitalbitbox.py | 5 +++ hwilib/devices/ledger.py | 5 +++ hwilib/devices/trezor.py | 5 +++ hwilib/hwwclient.py | 64 +++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+) diff --git a/hwilib/cli.py b/hwilib/cli.py index fd25d2a31..f0c352523 100644 --- a/hwilib/cli.py +++ b/hwilib/cli.py @@ -5,6 +5,7 @@ displayaddress, enumerate, find_device, + get_client_class, get_client, getmasterxpub, getxpub, @@ -22,6 +23,7 @@ ) from .errors import ( handle_errors, + BAD_ARGUMENT, DEVICE_CONN_ERROR, HELP_TEXT, MISSING_ARGUMENTS, @@ -88,6 +90,10 @@ def send_pin_handler(args, client): def install_udev_rules_handler(args): return install_udev_rules('udev', args.location) +def getfeatures_handler(args): + client_class = get_client_class(args.device_type) + return client_class.get_features() + class HWIHelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass @@ -208,6 +214,9 @@ def process_commands(cli_args): sendpin_parser.add_argument('pin', help='The numeric positions of the PIN') sendpin_parser.set_defaults(func=send_pin_handler) + getfeatures_parser = subparsers.add_parser('getfeatures', help='Returns the supported features for the given device type') + getfeatures_parser.set_defaults(func=getfeatures_handler) + if sys.platform.startswith("linux"): udevrules_parser = subparsers.add_parser('installudevrules', help='Install and load the udev rule files for the hardware wallet devices') udevrules_parser.add_argument('--location', help='The path where the udev rules files will be copied', default='/etc/udev/rules.d/') @@ -254,6 +263,14 @@ def process_commands(cli_args): result = args.func(args) return result + # Do get features + if command == 'getfeatures': + if not args.device_type: + return {'error': 'Device type needs to be specified to get features', 'code': BAD_ARGUMENT} + with handle_errors(result=result, debug=args.debug): + result = args.func(args) + return result + # Auto detect if we are using fingerprint or type to identify device if args.fingerprint or (args.device_type and not args.device_path): client = find_device(args.password, args.device_type, args.fingerprint, args.expert) diff --git a/hwilib/devices/coldcard.py b/hwilib/devices/coldcard.py index a196f90d9..7ea18ea9a 100644 --- a/hwilib/devices/coldcard.py +++ b/hwilib/devices/coldcard.py @@ -340,6 +340,11 @@ def send_pin(self, pin): def toggle_passphrase(self): raise UnavailableActionError('The Coldcard does not support toggling passphrase from the host') + # Get HWI features for this device + @classmethod + def get_features(self): + raise NotImplementedError('The Coldcard does not implement this method') + def enumerate(password=''): results = [] devices = hid.enumerate(COINKITE_VID, CKCC_PID) diff --git a/hwilib/devices/digitalbitbox.py b/hwilib/devices/digitalbitbox.py index cef877347..a55cd698d 100644 --- a/hwilib/devices/digitalbitbox.py +++ b/hwilib/devices/digitalbitbox.py @@ -617,6 +617,11 @@ def send_pin(self, pin): def toggle_passphrase(self): raise UnavailableActionError('The Digital Bitbox does not support toggling passphrase from the host') + # Get HWI features for this device + @classmethod + def get_features(self): + raise NotImplementedError('The Digital Bitbox does not implement this method') + class Digitalbitbox01Client(DigitalbitboxClient): def __init__(self, path, password='', expert=False): super(Digitalbitbox01Client, self).__init__(path, password, expert) diff --git a/hwilib/devices/ledger.py b/hwilib/devices/ledger.py index 51d955197..bd0b7eecd 100644 --- a/hwilib/devices/ledger.py +++ b/hwilib/devices/ledger.py @@ -387,6 +387,11 @@ def send_pin(self, pin): def toggle_passphrase(self): raise UnavailableActionError('The Ledger Nano S and X do not support toggling passphrase from the host') + # Get HWI features for this device + @classmethod + def get_features(self): + raise NotImplementedError('The Ledger Nano S and X does not implement this method') + class LedgerNanoSClient(LedgerClient): def __init__(self, path, password='', expert=False): super(LedgerNanoSClient, self).__init__(path, password, expert) diff --git a/hwilib/devices/trezor.py b/hwilib/devices/trezor.py index 5abe98706..e310fb1f0 100644 --- a/hwilib/devices/trezor.py +++ b/hwilib/devices/trezor.py @@ -551,6 +551,11 @@ def toggle_passphrase(self): print(PIN_MATRIX_DESCRIPTION, file=sys.stderr) return {'success': True} + # Get HWI features for this device + @classmethod + def get_features(self): + raise NotImplementedError('The {} does not implement this method'.format(self.type)) + class Trezor1Client(TrezorClient): def __init__(self, path, password='', expert=False): super(Trezor1Client, self).__init__(path, password, expert) diff --git a/hwilib/hwwclient.py b/hwilib/hwwclient.py index e27db11f9..6ca4ca9e2 100644 --- a/hwilib/hwwclient.py +++ b/hwilib/hwwclient.py @@ -4,6 +4,60 @@ from .descriptor import Descriptor from .serializations import PSBT +from enum import IntEnum + +class DeviceFeature(IntEnum): + SUPPORTED = 1 # The device supports the feature and so does HWI + NOT_SUPPORTED = 2 # The device supports the feature but HWI has not implemented it yet + FIRMWARE_NOT_SUPPORTED = 3 # The firmware does not support the feature so HWI cannot + +class SupportedFeatures(object): + + def __init__(self) -> None: + self.getxpub = DeviceFeature.NOT_SUPPORTED + self.signmessage = DeviceFeature.NOT_SUPPORTED + self.setup = DeviceFeature.NOT_SUPPORTED + self.wipe = DeviceFeature.NOT_SUPPORTED + self.recover = DeviceFeature.NOT_SUPPORTED + self.backup = DeviceFeature.NOT_SUPPORTED + self.sign_p2pkh = DeviceFeature.NOT_SUPPORTED + self.sign_p2sh_p2wpkh = DeviceFeature.NOT_SUPPORTED + self.sign_p2wpkh = DeviceFeature.NOT_SUPPORTED + self.sign_multi_p2sh = DeviceFeature.NOT_SUPPORTED + self.sign_multi_p2sh_p2wsh = DeviceFeature.NOT_SUPPORTED + self.sign_multi_p2wsh = DeviceFeature.NOT_SUPPORTED + self.sign_multi_bare = DeviceFeature.NOT_SUPPORTED + self.sign_arbitrary_bare = DeviceFeature.NOT_SUPPORTED + self.sign_arbitrary_p2sh = DeviceFeature.NOT_SUPPORTED + self.sign_arbitrary_p2sh_p2wsh = DeviceFeature.NOT_SUPPORTED + self.sign_arbitrary_p2wsh = DeviceFeature.NOT_SUPPORTED + self.sign_coinjoin = DeviceFeature.NOT_SUPPORTED + self.sign_mixed_segwit = DeviceFeature.NOT_SUPPORTED + self.display_address = DeviceFeature.NOT_SUPPORTED + + def get_printable_dict(self) -> Dict[str, DeviceFeature]: + d = {} + d['getxpub'] = self.getxpub + d['signmessage'] = self.signmessage + d['setup'] = self.setup + d['wipe'] = self.wipe + d['recover'] = self.recover + d['backup'] = self.backup + d['sign_p2pkh'] = self.sign_p2pkh + d['sign_p2sh_p2wpkh'] = self.sign_p2sh_p2wpkh + d['sign_p2wpkh'] = self.sign_p2wpkh + d['sign_multi_p2sh'] = self.sign_multi_p2sh + d['sign_multi_p2sh_p2wsh'] = self.sign_multi_p2sh_p2wsh + d['sign_multi_p2wsh'] = self.sign_multi_p2wsh + d['sign_multi_bare'] = self.sign_multi_bare + d['sign_arbitrary_bare'] = self.sign_arbitrary_bare + d['sign_arbitrary_p2sh'] = self.sign_arbitrary_p2sh + d['sign_arbitrary_p2sh_p2wsh'] = self.sign_arbitrary_p2sh_p2wsh + d['sign_arbitrary_p2wsh'] = self.sign_arbitrary_p2wsh + d['sign_coinjoin'] = self.sign_coinjoin + d['sign_mixed_segwit'] = self.sign_mixed_segwit + d['display_address'] = self.display_address + return d class HardwareWalletClient(object): """Create a client for a HID device that has already been opened. @@ -185,3 +239,13 @@ def toggle_passphrase(self) -> Dict[str, Union[bool, str, int]]: """ raise NotImplementedError("The HardwareWalletClient base class " "does not implement this method") + + @classmethod + def get_features(self) -> 'SupportedFeatures': + """ + Get features. + + Returns an object with a listing of the features supported by this device. + """ + raise NotImplementedError("The HardwareWalletClient base class " + "does not implement this method") From 44140ee24695a7bede07e7ee19935c82823897dd Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 3 Jan 2020 16:44:51 -0500 Subject: [PATCH 5/5] Implement get_features for all clients. --- hwilib/devices/coldcard.py | 31 ++++++++++++++-- hwilib/devices/digitalbitbox.py | 31 ++++++++++++++-- hwilib/devices/keepkey.py | 32 +++++++++++++++++ hwilib/devices/ledger.py | 31 ++++++++++++++-- hwilib/devices/trezor.py | 64 +++++++++++++++++++++++++++++++-- 5 files changed, 181 insertions(+), 8 deletions(-) diff --git a/hwilib/devices/coldcard.py b/hwilib/devices/coldcard.py index 7ea18ea9a..28bd0936a 100644 --- a/hwilib/devices/coldcard.py +++ b/hwilib/devices/coldcard.py @@ -2,7 +2,11 @@ from typing import Dict, Union -from ..hwwclient import HardwareWalletClient +from ..hwwclient import ( + DeviceFeature, + HardwareWalletClient, + SupportedFeatures, +) from ..errors import ( ActionCanceledError, BadArgumentError, @@ -94,6 +98,29 @@ def func(*args, **kwargs): # This class extends the HardwareWalletClient for ColdCard specific things class ColdcardClient(HardwareWalletClient): + # Setup features + features = SupportedFeatures() + features.getxpub = DeviceFeature.SUPPORTED + features.signmessage = DeviceFeature.SUPPORTED + features.setup = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.wipe = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.recover = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.backup = DeviceFeature.SUPPORTED + features.sign_p2pkh = DeviceFeature.SUPPORTED + features.sign_p2sh_p2wpkh = DeviceFeature.SUPPORTED + features.sign_p2wpkh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_coinjoin = DeviceFeature.SUPPORTED + features.sign_mixed_segwit = DeviceFeature.SUPPORTED + features.display_address = DeviceFeature.SUPPORTED + def __init__(self, path, password='', expert=False): super(ColdcardClient, self).__init__(path, password, expert) # Simulator hard coded pipe socket @@ -343,7 +370,7 @@ def toggle_passphrase(self): # Get HWI features for this device @classmethod def get_features(self): - raise NotImplementedError('The Coldcard does not implement this method') + return self.features.get_printable_dict() def enumerate(password=''): results = [] diff --git a/hwilib/devices/digitalbitbox.py b/hwilib/devices/digitalbitbox.py index a55cd698d..73222d702 100644 --- a/hwilib/devices/digitalbitbox.py +++ b/hwilib/devices/digitalbitbox.py @@ -15,7 +15,11 @@ import time from typing import Dict, Union -from ..hwwclient import HardwareWalletClient +from ..hwwclient import ( + DeviceFeature, + HardwareWalletClient, + SupportedFeatures, +) from ..errors import ( ActionCanceledError, BadArgumentError, @@ -326,6 +330,29 @@ def format_backup_filename(name): # This class extends the HardwareWalletClient for Digital Bitbox specific things class DigitalbitboxClient(HardwareWalletClient): + # Setup features + features = SupportedFeatures() + features.getxpub = DeviceFeature.SUPPORTED + features.signmessage = DeviceFeature.SUPPORTED + features.setup = DeviceFeature.SUPPORTED + features.wipe = DeviceFeature.SUPPORTED + features.recover = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.backup = DeviceFeature.SUPPORTED + features.sign_p2pkh = DeviceFeature.SUPPORTED + features.sign_p2sh_p2wpkh = DeviceFeature.SUPPORTED + features.sign_p2wpkh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_bare = DeviceFeature.SUPPORTED + features.sign_arbitrary_bare = DeviceFeature.SUPPORTED + features.sign_arbitrary_p2sh = DeviceFeature.SUPPORTED + features.sign_arbitrary_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_arbitrary_p2wsh = DeviceFeature.SUPPORTED + features.sign_coinjoin = DeviceFeature.SUPPORTED + features.sign_mixed_segwit = DeviceFeature.SUPPORTED + features.display_address = DeviceFeature.FIRMWARE_NOT_SUPPORTED + def __init__(self, path, password, expert=False): super(DigitalbitboxClient, self).__init__(path, password, expert) if not password: @@ -620,7 +647,7 @@ def toggle_passphrase(self): # Get HWI features for this device @classmethod def get_features(self): - raise NotImplementedError('The Digital Bitbox does not implement this method') + return self.features.get_printable_dict() class Digitalbitbox01Client(DigitalbitboxClient): def __init__(self, path, password='', expert=False): diff --git a/hwilib/devices/keepkey.py b/hwilib/devices/keepkey.py index ab69ef6fc..507118f94 100644 --- a/hwilib/devices/keepkey.py +++ b/hwilib/devices/keepkey.py @@ -6,6 +6,10 @@ common_err_msgs, handle_errors, ) +from ..hwwclient import ( + DeviceFeature, + SupportedFeatures, +) from .trezorlib.transport import ( enumerate_devices, KEEPKEY_VENDOR_IDS, @@ -15,10 +19,38 @@ py_enumerate = enumerate # Need to use the enumerate built-in but there's another function already named that class KeepkeyClient(TrezorClient): + + # Setup features + features = SupportedFeatures() + features.getxpub = DeviceFeature.SUPPORTED + features.signmessage = DeviceFeature.SUPPORTED + features.setup = DeviceFeature.SUPPORTED + features.wipe = DeviceFeature.SUPPORTED + features.recover = DeviceFeature.SUPPORTED + features.backup = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_p2pkh = DeviceFeature.SUPPORTED + features.sign_p2sh_p2wpkh = DeviceFeature.SUPPORTED + features.sign_p2wpkh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_coinjoin = DeviceFeature.SUPPORTED + features.sign_mixed_segwit = DeviceFeature.SUPPORTED + features.display_address = DeviceFeature.SUPPORTED + def __init__(self, path, password='', expert=False): super(KeepkeyClient, self).__init__(path, password, expert) self.type = 'Keepkey' + @classmethod + def get_features(self): + return self.features.get_printable_dict() + def enumerate(password=''): results = [] for dev in enumerate_devices(): diff --git a/hwilib/devices/ledger.py b/hwilib/devices/ledger.py index bd0b7eecd..589a1f123 100644 --- a/hwilib/devices/ledger.py +++ b/hwilib/devices/ledger.py @@ -2,7 +2,11 @@ from typing import Dict, Union -from ..hwwclient import HardwareWalletClient +from ..hwwclient import ( + DeviceFeature, + HardwareWalletClient, + SupportedFeatures, +) from ..errors import ( ActionCanceledError, BadArgumentError, @@ -100,6 +104,29 @@ def func(*args, **kwargs): # This class extends the HardwareWalletClient for Ledger Nano S and Nano X specific things class LedgerClient(HardwareWalletClient): + # Setup features + features = SupportedFeatures() + features.getxpub = DeviceFeature.SUPPORTED + features.signmessage = DeviceFeature.SUPPORTED + features.setup = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.wipe = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.recover = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.backup = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_p2pkh = DeviceFeature.SUPPORTED + features.sign_p2sh_p2wpkh = DeviceFeature.SUPPORTED + features.sign_p2wpkh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_bare = DeviceFeature.SUPPORTED + features.sign_arbitrary_bare = DeviceFeature.SUPPORTED + features.sign_arbitrary_p2sh = DeviceFeature.SUPPORTED + features.sign_arbitrary_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_arbitrary_p2wsh = DeviceFeature.SUPPORTED + features.sign_coinjoin = DeviceFeature.SUPPORTED + features.sign_mixed_segwit = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.display_address = DeviceFeature.SUPPORTED + def __init__(self, path, password='', expert=False): super(LedgerClient, self).__init__(path, password, expert) self.type = 'Ledger Nano S and X' @@ -390,7 +417,7 @@ def toggle_passphrase(self): # Get HWI features for this device @classmethod def get_features(self): - raise NotImplementedError('The Ledger Nano S and X does not implement this method') + return self.features.get_printable_dict() class LedgerNanoSClient(LedgerClient): def __init__(self, path, password='', expert=False): diff --git a/hwilib/devices/trezor.py b/hwilib/devices/trezor.py index e310fb1f0..24155868f 100644 --- a/hwilib/devices/trezor.py +++ b/hwilib/devices/trezor.py @@ -2,7 +2,11 @@ from typing import Dict, Union -from ..hwwclient import HardwareWalletClient +from ..hwwclient import ( + DeviceFeature, + HardwareWalletClient, + SupportedFeatures, +) from ..errors import ( ActionCanceledError, BadArgumentError, @@ -554,18 +558,74 @@ def toggle_passphrase(self): # Get HWI features for this device @classmethod def get_features(self): - raise NotImplementedError('The {} does not implement this method'.format(self.type)) + raise UnavailableActionError('A specific Trezor model must be specified to get the features') class Trezor1Client(TrezorClient): + + # Setup features + features = SupportedFeatures() + features.getxpub = DeviceFeature.SUPPORTED + features.signmessage = DeviceFeature.SUPPORTED + features.setup = DeviceFeature.SUPPORTED + features.wipe = DeviceFeature.SUPPORTED + features.recover = DeviceFeature.SUPPORTED + features.backup = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_p2pkh = DeviceFeature.SUPPORTED + features.sign_p2sh_p2wpkh = DeviceFeature.SUPPORTED + features.sign_p2wpkh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_coinjoin = DeviceFeature.SUPPORTED + features.sign_mixed_segwit = DeviceFeature.SUPPORTED + features.display_address = DeviceFeature.SUPPORTED + def __init__(self, path, password='', expert=False): super(Trezor1Client, self).__init__(path, password, expert) self.type = 'Trezor 1' + @classmethod + def get_features(self): + return self.features.get_printable_dict() + class TrezorTClient(TrezorClient): + + # Setup features + features = SupportedFeatures() + features.getxpub = DeviceFeature.SUPPORTED + features.signmessage = DeviceFeature.SUPPORTED + features.setup = DeviceFeature.SUPPORTED + features.wipe = DeviceFeature.SUPPORTED + features.recover = DeviceFeature.SUPPORTED + features.backup = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_p2pkh = DeviceFeature.SUPPORTED + features.sign_p2sh_p2wpkh = DeviceFeature.SUPPORTED + features.sign_p2wpkh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh = DeviceFeature.SUPPORTED + features.sign_multi_p2sh_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_p2wsh = DeviceFeature.SUPPORTED + features.sign_multi_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_bare = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2sh_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_arbitrary_p2wsh = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.sign_coinjoin = DeviceFeature.SUPPORTED + features.sign_mixed_segwit = DeviceFeature.FIRMWARE_NOT_SUPPORTED + features.display_address = DeviceFeature.SUPPORTED + def __init__(self, path, password='', expert=False): super(TrezorTClient, self).__init__(path, password, expert) self.type = 'Trezor T' + @classmethod + def get_features(self): + return self.features.get_printable_dict() + def enumerate(password=''): results = [] for dev in enumerate_devices():