From 2c962abe518a1d06ef45a67733964b418cb26d1a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 17 Jun 2020 19:25:52 +0200 Subject: [PATCH] network: randomise the order of address subscriptions Before this, we were subscribing to our addresses in their bip32 order, leaking this information to servers. While this leak seems mostly harmless, it is trivial to fix. --- electrum/lnwatcher.py | 6 +++--- electrum/lnworker.py | 6 +++--- electrum/synchronizer.py | 4 ++-- electrum/util.py | 11 ++++++++++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py index 0da725e9f..163ee23ac 100644 --- a/electrum/lnwatcher.py +++ b/electrum/lnwatcher.py @@ -11,7 +11,7 @@ from typing import NamedTuple, Dict from . import util from .sql_db import SqlDB, sql from .wallet_db import WalletDB -from .util import bh2u, bfh, log_exceptions, ignore_exceptions, TxMinedInfo +from .util import bh2u, bfh, log_exceptions, ignore_exceptions, TxMinedInfo, random_shuffled_copy from .address_synchronizer import AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED from .transaction import Transaction, TxOutpoint @@ -278,8 +278,8 @@ class WatchTower(LNWatcher): async def start_watching(self): # I need to watch the addresses from sweepstore - l = await self.sweepstore.list_channels() - for outpoint, address in l: + lst = await self.sweepstore.list_channels() + for outpoint, address in random_shuffled_copy(lst): self.add_channel(outpoint, address) async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders): diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 02cd671ce..fbb97b09b 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -35,7 +35,7 @@ from .crypto import sha256 from .bip32 import BIP32Node from .util import bh2u, bfh, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions from .util import ignore_exceptions, make_aiohttp_session, SilentTaskGroup -from .util import timestamp_to_datetime +from .util import timestamp_to_datetime, random_shuffled_copy from .util import MyEncoder from .logging import Logger from .lntransport import LNTransport, LNResponderTransport @@ -499,7 +499,7 @@ class LNWallet(LNWorker): # note: accessing channels (besides simple lookup) needs self.lock! self._channels = {} # type: Dict[bytes, Channel] channels = self.db.get_dict("channels") - for channel_id, c in channels.items(): + for channel_id, c in random_shuffled_copy(channels.items()): self._channels[bfh(channel_id)] = Channel(c, sweep_address=self.sweep_address, lnworker=self) self.pending_payments = defaultdict(asyncio.Future) # type: Dict[bytes, asyncio.Future[BarePaymentAttemptLog]] @@ -1397,7 +1397,7 @@ class LNBackups(Logger): self.wallet = wallet self.db = wallet.db self.channel_backups = {} - for channel_id, cb in self.db.get_dict("channel_backups").items(): + for channel_id, cb in random_shuffled_copy(self.db.get_dict("channel_backups").items()): self.channel_backups[bfh(channel_id)] = ChannelBackup(cb, sweep_address=self.sweep_address, lnworker=self) @property diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py index 6363eeab8..e922ebc3e 100644 --- a/electrum/synchronizer.py +++ b/electrum/synchronizer.py @@ -32,7 +32,7 @@ from aiorpcx import TaskGroup, run_in_thread, RPCError from . import util from .transaction import Transaction, PartialTransaction -from .util import bh2u, make_aiohttp_session, NetworkJobOnDefaultServer +from .util import bh2u, make_aiohttp_session, NetworkJobOnDefaultServer, random_shuffled_copy from .bitcoin import address_to_scripthash, is_address from .network import UntrustedServerReturnedError from .logging import Logger @@ -240,7 +240,7 @@ class Synchronizer(SynchronizerBase): if history == ['*']: continue await self._request_missing_txs(history, allow_server_not_finding_tx=True) # add addresses to bootstrap - for addr in self.wallet.get_addresses(): + for addr in random_shuffled_copy(self.wallet.get_addresses()): await self._add_address(addr) # main loop while True: diff --git a/electrum/util.py b/electrum/util.py index 36296ff3c..e0b0fe092 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -24,7 +24,7 @@ import binascii import os, sys, re, json from collections import defaultdict, OrderedDict from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable, Any, - Sequence, Dict, Generic, TypeVar) + Sequence, Dict, Generic, TypeVar, List, Iterable) from datetime import datetime import decimal from decimal import Decimal @@ -1398,3 +1398,12 @@ class JsonRPCClient: async def coro(*args): return await self.request(endpoint, *args) setattr(self, endpoint, coro) + + +T = TypeVar('T') + +def random_shuffled_copy(x: Iterable[T]) -> List[T]: + """Returns a shuffled copy of the input.""" + x_copy = list(x) # copy + random.shuffle(x_copy) # shuffle in-place + return x_copy