diff --git a/hwilib/devices/coldcard.py b/hwilib/devices/coldcard.py index 7362ce4d3..78b1711d6 100644 --- a/hwilib/devices/coldcard.py +++ b/hwilib/devices/coldcard.py @@ -1,7 +1,7 @@ # Coldcard interaction script from binascii import b2a_hex -from ..hwwclient import HardwareWalletClient +from ..hwwclient import DeviceFeature, HardwareWalletClient, SupportedFeatures from ..errors import ActionCanceledError, BadArgumentError, DeviceBusyError, DeviceFailureError, UnavailableActionError, common_err_msgs, handle_errors from .ckcc.client import ColdcardDevice, COINKITE_VID, CKCC_PID from .ckcc.protocol import CCProtocolPacker, CCBusyError, CCProtoError, CCUserRefused @@ -36,6 +36,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=''): super(ColdcardClient, self).__init__(path, password) # Simulator hard coded pipe socket @@ -240,7 +263,7 @@ def send_pin(self, pin): # 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 2361e8db2..407e80f34 100644 --- a/hwilib/devices/digitalbitbox.py +++ b/hwilib/devices/digitalbitbox.py @@ -14,7 +14,7 @@ import sys import time -from ..hwwclient import HardwareWalletClient +from ..hwwclient import DeviceFeature, HardwareWalletClient, SupportedFeatures from ..errors import ActionCanceledError, BadArgumentError, DeviceFailureError, DeviceAlreadyInitError, DEVICE_NOT_INITIALIZED, DeviceNotReadyError, NoPasswordError, UnavailableActionError, common_err_msgs, handle_errors from ..serializations import CTransaction, hash256, ser_sig_der, ser_sig_compact, ser_compact_size from ..base58 import get_xpub_fingerprint, xpub_main_2_test, get_xpub_fingerprint_hex @@ -296,6 +296,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): super(DigitalbitboxClient, self).__init__(path, password) if not password: @@ -581,7 +604,7 @@ def send_pin(self, pin): # 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=''): diff --git a/hwilib/devices/keepkey.py b/hwilib/devices/keepkey.py index 3c0bd900a..01e2f22c7 100644 --- a/hwilib/devices/keepkey.py +++ b/hwilib/devices/keepkey.py @@ -1,6 +1,7 @@ # KeepKey interaction script from ..errors import DEVICE_NOT_INITIALIZED, DeviceNotReadyError, common_err_msgs, handle_errors +from ..hwwclient import DeviceFeature, SupportedFeatures from .trezorlib.transport import enumerate_devices, KEEPKEY_VENDOR_IDS from .trezor import TrezorClient from ..base58 import get_xpub_fingerprint_hex @@ -8,10 +9,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=''): super(KeepkeyClient, self).__init__(path, password) 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 9be54ee7f..ecfb596fa 100644 --- a/hwilib/devices/ledger.py +++ b/hwilib/devices/ledger.py @@ -1,6 +1,6 @@ # Ledger interaction script -from ..hwwclient import HardwareWalletClient +from ..hwwclient import DeviceFeature, HardwareWalletClient, SupportedFeatures from ..errors import ActionCanceledError, BadArgumentError, DeviceConnectionError, DeviceFailureError, UnavailableActionError, common_err_msgs, handle_errors from .btchip.bitcoinTransaction import bitcoinTransaction from .btchip.btchip import btchip @@ -72,6 +72,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=''): super(LedgerClient, self).__init__(path, password) self.type = 'Ledger Nano S and X' @@ -351,7 +374,7 @@ def send_pin(self, pin): # 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=''): diff --git a/hwilib/devices/trezor.py b/hwilib/devices/trezor.py index ab0576651..f7bb8708f 100644 --- a/hwilib/devices/trezor.py +++ b/hwilib/devices/trezor.py @@ -1,6 +1,6 @@ # Trezor interaction script -from ..hwwclient import HardwareWalletClient +from ..hwwclient import DeviceFeature, HardwareWalletClient, SupportedFeatures from ..errors import ActionCanceledError, BadArgumentError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, DeviceConnectionError, DEVICE_NOT_INITIALIZED, DeviceNotReadyError, UnavailableActionError, common_err_msgs, handle_errors from .trezorlib.client import TrezorClient as Trezor from .trezorlib.debuglink import TrezorClientDebugLink @@ -432,18 +432,74 @@ def send_pin(self, pin): # 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=''): super(Trezor1Client, self).__init__(path, password) 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=''): super(TrezorTClient, self).__init__(path, password) self.type = 'Trezor T' + @classmethod + def get_features(self): + return self.features.get_printable_dict() + def enumerate(password=''): results = [] for dev in enumerate_devices(): diff --git a/hwilib/hwwclient.py b/hwilib/hwwclient.py index 8c934ee10..54bd94d0f 100644 --- a/hwilib/hwwclient.py +++ b/hwilib/hwwclient.py @@ -1,3 +1,60 @@ +# General device client class and related constants and enums + +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): + 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): + 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 + # This is an abstract class that defines all of the methods that each Hardware # wallet subclass must implement. class HardwareWalletClient(object):