Skip to content

Commit

Permalink
Merge #644: Use Optional for passphrase
Browse files Browse the repository at this point in the history
9ed9dbe trezor: Default to empty string passphrase (Andrew Chow)
0c4df0c trezor, test: Test enumerate with empty passphrase (Andrew Chow)
d74fcbc test: Handle Optional emulator password (Andrew Chow)
d4cf550 Change passphrase (password) to Optional (Andrew Chow)

Pull request description:

  We should be distinguishing from the empty passphrase and no passphrase provided, so instead of defaulting to the empty passphrase, change passphrase (aka password) to an `Optional`. This way, if it is not provided, it is `None` and empty passphrases can be handled correctly.

  Fixes #639

Top commit has no ACKs.

Tree-SHA512: aac6763934dd40fd55c7ad0ed9bb57e387ce9eefeb4c198521fb09faaeed538966bc50f598c024dfffdc8e6a5a646f87ff64cb77103c296ec2c30ba992763274
  • Loading branch information
achow101 committed Dec 8, 2022
2 parents 857e935 + 9ed9dbe commit e7af45d
Show file tree
Hide file tree
Showing 12 changed files with 44 additions and 30 deletions.
2 changes: 1 addition & 1 deletion hwilib/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def get_parser() -> HWIArgumentParser:
parser = HWIArgumentParser(description='Hardware Wallet Interface, version {}.\nAccess and send commands to a hardware wallet device. Responses are in JSON format.'.format(__version__))
parser.add_argument('--device-path', '-d', help='Specify the device path of the device to connect to')
parser.add_argument('--device-type', '-t', help='Specify the type of device that will be connected. If `--device-path` not given, the first device of this type enumerated is used.')
parser.add_argument('--password', '-p', help='Device password if it has one (e.g. DigitalBitbox)', default='')
parser.add_argument('--password', '-p', help='Device password if it has one (e.g. DigitalBitbox)')
parser.add_argument('--stdinpass', help='Enter the device password on the command line', action='store_true')
parser.add_argument('--chain', help='Select chain to work with', type=Chain.argparse, choices=list(Chain), default=Chain.MAIN) # type: ignore
parser.add_argument('--debug', help='Print debug statements', action='store_true')
Expand Down
6 changes: 3 additions & 3 deletions hwilib/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@


# Get the client for the device
def get_client(device_type: str, device_path: str, password: str = "", expert: bool = False, chain: Chain = Chain.MAIN) -> Optional[HardwareWalletClient]:
def get_client(device_type: str, device_path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN) -> Optional[HardwareWalletClient]:
"""
Returns a HardwareWalletClient for the given device type at the device path
Expand Down Expand Up @@ -101,7 +101,7 @@ def get_client(device_type: str, device_path: str, password: str = "", expert: b
return client

# Get a list of all available hardware wallets
def enumerate(password: str = "") -> List[Dict[str, Any]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, Any]]:
"""
Enumerate all of the devices that HWI can potentially access.
Expand All @@ -124,7 +124,7 @@ def enumerate(password: str = "") -> List[Dict[str, Any]]:

# Fingerprint or device type required
def find_device(
password: str = "",
password: Optional[str] = None,
device_type: Optional[str] = None,
fingerprint: Optional[str] = None,
expert: bool = False,
Expand Down
8 changes: 4 additions & 4 deletions hwilib/devices/bitbox02.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def _xpubs_equal_ignoring_version(xpub1: bytes, xpub2: bytes) -> bool:
return xpub1[4:] == xpub2[4:]


def enumerate(password: str = "") -> List[Dict[str, object]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, object]]:
"""
Enumerate all BitBox02 devices. Bootloaders excluded.
"""
Expand Down Expand Up @@ -259,15 +259,15 @@ def func(*args, **kwargs): # type: ignore

