lnonion/onion_wire: encrypted_data -> encrypted_recipient_data
What changed, and why it matters
This commit is a routine cleanup to match a recent change in the Lightning Network specifications. It renames a field from 'encrypted_data' to 'encrypted_recipient_data' everywhere and removes an old workaround that handled two different names. There is no indication this fixes a security bug; it is a standards-alignment refactor.
No security action required. Treat as normal standards-compliance refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch unifies the TLV field naming for encrypted recipient data in blinded paths, per BOLTS commit 3c0fd9ad901eeef9ef6890c1bb85592cbf59e3d3. It removes the tlv_stream_name parameter from encrypt_hops_recipient_data, drops the conditional erd_key mapping, updates onion_wire.csv to use encrypted_recipient_data consistently, and renames current_blinding_point/blinding to current_path_key/path_key. Tests are updated accordingly. No cryptographic logic or validation behavior is changed.
Changed components
electrum/lnonion.pyelectrum/lnwire/onion_wire.csvelectrum/onion_message.pytests/test_onion_message.pyInspect captured patch +17 / −36
diff --git a/electrum/lnonion.py b/electrum/lnonion.py
index a334fee..0770025 100644
--- a/electrum/lnonion.py
+++ b/electrum/lnonion.py
@@ -288,29 +288,16 @@ def decrypt_onionmsg_data_tlv(*, shared_secret: bytes, encrypted_recipient_data:
def encrypt_hops_recipient_data(
- tlv_stream_name: str,
hops_data: List[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)
- # work around immutablility of OnionHopsDataSingle
- hop_payload = {'encrypted_recipient_data': {erd_key: encrypted_recipient_data}}
- hop_payload.update(hops_data[i].payload)
- hops_data[i] = OnionHopsDataSingle(tlv_stream_name=hops_data[i].tlv_stream_name, payload=hop_payload, blind_fields=hops_data[i].blind_fields)
+ """Encrypt plaintext OnionHopsDataSingle.blind_fields into encrypted_recipient_data"""
+ for i, (hop_data, hop_shared_secret) in enumerate(zip(hops_data, hop_shared_secrets)):
+ assert 'encrypted_recipient_data' not in hop_data.payload, hop_data
+ encrypted_recipient_data = encrypt_onionmsg_data_tlv(shared_secret=hop_shared_secret, **hop_data.blind_fields)
+ new_hop_payload = {'encrypted_recipient_data': {'encrypted_recipient_data': encrypted_recipient_data}}
+ new_hop_payload.update(hop_data.payload) # keep other fields
+ hops_data[i] = replace(hop_data, payload=new_hop_payload)
def calc_hops_data_for_payment(
diff --git a/electrum/lnwire/onion_wire.csv b/electrum/lnwire/onion_wire.csv
index 06b55da..1353120 100644
--- a/electrum/lnwire/onion_wire.csv
+++ b/electrum/lnwire/onion_wire.csv
@@ -8,9 +8,9 @@ tlvtype,payload,payment_data,8
tlvdata,payload,payment_data,payment_secret,byte,32
tlvdata,payload,payment_data,total_msat,tu64,
tlvtype,payload,encrypted_recipient_data,10
-tlvdata,payload,encrypted_recipient_data,encrypted_data,byte,...
-tlvtype,payload,current_blinding_point,12
-tlvdata,payload,current_blinding_point,blinding,point,
+tlvdata,payload,encrypted_recipient_data,encrypted_recipient_data,byte,...
+tlvtype,payload,current_path_key,12
+tlvdata,payload,current_path_key,path_key,point,
tlvtype,payload,payment_metadata,16
tlvdata,payload,payment_metadata,payment_metadata,byte,...
tlvtype,payload,total_amount_msat,18
diff --git a/electrum/onion_message.py b/electrum/onion_message.py
index a98775e..1c851c1 100644
--- a/electrum/onion_message.py
+++ b/electrum/onion_message.py
@@ -237,7 +237,7 @@ def create_route_to_introduction_point(
)
hops_data.append(final_hop_pre_ip)
- encrypt_hops_recipient_data(tlv_stream_name='onionmsg_tlv', hops_data=hops_data, hop_shared_secrets=hop_shared_secrets)
+ encrypt_hops_recipient_data(hops_data=hops_data, hop_shared_secrets=hop_shared_secrets)
path_key = ecc.ECPrivkey(session_key).get_public_key_bytes()
return peer, path_key, hops_data, blinded_node_ids
@@ -366,7 +366,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_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
+ encrypt_hops_recipient_data(hops_data, hop_shared_secrets)
packet = new_onion_packet(blinded_node_ids, session_key, hops_data)
packet_b = packet.to_bytes()
diff --git a/tests/test_onion_message.py b/tests/test_onion_message.py
index 63e5803..aebd372 100644
--- a/tests/test_onion_message.py
+++ b/tests/test_onion_message.py
@@ -106,7 +106,7 @@ class TestOnionMessage(ElectrumTestCase):
)
]
- encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
+ encrypt_hops_recipient_data(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)
@@ -128,18 +128,18 @@ class TestOnionMessage(ElectrumTestCase):
),
]
hops_data = hops_data_for_message('short_message') # fit in HOPS_DATA_SIZE
- encrypt_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
+ encrypt_hops_recipient_data(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_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
+ encrypt_hops_recipient_data(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_hops_recipient_data('onionmsg_tlv', hops_data, hop_shared_secrets)
+ encrypt_hops_recipient_data(hops_data, hop_shared_secrets)
with self.assertRaises(InvalidPayloadSize):
new_onion_packet(blinded_node_ids, SESSION_KEY, hops_data, onion_message=True)
@@ -244,11 +244,7 @@ class TestOnionMessage(ElectrumTestCase):
),
]
# 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)
- 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)
+ encrypt_hops_recipient_data(hops_data, hop_shared_secrets)
blinded_path_blinded_ids = []
for i, x in enumerate(blinded_path_to_dave.get('path')):
@@ -263,8 +259,6 @@ class TestOnionMessage(ElectrumTestCase):
payload=payload),
)
payment_path_pubkeys = blinded_node_ids + blinded_path_blinded_ids
- hop_shared_secrets, _ = get_shared_secrets_along_route(payment_path_pubkeys, SESSION_KEY)
- 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)
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.