Merge pull request #10532 from spesmilo/variable_trampoline_onions

trampoline: allow trampoline onion packets of arbitrary size
This commit is contained in:
ThomasV
2026-03-24 12:11:56 +01:00
committed by GitHub
5 changed files with 11 additions and 31 deletions
+4 -17
View File
@@ -47,7 +47,6 @@ if TYPE_CHECKING:
HOPS_DATA_SIZE = 1300 # also sometimes called routingInfoSize in bolt-04
TRAMPOLINE_HOPS_DATA_SIZE = 400
PER_HOP_HMAC_SIZE = 32
ONION_MESSAGE_LARGE_SIZE = 32768
@@ -134,7 +133,6 @@ class OnionPacket:
def __post_init__(self):
assert len(self.public_key) == 33
assert len(self.hops_data) in [HOPS_DATA_SIZE, TRAMPOLINE_HOPS_DATA_SIZE, ONION_MESSAGE_LARGE_SIZE]
assert len(self.hmac) == PER_HOP_HMAC_SIZE
if not ecc.ECPubkey.is_pubkey_bytes(self.public_key):
raise InvalidOnionPubkey()
@@ -144,14 +142,10 @@ class OnionPacket:
ret += self.public_key
ret += self.hops_data
ret += self.hmac
if len(ret) - 66 not in [HOPS_DATA_SIZE, TRAMPOLINE_HOPS_DATA_SIZE, ONION_MESSAGE_LARGE_SIZE]:
raise Exception('unexpected length {}'.format(len(ret)))
return ret
@classmethod
def from_bytes(cls, b: bytes) -> 'OnionPacket':
if len(b) - 66 not in [HOPS_DATA_SIZE, TRAMPOLINE_HOPS_DATA_SIZE, ONION_MESSAGE_LARGE_SIZE]:
raise Exception('unexpected length {}'.format(len(b)))
return OnionPacket(
public_key=b[1:34],
hops_data=b[34:-32],
@@ -217,7 +211,7 @@ def new_onion_packet(
# FIXME: serializing here and again below. cache bytes in OnionHopsDataSingle? _raw_bytes_payload?
payload_size += PER_HOP_HMAC_SIZE + len(hops_data[i].to_bytes())
if trampoline:
data_size = TRAMPOLINE_HOPS_DATA_SIZE
data_size = payload_size
elif onion_message:
if payload_size <= HOPS_DATA_SIZE:
data_size = HOPS_DATA_SIZE
@@ -446,7 +440,7 @@ def process_onion_packet(
raise InvalidOnionMac()
# peel an onion layer off
rho_key = get_bolt04_onion_key(b'rho', shared_secret)
data_size = TRAMPOLINE_HOPS_DATA_SIZE if is_trampoline else HOPS_DATA_SIZE
data_size = len(onion_packet.hops_data) if is_trampoline else HOPS_DATA_SIZE
if is_onion_message and len(onion_packet.hops_data) > HOPS_DATA_SIZE:
data_size = ONION_MESSAGE_LARGE_SIZE
stream_bytes = generate_cipher_stream(rho_key, 2 * data_size)
@@ -459,15 +453,8 @@ def process_onion_packet(
if trampoline_onion_packet:
if is_trampoline:
raise Exception("found nested trampoline inside trampoline")
top_version = trampoline_onion_packet.get('version')
top_public_key = trampoline_onion_packet.get('public_key')
top_hops_data = trampoline_onion_packet.get('hops_data')
top_hops_data_fd = io.BytesIO(top_hops_data)
top_hmac = trampoline_onion_packet.get('hmac')
trampoline_onion_packet = OnionPacket(
public_key=top_public_key,
hops_data=top_hops_data_fd.read(TRAMPOLINE_HOPS_DATA_SIZE),
hmac=top_hmac)
trampoline_onion_packet = trampoline_onion_packet['trampoline_onion_packet']
trampoline_onion_packet = OnionPacket.from_bytes(trampoline_onion_packet)
# calc next ephemeral key
blinding_factor = sha256(onion_packet.public_key + shared_secret)
blinding_factor_int = int.from_bytes(blinding_factor, byteorder="big")
+1 -4
View File
@@ -22,10 +22,7 @@ tlvdata,payload,outgoing_node_id,outgoing_node_id,byte,33
tlvtype,payload,invoice_routing_info,66099
tlvdata,payload,invoice_routing_info,invoice_routing_info,byte,...
tlvtype,payload,trampoline_onion_packet,66100
tlvdata,payload,trampoline_onion_packet,version,byte,1
tlvdata,payload,trampoline_onion_packet,public_key,byte,33
tlvdata,payload,trampoline_onion_packet,hops_data,byte,400
tlvdata,payload,trampoline_onion_packet,hmac,byte,32
tlvdata,payload,trampoline_onion_packet,trampoline_onion_packet,byte,...
tlvtype,encrypted_data_tlv,padding,1
tlvdata,encrypted_data_tlv,padding,padding,byte,...
tlvtype,encrypted_data_tlv,short_channel_id,2
1 tlvtype,payload,amt_to_forward,2
22 tlvtype,payload,invoice_routing_info,66099
23 tlvdata,payload,invoice_routing_info,invoice_routing_info,byte,...
24 tlvtype,payload,trampoline_onion_packet,66100
25 tlvdata,payload,trampoline_onion_packet,version,byte,1 tlvdata,payload,trampoline_onion_packet,trampoline_onion_packet,byte,...
tlvdata,payload,trampoline_onion_packet,public_key,byte,33
tlvdata,payload,trampoline_onion_packet,hops_data,byte,400
tlvdata,payload,trampoline_onion_packet,hmac,byte,32
26 tlvtype,encrypted_data_tlv,padding,1
27 tlvdata,encrypted_data_tlv,padding,padding,byte,...
28 tlvtype,encrypted_data_tlv,short_channel_id,2
+1 -4
View File
@@ -4093,10 +4093,7 @@ class LNWallet(Logger):
self.logger.info(f'adding trampoline onion to final payload')
trampoline_payload = dict(hops_data[-1].payload)
trampoline_payload["trampoline_onion_packet"] = {
"version": trampoline_onion.version,
"public_key": trampoline_onion.public_key,
"hops_data": trampoline_onion.hops_data,
"hmac": trampoline_onion.hmac
"trampoline_onion_packet": trampoline_onion.to_bytes()
}
hops_data[-1] = dataclasses.replace(hops_data[-1], payload=trampoline_payload)
if t_hops_data := trampoline_onion._debug_hops_data: # None if trampoline-forwarding
+4 -2
View File
@@ -7,7 +7,7 @@ from types import MappingProxyType
from .lnutil import LnFeatures, PaymentFeeBudget, FeeBudgetExceeded
from .lnonion import (
calc_hops_data_for_payment, new_onion_packet, OnionPacket, TRAMPOLINE_HOPS_DATA_SIZE, PER_HOP_HMAC_SIZE
calc_hops_data_for_payment, new_onion_packet, OnionPacket, PER_HOP_HMAC_SIZE
)
from .lnrouter import TrampolineEdge, is_route_within_budget, LNPaymentTRoute
from .lnutil import NoPathFound
@@ -40,6 +40,8 @@ TRAMPOLINE_NODES_SIGNET = {
_TRAMPOLINE_NODES_UNITTESTS = {} # used in unit tests
TRAMPOLINE_HOPS_MAX_DATA_SIZE = 500
def hardcoded_trampoline_nodes() -> Mapping[str, LNPeerAddr]:
if _TRAMPOLINE_NODES_UNITTESTS:
@@ -332,7 +334,7 @@ def create_trampoline_onion(
payload = dict(hops_data[index].payload)
# try different r_tag order on each attempt
invoice_routing_info = random_shuffled_copy(route[index].invoice_routing_info)
remaining_payload_space = TRAMPOLINE_HOPS_DATA_SIZE \
remaining_payload_space = TRAMPOLINE_HOPS_MAX_DATA_SIZE \
- sum(len(hop.to_bytes()) + PER_HOP_HMAC_SIZE for hop in hops_data)
routing_info_to_use = []
for encoded_r_tag in invoice_routing_info:
+1 -4
View File
@@ -2742,10 +2742,7 @@ class TestPeerForwarding(TestPeer):
assert len(hops_data) == 1
new_payload = dict(hops_data[0].payload)
new_payload['trampoline_onion_packet'] = {
"version": modified_trampoline_onion.version,
"public_key": modified_trampoline_onion.public_key,
"hops_data": modified_trampoline_onion.hops_data,
"hmac": modified_trampoline_onion.hmac,
"trampoline_onion_packet": modified_trampoline_onion.to_bytes()
}
hops_data[0] = dataclasses.replace(hops_data[0], payload=MappingProxyType(new_payload))
modified_trampoline_onion = None