From b3808b7920af3f991af3a32871227973902fc1cc Mon Sep 17 00:00:00 2001 From: Ilya Artemov Date: Thu, 16 Apr 2026 10:23:26 +0200 Subject: [PATCH 1/3] Using GET_MASTER_FINGERPRINT for Legacy Client to get the root public key fingerprint --- electrum/plugins/ledger/ledger.py | 45 +++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index 408c82fad..5dcc56c78 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -347,8 +347,9 @@ class Ledger_Client(HardwareClientBase, ABC): def __init__(self, *, plugin: HW_PluginBase): HardwareClientBase.__init__(self, plugin=plugin) + @abstractmethod def get_master_fingerprint(self) -> bytes: - return self.request_root_fingerprint_from_device() + pass @abstractmethod def show_address(self, address_path: str, txin_type: str): @@ -390,6 +391,31 @@ class Ledger_Client_Legacy(Ledger_Client): self._product_key = product_key self._soft_device_id = None + def _get_master_fingerprint(self) -> bytes: + """Return the 4-byte master (root) key fingerprint. + + Tries the dedicated GET_MASTER_FINGERPRINT APDU first (INS 0xD0), + which does NOT require DERIVE_MASTER permission. If the device + does not support it (old firmware), falls back to + getWalletPublicKey("") + HASH160. + """ + try: + return self.dongleObject.getMasterFingerprint() + except BTChipException as e: + if e.sw in (0x6d00, 0x6a80): # INS not supported / bad data + _logger.info("getMasterFingerprint APDU not supported (sw=0x%04x), " + "falling back to getWalletPublicKey", e.sw) + else: + raise + return self._get_node_fingerprint("") + + def _get_node_fingerprint(self, bip32_path: str) -> bytes: + """Return the 4-byte fingerprint for an arbitrary BIP32 node + by calling getWalletPublicKey + HASH160. + """ + nodeData = self.dongleObject.getWalletPublicKey(bip32_path) + return hash_160(compress_public_key(nodeData['publicKey']))[0:4] + def is_pairable(self): return True @@ -424,7 +450,7 @@ class Ledger_Client_Legacy(Ledger_Client): # modern ledger can provide xpub without user interaction # (hw1 would prompt for PIN) if not self.is_hw1(): - self._soft_device_id = self.request_root_fingerprint_from_device() + self._soft_device_id = self._get_master_fingerprint().hex() return self._soft_device_id def is_hw1(self) -> bool: @@ -433,6 +459,14 @@ class Ledger_Client_Legacy(Ledger_Client): def device_model_name(self): return LedgerPlugin.device_name_from_product_key(self._product_key) + @runs_in_hwd_thread + def request_root_fingerprint_from_device(self) -> str: + return self._get_master_fingerprint().hex() + + @runs_in_hwd_thread + def get_master_fingerprint(self) -> bytes: + return self._get_master_fingerprint() + @runs_in_hwd_thread def has_usable_connection_with_device(self): try: @@ -460,9 +494,10 @@ class Ledger_Client_Legacy(Ledger_Client): bip32_path = bip32_path[2:] # cut off "m/" if len(bip32_intpath) >= 1: prevPath = bip32.convert_bip32_intpath_to_strpath(bip32_intpath[:-1])[2:] - nodeData = self.dongleObject.getWalletPublicKey(prevPath) - publicKey = compress_public_key(nodeData['publicKey']) - fingerprint_bytes = hash_160(publicKey)[0:4] + if len(prevPath) == 0: + fingerprint_bytes = self._get_master_fingerprint() + else: + fingerprint_bytes = self._get_node_fingerprint(prevPath) childnum_bytes = bip32_intpath[-1].to_bytes(length=4, byteorder="big") else: fingerprint_bytes = bytes(4) From 44570bfa3b7e04510729ee5d453bf970042687fb Mon Sep 17 00:00:00 2001 From: Ilya Artemov Date: Fri, 24 Apr 2026 09:09:00 +0200 Subject: [PATCH 2/3] Bump minimum required version of ledger_bitcoin (build-time and runtime) --- contrib/requirements/requirements-hw.txt | 2 +- electrum/plugins/ledger/ledger.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/requirements/requirements-hw.txt b/contrib/requirements/requirements-hw.txt index 2a6f7d980..a0a8a4c8b 100644 --- a/contrib/requirements/requirements-hw.txt +++ b/contrib/requirements/requirements-hw.txt @@ -14,7 +14,7 @@ hidapi>=0.7.99.post15 libusb1>=1.6 # device plugin: ledger -ledger-bitcoin>=0.2.0,<1.0 +ledger-bitcoin>=0.4.1,<1.0 hidapi # device plugin: coldcard diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index 5dcc56c78..f00497f46 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -1251,7 +1251,7 @@ class Ledger_KeyStore(Hardware_KeyStore): class LedgerPlugin(HW_PluginBase): keystore_class = Ledger_KeyStore - minimum_library = (0, 2, 0) + minimum_library = (0, 4, 1) maximum_library = (1, 0) DEVICE_IDS = [(0x2581, 0x1807), # HW.1 legacy btchip # not supported anymore (but we log an exception) (0x2581, 0x2b7c), # HW.1 transitional production # not supported anymore From 1096ebcd6a196b1ba0f676f09356e8cbb6bf7e31 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 24 Apr 2026 13:51:07 +0000 Subject: [PATCH 3/3] build: update pinned ledger-bitcoin (partial rerun freeze_packages) --- contrib/deterministic-build/requirements-hw.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index 7e9f6d3f0..99956bb9c 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -349,9 +349,9 @@ hidapi==0.14.0.post4 \ idna==3.10 \ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 -ledger-bitcoin==0.4.0 \ - --hash=sha256:2242452e78cf4b57c8b8d3509e831860fd4851b0a1bfab95f2f5e3f47d4d1500 \ - --hash=sha256:a33e78710671ec21e1003d0483406e955b48866ccf515fd8e7d8d81f4e1c1cf9 +ledger-bitcoin==0.4.1 \ + --hash=sha256:3cb4297ed7e557ef98349cdcbd667ef7368c047d6818c7cdcbca7af98b8006b6 \ + --hash=sha256:b1fe1cdfd0f869f1e27968118832b11708904f71840b0a97219a0492ce1a7002 ledgercomm==1.2.1 \ --hash=sha256:015cfc05f16b8c59f8cc1d9fc0b8935923f1fcc3806d33eeb6b0e055b44f5a91 \ --hash=sha256:8ffef5703355b8ec7b73bca325f70288f4d0dafcb299c09833de9c197fb6dd34