onion_message: split send_onion_message_to

Factor out code from `send_onion_message_to` into a separate
function `_create_route_to_introduction_point` to make it
easier to reason about it and more testable.
This commit is contained in:
f321x
2026-02-19 14:52:07 +01:00
parent 2e0f263269
commit 4134dc7b25
+80 -59
View File
@@ -170,7 +170,11 @@ def create_onion_message_route_to(lnwallet: 'LNWallet', node_id: bytes) -> Seque
invoice_amount_msat=10000, # TODO: do this without amount constraints
node_filter=lambda x, y: True if x == lnwallet.node_keypair.pubkey else is_onion_message_node(x, y),
my_sending_channels=my_sending_channels
): return path
):
# first edge must be to our peer
peer = lnwallet.lnpeermgr.get_peer_by_pubkey(path[0].end_node)
assert peer, 'first hop not a peer'
return path
# alt: dest is existing peer?
if lnwallet.lnpeermgr.get_peer_by_pubkey(node_id):
@@ -184,6 +188,75 @@ def create_onion_message_route_to(lnwallet: 'LNWallet', node_id: bytes) -> Seque
raise NoRouteFound('no path found')
def create_route_to_introduction_point(
lnwallet: 'LNWallet',
blinded_path: dict,
introduction_point: bytes,
session_key: bytes
):
hops_data = []
blinded_node_ids = []
peer = lnwallet.lnpeermgr.get_peer_by_pubkey(introduction_point)
# if blinded path introduction point is our direct peer, no need to route-find
if peer:
# start of blinded path is our peer
path_key = blinded_path['first_path_key']
return peer, path_key, hops_data, blinded_node_ids
path = create_onion_message_route_to(lnwallet, introduction_point)
# last edge is to introduction point and start of blinded path. remove from route
assert path[-1].end_node == introduction_point, 'last hop in route must be introduction point'
peer = lnwallet.lnpeermgr.get_peer_by_pubkey(path[0].end_node)
assert peer, "first hop is not a peer"
path = path[:-1]
if len(path) == 0:
# we pass the onion directly to the introduction point
path_key = blinded_path['first_path_key']
else:
# we construct a route to the introduction point
payment_path_pubkeys = [edge.end_node for edge in path]
hop_shared_secrets, blinded_node_ids = get_shared_secrets_along_route(
payment_path_pubkeys,
session_key)
for edge in path[:-1]:
hop = OnionHopsDataSingle(
tlv_stream_name='onionmsg_tlv',
blind_fields={'next_node_id': {'node_id': edge.end_node}},
)
hops_data.append(hop)
# final hop pre-ip, add next_path_key_override
final_hop_pre_ip = OnionHopsDataSingle(
tlv_stream_name='onionmsg_tlv',
blind_fields={
'next_node_id': {'node_id': introduction_point},
'next_path_key_override': {'path_key': blinded_path['first_path_key']},
},
)
hops_data.append(final_hop_pre_ip)
# encrypt encrypted_data_tlv here
for i, hop in enumerate(hops_data):
encrypted_recipient_data = encrypt_onionmsg_data_tlv(
shared_secret=hop_shared_secrets[i],
**hop.blind_fields)
payload = dict(hop.payload)
payload['encrypted_recipient_data'] = {
'encrypted_recipient_data': encrypted_recipient_data
}
hops_data[i] = dataclasses.replace(hop, payload=payload)
path_key = ecc.ECPrivkey(session_key).get_public_key_bytes()
return peer, path_key, hops_data, blinded_node_ids
def send_onion_message_to(
lnwallet: 'LNWallet',
node_id_or_blinded_path: bytes,
@@ -213,10 +286,10 @@ def send_onion_message_to(
# https://github.com/lightning/bolts/blob/master/04-onion-routing.md
# "MUST set first_node_id to N0"
hops_data = []
blinded_node_ids = []
if lnwallet.node_keypair.pubkey == introduction_point:
hops_data = []
blinded_node_ids = []
# blinded path introduction point is me
our_blinding = blinded_path['first_path_key']
our_payload = blinded_path['path'][0]
@@ -248,65 +321,13 @@ def send_onion_message_to(
else:
# we need a route to introduction point
r = create_route_to_introduction_point(lnwallet, blinded_path, introduction_point, session_key)
peer, path_key, hops_data, blinded_node_ids = r
remaining_blinded_path = blinded_path['path']
if not isinstance(remaining_blinded_path, list): # doesn't return list when num items == 1
remaining_blinded_path = [remaining_blinded_path]
peer = lnwallet.lnpeermgr.get_peer_by_pubkey(introduction_point)
# if blinded path introduction point is our direct peer, no need to route-find
if peer:
# start of blinded path is our peer
path_key = blinded_path['first_path_key']
else:
path = create_onion_message_route_to(lnwallet, introduction_point)
# first edge must be to our peer
peer = lnwallet.lnpeermgr.get_peer_by_pubkey(path[0].end_node)
assert peer, 'first hop not a peer'
# last edge is to introduction point and start of blinded path. remove from route
assert path[-1].end_node == introduction_point, 'last hop in route must be introduction point'
path = path[:-1]
if len(path) == 0:
path_key = blinded_path['first_path_key']
else:
payment_path_pubkeys = [edge.end_node for edge in path]
hop_shared_secrets, blinded_node_ids = get_shared_secrets_along_route(
payment_path_pubkeys,
session_key)
hops_data = [
OnionHopsDataSingle(
tlv_stream_name='onionmsg_tlv',
blind_fields={'next_node_id': {'node_id': x.end_node}},
) for x in path[:-1]
]
# final hop pre-ip, add next_path_key_override
final_hop_pre_ip = OnionHopsDataSingle(
tlv_stream_name='onionmsg_tlv',
blind_fields={
'next_node_id': {'node_id': introduction_point},
'next_path_key_override': {'path_key': blinded_path['first_path_key']},
},
)
hops_data.append(final_hop_pre_ip)
# encrypt encrypted_data_tlv here
for i in range(len(hops_data)):
encrypted_recipient_data = encrypt_onionmsg_data_tlv(
shared_secret=hop_shared_secrets[i],
**hops_data[i].blind_fields)
payload = dict(hops_data[i].payload)
payload['encrypted_recipient_data'] = {
'encrypted_recipient_data': encrypted_recipient_data
}
hops_data[i] = dataclasses.replace(hops_data[i], payload=payload)
path_key = ecc.ECPrivkey(session_key).get_public_key_bytes()
# append (remaining) blinded path and payload
blinded_path_blinded_ids = []
for i, onionmsg_hop in enumerate(remaining_blinded_path):