Merge branch '202604_pr10603_ledger'

adapt Ledger_Client_Legacy to work with newer ledger bitcoin app

manual merge of https://github.com/spesmilo/electrum/pull/10603
This commit is contained in:
SomberNight
2026-04-24 13:57:23 +00:00
3 changed files with 45 additions and 10 deletions
+41 -6
View File
@@ -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)
@@ -1216,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