electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit e85fb25146e793bcecf9a4e666e6fae315bb0159
parent 0848aa259d0a81c4ee171f0e95c5f47878d29eb7
Author: ThomasV <thomasv@electrum.org>
Date:   Wed, 26 Feb 2020 11:01:53 +0100

lnpeer: verify signature in closing_signed

Diffstat:
Melectrum/lnchannel.py | 4++--
Melectrum/lnpeer.py | 16+++++++++++++---
Melectrum/transaction.py | 45++++++++++++++++++++++++++-------------------
3 files changed, 41 insertions(+), 24 deletions(-)

diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py @@ -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={ @@ -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=[], diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py @@ -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: @@ -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 diff --git a/electrum/transaction.py b/electrum/transaction.py @@ -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)