diff --git a/hwilib/devices/coldcard.py b/hwilib/devices/coldcard.py index 681632c76..a6c9c25ea 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='', expert=False): super(ColdcardClient, self).__init__(path, password, expert) # Simulator hard coded pipe socket @@ -249,7 +272,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 eff61a62a..027886455 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, ExtendedKey, hash256, ser_sig_der, ser_sig_compact, ser_compact_size from ..base58 import get_xpub_fingerprint, xpub_main_2_test @@ -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, expert=False): super(DigitalbitboxClient, self).__init__(path, password, expert) if not password: @@ -590,7 +613,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 e103477b6..6dc1a7b5e 100644 --- a/hwilib/devices/keepkey.py +++ b/hwilib/devices/keepkey.py @@ -1,16 +1,45 @@ # 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 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 b2f3c8557..1cb2af3ae 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 @@ -71,6 +71,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' @@ -361,7 +384,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 990f74b8a..a57ffca2d 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 @@ -443,18 +443,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(): diff --git a/hwilib/hwwclient.py b/hwilib/hwwclient.py index 7f7e0f1f4..f216ac94a 100644 --- a/hwilib/hwwclient.py +++ b/hwilib/hwwclient.py @@ -1,4 +1,60 @@ +# General device client class and related constants and enums + from .base58 import get_xpub_fingerprint_hex +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.