# This class extends the HardwareWalletClient for BitBox02 specific things
class Bitbox02Client(HardwareWalletClient):
def __init__(self, path: str, password: str = "", expert: bool = False, chain: Chain = Chain.MAIN) -> None:
def __init__(self, path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN) -> None:
"""
Initializes a new BitBox02 client instance.
"""
super().__init__(path, password=password, expert=expert, chain=chain)
if password:
if password is not None:
raise BadArgumentError(
"The BitBox02 does not accept a passphrase from the host. Please enable the passphrase option and enter the passphrase on the device during unlock."
)
super().__init__(path, password=password, expert=expert, chain=chain)

hid_device = hid.device()
hid_device.open_path(path.encode())
Expand Down
5 changes: 3 additions & 2 deletions hwilib/devices/coldcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from typing import (
Any,
Callable,
Optional,
)

CC_SIMULATOR_SOCK = '/tmp/ckcc-simulator.sock'
Expand All @@ -89,7 +90,7 @@ def func(*args: Any, **kwargs: Any) -> Any:
# This class extends the HardwareWalletClient for ColdCard specific things
class ColdcardClient(HardwareWalletClient):

def __init__(self, path: str, password: str = "", expert: bool = False, chain: Chain = Chain.MAIN) -> None:
def __init__(self, path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN) -> None:
super(ColdcardClient, self).__init__(path, password, expert, chain)
# Simulator hard coded pipe socket
if path == CC_SIMULATOR_SOCK:
Expand Down Expand Up @@ -398,7 +399,7 @@ def can_sign_taproot(self) -> bool:
return False


def enumerate(password: str = "") -> List[Dict[str, Any]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, Any]]:
results = []
devices = hid.enumerate(COINKITE_VID, CKCC_PID)
devices.append({'path': CC_SIMULATOR_SOCK.encode()})
Expand Down
13 changes: 7 additions & 6 deletions hwilib/devices/digitalbitbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Callable,
Dict,
List,
Optional,
Tuple,
Union,
)
Expand Down Expand Up @@ -345,17 +346,17 @@ def format_backup_filename(name: str) -> str:
# This class extends the HardwareWalletClient for Digital Bitbox specific things
class DigitalbitboxClient(HardwareWalletClient):

def __init__(self, path: str, password: str, expert: bool = False, chain: Chain = Chain.MAIN) -> None:
def __init__(self, path: str, password: Optional[str], expert: bool = False, chain: Chain = Chain.MAIN) -> None:
"""
The `DigitalbitboxClient` is a `HardwareWalletClient` for interacting with BitBox01 devices (previously known as the Digital BitBox).
:param path: Path to the device as given by `enumerate`
:param password: The password required to communicate with the device. Must be provided.
:param expert: Whether to be in expert mode and return additional information.
"""
super(DigitalbitboxClient, self).__init__(path, password, expert, chain)
if not password:
if password is None:
raise NoPasswordError('Password must be supplied for digital BitBox')
super(DigitalbitboxClient, self).__init__(path, password, expert, chain)
if path.startswith('udp:'):
split_path = path.split(':')
ip = split_path[1]
Expand All @@ -364,7 +365,7 @@ def __init__(self, path: str, password: str, expert: bool = False, chain: Chain
else:
self.device = hid.device()
self.device.open_path(path.encode())
self.password = password
self.password: str = password

@digitalbitbox_exception
def get_pubkey_at_path(self, path: str) -> ExtendedKey:
Expand Down Expand Up @@ -678,7 +679,7 @@ def can_sign_taproot(self) -> bool:
return False


def enumerate(password: str = "") -> List[Dict[str, Any]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, Any]]:
results = []
devices = hid.enumerate(DBB_VENDOR_ID, DBB_DEVICE_ID)
# Try connecting to simulator
Expand Down Expand Up @@ -707,7 +708,7 @@ def enumerate(password: str = "") -> List[Dict[str, Any]]:
client = DigitalbitboxClient(path, password)

