diff --git a/hwilib/_cli.py b/hwilib/_cli.py index e0afa7dd2..14e9eb4cf 100644 --- a/hwilib/_cli.py +++ b/hwilib/_cli.py @@ -57,7 +57,8 @@ def backup_device_handler(args: argparse.Namespace, client: HardwareWalletClient return backup_device(client, label=args.label, backup_passphrase=args.backup_passphrase) def displayaddress_handler(args: argparse.Namespace, client: HardwareWalletClient) -> Dict[str, str]: - return displayaddress(client, desc=args.desc, path=args.path, addr_type=args.addr_type) + return displayaddress(client, desc=args.desc, path=args.path, addr_type=args.addr_type, + wallet_id=args.wallet, change=args.change, index=args.idx) def enumerate_handler(args: argparse.Namespace) -> List[Dict[str, Any]]: return enumerate(password=args.password, expert=args.expert, chain=args.chain, allow_emulators=args.allow_emulators) @@ -194,7 +195,10 @@ def get_parser() -> HWIArgumentParser: group = displayaddr_parser.add_mutually_exclusive_group(required=True) group.add_argument('--desc', help='Output Descriptor. E.g. wpkh([00000000/84h/0h/0h]xpub.../0/0), where 00000000 must match --fingerprint and xpub can be obtained with getxpub. See doc/descriptors.md in Bitcoin Core') 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/*``') + group.add_argument('--wallet', help='Unique identifier of the wallet enrolled on the device') displayaddr_parser.add_argument("--addr-type", help="The address type to display", type=AddressType.argparse, choices=list(AddressType), default=AddressType.WIT) # type: ignore + displayaddr_parser.add_argument("--change", action='store_true', help="Use internal chain") # type: ignore + displayaddr_parser.add_argument("--idx", type=int, default=0, help="The index for desired address") # type: ignore displayaddr_parser.set_defaults(func=displayaddress_handler) setupdev_parser = subparsers.add_parser('setup', help='Setup a device. Passphrase protection uses the password given by -p. Requires interactive mode') diff --git a/hwilib/commands.py b/hwilib/commands.py index 6d192aa5f..3e6a5d707 100644 --- a/hwilib/commands.py +++ b/hwilib/commands.py @@ -441,7 +441,10 @@ def displayaddress( client: HardwareWalletClient, path: Optional[str] = None, desc: Optional[str] = None, - addr_type: AddressType = AddressType.WIT + addr_type: AddressType = AddressType.WIT, + wallet_id: Optional[str] = None, + change: bool = False, + index: int = 0, ) -> Dict[str, str]: """ Display an address on the device for client. @@ -451,6 +454,9 @@ def displayaddress( :param path: The path of the address to display. Mutually exclusive with ``desc`` :param desc: The descriptor to display the address for. Mutually exclusive with ``path`` :param addr_type: The address type to return. Only works with ``path`` + :param wallet_id: Name of the complex wallet enrolled on the HWW device + :param change: Use internal chain. Only works with ``wallet_id`` + :param index: Address index. Only works with ``wallet_id`` :return: A dictionary containing the address displayed. Returned as ``{"address": }``. :raises: BadArgumentError: if an argument is malformed, missing, or conflicts. @@ -492,6 +498,13 @@ def displayaddress( elif isinstance(descriptor, TRDescriptor): addr_type = AddressType.TAP return {"address": client.display_singlesig_address(pubkey.get_full_derivation_path(0), addr_type)} + + elif wallet_id is not None: + if not getattr(client, "display_address_by_wallet_id"): + raise UnavailableActionError("No support for querying address by wallet id") + + return {"address": client.display_address_by_wallet_id(wallet_id, change, index)} + raise BadArgumentError("Missing both path and descriptor") def setup_device(client: HardwareWalletClient, label: str = "", backup_passphrase: str = "") -> Dict[str, bool]: diff --git a/hwilib/devices/coldcard.py b/hwilib/devices/coldcard.py index 1efe889b9..ff64cdb28 100644 --- a/hwilib/devices/coldcard.py +++ b/hwilib/devices/coldcard.py @@ -281,6 +281,9 @@ def display_multisig_address( addr_type: AddressType, multisig: MultisigDescriptor, ) -> str: + if self.is_edge: + raise UnavailableActionError("Coldcard EDGE does not support this command." + " Use displayaddress with 'wallet_name' parameter") if not multisig.is_sorted: raise BadArgumentError("Coldcards only allow sortedmulti descriptors") @@ -321,6 +324,14 @@ def display_multisig_address( self.device.send_recv(CCProtocolPacker.sim_keypress(b'y')) return address + @coldcard_exception + def display_address_by_wallet_id(self, name: str, change: bool, index: int) -> str: + if not self.is_edge: + raise UnavailableActionError("Coldcard does not support this command." + " Use displayaddress with 'desc' parameter") + return self.device.send_recv(CCProtocolPacker.miniscript_address(name, change, index), + timeout=None) + def setup_device(self, label: str = "", passphrase: str = "") -> bool: """ The Coldcard does not support setup via software. diff --git a/hwilib/hwwclient.py b/hwilib/hwwclient.py index 565afcf44..da7ca0ea1 100644 --- a/hwilib/hwwclient.py +++ b/hwilib/hwwclient.py @@ -135,6 +135,23 @@ def display_multisig_address( raise NotImplementedError("The HardwareWalletClient base class " "does not implement this method") + def display_address_by_wallet_id( + self, + uid: str, + change: bool, + index: int + ) -> str: + """ + Display and return the address of wallet enrolled on a device with specific id. + + :param uid: Unique identifier of wallet enrolled on the device + :param change: Return address from internal chain. + :param index: Return address from index. + :return: The retrieved address also being shown by the device + """ + raise NotImplementedError("The HardwareWalletClient base class " + "does not implement this method") + def wipe_device(self) -> bool: """ Wipe the device.