wallet: refactor: add encrypt_message method, consolidated from UIs
What changed, and why it matters
This commit is a simple code cleanup: it moves the message-encryption logic from two separate places (the command-line tool and the Qt graphical interface) into a single shared method on the wallet class. The actual encryption behavior is unchanged, and no security vulnerability is introduced or fixed.
No security action required; treat as normal maintenance refactor.
Security signals we found
No change to cryptographic primitives or parameters
No change to trust boundaries or input sources
Pure refactor consolidating duplicate code
Error handling change is cosmetic, not a security boundary change
Evidence from the diff
The patch refactors ECIES message encryption by adding Abstract_Wallet.encrypt_message() and replacing duplicate implementations in Commands.encrypt() and ElectrumWindow.encrypt_message(). The new helper performs identical validation (hex pubkey, string/bytes message, valid EC point) and calls crypto.ecies_encrypt_message() with the same parameters. Error handling is slightly broadened in the Qt path (UserFacingException is caught generically), but the underlying cryptographic operation and inputs remain the same.
Changed components
electrum/wallet.pyelectrum/commands.pyelectrum/gui/qt/main_window.pyInspect captured patch +26 / −18
diff --git a/electrum/commands.py b/electrum/commands.py
index f81628f..3f83148 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -1226,15 +1226,7 @@ class Commands(Logger):
arg:str:pubkey:Public key
arg:str:message:Clear text message. Use quotes if it contains spaces.
"""
- if not is_hex_str(pubkey):
- raise UserFacingException(f"pubkey must be a hex string instead of {repr(pubkey)}")
- try:
- message = to_bytes(message)
- except TypeError:
- 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')
+ return Abstract_Wallet.encrypt_message(pubkey=pubkey, message=message)
@command('wp')
async def decrypt(self, pubkey, encrypted, password=None, wallet: Abstract_Wallet = None) -> str:
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index 4811579..de31458 100644
--- a/electrum/gui/qt/main_window.py
+++ b/electrum/gui/qt/main_window.py
@@ -2253,17 +2253,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
pubkey_e: QLineEdit,
encrypted_e: QTextEdit,
) -> None:
- from electrum import crypto
message = message_e.toPlainText()
- message = message.encode('utf-8')
try:
- public_key = ecc.ECPubkey(bfh(pubkey_e.text()))
- except BaseException as e:
- self.logger.exception('Invalid Public key')
- self.show_warning(_('Invalid Public key'))
+ encrypted = self.wallet.encrypt_message(
+ pubkey=pubkey_e.text(),
+ message=message,
+ )
+ except UserFacingException as e:
+ self.show_warning(str(e))
return
- encrypted = crypto.ecies_encrypt_message(public_key, message)
- encrypted_e.setText(encrypted.decode('ascii'))
+ encrypted_e.setText(encrypted)
def encrypt_message(self, address: str = "") -> None:
d = WindowModalDialog(self, _('Encrypt/decrypt Message'))
diff --git a/electrum/wallet.py b/electrum/wallet.py
index e29ccd4..5c02870 100644
--- a/electrum/wallet.py
+++ b/electrum/wallet.py
@@ -48,6 +48,7 @@ from aiorpcx import ignore_after, run_in_thread
from . import util, keystore, transaction, bitcoin, coinchooser, bip32, descriptor
from . import constants
+from . import crypto
from .i18n import _
from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath, convert_bip32_strpath_to_intpath
from .logging import get_logger, Logger
@@ -56,7 +57,7 @@ from .util import (
WalletFileException, BitcoinException, InvalidPassword, format_time, timestamp_to_datetime,
Satoshis, Fiat, TxMinedInfo, quantize_feerate, OrderedDictWithIndex, multisig_type, parse_max_spend,
OnchainHistoryItem, read_json_file, write_json_file, UserFacingException, FileImportFailed, EventListener,
- event_listener
+ event_listener, is_hex_str,
)
from .bitcoin import COIN, is_address, is_minikey, relayfee, dust_threshold, DummyAddress, DummyAddressUsedInTxException
from .keystore import (
@@ -3257,6 +3258,22 @@ class Abstract_Wallet(ABC, Logger, EventListener):
index = self.get_address_index(addr)
return self.keystore.decrypt_message(index, message, password)
+ @classmethod
+ def encrypt_message(cls, *, pubkey: Any | str, message: Any | str) -> str:
+ try:
+ message = util.to_bytes(message)
+ except TypeError:
+ raise UserFacingException(f"message must be a str instead of {type(message)}") from None
+ if not is_hex_str(pubkey):
+ raise UserFacingException(f"pubkey must be a hex string instead of {type(pubkey)}")
+ pubkey_bytes = bytes.fromhex(pubkey)
+ try:
+ eckey = ecc.ECPubkey(pubkey_bytes)
+ except ecc.InvalidECPointException as e:
+ raise UserFacingException(_("Invalid Public key")) from e
+ encrypted = crypto.ecies_encrypt_message(eckey, message)
+ return encrypted.decode("ascii")
+
@abstractmethod
def pubkeys_to_address(self, pubkeys: Sequence[str]) -> Optional[str]:
pass
Why this scored 15/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.