Browse Source

lnpeer: obfuscate error pakets of forwarded htlcs, that we

propageate back to the sender.

lnworker: in htlc_fulfilled and htlc_failed, return early if the
htlc was forwarded, so that we do not trigger invoice callbacks
master
ThomasV 2 years ago
parent
commit
4c42840c1c
  1. 9
      electrum/lnonion.py
  2. 6
      electrum/lnpeer.py
  3. 18
      electrum/lnworker.py

9
electrum/lnonion.py

@ -396,7 +396,7 @@ class OnionRoutingFailure(Exception):
def construct_onion_error( def construct_onion_error(
reason: OnionRoutingFailure, reason: OnionRoutingFailure,
onion_packet: OnionPacket, their_public_key: bytes,
our_onion_private_key: bytes, our_onion_private_key: bytes,
) -> bytes: ) -> bytes:
# create payload # create payload
@ -409,11 +409,14 @@ def construct_onion_error(
error_packet += pad_len.to_bytes(2, byteorder="big") error_packet += pad_len.to_bytes(2, byteorder="big")
error_packet += bytes(pad_len) error_packet += bytes(pad_len)
# add hmac # add hmac
shared_secret = get_ecdh(our_onion_private_key, onion_packet.public_key) shared_secret = get_ecdh(our_onion_private_key, their_public_key)
um_key = get_bolt04_onion_key(b'um', shared_secret) um_key = get_bolt04_onion_key(b'um', shared_secret)
hmac_ = hmac_oneshot(um_key, msg=error_packet, digest=hashlib.sha256) hmac_ = hmac_oneshot(um_key, msg=error_packet, digest=hashlib.sha256)
error_packet = hmac_ + error_packet error_packet = hmac_ + error_packet
# obfuscate return error_packet
def obfuscate_onion_error(error_packet, their_public_key, our_onion_private_key):
shared_secret = get_ecdh(our_onion_private_key, their_public_key)
ammag_key = get_bolt04_onion_key(b'ammag', shared_secret) ammag_key = get_bolt04_onion_key(b'ammag', shared_secret)
stream_bytes = generate_cipher_stream(ammag_key, len(error_packet)) stream_bytes = generate_cipher_stream(ammag_key, len(error_packet))
error_packet = xor_bytes(error_packet, stream_bytes) error_packet = xor_bytes(error_packet, stream_bytes)

6
electrum/lnpeer.py

@ -28,7 +28,7 @@ from .bitcoin import make_op_return, DummyAddress
from .transaction import PartialTxOutput, match_script_against_template, Sighash from .transaction import PartialTxOutput, match_script_against_template, Sighash
from .logging import Logger from .logging import Logger
from .lnonion import (new_onion_packet, OnionFailureCode, calc_hops_data_for_payment, from .lnonion import (new_onion_packet, OnionFailureCode, calc_hops_data_for_payment,
process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailure, process_onion_packet, OnionPacket, construct_onion_error, obfuscate_onion_error, OnionRoutingFailure,
ProcessedOnionPacket, UnsupportedOnionPacketVersion, InvalidOnionMac, InvalidOnionPubkey, ProcessedOnionPacket, UnsupportedOnionPacketVersion, InvalidOnionMac, InvalidOnionPubkey,
OnionFailureCodeMetaFlag) OnionFailureCodeMetaFlag)
from .lnchannel import Channel, RevokeAndAck, RemoteCtnTooFarInFuture, ChannelState, PeerState, ChanCloseOption, CF_ANNOUNCE_CHANNEL from .lnchannel import Channel, RevokeAndAck, RemoteCtnTooFarInFuture, ChannelState, PeerState, ChanCloseOption, CF_ANNOUNCE_CHANNEL
@ -2405,7 +2405,9 @@ class Peer(Logger):
onion_packet_bytes=onion_packet_bytes, onion_packet_bytes=onion_packet_bytes,
onion_packet=onion_packet) onion_packet=onion_packet)
except OnionRoutingFailure as e: except OnionRoutingFailure as e:
error_bytes = construct_onion_error(e, onion_packet, our_onion_private_key=self.privkey) error_bytes = construct_onion_error(e, onion_packet.public_key, our_onion_private_key=self.privkey)
if error_bytes:
error_bytes = obfuscate_onion_error(error_bytes, onion_packet.public_key, our_onion_private_key=self.privkey)
if fw_info: if fw_info:
unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, fw_info unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, fw_info
self.lnworker.downstream_htlc_to_upstream_peer_map[fw_info] = self.pubkey self.lnworker.downstream_htlc_to_upstream_peer_map[fw_info] = self.pubkey

