From 4d9deff0914c869f0f4a2014b95fe5d35dc2bd75 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Sun, 9 Feb 2025 22:41:16 -0800 Subject: [PATCH 1/5] Switch RequiredWrapper to LengthReadable In a subsequent commit, we will be storing `TrampolineOnionPacket`s within `PendingHTLCRouting`, requiring that they be serialized for storage. To do so, `RequiredWrapper`'s requirements must be loosened to only require `LengthReadable` instead of `Readable`. --- lightning/src/util/ser.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 083cfa2ad6c..21997c09c1a 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -403,10 +403,10 @@ impl MaybeReadable for T { /// /// This is not exported to bindings users as manual TLV building is not currently supported in bindings pub struct RequiredWrapper(pub Option); -impl Readable for RequiredWrapper { +impl LengthReadable for RequiredWrapper { #[inline] - fn read(reader: &mut R) -> Result { - Ok(Self(Some(Readable::read(reader)?))) + fn read_from_fixed_length_buffer(reader: &mut R) -> Result { + Ok(Self(Some(LengthReadable::read_from_fixed_length_buffer(reader)?))) } } impl> ReadableArgs for RequiredWrapper { From 5cc6bb177b8be6126ae79897394ecd6f0b2201b2 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Sun, 9 Feb 2025 22:42:25 -0800 Subject: [PATCH 2/5] Add TrampolineForward variant to PendingHTLCRouting Forwarding Trampoline packets requires storing their shared secrets on top of the outer onion's shared secrets, as well as referencing the next hop by its node ID as opposed to by an SCID. We modify PendingHTLCRouting to adequately represent this information. --- lightning/src/ln/channelmanager.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index eb5bd89575a..66af345ff7d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -131,7 +131,7 @@ pub use crate::ln::outbound_payment::{Bolt12PaymentError, ProbeSendFailure, Retr #[cfg(test)] pub(crate) use crate::ln::outbound_payment::PaymentSendFailure; use crate::ln::script::ShutdownScript; - +use crate::routing::gossip::NodeId; // We hold various information about HTLC relay in the HTLC objects in Channel itself: // // Upon receipt of an HTLC from a peer, we'll give it a PendingHTLCStatus indicating if it should @@ -169,6 +169,25 @@ pub enum PendingHTLCRouting { /// The absolute CLTV of the inbound HTLC incoming_cltv_expiry: Option, }, + + /// An HTLC which should be forwarded on to another Trampoline node. + TrampolineForward { + /// The onion shared secret we build with the sender (or the preceding Trampoline node) used + /// to decrypt the onion. + /// + /// This is later used to encrypt failure packets in the event that the HTLC is failed. + incoming_shared_secret: [u8; 32], + /// The onion which should be included in the forwarded HTLC, telling the next hop what to + /// do with the HTLC. + onion_packet: msgs::TrampolineOnionPacket, + /// The node ID of the Trampoline node which we need to route this HTLC to. + node_id: NodeId, // This should be NonZero eventually when we bump MSRV + /// Set if this HTLC is being forwarded within a blinded path. + blinded: Option, + /// The absolute CLTV of the inbound HTLC + incoming_cltv_expiry: Option, + }, + /// The onion indicates that this is a payment for an invoice (supposedly) generated by us. /// /// Note that at this point, we have not checked that the invoice being paid was actually @@ -279,6 +298,7 @@ impl PendingHTLCRouting { fn incoming_cltv_expiry(&self) -> Option { match self { Self::Forward { incoming_cltv_expiry, .. } => *incoming_cltv_expiry, + Self::TrampolineForward { incoming_cltv_expiry, .. } => *incoming_cltv_expiry, Self::Receive { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry), Self::ReceiveKeysend { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry), } @@ -8909,6 +8929,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ for (forward_info, prev_htlc_id) in pending_forwards.drain(..) { let scid = match forward_info.routing { PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id, + PendingHTLCRouting::TrampolineForward { .. } => 0, PendingHTLCRouting::Receive { .. } => 0, PendingHTLCRouting::ReceiveKeysend { .. } => 0, }; @@ -12476,6 +12497,13 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting, (9, payment_context, option), (11, invoice_request, option), }, + (3, TrampolineForward) => { + (0, incoming_shared_secret, required), + (1, onion_packet, required), + (2, blinded, option), + (3, node_id, required), + (4, incoming_cltv_expiry, option), + } ); impl_writeable_tlv_based!(PendingHTLCInfo, { From 51cecfcaa04a1c3c237f64ac9eae031fb9fc2c3a Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Sun, 2 Feb 2025 23:09:30 -0800 Subject: [PATCH 3/5] Introduce HopConnector enum In order to move the Trampoline-specific data from the onion decryption layer to the PendingHTLCRouting enum, we need to make the NextPacketDetails more descriptive, which we do here by virtue of converting the outbound_scid field into an enum than is more expressive in its Trampoline variant. --- lightning/src/ln/channelmanager.rs | 35 +++++++++++++++++++++--------- lightning/src/ln/onion_payment.rs | 22 +++++++++++++++---- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 66af345ff7d..2dc8ceb6051 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -56,7 +56,7 @@ use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelType #[cfg(any(feature = "_test_utils", test))] use crate::types::features::Bolt11InvoiceFeatures; use crate::routing::router::{BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteParameters, Router}; -use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, InboundHTLCErr, NextPacketDetails}; +use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, InboundHTLCErr, NextPacketDetails, HopConnector}; use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; @@ -4262,11 +4262,15 @@ where // we don't allow forwards outbound over them. return Err(("Refusing to forward to a private channel based on our config.", 0x4000 | 10)); } - if chan.context.get_channel_type().supports_scid_privacy() && next_packet.outgoing_scid != chan.context.outbound_scid_alias() { - // `option_scid_alias` (referred to in LDK as `scid_privacy`) means - // "refuse to forward unless the SCID alias was used", so we pretend - // we don't have the channel here. - return Err(("Refusing to forward over real channel SCID as our counterparty requested.", 0x4000 | 10)); + if let HopConnector::ShortChannelId(outgoing_scid) = next_packet.outgoing_connector { + if chan.context.get_channel_type().supports_scid_privacy() && outgoing_scid != chan.context.outbound_scid_alias() { + // `option_scid_alias` (referred to in LDK as `scid_privacy`) means + // "refuse to forward unless the SCID alias was used", so we pretend + // we don't have the channel here. + return Err(("Refusing to forward over real channel SCID as our counterparty requested.", 0x4000 | 10)); + } + } else { + return Err(("Cannot forward by Node ID without SCID.", 0x4000 | 10)); } // Note that we could technically not return an error yet here and just hope @@ -4318,7 +4322,13 @@ where fn can_forward_htlc( &self, msg: &msgs::UpdateAddHTLC, next_packet_details: &NextPacketDetails ) -> Result<(), (&'static str, u16)> { - match self.do_funded_channel_callback(next_packet_details.outgoing_scid, |chan: &mut FundedChannel| { + let outgoing_scid = match next_packet_details.outgoing_connector { + HopConnector::ShortChannelId(scid) => scid, + HopConnector::Trampoline { .. } => { + return Err(("Cannot forward by Node ID without SCID.", 0x4000 | 10)); + } + }; + match self.do_funded_channel_callback(outgoing_scid, |chan: &mut FundedChannel| { self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details) }) { Some(Ok(())) => {}, @@ -4327,8 +4337,8 @@ where // If we couldn't find the channel info for the scid, it may be a phantom or // intercept forward. if (self.default_configuration.accept_intercept_htlcs && - fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, next_packet_details.outgoing_scid, &self.chain_hash)) || - fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, next_packet_details.outgoing_scid, &self.chain_hash) + fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, outgoing_scid, &self.chain_hash)) || + fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, outgoing_scid, &self.chain_hash) {} else { return Err(("Don't have available channel for forwarding as requested.", 0x4000 | 10)); } @@ -5680,7 +5690,12 @@ where }; let is_intro_node_blinded_forward = next_hop.is_intro_node_blinded_forward(); - let outgoing_scid_opt = next_packet_details_opt.as_ref().map(|d| d.outgoing_scid); + let outgoing_scid_opt = next_packet_details_opt.as_ref().and_then(|d| { + match d.outgoing_connector { + HopConnector::ShortChannelId(scid) => { Some(scid) } + HopConnector::Trampoline { .. } => { None } + } + }); // Process the HTLC on the incoming channel. match self.do_funded_channel_callback(incoming_scid, |chan: &mut FundedChannel| { diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 8b560f72624..a3d34bf3bc9 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -17,6 +17,7 @@ use crate::types::features::BlindedHopFeatures; use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; +use crate::routing::gossip::NodeId; use crate::sign::{NodeSigner, Recipient}; use crate::util::logger::Logger; @@ -298,7 +299,7 @@ where Ok(match hop { onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => { let NextPacketDetails { - next_packet_pubkey, outgoing_amt_msat: _, outgoing_scid: _, outgoing_cltv_value + next_packet_pubkey, outgoing_amt_msat: _, outgoing_connector: _, outgoing_cltv_value } = match next_packet_details_opt { Some(next_packet_details) => next_packet_details, // Forward should always include the next hop details @@ -336,9 +337,22 @@ where }) } +pub(super) enum HopConnector { + // scid-based routing + ShortChannelId(u64), + // Trampoline-based routing + #[allow(unused)] + Trampoline { + // shared secret to derive keys for error decoding + shared_secret: [u8; 32], + // node ID to get to the next Trampoline hop + node_id: NodeId, + }, +} + pub(super) struct NextPacketDetails { pub(super) next_packet_pubkey: Result, - pub(super) outgoing_scid: u64, + pub(super) outgoing_connector: HopConnector, pub(super) outgoing_amt_msat: u64, pub(super) outgoing_cltv_value: u32, } @@ -432,7 +446,7 @@ where let next_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx, msg.onion_routing_packet.public_key.unwrap(), &shared_secret); NextPacketDetails { - next_packet_pubkey, outgoing_scid: short_channel_id, + next_packet_pubkey, outgoing_connector: HopConnector::ShortChannelId(short_channel_id), outgoing_amt_msat: amt_to_forward, outgoing_cltv_value } }, @@ -453,7 +467,7 @@ where let next_packet_pubkey = onion_utils::next_hop_pubkey(&secp_ctx, msg.onion_routing_packet.public_key.unwrap(), &shared_secret); NextPacketDetails { - next_packet_pubkey, outgoing_scid: short_channel_id, outgoing_amt_msat: amt_to_forward, + next_packet_pubkey, outgoing_connector: HopConnector::ShortChannelId(short_channel_id), outgoing_amt_msat: amt_to_forward, outgoing_cltv_value } }, From 0e61db79931a49d57e6fbb1c64160326389fd8c0 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Mon, 10 Feb 2025 00:00:33 -0800 Subject: [PATCH 4/5] Parse inbound TrampolineEntrypoint payload --- lightning/src/ln/msgs.rs | 19 +++++++++++++++++++ lightning/src/ln/onion_payment.rs | 14 ++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 8204d90076a..82d2ceb6f0f 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1775,6 +1775,13 @@ mod fuzzy_internal_msgs { amt_to_forward: u64, outgoing_cltv_value: u32, }, + #[allow(unused)] + TrampolineEntrypoint { + amt_to_forward: u64, + outgoing_cltv_value: u32, + multipath_trampoline_data: Option, + trampoline_packet: TrampolineOnionPacket, + }, Receive { payment_data: Option, payment_metadata: Option>, @@ -2858,6 +2865,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh let mut payment_metadata: Option>> = None; let mut total_msat = None; let mut keysend_preimage: Option = None; + let mut trampoline_onion_packet: Option = None; let mut invoice_request: Option = None; let mut custom_tlvs = Vec::new(); @@ -2872,6 +2880,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh (12, intro_node_blinding_point, option), (16, payment_metadata, option), (18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (20, trampoline_onion_packet, option), (77_777, invoice_request, option), // See https://github.com/lightning/blips/blob/master/blip-0003.md (5482373484, keysend_preimage, option) @@ -2950,6 +2959,16 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?, outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, }) + } else if let Some(trampoline_onion_packet) = trampoline_onion_packet { + if payment_metadata.is_some() || encrypted_tlvs_opt.is_some() || + total_msat.is_some() + { return Err(DecodeError::InvalidValue) } + Ok(Self::TrampolineEntrypoint { + amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?, + outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, + multipath_trampoline_data: payment_data, + trampoline_packet: trampoline_onion_packet, + }) } else { if encrypted_tlvs_opt.is_some() || total_msat.is_some() || invoice_request.is_some() { return Err(DecodeError::InvalidValue) diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index a3d34bf3bc9..be6a250966d 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -99,6 +99,9 @@ pub(super) fn create_fwd_pending_htlc_info( (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point, next_blinding_override) }, + msgs::InboundOnionPayload::TrampolineEntrypoint {..} => { + todo!() + }, msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } => return Err(InboundHTLCErr { msg: "Final Node OnionHopData provided for us as an intermediary node", @@ -166,6 +169,9 @@ pub(super) fn create_recv_pending_htlc_info( sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context), intro_node_blinding_point.is_none(), true, invoice_request) } + msgs::InboundOnionPayload::TrampolineEntrypoint { .. } => { + todo!() + }, msgs::InboundOnionPayload::Forward { .. } => { return Err(InboundHTLCErr { err_code: 0x4000|22, @@ -471,6 +477,14 @@ where outgoing_cltv_value } }, + onion_utils::Hop::Forward { + next_hop_data: msgs::InboundOnionPayload::TrampolineEntrypoint { .. }, .. + } => { + return_err!("TrampolineEntrypoint data provided in intermediary node", 0x4000 | 22, &[0; 0]); + }, + onion_utils::Hop::Receive(msgs::InboundOnionPayload::TrampolineEntrypoint { .. }) => { + todo!() + } onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)), onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } | onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::BlindedReceive { .. }, .. } => From 63608c2fa7f23395e859ea433a17410a35a927a4 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Mon, 10 Feb 2025 00:27:45 -0800 Subject: [PATCH 5/5] Decrypt inbound Trampoline packets --- lightning/src/ln/msgs.rs | 138 ++++++++++++++++++++++++++++++ lightning/src/ln/onion_payment.rs | 42 ++++++++- lightning/src/ln/onion_utils.rs | 39 +++++++++ 3 files changed, 215 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 82d2ceb6f0f..7e445bc08e7 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1812,6 +1812,36 @@ mod fuzzy_internal_msgs { } } + #[allow(unused)] + pub enum InboundTrampolinePayload { + Forward { + /// The value, in msat, of the payment after this hop's fee is deducted. + amt_to_forward: u64, + outgoing_cltv_value: u32, + /// The node id to which the trampoline node must find a route. + outgoing_node_id: PublicKey, + }, + BlindedForward { + short_channel_id: u64, + payment_relay: PaymentRelay, + payment_constraints: PaymentConstraints, + features: BlindedHopFeatures, + intro_node_blinding_point: Option, + next_blinding_override: Option, + }, + BlindedReceive { + sender_intended_htlc_amt_msat: u64, + total_msat: u64, + cltv_expiry_height: u32, + payment_secret: PaymentSecret, + payment_constraints: PaymentConstraints, + payment_context: PaymentContext, + intro_node_blinding_point: Option, + keysend_preimage: Option, + custom_tlvs: Vec<(u64, Vec)>, + } + } + pub(crate) enum OutboundOnionPayload<'a> { Forward { short_channel_id: u64, @@ -2990,6 +3020,114 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh } } +impl ReadableArgs<(Option, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner { + fn read(r: &mut R, args: (Option, NS)) -> Result { + let (update_add_blinding_point, node_signer) = args; + + let mut amt = None; + let mut cltv_value = None; + let mut payment_data: Option = None; + let mut encrypted_tlvs_opt: Option>> = None; + let mut intro_node_blinding_point = None; + let mut outgoing_node_id: Option = None; + let mut total_msat = None; + let mut keysend_preimage: Option = None; + let mut custom_tlvs = Vec::new(); + + let tlv_len = ::read(r)?; + let mut rd = FixedLengthReader::new(r, tlv_len.0); + decode_tlv_stream_with_custom_tlv_decode!(&mut rd, { + (2, amt, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (4, cltv_value, (option, encoding: (u32, HighZeroBytesDroppedBigSize))), + (8, payment_data, option), + (10, encrypted_tlvs_opt, option), + (12, intro_node_blinding_point, option), + (14, outgoing_node_id, option), + (18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + // See https://github.com/lightning/blips/blob/master/blip-0003.md + (5482373484, keysend_preimage, option) + }, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result { + if msg_type < 1 << 16 { return Ok(false) } + let mut value = Vec::new(); + msg_reader.read_to_limit(&mut value, u64::MAX)?; + custom_tlvs.push((msg_type, value)); + Ok(true) + }); + + if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } + if intro_node_blinding_point.is_some() && update_add_blinding_point.is_some() { + return Err(DecodeError::InvalidValue) + } + + if let Some(blinding_point) = intro_node_blinding_point.or(update_add_blinding_point) { + if payment_data.is_some() { + return Err(DecodeError::InvalidValue) + } + let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0; + let enc_tlvs_ss = node_signer.ecdh(Recipient::Node, &blinding_point, None) + .map_err(|_| DecodeError::InvalidValue)?; + let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes()); + let mut s = Cursor::new(&enc_tlvs); + let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64); + match ChaChaPolyReadAdapter::read(&mut reader, rho)? { + ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs { + short_channel_id, payment_relay, payment_constraints, features, next_blinding_override + })} => { + if amt.is_some() || cltv_value.is_some() || total_msat.is_some() || + keysend_preimage.is_some() + { + return Err(DecodeError::InvalidValue) + } + Ok(Self::BlindedForward { + short_channel_id, + payment_relay, + payment_constraints, + features, + intro_node_blinding_point, + next_blinding_override, + }) + }, + ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(receive_tlvs) } => { + let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs; + let expanded_key = node_signer.get_inbound_payment_key(); + if tlvs.verify_for_offer_payment(hmac, nonce, &expanded_key).is_err() { + return Err(DecodeError::InvalidValue); + } + + let UnauthenticatedReceiveTlvs { + payment_secret, payment_constraints, payment_context, + } = tlvs; + if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } + Ok(Self::BlindedReceive { + sender_intended_htlc_amt_msat: amt.ok_or(DecodeError::InvalidValue)?, + total_msat: total_msat.ok_or(DecodeError::InvalidValue)?, + cltv_expiry_height: cltv_value.ok_or(DecodeError::InvalidValue)?, + payment_secret, + payment_constraints, + payment_context, + intro_node_blinding_point, + keysend_preimage, + custom_tlvs, + }) + }, + } + } else if let Some(outgoing_node_id) = outgoing_node_id { + if payment_data.is_some() || encrypted_tlvs_opt.is_some() || + total_msat.is_some() + { return Err(DecodeError::InvalidValue) } + Ok(Self::Forward { + outgoing_node_id, + amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?, + outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, + }) + } else { + // unblinded Trampoline receives are not supported + return Err(DecodeError::InvalidValue); + } + } +} + + impl Writeable for Ping { fn write(&self, w: &mut W) -> Result<(), io::Error> { self.ponglen.write(w)?; diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index be6a250966d..7ad22dadcb9 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -16,7 +16,7 @@ use crate::ln::channelmanager::{BlindedFailure, BlindedForward, CLTV_FAR_FAR_AWA use crate::types::features::BlindedHopFeatures; use crate::ln::msgs; use crate::ln::onion_utils; -use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; +use crate::ln::onion_utils::{HTLCFailReason, InboundTrampolineHop, INVALID_ONION_BLINDING}; use crate::routing::gossip::NodeId; use crate::sign::{NodeSigner, Recipient}; use crate::util::logger::Logger; @@ -432,7 +432,7 @@ where let next_hop = match onion_utils::decode_next_payment_hop( shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, - msg.payment_hash, msg.blinding_point, node_signer + msg.payment_hash, msg.blinding_point, &(*node_signer) ) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { @@ -482,8 +482,42 @@ where } => { return_err!("TrampolineEntrypoint data provided in intermediary node", 0x4000 | 22, &[0; 0]); }, - onion_utils::Hop::Receive(msgs::InboundOnionPayload::TrampolineEntrypoint { .. }) => { - todo!() + onion_utils::Hop::Receive(msgs::InboundOnionPayload::TrampolineEntrypoint { ref trampoline_packet, .. }) => { + { + let trampoline_shared_secret = node_signer.ecdh( + Recipient::Node, &trampoline_packet.public_key, blinded_node_id_tweak.as_ref(), + ).unwrap().secret_bytes(); + let next_trampoline_hop = match onion_utils::decode_next_trampoline_hop( + trampoline_shared_secret, &trampoline_packet.hop_data[..], trampoline_packet.hmac, + msg.payment_hash, msg.blinding_point, node_signer, + ) { + Ok(res) => res, + Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { + return_malformed_err!(err_msg, err_code); + } + Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => { + return_err!(err_msg, err_code, &[0; 0]); + } + }; + match next_trampoline_hop { + InboundTrampolineHop::Forward { next_hop_data: msgs::InboundTrampolinePayload::Forward { amt_to_forward, outgoing_cltv_value, outgoing_node_id }, .. } => { + let next_packet_pubkey = onion_utils::next_hop_pubkey(&secp_ctx, + msg.onion_routing_packet.public_key.unwrap(), &trampoline_shared_secret); + NextPacketDetails { + next_packet_pubkey, + outgoing_connector: HopConnector::Trampoline{ + shared_secret: trampoline_shared_secret, + node_id: NodeId::from_pubkey(&outgoing_node_id), + }, + outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value, + } + } + _ => { + todo!(); + } + } + } } onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)), onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } | diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 6e0a162406c..ca8f80f8ef2 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1444,6 +1444,23 @@ impl Hop { } } +/// Data decrypted from a payment's Trampoline onion payload. +#[allow(unused)] +pub(crate) enum InboundTrampolineHop { + /// This onion payload was for us, not for forwarding to a next-hop. Contains information for + /// verifying the incoming payment. + Receive(msgs::InboundTrampolinePayload), + /// This onion payload needs to be forwarded to a next-hop. + Forward { + /// Onion payload data used in forwarding the payment. + next_hop_data: msgs::InboundTrampolinePayload, + /// HMAC of the next hop's onion packet. + next_hop_hmac: [u8; 32], + /// Bytes of the onion packet we're forwarding. + new_packet_bytes: [u8; ONION_DATA_LEN], + }, +} + /// Error returned when we fail to decode the onion packet. #[derive(Debug)] pub(crate) enum OnionDecodeErr { @@ -1475,6 +1492,28 @@ where } } +pub(crate) fn decode_next_trampoline_hop( + shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash, + blinding_point: Option, node_signer: NS, +) -> Result +where + NS::Target: NodeSigner, +{ + match decode_next_hop( + shared_secret, + hop_data, + hmac_bytes, + Some(payment_hash), + (blinding_point, node_signer), + ) { + Ok((next_hop_data, None)) => Ok(InboundTrampolineHop::Receive(next_hop_data)), + Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => { + Ok(InboundTrampolineHop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes }) + }, + Err(e) => Err(e), + } +} + /// Build a payment onion, returning the first hop msat and cltv values as well. /// `cur_block_height` should be set to the best known block height + 1. pub fn create_payment_onion(