1
0

onion_message: move encrypt_onionmsg_tlv_hops_data() to lnonion as encrypt_hops_recipient_data()

and add support payloads other than onionmsg_tlv
This commit is contained in:
Sander van Grieken
2025-11-25 15:10:59 +01:00
parent a6e103b63c
commit 23d5ed87e7
3 changed files with 36 additions and 29 deletions

View File

@@ -281,6 +281,29 @@ def decrypt_onionmsg_data_tlv(*, shared_secret: bytes, encrypted_recipient_data:
return recipient_data
def encrypt_hops_recipient_data(
tlv_stream_name: str,
hops_data: Sequence[OnionHopsDataSingle],
hop_shared_secrets: Sequence[bytes]
) -> None:
"""encrypt unencrypted encrypted_recipient_data for hops with blind_fields.
NOTE: contents of payload.encrypted_recipient_data is slightly different for 'payload'
vs 'oniomsg_tlv' tlv_stream_names, so we map to the correct key here based on tlv_stream_name.
We can also change onion_wire.csv to use the same key, but as we import that from specs it might
regress in the future, so I rather make it explicit in code here.
"""
# key naming payload TLV vs onionmsg_tlv TLV
erd_key = 'encrypted_recipient_data' if tlv_stream_name == 'onionmsg_tlv' else 'encrypted_data'
num_hops = len(hops_data)
for i in range(num_hops):
if hops_data[i].tlv_stream_name == tlv_stream_name and 'encrypted_recipient_data' not in hops_data[i].payload:
# construct encrypted_recipient_data from blind_fields
encrypted_recipient_data = encrypt_onionmsg_data_tlv(shared_secret=hop_shared_secrets[i], **hops_data[i].blind_fields)
hops_data[i].payload['encrypted_recipient_data'] = {erd_key: encrypted_recipient_data}
def calc_hops_data_for_payment(
route: 'LNPaymentRoute',
amount_msat: int, # that final recipient receives

View File

@@ -41,7 +41,7 @@ from electrum.crypto import sha256, get_ecdh
from electrum.lnmsg import OnionWireSerializer
from electrum.lnonion import (get_bolt04_onion_key, OnionPacket, process_onion_packet,
OnionHopsDataSingle, decrypt_onionmsg_data_tlv, encrypt_onionmsg_data_tlv,
get_shared_secrets_along_route, new_onion_packet)
get_shared_secrets_along_route, new_onion_packet, encrypt_hops_recipient_data)
from electrum.lnutil import LnFeatures
from electrum.util import OldTaskGroup, log_exceptions
@@ -140,21 +140,6 @@ def is_onion_message_node(node_id: bytes, node_info: Optional['NodeInfo']) -> bo
return LnFeatures(node_info.features).supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)
def encrypt_onionmsg_tlv_hops_data(
hops_data: List[OnionHopsDataSingle],
hop_shared_secrets: Sequence[bytes]
) -> None:
"""encrypt unencrypted onionmsg_tlv.encrypted_recipient_data for hops with blind_fields"""
num_hops = len(hops_data)
for i in range(num_hops):
if hops_data[i].tlv_stream_name == 'onionmsg_tlv' and 'encrypted_recipient_data' not in hops_data[i].payload:
# construct encrypted_recipient_data from blind_fields
encrypted_recipient_data = encrypt_onionmsg_data_tlv(shared_secret=hop_shared_secrets[i], **hops_data[i].blind_fields)
new_payload = dict(hops_data[i].payload)
new_payload['encrypted_recipient_data'] = {'encrypted_recipient_data': encrypted_recipient_data}
hops_data[i] = dataclasses.replace(hops_data[i], payload=new_payload)
def create_onion_message_route_to(lnwallet: 'LNWallet', node_id: bytes) -> Sequence[PathEdge]:
"""Constructs a route to the destination node_id, first by starting with peers with existing channels,
and if no route found, opening a direct peer connection if node_id is found with an address in
@@ -325,7 +310,7 @@ def send_onion_message_to(
payment_path_pubkeys = blinded_node_ids + blinded_path_blinded_ids
hop_shared_secrets, _ = get_shared_secrets_along_route(payment_path_pubkeys, session_key)
encrypt_onionmsg_tlv_hops_data(hops_data, hop_shared_secrets)
encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, onion_message=True)
packet_b = packet.to_bytes()
@@ -365,7 +350,7 @@ def send_onion_message_to(
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)
encrypt_onionmsg_tlv_hops_data(hops_data, hop_shared_secrets)
encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
packet = new_onion_packet(blinded_node_ids, session_key, hops_data)
packet_b = packet.to_bytes()

View File

@@ -13,15 +13,13 @@ from electrum_ecc import ECPrivkey
from electrum import SimpleConfig
from electrum.lnmsg import decode_msg, OnionWireSerializer
from electrum.lnonion import (
OnionHopsDataSingle, OnionPacket,
process_onion_packet, get_bolt04_onion_key, encrypt_onionmsg_data_tlv,
get_shared_secrets_along_route, new_onion_packet, ONION_MESSAGE_LARGE_SIZE,
HOPS_DATA_SIZE, InvalidPayloadSize)
OnionHopsDataSingle, OnionPacket, process_onion_packet, get_bolt04_onion_key, encrypt_onionmsg_data_tlv,
get_shared_secrets_along_route, new_onion_packet, ONION_MESSAGE_LARGE_SIZE, HOPS_DATA_SIZE, InvalidPayloadSize,
encrypt_hops_recipient_data)
from electrum.crypto import get_ecdh, privkey_to_pubkey
from electrum.lnutil import LnFeatures, Keypair
from electrum.onion_message import (
blinding_privkey, create_blinded_path, encrypt_onionmsg_tlv_hops_data,
OnionMessageManager, NoRouteFound, Timeout
blinding_privkey, create_blinded_path,OnionMessageManager, NoRouteFound, Timeout
)
from electrum.util import bfh, read_json_file, OldTaskGroup, get_asyncio_loop
from electrum.logging import console_stderr_handler
@@ -104,7 +102,7 @@ class TestOnionMessage(ElectrumTestCase):
)
]
encrypt_onionmsg_tlv_hops_data(hops_data, hop_shared_secrets)
encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
packet = new_onion_packet(blinded_node_ids, SESSION_KEY, hops_data, onion_message=True)
self.assertEqual(packet.to_bytes(), ONION_MESSAGE_PACKET)
@@ -126,18 +124,19 @@ class TestOnionMessage(ElectrumTestCase):
),
]
hops_data = hops_data_for_message('short_message') # fit in HOPS_DATA_SIZE
encrypt_onionmsg_tlv_hops_data(hops_data, hop_shared_secrets)
encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
packet = new_onion_packet(blinded_node_ids, SESSION_KEY, hops_data, onion_message=True)
self.assertEqual(len(packet.to_bytes()), HOPS_DATA_SIZE + 66)
hops_data = hops_data_for_message('A' * HOPS_DATA_SIZE) # fit in ONION_MESSAGE_LARGE_SIZE
encrypt_onionmsg_tlv_hops_data(hops_data, hop_shared_secrets)
encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
packet = new_onion_packet(blinded_node_ids, SESSION_KEY, hops_data, onion_message=True)
self.assertEqual(len(packet.to_bytes()), ONION_MESSAGE_LARGE_SIZE + 66)
hops_data = hops_data_for_message('A' * ONION_MESSAGE_LARGE_SIZE) # does not fit in ONION_MESSAGE_LARGE_SIZE
encrypt_onionmsg_tlv_hops_data(hops_data, hop_shared_secrets)
encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
with self.assertRaises(InvalidPayloadSize):
new_onion_packet(blinded_node_ids, SESSION_KEY, hops_data, onion_message=True)
@@ -261,7 +260,7 @@ class TestOnionMessage(ElectrumTestCase):
)
payment_path_pubkeys = blinded_node_ids + blinded_path_blinded_ids
hop_shared_secrets, _ = get_shared_secrets_along_route(payment_path_pubkeys, SESSION_KEY)
encrypt_onionmsg_tlv_hops_data(hops_data, hop_shared_secrets)
encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
packet = new_onion_packet(payment_path_pubkeys, SESSION_KEY, hops_data, onion_message=True)
self.assertEqual(packet.to_bytes(), ONION_MESSAGE_PACKET)