bitcoin.py: add helper func: neuter_bitcoin_address
What changed, and why it matters
This commit is a privacy improvement, not a security fix for an attack. It adds a small helper that truncates Bitcoin addresses before putting them into error messages. The goal is to protect users if those error messages are sent to a crash reporter or shown in logs, so that full addresses (which could identify transactions or balances) are not leaked. No vulnerability is being patched.
No security response needed. Treat as a routine privacy-hardening commit. Reviewers may verify that all error paths exposing addresses now use the helper consistently.
Security signals we found
Privacy hardening for error/telemetry messages
No validation logic changed
No cryptographic or network behavior changed
No bug or vulnerability is patched in the diff
Evidence from the diff
The patch introduces neuter_bitcoin_address(addr) in electrum/bitcoin.py, which returns only the first five and last two characters of an address plus its length. It then replaces direct address inclusion in BitcoinException and ValueError messages in bitcoin.py, synchronizer.py, and wallet_db.py with the neutered form. This is a defensive privacy hardening measure for exception telemetry/logging, not a code change that alters validation logic or fixes an exploitable bug.
Changed components
electrum/bitcoin.pyelectrum/synchronizer.pyelectrum/wallet_db.pyInspect captured patch +18 / −6
diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py
index 562b53e..7afde6e 100644
--- a/electrum/bitcoin.py
+++ b/electrum/bitcoin.py
@@ -439,7 +439,7 @@ def script_to_address(script: bytes, *, net=None) -> Optional[str]:
def address_to_script(addr: str, *, net=None) -> bytes:
if net is None: net = constants.net
if not is_address(addr, net=net):
- raise BitcoinException(f"invalid bitcoin address: {addr}")
+ raise BitcoinException(f"invalid bitcoin address: {neuter_bitcoin_address(addr)}")
witver, witprog = segwit_addr.decode_segwit_address(net.SEGWIT_HRP, addr)
if witprog is not None:
if not (0 <= witver <= 16):
@@ -455,6 +455,17 @@ def address_to_script(addr: str, *, net=None) -> bytes:
return script
+def neuter_bitcoin_address(addr: str) -> str:
+ """Truncate a bitcoin address, for display in errors that might get sent to the crash reporter,
+ to reduce harm to the user's privacy.
+ """
+ assert isinstance(addr, str), type(addr)
+ if len(addr) <= 7:
+ return addr
+ neutered_addr = addr[:5] + '..' + addr[-2:]
+ return f"{neutered_addr!r} (len={len(addr)})"
+
+
class OnchainOutputType(Enum):
"""Opaque types of scriptPubKeys.
In case of p2sh, p2wsh and similar, no knowledge of redeem script, etc.
@@ -470,7 +481,7 @@ def address_to_payload(addr: str, *, net=None) -> Tuple[OnchainOutputType, bytes
"""Return (type, pubkey hash / witness program) for an address."""
if net is None: net = constants.net
if not is_address(addr, net=net):
- raise BitcoinException(f"invalid bitcoin address: {addr}")
+ raise BitcoinException(f"invalid bitcoin address: {neuter_bitcoin_address(addr)}")
witver, witprog = segwit_addr.decode_segwit_address(net.SEGWIT_HRP, addr)
if witprog is not None:
if witver == 0:
diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py
index c231eab..ed7369d 100644
--- a/electrum/synchronizer.py
+++ b/electrum/synchronizer.py
@@ -33,7 +33,7 @@ from aiorpcx import run_in_thread, RPCError
from . import util
from .transaction import Transaction, PartialTransaction
from .util import make_aiohttp_session, NetworkJobOnDefaultServer, random_shuffled_copy, OldTaskGroup
-from .bitcoin import address_to_scripthash, is_address
+from .bitcoin import address_to_scripthash, is_address, neuter_bitcoin_address
from .logging import Logger
from .interface import GracefulDisconnect, NetworkTimeout
@@ -84,12 +84,12 @@ class SynchronizerBase(NetworkJobOnDefaultServer):
self.session.unsubscribe(self.status_queue)
def add(self, addr: str) -> None:
- if not is_address(addr): raise ValueError(f"invalid bitcoin address {addr}")
+ if not is_address(addr): raise ValueError(f"invalid bitcoin address {neuter_bitcoin_address(addr)}")
self._adding_addrs.add(addr) # this lets is_up_to_date already know about addr
async def _add_address(self, addr: str):
try:
- if not is_address(addr): raise ValueError(f"invalid bitcoin address {addr}")
+ if not is_address(addr): raise ValueError(f"invalid bitcoin address {neuter_bitcoin_address(addr)}")
if addr in self.requested_addrs: return
self.requested_addrs.add(addr)
await self.taskgroup.spawn(self._subscribe_to_address, addr)
diff --git a/electrum/wallet_db.py b/electrum/wallet_db.py
index efb1ed7..a5d146a 100644
--- a/electrum/wallet_db.py
+++ b/electrum/wallet_db.py
@@ -1415,9 +1415,10 @@ class WalletDBUpgrader(Logger):
if len(recv_addrs) > 0:
first_address = recv_addrs[0]
if not bitcoin.is_address(first_address):
+ neutered_addr = first_address[:5] + '..' + first_address[-2:]
raise WalletFileException(
f"The addresses in this wallet are not bitcoin addresses. "
- f"e.g. {first_address!r}")
+ f"e.g. {neutered_addr} (len={len(first_address)})")
# if so, save genesis hash
self.data['genesis_blockhash'] = constants.net.GENESIS
self.data['seed_version'] = 71
Why this scored 22/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.