keystore: get_private_keys should not return None
What changed, and why it matters
This commit fixes a small but real bug in Electrum's handling of private key imports. Previously, when a user pasted an empty or invalid list of private keys, the function returned 'None' (meaning 'nothing'). Some parts of the code treated 'None' as if keys were present, which could let an empty or invalid import proceed unexpectedly. The fix makes the function return an empty list instead, so callers correctly recognize there are no valid keys. The direct security impact is limited because the user still has to initiate an import and supply input, but it removes a logic trap that could mishandle private key material.
Treat as a low-severity hardening fix. Review related callers of get_private_keys to ensure no other code still relies on None semantics. No urgent patch deployment required, but include in normal release cycle.
Security signals we found
Return-value sentinel bug: None vs empty list confusion
Private key import code path affected
UI validation logic changed to use bool() instead of 'is not None'
Type annotation tightened from Optional[Sequence[str]] to Sequence[str]
Issue reference #10200 suggests user-reported bug
Evidence from the diff
The patch changes keystore.get_private_keys() to return an empty Sequence[str] ([] instead of None) when the input contains no valid private keys. It updates the Qt main_window caller to check truthiness (bool()) rather than ‘is not None’, and adjusts the type annotation in wallet.py’s import_private_keys to accept Sequence[str]. The issue (#10200) appears to be that callers checking ‘is not None’ would treat an empty/invalid key list as valid, potentially allowing import_private_keys to run with no keys or misleading UI state. The fix aligns return type and caller logic, reducing the chance of mishandling private key import flows.
Changed components
electrum/keystore.py:get_private_keyselectrum/gui/qt/main_window.py:private key import dialogelectrum/wallet.py:Imported_Wallet.import_private_keysInspect captured patch +5 / −5
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index 45783aa..bfef3e3 100644
--- a/electrum/gui/qt/main_window.py
+++ b/electrum/gui/qt/main_window.py
@@ -2592,14 +2592,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
if bitcoin.is_address(addr):
return addr
- def get_pk(*, raise_on_error=False):
+ def get_pk(*, raise_on_error=False) -> Sequence[str]:
text = str(keys_e.toPlainText())
return keystore.get_private_keys(text, raise_on_error=raise_on_error)
def on_edit():
valid_privkeys = False
try:
- valid_privkeys = get_pk(raise_on_error=True) is not None
+ valid_privkeys = bool(get_pk(raise_on_error=True))
except Exception as e:
button.setToolTip(f'{_("Error")}: {repr(e)}')
else:
diff --git a/electrum/keystore.py b/electrum/keystore.py
index 340dcd4..65c4ec3 100644
--- a/electrum/keystore.py
+++ b/electrum/keystore.py
@@ -1134,7 +1134,7 @@ def is_address_list(text: str) -> bool:
return bool(parts) and all(bitcoin.is_address(x) for x in parts)
-def get_private_keys(text: str, *, allow_spaces_inside_key=True, raise_on_error=False) -> Optional[Sequence[str]]:
+def get_private_keys(text: str, *, allow_spaces_inside_key=True, raise_on_error=False) -> Sequence[str]:
if allow_spaces_inside_key: # see #1612
parts = text.split('\n')
parts = map(lambda x: ''.join(x.split()), parts)
@@ -1143,7 +1143,7 @@ def get_private_keys(text: str, *, allow_spaces_inside_key=True, raise_on_error=
parts = text.split()
if bool(parts) and all(bitcoin.is_private_key(x, raise_on_error=raise_on_error) for x in parts):
return parts
- return None
+ return []
def is_private_key_list(text: str, *, allow_spaces_inside_key: bool = True, raise_on_error: bool = False) -> bool:
diff --git a/electrum/wallet.py b/electrum/wallet.py
index e28778a..843d521 100644
--- a/electrum/wallet.py
+++ b/electrum/wallet.py
@@ -3755,7 +3755,7 @@ class Imported_Wallet(Simple_Wallet):
x = self.db.get_imported_address(address)
return x.get('pubkey') if x else None
- def import_private_keys(self, keys: List[str], password: Optional[str], *,
+ def import_private_keys(self, keys: Sequence[str], password: Optional[str], *,
write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]:
good_addr = [] # type: List[str]
bad_keys = [] # type: List[Tuple[str, str]]
Why this scored 33/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.