URI: 
       tlnpeer: verify signature in closing_signed - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit e85fb25146e793bcecf9a4e666e6fae315bb0159
   DIR parent 0848aa259d0a81c4ee171f0e95c5f47878d29eb7
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Wed, 26 Feb 2020 11:01:53 +0100
       
       lnpeer: verify signature in closing_signed
       
       Diffstat:
         M electrum/lnchannel.py               |       4 ++--
         M electrum/lnpeer.py                  |      16 +++++++++++++---
         M electrum/transaction.py             |      45 ++++++++++++++++++-------------
       
       3 files changed, 41 insertions(+), 24 deletions(-)
       ---
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -821,7 +821,7 @@ class Channel(Logger):
                    htlcs=htlcs)
        
            def make_closing_tx(self, local_script: bytes, remote_script: bytes,
       -                        fee_sat: int) -> Tuple[bytes, PartialTransaction]:
       +                        fee_sat: int, *, drop_remote = False) -> Tuple[bytes, PartialTransaction]:
                """ cooperative close """
                _, outputs = make_commitment_outputs(
                        fees_per_participant={
       t@@ -829,7 +829,7 @@ class Channel(Logger):
                            REMOTE: fee_sat * 1000 if not self.constraints.is_initiator else 0,
                        },
                        local_amount_msat=self.balance(LOCAL),
       -                remote_amount_msat=self.balance(REMOTE),
       +                remote_amount_msat=self.balance(REMOTE) if not drop_remote else 0,
                        local_script=bh2u(local_script),
                        remote_script=bh2u(remote_script),
                        htlcs=[],
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -1394,8 +1394,9 @@ class Peer(Logger):
                # BOLT2: The sending node MUST set fee less than or equal to the base fee of the final ctx
                max_fee = chan.get_latest_fee(LOCAL if is_local else REMOTE)
                our_fee = min(our_fee, max_fee)
       +        drop_remote = False
                def send_closing_signed():
       -            our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=our_fee)
       +            our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=our_fee, drop_remote=drop_remote)
                    self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig)
                # the funder sends the first 'closing_signed' message
                if chan.constraints.is_initiator:
       t@@ -1405,10 +1406,19 @@ class Peer(Logger):
                    # FIXME: the remote SHOULD send closing_signed, but some don't.
                    cs_payload = await self.wait_for_message('closing_signed', chan.channel_id)
                    their_fee = int.from_bytes(cs_payload['fee_satoshis'], 'big')
       -            their_sig = cs_payload['signature']
       -            # TODO: verify their sig
                    if their_fee > max_fee:
                        raise Exception(f'the proposed fee exceeds the base fee of the latest commitment transaction {is_local, their_fee, max_fee}')
       +            their_sig = cs_payload['signature']
       +            # verify their sig: they might have dropped their output
       +            our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=False)
       +            if closing_tx.verify_signature(0, their_sig):
       +                drop_remote = False
       +            else:
       +                our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=True)
       +                if closing_tx.verify_signature(0, their_sig):
       +                    drop_remote = True
       +                else:
       +                    raise Exception('failed to verify their signature')
                    # Agree if difference is lower or equal to one (see below)
                    if abs(our_fee - their_fee) < 2:
                        our_fee = their_fee
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -1822,31 +1822,38 @@ class PartialTransaction(Transaction):
                if len(self.inputs()) != len(signatures):
                    raise Exception('expected {} signatures; got {}'.format(len(self.inputs()), len(signatures)))
                for i, txin in enumerate(self.inputs()):
       -            pubkeys = [pk.hex() for pk in txin.pubkeys]
                    sig = signatures[i]
                    if bfh(sig) in list(txin.part_sigs.values()):
                        continue
       -            pre_hash = sha256d(bfh(self.serialize_preimage(i)))
       -            sig_string = ecc.sig_string_from_der_sig(bfh(sig[:-2]))
       -            for recid in range(4):
       -                try:
       -                    public_key = ecc.ECPubkey.from_sig_string(sig_string, recid, pre_hash)
       -                except ecc.InvalidECPointException:
       -                    # the point might not be on the curve for some recid values
       -                    continue
       -                pubkey_hex = public_key.get_public_key_hex(compressed=True)
       -                if pubkey_hex in pubkeys:
       -                    try:
       -                        public_key.verify_message_hash(sig_string, pre_hash)
       -                    except Exception:
       -                        _logger.exception('')
       -                        continue
       -                    _logger.info(f"adding sig: txin_idx={i}, signing_pubkey={pubkey_hex}, sig={sig}")
       -                    self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_hex, sig=sig)
       -                    break
       +            sig_bytes = ecc.sig_string_from_der_sig(bfh(sig[:-2]))
       +            signing_pubkey = self.verify_signature(i, sig_bytes)
       +            if signing_pubkey:
       +                _logger.info(f"adding sig: txin_idx={i}, signing_pubkey={signing_pubkey.hex()}, sig={sig}")
       +                self.add_signature_to_txin(txin_idx=i, signing_pubkey=signing_pubkey.hex(), sig=sig)
                # redo raw
                self.invalidate_ser_cache()
        
       +    def verify_signature(self, i: int, sig: bytes) -> bytes:
       +        # returns the signing pubkey if verification passes
       +        txin = self.inputs()[i]
       +        pubkeys = [pk for pk in txin.pubkeys]
       +        pre_hash = sha256d(bfh(self.serialize_preimage(i)))
       +        for recid in range(4):
       +            try:
       +                public_key = ecc.ECPubkey.from_sig_string(sig, recid, pre_hash)
       +            except ecc.InvalidECPointException:
       +                # the point might not be on the curve for some recid values
       +                continue
       +            pubkey = public_key.get_public_key_bytes(compressed=True)
       +            if pubkey in pubkeys:
       +                try:
       +                    public_key.verify_message_hash(sig, pre_hash)
       +                except Exception:
       +                    _logger.exception('')
       +                    continue
       +                return pubkey
       +        return False
       +
            def add_signature_to_txin(self, *, txin_idx: int, signing_pubkey: str, sig: str):
                txin = self._inputs[txin_idx]
                txin.part_sigs[bfh(signing_pubkey)] = bfh(sig)