Skip to content

Commit

Permalink
lnonion: rm support for legacy (pre-TLV) onions
Browse files Browse the repository at this point in the history
  • Loading branch information
SomberNight committed Jun 29, 2023
1 parent fcd296f commit 2e30969
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 230 deletions.
110 changes: 26 additions & 84 deletions electrum/lnonion.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@

HOPS_DATA_SIZE = 1300 # also sometimes called routingInfoSize in bolt-04
TRAMPOLINE_HOPS_DATA_SIZE = 400
LEGACY_PER_HOP_FULL_SIZE = 65
PER_HOP_HMAC_SIZE = 32


Expand All @@ -51,53 +50,9 @@ class InvalidOnionMac(Exception): pass
class InvalidOnionPubkey(Exception): pass


class LegacyHopDataPayload:

def __init__(self, *, short_channel_id: bytes, amt_to_forward: int, outgoing_cltv_value: int):
self.short_channel_id = ShortChannelID(short_channel_id)
self.amt_to_forward = amt_to_forward
self.outgoing_cltv_value = outgoing_cltv_value

def to_bytes(self) -> bytes:
ret = self.short_channel_id
ret += int.to_bytes(self.amt_to_forward, length=8, byteorder="big", signed=False)
ret += int.to_bytes(self.outgoing_cltv_value, length=4, byteorder="big", signed=False)
ret += bytes(12) # padding
if len(ret) != 32:
raise Exception('unexpected length {}'.format(len(ret)))
return ret

def to_tlv_dict(self) -> dict:
d = {
"amt_to_forward": {"amt_to_forward": self.amt_to_forward},
"outgoing_cltv_value": {"outgoing_cltv_value": self.outgoing_cltv_value},
"short_channel_id": {"short_channel_id": self.short_channel_id},
}
return d

@classmethod
def from_bytes(cls, b: bytes) -> 'LegacyHopDataPayload':
if len(b) != 32:
raise Exception('unexpected length {}'.format(len(b)))
return LegacyHopDataPayload(
short_channel_id=b[:8],
amt_to_forward=int.from_bytes(b[8:16], byteorder="big", signed=False),
outgoing_cltv_value=int.from_bytes(b[16:20], byteorder="big", signed=False),
)

@classmethod
def from_tlv_dict(cls, d: dict) -> 'LegacyHopDataPayload':
return LegacyHopDataPayload(
short_channel_id=d["short_channel_id"]["short_channel_id"] if "short_channel_id" in d else b"\x00" * 8,
amt_to_forward=d["amt_to_forward"]["amt_to_forward"],
outgoing_cltv_value=d["outgoing_cltv_value"]["outgoing_cltv_value"],
)


class OnionHopsDataSingle: # called HopData in lnd

def __init__(self, *, is_tlv_payload: bool, payload: dict = None):
self.is_tlv_payload = is_tlv_payload
def __init__(self, *, payload: dict = None):
if payload is None:
payload = {}
self.payload = payload
Expand All @@ -107,29 +62,20 @@ def __init__(self, *, is_tlv_payload: bool, payload: dict = None):
def to_bytes(self) -> bytes:
hmac_ = self.hmac if self.hmac is not None else bytes(PER_HOP_HMAC_SIZE)
if self._raw_bytes_payload is not None:
ret = write_bigsize_int(len(self._raw_bytes_payload))
ret += self._raw_bytes_payload
ret += hmac_
return ret
if not self.is_tlv_payload:
ret = b"\x00" # realm==0
legacy_payload = LegacyHopDataPayload.from_tlv_dict(self.payload)
ret += legacy_payload.to_bytes()
ret = self._raw_bytes_payload
ret += hmac_
if len(ret) != LEGACY_PER_HOP_FULL_SIZE:
raise Exception('unexpected length {}'.format(len(ret)))
return ret
else: # tlv
payload_fd = io.BytesIO()
OnionWireSerializer.write_tlv_stream(fd=payload_fd,
tlv_stream_name="payload",
**self.payload)
payload_bytes = payload_fd.getvalue()
with io.BytesIO() as fd:
fd.write(write_bigsize_int(len(payload_bytes)))
fd.write(payload_bytes)
fd.write(hmac_)
return fd.getvalue()
# adding TLV payload. note: legacy hop data format no longer supported.
payload_fd = io.BytesIO()
OnionWireSerializer.write_tlv_stream(fd=payload_fd,
tlv_stream_name="payload",
**self.payload)
payload_bytes = payload_fd.getvalue()
with io.BytesIO() as fd:
fd.write(write_bigsize_int(len(payload_bytes)))
fd.write(payload_bytes)
fd.write(hmac_)
return fd.getvalue()

