URI: 
       tLN cooperative close: avoid address-reuse (#6590) - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 3eba26b3980719647d826dfb664e39fd51ef0893
   DIR parent fc89c8ffa9b1ab4012331447090b36f6f15b13fc
  HTML Author: ghost43 <somber.night@protonmail.com>
       Date:   Tue, 15 Sep 2020 15:37:47 +0000
       
       LN cooperative close: avoid address-reuse (#6590)
       
       Previously if we coop-closed multiple channels in the same session,
       tthey would reuse the wallet address.
       Diffstat:
         M electrum/lnchannel.py               |      24 +++++++++++++++++++++---
         M electrum/lnworker.py                |       3 ++-
         M electrum/tests/test_lnchannel.py    |       5 ++---
         M electrum/tests/test_lnpeer.py       |       3 +++
       
       4 files changed, 28 insertions(+), 7 deletions(-)
       ---
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -145,7 +145,7 @@ class AbstractChannel(Logger, ABC):
            config: Dict[HTLCOwner, Union[LocalConfig, RemoteConfig]]
            _sweep_info: Dict[str, Dict[str, 'SweepInfo']]
            lnworker: Optional['LNWallet']
       -    sweep_address: str
       +    _fallback_sweep_address: str
            channel_id: bytes
            funding_outpoint: Outpoint
            node_id: bytes
       t@@ -307,6 +307,20 @@ class AbstractChannel(Logger, ABC):
                if self.get_state() == ChannelState.CLOSED and not keep_watching:
                    self.set_state(ChannelState.REDEEMED)
        
       +    @property
       +    def sweep_address(self) -> str:
       +        # TODO: in case of unilateral close with pending HTLCs, this address will be reused
       +        addr = None
       +        if self.is_static_remotekey_enabled():
       +            our_payment_pubkey = self.config[LOCAL].payment_basepoint.pubkey
       +            addr = make_commitment_output_to_remote_address(our_payment_pubkey)
       +        if addr is None:
       +            addr = self._fallback_sweep_address
       +        assert addr
       +        if self.lnworker:
       +            assert self.lnworker.wallet.is_mine(addr)
       +        return addr
       +
            @abstractmethod
            def is_initiator(self) -> bool:
                pass
       t@@ -367,6 +381,10 @@ class AbstractChannel(Logger, ABC):
                """
                pass
        
       +    @abstractmethod
       +    def is_static_remotekey_enabled(self) -> bool:
       +        pass
       +
        
        class ChannelBackup(AbstractChannel):
            """
       t@@ -383,7 +401,7 @@ class ChannelBackup(AbstractChannel):
                Logger.__init__(self)
                self.cb = cb
                self._sweep_info = {}
       -        self.sweep_address = sweep_address
       +        self._fallback_sweep_address = sweep_address
                self.storage = {} # dummy storage
                self._state = ChannelState.OPENING
                self.config = {}
       t@@ -485,7 +503,7 @@ class Channel(AbstractChannel):
                self.name = name
                Logger.__init__(self)
                self.lnworker = lnworker
       -        self.sweep_address = sweep_address
       +        self._fallback_sweep_address = sweep_address
                self.storage = state
                self.db_lock = self.storage.db.lock if self.storage.db else threading.RLock()
                self.config = {}
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -494,7 +494,8 @@ class LNWallet(LNWorker):
                self.features |= LnFeatures.OPTION_STATIC_REMOTEKEY_REQ
                self.payments = self.db.get_dict('lightning_payments')     # RHASH -> amount, direction, is_paid  # FIXME amt should be msat
                self.preimages = self.db.get_dict('lightning_preimages')   # RHASH -> preimage
       -        self.sweep_address = wallet.get_new_sweep_address_for_channel()  # TODO possible address-reuse
       +        # note: this sweep_address is only used as fallback; as it might result in address-reuse
       +        self.sweep_address = wallet.get_new_sweep_address_for_channel()
                self.logs = defaultdict(list)  # type: Dict[str, List[PaymentAttemptLog]]  # key is RHASH  # (not persisted)
                self.is_routing = set()        # (not persisted) keys of invoices that are in PR_ROUTING state
                # used in tests
   DIR diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py
       t@@ -172,9 +172,8 @@ def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
            alice.config[REMOTE].next_per_commitment_point = bob_second
            bob.config[REMOTE].next_per_commitment_point = alice_second
        
       -    # TODO: sweep_address in lnchannel.py should use static_remotekey
       -    alice.sweep_address = bitcoin.pubkey_to_address('p2wpkh', alice.config[LOCAL].payment_basepoint.pubkey.hex())
       -    bob.sweep_address = bitcoin.pubkey_to_address('p2wpkh', bob.config[LOCAL].payment_basepoint.pubkey.hex())
       +    alice._fallback_sweep_address = bitcoin.pubkey_to_address('p2wpkh', alice.config[LOCAL].payment_basepoint.pubkey.hex())
       +    bob._fallback_sweep_address = bitcoin.pubkey_to_address('p2wpkh', bob.config[LOCAL].payment_basepoint.pubkey.hex())
        
            alice._ignore_max_htlc_value = True
            bob._ignore_max_htlc_value = True
   DIR diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py
       t@@ -103,6 +103,9 @@ class MockWallet:
            def is_lightning_backup(self):
                return False
        
       +    def is_mine(self, addr):
       +        return True
       +
        
        class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
            def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_queue):