# Check initialized
reply = send_encrypt('{"device" : "info"}', password, client.device)
reply = send_encrypt('{"device" : "info"}', "" if password is None else password, client.device)
if 'error' in reply and (reply['error']['code'] == 101 or reply['error']['code'] == '101'):
d_data['error'] = 'Not initialized'
d_data['code'] = DEVICE_NOT_INITIALIZED
Expand Down
4 changes: 2 additions & 2 deletions hwilib/devices/jade.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def _get_multisig_name(type: str, threshold: int, signers: List[Tuple[bytes, Seq
hash_summary = sha256(summary.encode()).hex()
return 'hwi' + hash_summary[:12]

def __init__(self, path: str, password: str = '', expert: bool = False, chain: Chain = Chain.MAIN, timeout: Optional[int] = None) -> None:
def __init__(self, path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN, timeout: Optional[int] = None) -> None:
super(JadeClient, self).__init__(path, password, expert, chain)
self.jade = JadeAPI.create_serial(path, timeout=timeout)
self.jade.connect()
Expand Down Expand Up @@ -508,7 +508,7 @@ def can_sign_taproot(self) -> bool:
return False


def enumerate(password: str = '') -> List[Dict[str, Any]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, Any]]:
results = []

def _get_device_entry(device_model: str, device_path: str) -> Dict[str, Any]:
Expand Down
6 changes: 3 additions & 3 deletions hwilib/devices/keepkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def __init__(


class KeepkeyClient(TrezorClient):
def __init__(self, path: str, password: str = "", expert: bool = False, chain: Chain = Chain.MAIN) -> None:
def __init__(self, path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN) -> None:
"""
The `KeepkeyClient` is a `HardwareWalletClient` for interacting with the Keepkey.
Expand Down Expand Up @@ -171,7 +171,7 @@ def can_sign_taproot(self) -> bool:
return False


def enumerate(password: str = "") -> List[Dict[str, Any]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, Any]]:
results = []
devs = hid.HidTransport.enumerate(usb_ids=KEEPKEY_HID_IDS)
devs.extend(webusb.WebUsbTransport.enumerate(usb_ids=KEEPKEY_WEBUSB_IDS))
Expand Down Expand Up @@ -202,7 +202,7 @@ def enumerate(password: str = "") -> List[Dict[str, Any]]:
d_data['needs_passphrase_sent'] = client.client.features.passphrase_protection # always need the passphrase sent for Keepkey if it has passphrase protection enabled
if d_data['needs_pin_sent']:
raise DeviceNotReadyError('Keepkey is locked. Unlock by using \'promptpin\' and then \'sendpin\'.')
if d_data['needs_passphrase_sent'] and not password:
if d_data['needs_passphrase_sent'] and password is None:
raise DeviceNotReadyError("Passphrase needs to be specified before the fingerprint information can be retrieved")
if client.client.features.initialized:
d_data['fingerprint'] = client.get_master_fingerprint().hex()
Expand Down
4 changes: 2 additions & 2 deletions hwilib/devices/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def func(*args: Any, **kwargs: Any) -> Any:
# This class extends the HardwareWalletClient for Ledger Nano S and Nano X specific things
class LedgerClient(HardwareWalletClient):

def __init__(self, path: str, password: str = "", expert: bool = False, chain: Chain = Chain.MAIN) -> None:
def __init__(self, path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN) -> None:
super(LedgerClient, self).__init__(path, password, expert, chain)

is_debug = logging.getLogger().getEffectiveLevel() == logging.DEBUG
Expand Down Expand Up @@ -539,7 +539,7 @@ def can_sign_taproot(self) -> bool:
return isinstance(self.client, NewClient)


def enumerate(password: str = '') -> List[Dict[str, Any]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, Any]]:
results = []
devices = []
devices.extend(hid.enumerate(LEDGER_VENDOR_ID, 0))
Expand Down
12 changes: 8 additions & 4 deletions hwilib/devices/trezor.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,16 @@ class TrezorClient(HardwareWalletClient):
def __init__(
self,
path: str,
password: str = "",
password: Optional[str] = None,
expert: bool = False,
chain: Chain = Chain.MAIN,
hid_ids: Set[Tuple[int, int]] = HID_IDS,
webusb_ids: Set[Tuple[int, int]] = WEBUSB_IDS,
sim_path: str = SIMULATOR_PATH,
model: Optional[TrezorModel] = None
) -> None:
if password is None:
password = ""
super(TrezorClient, self).__init__(path, password, expert, chain)
self.simulator = False
transport = get_path_transport(path, hid_ids, webusb_ids, sim_path)
Expand Down Expand Up @@ -326,6 +328,8 @@ def _check_unlocked(self) -> None:
self.client.ui.disallow_passphrase()
if self.client.features.pin_protection and not self.client.features.unlocked:
raise DeviceNotReadyError('{} is locked. Unlock by using \'promptpin\' and then \'sendpin\'.'.format(self.type))
if self.client.features.passphrase_protection and self.password is None:
raise NoPasswordError("Passphrase protection is enabled, passphrase must be provided")

def _supports_external(self) -> bool:
if self.client.features.model == "1" and self.client.version <= (1, 10, 5):
Expand Down Expand Up @@ -843,7 +847,7 @@ def can_sign_taproot(self) -> bool:
return bool(self.client.version >= (1, 10, 4))


def enumerate(password: str = "") -> List[Dict[str, Any]]:
def enumerate(password: Optional[str] = None) -> List[Dict[str, Any]]:
results = []
devs = hid.HidTransport.enumerate()
devs.extend(webusb.WebUsbTransport.enumerate())
Expand Down Expand Up @@ -876,8 +880,8 @@ def enumerate(password: str = "") -> List[Dict[str, Any]]:
d_data['needs_passphrase_sent'] = False
if d_data['needs_pin_sent']:
raise DeviceNotReadyError('Trezor is locked. Unlock by using \'promptpin\' and then \'sendpin\'.')
if d_data['needs_passphrase_sent'] and not password:
raise DeviceNotReadyError("Passphrase needs to be specified before the fingerprint information can be retrieved")
if d_data['needs_passphrase_sent'] and password is None:
d_data["warnings"] = [["Passphrase protection enabled but passphrase was not provided. Using default passphrase of the empty string (\"\")"]]
if client.client.features.initialized:
d_data['fingerprint'] = client.get_master_fingerprint().hex()
d_data['needs_passphrase_sent'] = False # Passphrase is always needed for the above to have worked, so it's already sent
Expand Down
2 changes: 1 addition & 1 deletion hwilib/hwwclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HardwareWalletClient(object):
that hardware wallet subclasses should implement.
"""

def __init__(self, path: str, password: str, expert: bool, chain: Chain = Chain.MAIN) -> None:
def __init__(self, path: str, password: Optional[str], expert: bool, chain: Chain = Chain.MAIN) -> None:
"""
:param path: Path to the device as returned by :func:`~hwilib.commands.enumerate`
:param password: A password/passphrase to use with the device.
Expand Down
4 changes: 2 additions & 2 deletions test/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def __init__(self, bitcoind, emulator=None, interface='library', methodName='run
self.emulator = emulator

self.dev_args = ['-t', self.emulator.type, '-d', self.emulator.path, '--chain', 'test']
if self.emulator.password:
if self.emulator.password is not None:
self.dev_args.extend(['-p', self.emulator.password])

self.interface = interface
Expand Down Expand Up @@ -173,7 +173,7 @@ def do_command(self, args):
return process_commands(args)

def get_password_args(self):
if self.emulator.password:
if self.emulator.password is not None:
return ['-p', self.emulator.password]
return []

Expand Down
8 changes: 8 additions & 0 deletions test/test_trezor.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ def test_passphrase(self):
else:
self.fail("Did not enumerate device")
result = self.do_command(self.dev_args + ['-p', 'pass', 'enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
fpr = dev['fingerprint']
break
else:
self.fail("Did not enumerate device")
result = self.do_command(self.dev_args + ['-p', '', 'enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
Expand Down

0 comments on commit e7af45d

Please sign in to comment.