Browse Source

lnpeer: verify signature in closing_signed

hard-fail-on-bad-server-string
ThomasV 5 years ago
parent
commit
e85fb25146
  1. 4
      electrum/lnchannel.py
  2. 16
      electrum/lnpeer.py
  3. 45
      electrum/transaction.py

4
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=[],

16
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

45
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)

Loading…
Cancel
Save