wallet: sweep_preparations to raise UserFacingException on p2sh/etc
What changed, and why it matters
This commit fixes a bug in Electrum's 'sweep' feature, which lets users move funds from a private key into their wallet. Previously, if the private key's address type was unsupported (like some multi-signature or older script types), the app would crash with a confusing internal error instead of showing a friendly message. The patch catches that case and shows a proper user-facing error. It also adds a small guard in the mobile/QML interface to avoid trying to build a sweep transaction before the inputs are ready.
Treat as a routine bug-fix / hardening patch. No urgent security action required. Users should upgrade through normal channels. If running a fork or custom build, ensure the same exception handling and QML guard are applied.
Security signals we found
Crash/error-handling hardening in wallet sweep flow
User-facing exception replaces internal exception to prevent unexpected termination
QML sweep finalizer guards against uninitialized state
No cryptographic, authentication, or authorization changes
Evidence from the diff
The change replaces a raw NotImplementedError with a new typed exception, NotLegacySinglesigScriptType, in descriptor.py. wallet.py’s sweep_preparations now catches that exception and raises a UserFacingException with a translated message. A related call site in Imported_Wallet is updated to catch the new exception type. In the QML GUI, QETxSweepFinalizer now asserts that _txins is set before make_sweep_tx and skips update() if _txins is None, preventing an AttributeError/TypeError on incomplete initialization.
Changed components
electrum/descriptor.pyelectrum/wallet.pyelectrum/gui/qml/qetxfinalizer.pyImported_Wallet sweep flowQML sweep transaction finalizerInspect captured patch +13 / −3
diff --git a/electrum/descriptor.py b/electrum/descriptor.py
index 3a278c4..fcf2dd6 100644
--- a/electrum/descriptor.py
+++ b/electrum/descriptor.py
@@ -1031,6 +1031,9 @@ def parse_descriptor(desc: str) -> 'Descriptor':
#####
+class NotLegacySinglesigScriptType(Exception): pass
+
+
def get_singlesig_descriptor_from_legacy_leaf(*, pubkey: str, script_type: str) -> Optional[Descriptor]:
pubkey = PubkeyProvider.parse(pubkey)
if script_type == 'p2pk':
@@ -1043,7 +1046,7 @@ def get_singlesig_descriptor_from_legacy_leaf(*, pubkey: str, script_type: str)
wpkh = WPKHDescriptor(pubkey=pubkey)
return SHDescriptor(subdescriptor=wpkh)
else:
- raise NotImplementedError(f"unexpected {script_type=}")
+ raise NotLegacySinglesigScriptType(f"unexpected {script_type=}")
def create_dummy_descriptor_from_address(addr: Optional[str]) -> 'Descriptor':
diff --git a/electrum/gui/qml/qetxfinalizer.py b/electrum/gui/qml/qetxfinalizer.py
index accbcfb..368d0c6 100644
--- a/electrum/gui/qml/qetxfinalizer.py
+++ b/electrum/gui/qml/qetxfinalizer.py
@@ -999,6 +999,7 @@ class QETxSweepFinalizer(QETxFinalizer):
def make_sweep_tx(self):
address = self._wallet.wallet.get_receiving_address()
assert self._wallet.wallet.is_mine(address)
+ assert self._txins is not None
coins, keypairs = copy.deepcopy(self._txins)
outputs = [PartialTxOutput.from_address_and_value(address, value='!')]
@@ -1033,6 +1034,9 @@ class QETxSweepFinalizer(QETxFinalizer):
if not self._private_keys:
self._logger.debug('private keys not set, ignoring update()')
return
+ if self._txins is None:
+ self._logger.debug('txins not set, ignoring update()')
+ return
try:
# make unsigned transaction
diff --git a/electrum/wallet.py b/electrum/wallet.py
index 17c8be3..894c3a9 100644
--- a/electrum/wallet.py
+++ b/electrum/wallet.py
@@ -137,7 +137,10 @@ async def sweep_preparations(
async def find_utxos_for_privkey(txin_type: str, privkey: bytes, compressed: bool):
pubkey = ecc.ECPrivkey(privkey).get_public_key_bytes(compressed=compressed)
- desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type)
+ try:
+ desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type)
+ except descriptor.NotLegacySinglesigScriptType:
+ raise UserFacingException(_("Unsupported script-type ({}) for sweeping.").format(txin_type)) from None
await _append_utxos_to_inputs(
inputs=inputs,
network=network,
@@ -3710,7 +3713,7 @@ class Imported_Wallet(Simple_Wallet):
for txin_type in bitcoin.WIF_SCRIPT_TYPES.keys():
try:
addr2 = bitcoin.pubkey_to_address(txin_type, pubkey)
- except NotImplementedError:
+ except descriptor.NotLegacySinglesigScriptType:
pass
else:
if self.db.has_imported_address(addr2):
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.