18
electrum/lnworker.py

@ -2281,23 +2281,24 @@ class LNWallet(LNWorker):
info = info._replace(status=status) info = info._replace(status=status)
self.save_payment_info(info) self.save_payment_info(info)
def _on_maybe_forwarded_htlc_resolved(self, chan: Channel, htlc_id: int) -> None: def is_forwarded_htlc_notify(self, chan: Channel, htlc_id: int) -> None:
"""Called when an HTLC we offered on chan gets irrevocably fulfilled or failed. """Called when an HTLC we offered on chan gets irrevocably fulfilled or failed.
If we find this was a forwarded HTLC, the upstream peer is notified. If we find this was a forwarded HTLC, the upstream peer is notified.
""" """
fw_info = chan.short_channel_id.hex(), htlc_id fw_info = chan.short_channel_id.hex(), htlc_id
upstream_peer_pubkey = self.downstream_htlc_to_upstream_peer_map.get(fw_info) upstream_peer_pubkey = self.downstream_htlc_to_upstream_peer_map.get(fw_info)
if not upstream_peer_pubkey: if not upstream_peer_pubkey:
return return False
upstream_peer = self.peers.get(upstream_peer_pubkey) upstream_peer = self.peers.get(upstream_peer_pubkey)
if not upstream_peer: if upstream_peer:
return upstream_peer.downstream_htlc_resolved_event.set()
upstream_peer.downstream_htlc_resolved_event.set() upstream_peer.downstream_htlc_resolved_event.clear()
upstream_peer.downstream_htlc_resolved_event.clear() return True
def htlc_fulfilled(self, chan: Channel, payment_hash: bytes, htlc_id: int): def htlc_fulfilled(self, chan: Channel, payment_hash: bytes, htlc_id: int):
util.trigger_callback('htlc_fulfilled', payment_hash, chan, htlc_id) util.trigger_callback('htlc_fulfilled', payment_hash, chan, htlc_id)
self._on_maybe_forwarded_htlc_resolved(chan=chan, htlc_id=htlc_id) if self.is_forwarded_htlc_notify(chan=chan, htlc_id=htlc_id):
return
q = None q = None
if shi := self.sent_htlcs_info.get((payment_hash, chan.short_channel_id, htlc_id)): if shi := self.sent_htlcs_info.get((payment_hash, chan.short_channel_id, htlc_id)):
chan.pop_onion_key(htlc_id) chan.pop_onion_key(htlc_id)
@ -2328,7 +2329,8 @@ class LNWallet(LNWorker):
failure_message: Optional['OnionRoutingFailure']): failure_message: Optional['OnionRoutingFailure']):
util.trigger_callback('htlc_failed', payment_hash, chan, htlc_id) util.trigger_callback('htlc_failed', payment_hash, chan, htlc_id)
self._on_maybe_forwarded_htlc_resolved(chan=chan, htlc_id=htlc_id) if self.is_forwarded_htlc_notify(chan=chan, htlc_id=htlc_id):
return
q = None q = None
if shi := self.sent_htlcs_info.get((payment_hash, chan.short_channel_id, htlc_id)): if shi := self.sent_htlcs_info.get((payment_hash, chan.short_channel_id, htlc_id)):
onion_key = chan.pop_onion_key(htlc_id) onion_key = chan.pop_onion_key(htlc_id)

Loading…
Cancel
Save