Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List features #295

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions hwilib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
displayaddress,
enumerate,
find_device,
get_client_class,
get_client,
getmasterxpub,
getxpub,
Expand All @@ -22,6 +23,7 @@
)
from .errors import (
handle_errors,
BAD_ARGUMENT,
DEVICE_CONN_ERROR,
HELP_TEXT,
MISSING_ARGUMENTS,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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/')
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 20 additions & 9 deletions hwilib/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,32 @@ class AddressType(Enum):
WPKH = 2
SH_WPKH = 3

def get_client_class(device_type):
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, device_model + 'Client')
except (ImportError, AttributeError):
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):
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_constructor = get_client_class(device_type)
client = client_constructor(device_path, password, expert)
except ImportError:
except:
if client:
client.close()
raise UnknownDeviceError('Unknown device type specified')
raise

return client

Expand All @@ -81,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)
Expand Down
34 changes: 33 additions & 1 deletion hwilib/devices/coldcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

from typing import Dict, Union

from ..hwwclient import HardwareWalletClient
from ..hwwclient import (
DeviceFeature,
HardwareWalletClient,
SupportedFeatures,
)
from ..errors import (
ActionCanceledError,
BadArgumentError,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -340,6 +367,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):
return self.features.get_printable_dict()

def enumerate(password=''):
results = []
devices = hid.enumerate(COINKITE_VID, CKCC_PID)
Expand Down
39 changes: 38 additions & 1 deletion hwilib/devices/digitalbitbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -617,6 +644,16 @@ 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):
return self.features.get_printable_dict()

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)
Expand Down
32 changes: 32 additions & 0 deletions hwilib/devices/keepkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
common_err_msgs,
handle_errors,
)
from ..hwwclient import (
DeviceFeature,
SupportedFeatures,
)
from .trezorlib.transport import (
enumerate_devices,
KEEPKEY_VENDOR_IDS,
Expand All @@ -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():
Expand Down
57 changes: 50 additions & 7 deletions hwilib/devices/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

from typing import Dict, Union

from ..hwwclient import HardwareWalletClient
from ..hwwclient import (
DeviceFeature,
HardwareWalletClient,
SupportedFeatures,
)
from ..errors import (
ActionCanceledError,
BadArgumentError,
Expand Down Expand Up @@ -100,8 +104,32 @@ 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'

if path.startswith('tcp'):
split_path = path.split(':')
Expand Down Expand Up @@ -356,36 +384,51 @@ 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):
self.dongle.close()

# 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):
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):
return self.features.get_printable_dict()

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 = []
Expand Down
Loading