Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
WIP: Decrypt inbound Trampoline onions by arik-so · Pull Request #3585 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP: Decrypt inbound Trampoline onions by arik-so · Pull Request #3585 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP: Decrypt inbound Trampoline onions by arik-so · Pull Request #3585 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' WIP: Decrypt inbound Trampoline onions by arik-so · Pull Request #3585 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP: Decrypt inbound Trampoline onions by arik-so · Pull Request #3585 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP: Decrypt inbound Trampoline onions by arik-so · Pull Request #3585 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); WIP: Decrypt inbound Trampoline onions by arik-so · Pull Request #3585 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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};
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -169,6 +169,25 @@ pub enum PendingHTLCRouting {
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
/// The absolute CLTV of the inbound HTLC
incoming_cltv_expiry: Option<u32>,
},

/// 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
Expand DownExpand Up@@ -279,6 +298,7 @@ impl PendingHTLCRouting {
fn incoming_cltv_expiry(&self) -> Option<u32> {
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),
}
Expand DownExpand Up@@ -4242,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
Expand DownExpand Up@@ -4298,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<SP>| {
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<SP>| {
self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details)
}) {
Some(Ok(())) => {},
Expand All@@ -4307,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));
}
Expand DownExpand Up@@ -5660,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<SP>| {
Expand DownExpand Up@@ -8909,6 +8944,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,
};
Expand DownExpand Up@@ -12476,6 +12512,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, {
Expand Down
157 changes: 157 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
Expand DownExpand Up@@ -1805,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<PublicKey>,
next_blinding_override: Option<PublicKey>,
},
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<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
}
}

pub(crate) enum OutboundOnionPayload<'a> {
Forward {
short_channel_id: u64,
Expand DownExpand Up@@ -2858,6 +2895,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
let mut invoice_request: Option<InvoiceRequest> = None;
let mut custom_tlvs = Vec::new();

Expand All@@ -2872,6 +2910,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand DownExpand Up@@ -2950,6 +2989,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, 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)
Expand All@@ -2971,6 +3020,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}
}

impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
let (update_add_blinding_point, node_signer) = args;

let mut amt = None;
let mut cltv_value = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
let mut intro_node_blinding_point = None;
let mut outgoing_node_id: Option<PublicKey> = None;
let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
let mut custom_tlvs = Vec::new();

let tlv_len = <BigSize as Readable>::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<bool, DecodeError> {
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<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.ponglen.write(w)?;
Expand Down
Loading