From 3bc25e51a43d0d7a7b9b76f225be1844d9ccc5c8 Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 3 Feb 2026 00:09:18 +0530 Subject: [PATCH 1/2] feat: list/enumerate cypherock x1 device --- hwilib/devices/__init__.py | 3 ++- hwilib/devices/cypherock.py | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 hwilib/devices/cypherock.py diff --git a/hwilib/devices/__init__.py b/hwilib/devices/__init__.py index 77fa0ffba..ced3d1091 100644 --- a/hwilib/devices/__init__.py +++ b/hwilib/devices/__init__.py @@ -5,5 +5,6 @@ 'digitalbitbox', 'coldcard', 'bitbox02', - 'jade' + 'jade', + 'cypherock' ] diff --git a/hwilib/devices/cypherock.py b/hwilib/devices/cypherock.py new file mode 100644 index 000000000..bee5a6b4a --- /dev/null +++ b/hwilib/devices/cypherock.py @@ -0,0 +1,39 @@ +""" +Cypherock X1 +************ +""" + +import hid + +from typing import ( + Any, + Dict, + List, + Optional, +) +from ..common import ( + Chain, +) + +CYPHEROCK_VENDOR_ID = 0x3503 +CYPHEROCK_PRODUCT_ID = 0x0103 + +def enumerate(password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN, allow_emulators: bool = False) -> List[Dict[str, Any]]: + results = [] + devices = [] + devices.extend(hid.enumerate(CYPHEROCK_VENDOR_ID, CYPHEROCK_PRODUCT_ID)) + + for d in devices: + if d.get('path') is not None and d.get('serial_number') is not None: + d_data: Dict[str, Any] = {} + d_data['type'] = 'cypherock' + d_data['model'] = 'cypherock-x1' + d_data['label'] = None + d_data['needs_pin_sent'] = False + d_data['needs_passphrase_sent'] = False + d_data['path'] = d['path'].decode() + d_data['fingerprint'] = d['serial_number'] # Using the serial number as the fingerprint for now + + results.append(d_data) + + return results From 5c4a98fc03ecfbc41f2fa20549fc8d93539cc300 Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 3 Feb 2026 01:19:59 +0530 Subject: [PATCH 2/2] feat: added placeholder cypherock client class --- hwilib/devices/cypherock.py | 129 +++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/hwilib/devices/cypherock.py b/hwilib/devices/cypherock.py index bee5a6b4a..a132caa8a 100644 --- a/hwilib/devices/cypherock.py +++ b/hwilib/devices/cypherock.py @@ -3,21 +3,144 @@ ************ """ -import hid - from typing import ( Any, Dict, List, Optional, + Union ) + +from hwilib.descriptor import MultisigDescriptor +from hwilib.errors import DeviceConnectionError, UnavailableActionError +from hwilib.hwwclient import HardwareWalletClient +from hwilib.key import ExtendedKey +from hwilib.psbt import PSBT + from ..common import ( + AddressType, Chain, ) +import hid + CYPHEROCK_VENDOR_ID = 0x3503 CYPHEROCK_PRODUCT_ID = 0x0103 +class CypherockClient(HardwareWalletClient): + """ + The `CypherockClient` is a `HardwareWalletClient` for interacting with Cypherock X1 devices. + """ + + def __init__(self, path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN) -> None: + super(CypherockClient, self).__init__(path, password, expert, chain) + + try: + self.device = hid.device() + self.device.open_path(path.encode()) + except Exception as e: + raise DeviceConnectionError(f"Failed to connect to Cypherock X1: {e}") + + def get_master_fingerprint(self) -> bytes: + return bytes.fromhex(self.device.get_serial_number_string()) + + def get_pubkey_at_path(self, path: str) -> ExtendedKey: + raise NotImplementedError("The CypherockClient class " + "has not yet implemented this method") + + def sign_tx(self, tx: PSBT) -> PSBT: + raise NotImplementedError("The CypherockClient class " + "has not yet implemented this method") + + def sign_message(self, message: Union[str, bytes], keypath: str) -> str: + raise NotImplementedError("The CypherockClient class " + "has not yet implemented this method") + + def display_singlesig_address( + self, + keypath: str, + addr_type: AddressType, + ) -> str: + raise NotImplementedError("The CypherockClient class " + "has not yet implemented this method") + + def display_multisig_address( + self, + addr_type: AddressType, + multisig: MultisigDescriptor, + ) -> str: + raise NotImplementedError("The CypherockClient class " + "has not yet implemented this method") + + def setup_device(self, label: str = "", passphrase: str = "") -> bool: + """ + Cypherock X1 does not support setup via software. + + :raises UnavailableActionError: Always, this function is unavailable + """ + raise UnavailableActionError('The Cypherock X1 does not support software setup') + + def wipe_device(self) -> bool: + """ + Cypherock X1 does not support wiping via software. + + :raises UnavailableActionError: Always, this function is unavailable + """ + raise UnavailableActionError('The Cypherock X1 does not support wiping via software') + + def restore_device(self, label: str = "", word_count: int = 24) -> bool: + """ + Cypherock X1 does not support restoring via software. + + :raises UnavailableActionError: Always, this function is unavailable + """ + raise UnavailableActionError('The Cypherock X1 does not support restoring via software') + + def backup_device(self, label: str = "", passphrase: str = "") -> bool: + """ + Cypherock X1 does not support backing up via software. + + :raises UnavailableActionError: Always, this function is unavailable + """ + raise UnavailableActionError('The Cypherock X1 does not support creating a backup via software') + + def close(self) -> None: + self.device.close() + + def prompt_pin(self) -> bool: + """ + Cypherock X1 does not need a PIN sent from the host. + + :raises UnavailableActionError: Always, this function is unavailable + """ + raise UnavailableActionError('The Cypherock X1 does not need a PIN sent from the host') + + def send_pin(self, pin: str) -> bool: + """ + Cypherock X1 does not need a PIN sent from the host. + + :raises UnavailableActionError: Always, this function is unavailable + """ + raise UnavailableActionError('The Cypherock X1 does not need a PIN sent from the host') + + def toggle_passphrase(self) -> bool: + """ + Cypherock X1 does not support toggling passphrase from the host. + + :raises UnavailableActionError: Always, this function is unavailable + """ + raise UnavailableActionError('The Cypherock X1 does not support toggling passphrase from the host') + + def can_sign_taproot(self) -> bool: + """ + Cypherock X1 supports Taproot if the Firmware version is greater than or equal to 0.6.3089 + + :returns: True if Firmware version is greater than or equal to 0.6.3089, False otherwise. + """ + raise NotImplementedError("The CypherockClient class " + "has not yet implemented this method") + + def enumerate(password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN, allow_emulators: bool = False) -> List[Dict[str, Any]]: results = [] devices = [] @@ -32,7 +155,7 @@ def enumerate(password: Optional[str] = None, expert: bool = False, chain: Chain d_data['needs_pin_sent'] = False d_data['needs_passphrase_sent'] = False d_data['path'] = d['path'].decode() - d_data['fingerprint'] = d['serial_number'] # Using the serial number as the fingerprint for now + d_data['fingerprint'] = bytes.fromhex(d['serial_number']).hex() # Using the serial number as the fingerprint for now results.append(d_data)