wallet: refactor: add verify_message method, consolidated from UIs
What changed, and why it matters
This commit is a routine code cleanup: it moves message-signature verification out of three user-interface files into a single shared helper method in the wallet module. There is no security-relevant behavior change visible in the diff. The new helper behaves the same as the old inline code, and the remaining edits only tighten some unrelated error messages.
No action required. Treat as normal refactoring.
Security signals we found
No security-relevant functional change
Refactoring only: code deduplication
Exception handling broadened safely (ValueError covers binascii.Error and unicode errors)
No new dependencies or external inputs introduced
Evidence from the diff
The patch refactors verify_message into a classmethod on Abstract_Wallet. It removes duplicated base64 decoding and address validation from commands.py, qedaemon.py, and main_window.py, and centralizes them in wallet.py. The new helper catches ValueError instead of binascii.Error, which is a superset and still safe. Two unrelated error-message strings in encrypt/decrypt_message commands were narrowed from ‘string-like object’ to ‘str’. No cryptographic logic, parsing rules, or trust decisions were changed.
Changed components
electrum/wallet.pyelectrum/commands.pyelectrum/gui/qml/qedaemon.pyelectrum/gui/qt/main_window.pyInspect captured patch +26 / −30
diff --git a/electrum/commands.py b/electrum/commands.py
index 37686f9..f81628f 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -925,12 +925,7 @@ class Commands(Logger):
arg:str:message:Clear text message. Use quotes if it contains spaces.
arg:str:signature:The signature, base64-encoded.
"""
- try:
- sig = base64.b64decode(signature, validate=True)
- except binascii.Error:
- return False
- message = util.to_bytes(message)
- return bitcoin.verify_usermessage_with_address(address, sig, message)
+ return Abstract_Wallet.verify_message(address=address, signature=signature, message=message)
def _get_fee_policy(self, fee: str, feerate: str):
if fee is not None and feerate is not None:
@@ -1236,7 +1231,7 @@ class Commands(Logger):
try:
message = to_bytes(message)
except TypeError:
- raise UserFacingException(f"message must be a string-like object instead of {repr(message)}")
+ raise UserFacingException(f"message must be a str instead of {repr(message)}")
public_key = ecc.ECPubkey(bfh(pubkey))
encrypted = crypto.ecies_encrypt_message(public_key, message)
return encrypted.decode('utf-8')
@@ -1250,8 +1245,8 @@ class Commands(Logger):
"""
if not is_hex_str(pubkey):
raise UserFacingException(f"pubkey must be a hex string instead of {repr(pubkey)}")
- if not isinstance(encrypted, (str, bytes, bytearray)):
- raise UserFacingException(f"encrypted must be a string-like object instead of {repr(encrypted)}")
+ if not isinstance(encrypted, str):
+ raise UserFacingException(f"encrypted must be a str instead of {repr(encrypted)}")
decrypted = wallet.decrypt_message(pubkey, encrypted, password)
return decrypted.decode('utf-8')
diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py
index 679bf5a..2d1983d 100644
--- a/electrum/gui/qml/qedaemon.py
+++ b/electrum/gui/qml/qedaemon.py
@@ -1,4 +1,3 @@
-import base64
import os
import threading
from typing import TYPE_CHECKING
@@ -11,9 +10,8 @@ from electrum.logging import get_logger
from electrum.util import WalletFileException, standardize_path, InvalidPassword, send_exception_to_crash_reporter
from electrum.plugin import run_hook
from electrum.lnchannel import ChannelState
-from electrum.bitcoin import is_address
-from electrum.bitcoin import verify_usermessage_with_address
from electrum.storage import StorageReadWriteError, WalletStorage
+from electrum.wallet import Abstract_Wallet
from .auth import AuthMixin, auth_protect
from .qefx import QEFX
@@ -506,16 +504,9 @@ class QEDaemon(AuthMixin, QObject):
@pyqtSlot(str, str, str, result=bool)
def verifyMessage(self, address, message, signature):
address = address.strip()
- message = message.strip().encode('utf-8')
- if not is_address(address):
- return False
- try:
- # This can throw on invalid base64
- sig = base64.b64decode(str(signature.strip()), validate=True)
- verified = verify_usermessage_with_address(address, sig, message)
- except Exception as e:
- verified = False
- return verified
+ message = message.strip()
+ signature = signature.strip()
+ return Abstract_Wallet.verify_message(address=address, signature=signature, message=message)
@pyqtSlot(str, result=int)
def passwordStrength(self, password):
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index 9363dc2..4811579 100644
--- a/electrum/gui/qt/main_window.py
+++ b/electrum/gui/qt/main_window.py
@@ -2173,16 +2173,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
signature_e: ButtonsTextEdit,
) -> None:
address = address_e.text().strip()
- message = message_e.toPlainText().strip().encode('utf-8')
+ message = message_e.toPlainText().strip()
if not bitcoin.is_address(address):
self.show_message(_('Invalid Bitcoin address.'))
return
- try:
- # This can throw on invalid base64
- sig = base64.b64decode(str(signature_e.toPlainText()), validate=True)
- verified = bitcoin.verify_usermessage_with_address(address, sig, message)
- except Exception as e:
- verified = False
+ verified = self.wallet.verify_message(
+ address=address, signature=str(signature_e.toPlainText()), message=message)
if verified:
self.show_message(_("Signature verified"))
else:
diff --git a/electrum/wallet.py b/electrum/wallet.py
index c5252e1..e29ccd4 100644
--- a/electrum/wallet.py
+++ b/electrum/wallet.py
@@ -41,6 +41,7 @@ import threading
import enum
import asyncio
from dataclasses import dataclass
+import base64
import electrum_ecc as ecc
from aiorpcx import ignore_after, run_in_thread
@@ -3238,7 +3239,20 @@ class Abstract_Wallet(ABC, Logger, EventListener):
assert script_type != "address"
return self.keystore.sign_message(index, message, password, script_type=script_type)
- def decrypt_message(self, pubkey: str, message, password) -> bytes:
+ @classmethod
+ def verify_message(cls, *, address: str, signature: str, message: str) -> bool:
+ if not is_address(address):
+ return False
+ try:
+ sig = base64.b64decode(signature, validate=True)
+ except ValueError:
+ # note: unicode chars in signature would result in ValueError,
+ # so it is insufficient to catch binascii.Error(ValueError)
+ return False
+ message = util.to_bytes(message)
+ return bitcoin.verify_usermessage_with_address(address, sig, message)
+
+ def decrypt_message(self, pubkey: str, message: str, password) -> bytes:
addr = self.pubkeys_to_address([pubkey])
index = self.get_address_index(addr)
return self.keystore.decrypt_message(index, message, password)
Why this scored 13/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.