@classmethod
def from_fd(cls, fd: io.BytesIO) -> 'OnionHopsDataSingle':
Expand All @@ -139,14 +85,7 @@ def from_fd(cls, fd: io.BytesIO) -> 'OnionHopsDataSingle':
fd.seek(-1, io.SEEK_CUR) # undo read
if first_byte == b'\x00':
# legacy hop data format
b = fd.read(LEGACY_PER_HOP_FULL_SIZE)
if len(b) != LEGACY_PER_HOP_FULL_SIZE:
raise Exception(f'unexpected length {len(b)}')
ret = OnionHopsDataSingle(is_tlv_payload=False)
legacy_payload = LegacyHopDataPayload.from_bytes(b[1:33])
ret.payload = legacy_payload.to_tlv_dict()
ret.hmac = b[33:]
return ret
raise Exception("legacy hop data format no longer supported")
elif first_byte == b'\x01':
# reserved for future use
raise Exception("unsupported hop payload: length==1")
Expand All @@ -155,15 +94,15 @@ def from_fd(cls, fd: io.BytesIO) -> 'OnionHopsDataSingle':
hop_payload = fd.read(hop_payload_length)
if hop_payload_length != len(hop_payload):
raise Exception(f"unexpected EOF")
ret = OnionHopsDataSingle(is_tlv_payload=True)
ret = OnionHopsDataSingle()
ret.payload = OnionWireSerializer.read_tlv_stream(fd=io.BytesIO(hop_payload),
tlv_stream_name="payload")
ret.hmac = fd.read(PER_HOP_HMAC_SIZE)
assert len(ret.hmac) == PER_HOP_HMAC_SIZE
return ret

def __repr__(self):
return f"<OnionHopsDataSingle. is_tlv_payload={self.is_tlv_payload}. payload={self.payload}. hmac={self.hmac}>"
return f"<OnionHopsDataSingle. payload={self.payload}. hmac={self.hmac}>"


class OnionPacket:
Expand Down Expand Up @@ -226,8 +165,14 @@ def get_shared_secrets_along_route(payment_path_pubkeys: Sequence[bytes],
return hop_shared_secrets


def new_onion_packet(payment_path_pubkeys: Sequence[bytes], session_key: bytes,
hops_data: Sequence[OnionHopsDataSingle], associated_data: bytes, trampoline=False) -> OnionPacket:
def new_onion_packet(
payment_path_pubkeys: Sequence[bytes],
session_key: bytes,
hops_data: Sequence[OnionHopsDataSingle],
*,
associated_data: bytes,
trampoline: bool = False,
) -> OnionPacket:
num_hops = len(payment_path_pubkeys)
assert num_hops == len(hops_data)
hop_shared_secrets = get_shared_secrets_along_route(payment_path_pubkeys, session_key)
Expand Down Expand Up @@ -289,8 +234,7 @@ def calc_hops_data_for_payment(
"total_msat": total_msat,
"amount_msat": amt
}
hops_data = [OnionHopsDataSingle(
is_tlv_payload=route[-1].has_feature_varonion(), payload=hop_payload)]
hops_data = [OnionHopsDataSingle(payload=hop_payload)]
# payloads, backwards from last hop (but excluding the first edge):
for edge_index in range(len(route) - 1, 0, -1):
route_edge = route[edge_index]
Expand All @@ -304,9 +248,7 @@ def calc_hops_data_for_payment(
"short_channel_id": {"short_channel_id": route_edge.short_channel_id},
}
hops_data.append(
OnionHopsDataSingle(
is_tlv_payload=route[edge_index-1].has_feature_varonion(),
payload=hop_payload))
OnionHopsDataSingle(payload=hop_payload))
if not is_trampoline:
amt += route_edge.fee_for_edge(amt)
cltv += route_edge.cltv_expiry_delta
Expand Down
6 changes: 6 additions & 0 deletions electrum/lnrouter.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,12 @@ def _edge_cost(
route_edge = private_route_edges.get(short_channel_id, None)
if route_edge is None:
node_info = self.channel_db.get_node_info_for_node_id(node_id=end_node)
if node_info:
# it's ok if we are missing the node_announcement (node_info) for this node,
# but if we have it, we enforce that they support var_onion_optin
node_features = LnFeatures(node_info.features)
if not node_features.supports(LnFeatures.VAR_ONION_OPT):
return float('inf'), 0
route_edge = RouteEdge.from_channel_policy(
channel_policy=channel_policy,
short_channel_id=short_channel_id,
Expand Down
Loading

0 comments on commit 2e30969

Please sign in to comment.