diff --git a/hwilib/cli.py b/hwilib/cli.py index bca802d2b..1d5d014e7 100644 --- a/hwilib/cli.py +++ b/hwilib/cli.py @@ -15,7 +15,7 @@ def backup_device_handler(args, client): return backup_device(client, label=args.label, backup_passphrase=args.backup_passphrase) def displayaddress_handler(args, client): - return displayaddress(client, path=args.path, sh_wpkh=args.sh_wpkh, wpkh=args.wpkh) + return displayaddress(client, desc=args.desc, path=args.path, sh_wpkh=args.sh_wpkh, wpkh=args.wpkh) def enumerate_handler(args): return enumerate(password=args.password) @@ -95,7 +95,9 @@ def process_commands(args): getkeypool_parser.set_defaults(func=getkeypool_handler) displayaddr_parser = subparsers.add_parser('displayaddress', help='Display an address') - displayaddr_parser.add_argument('path', help='The BIP 32 derivation path of the key embedded in the address') + group = displayaddr_parser.add_mutually_exclusive_group() + group.add_argument('--desc', help='Descriptor request for which to return a descriptor with keys, e.g. wpkh([00000000/84h/1h/0h]/0/*). Alternatively, use --path with --sh_wpkh / --wpkh') + group.add_argument('--path', help='The BIP 32 derivation path of the key embedded in the address, default follows BIP43 convention, e.g. m/84h/0h/0h/1/*') displayaddr_parser.add_argument('--sh_wpkh', action='store_true', help='Display the p2sh-nested segwit address associated with this key path') displayaddr_parser.add_argument('--wpkh', action='store_true', help='Display the bech32 version of the address associated with this key path') displayaddr_parser.set_defaults(func=displayaddress_handler) diff --git a/hwilib/commands.py b/hwilib/commands.py index 24ad85752..0c0e015e8 100644 --- a/hwilib/commands.py +++ b/hwilib/commands.py @@ -9,6 +9,7 @@ from .base58 import xpub_to_address, xpub_to_pub_hex, get_xpub_fingerprint_as_id, get_xpub_fingerprint_hex from os.path import dirname, basename, isfile from .hwwclient import NoPasswordError, UnavailableActionError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError +from .descriptor import Descriptor # Error codes NO_DEVICE_PATH = -1 @@ -184,10 +185,23 @@ def getkeypool(client, path, start, end, internal=False, keypool=False, account= import_data.append(this_import) return import_data -def displayaddress(client, path, sh_wpkh=False, wpkh=False): - if sh_wpkh == True and wpkh == True: - return {'error':'Both `--wpkh` and `--sh_wpkh` can not be selected at the same time.','code':BAD_ARGUMENT} - return client.display_address(path, sh_wpkh, wpkh) +def displayaddress(client, path=None, desc=None, sh_wpkh=False, wpkh=False): + if path is not None: + if sh_wpkh == True and wpkh == True: + return {'error':'Both `--wpkh` and `--sh_wpkh` can not be selected at the same time.','code':BAD_ARGUMENT} + client.display_address(path, sh_wpkh, wpkh) + elif desc is not None: + descriptor = Descriptor.parse(desc, client.is_testnet) + if descriptor is None: + return {'error':'Unable to parse descriptor: ' + desc,'code':BAD_ARGUMENT} + if descriptor.m_path is None: + return {'error':'Descriptor missing origin info: ' + desc,'code':BAD_ARGUMENT} + if descriptor.origin_fingerprint != client.fingerprint: + return {'error':'Descriptor fingerprint does not match device: ' + desc,'code':BAD_ARGUMENT} + xpub = client.get_pubkey_at_path(descriptor.m_path_base)['xpub'] + if descriptor.base_key != xpub: + return {'error':'Key in descriptor does not match device: ' + desc,'code':BAD_ARGUMENT} + client.display_address(descriptor.m_path, descriptor.sh_wpkh, descriptor.wpkh) def setup_device(client, label='', backup_passphrase=''): try: diff --git a/test/test_device.py b/test/test_device.py index f3b95a8d1..393184930 100644 --- a/test/test_device.py +++ b/test/test_device.py @@ -380,15 +380,16 @@ def tearDown(self): self.emulator.stop() def test_display_address_bad_args(self): - result = process_commands(self.dev_args + ['displayaddress', '--sh_wpkh', '--wpkh', 'm/49h/1h/0h/0/0']) + result = process_commands(self.dev_args + ['displayaddress', '--sh_wpkh', '--wpkh', '--path', 'm/49h/1h/0h/0/0']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['code'], -7) def test_display_address(self): - process_commands(self.dev_args + ['displayaddress', 'm/44h/1h/0h/0/0']) - process_commands(self.dev_args + ['displayaddress', '--sh_wpkh', 'm/49h/1h/0h/0/0']) - process_commands(self.dev_args + ['displayaddress', '--wpkh', 'm/84h/1h/0h/0/0']) + process_commands(self.dev_args + ['displayaddress', '--path', 'm/44h/1h/0h/0/0']) + process_commands(self.dev_args + ['displayaddress', '--sh_wpkh', '--path', 'm/49h/1h/0h/0/0']) + process_commands(self.dev_args + ['displayaddress', '--wpkh', '--path', 'm/84h/1h/0h/0/0']) + process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh(m/84h/1h/0h/0/0)']) class TestSignMessage(DeviceTestCase): def setUp(self):