Using GET_MASTER_FINGERPRINT for Legacy Client to get the root public key fingerprint
What changed, and why it matters
This commit changes how Electrum's Ledger hardware wallet plugin obtains the wallet's master fingerprint. It adds a new, more direct command (GET_MASTER_FINGERPRINT) for older Ledger devices, while keeping a fallback to the previous method. The change appears intended to avoid requiring a special device permission (DERIVE_MASTER) when only the fingerprint is needed. There is no clear security bug in the patch itself, but it touches sensitive key-handling code and the commit message does not explain whether this fixes a vulnerability or is just a compatibility improvement.
Review the Ledger Python library's implementation of getMasterFingerprint() to confirm it does not silently derive or expose additional key material, and verify that status words 0x6d00 and 0x6a80 are the only safe fallback cases. If this change was made in response to a security report, request the vendor or maintainer to publish an advisory or issue reference.
Security signals we found
Change reduces device permission requirement for obtaining master fingerprint (DERIVE_MASTER no longer needed via new APDU)
Adds fallback code path for older firmware that still uses the previous getWalletPublicKey method
Touches root key fingerprint derivation, a sensitive wallet-identification value
No explicit security bug, CVE, or vulnerability description present in commit or supplied references
Commit title and message describe functionality, not a security fix
Evidence from the diff
The patch refactors Ledger_Client_Legacy in electrum/plugins/ledger/ledger.py. It makes Ledger_Client.get_master_fingerprint() abstract and implements _get_master_fingerprint() in the legacy subclass. This new method first calls dongleObject.getMasterFingerprint() (INS 0xD0), which the code comment says does not require DERIVE_MASTER permission, and falls back to getWalletPublicKey(“”) + HASH160 if the device returns status words 0x6d00 or 0x6a80. The same helper is reused for soft device ID and BIP32 fingerprint derivation. The diff shows a deliberate reduction in permission requirements for reading the root fingerprint, but no explicit security rationale is provided in the commit or references.
Changed components
electrum/plugins/ledger/ledger.pyLedger_Client_Legacy classLedger hardware wallet integrationInspect captured patch +40 / −5
diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py
index 408c82f..5dcc56c 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)
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.