Skip to content

Route blinding: support forwarding as the intro node #2540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1e12bdf
Parse blinding point in UpdateAddHTLC
valentinewallace Mar 23, 2023
7f765a3
onion_utils: extract decrypting faiure packet into method
valentinewallace Oct 26, 2023
b70525d
Parse blinded onion errors in tests only.
valentinewallace Oct 26, 2023
1596116
Persist outbound blinding points in Channel
valentinewallace Jul 13, 2023
b645237
Store whether a forwarded HTLC is blinded in PendingHTLCRouting
valentinewallace Oct 26, 2023
ae15ba8
Persist whether an HTLC is blinded in HTLCPreviousHopData.
valentinewallace Oct 26, 2023
21ae9fd
Set HTLCPreviousHopData::blinded on intro node forward.
valentinewallace Oct 26, 2023
a2b2fb0
Parameterize Channel's htlc forward method by outbound blinding point
valentinewallace Oct 26, 2023
50c850f
Set update_add blinding point on HTLC forward
valentinewallace Oct 26, 2023
1a7254c
Parse blinded forward-as-intro onion payloads
valentinewallace Oct 26, 2023
47d34c3
Support forwarding blinded HTLCs as intro node.
valentinewallace Oct 26, 2023
d2222c8
Remove now-unused Readable impl for ReceiveTlvs
valentinewallace Oct 25, 2023
918f09c
Test blinded forward failure to calculate outbound cltv expiry
valentinewallace Sep 15, 2023
c8adb54
Test blinded forwarding payload encoded as receive error case
valentinewallace Sep 15, 2023
67d2463
Correctly fail back on outbound channel check for blinded HTLC
valentinewallace Sep 15, 2023
8c0c3a3
Extract blinded route param creation into test util
valentinewallace Oct 2, 2023
09cf484
Correctly fail back blinded inbound fwd HTLCs when adding to a Channel
valentinewallace Oct 3, 2023
b767d37
Correctly fail back downstream-failed blinded HTLCs as intro
valentinewallace Oct 9, 2023
0a45870
Test intro node blinded HTLC failing in process_pending_htlc_fwds.
valentinewallace Oct 31, 2023
4d43ccd
Test intro node failing blinded intercept HTLC.
valentinewallace Oct 31, 2023
e510e3c
Add release note for blinded HTLC backwards compat.
valentinewallace Nov 7, 2023
6af786a
Test blinding point serialization in Channel.
valentinewallace Nov 13, 2023
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
17 changes: 1 addition & 16 deletions lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,21 +118,6 @@ impl Writeable for ReceiveTlvs {
}
}

// This will be removed once we support forwarding blinded HTLCs, because we'll always read a
// `BlindedPaymentTlvs` instead.
impl Readable for ReceiveTlvs {
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
_init_and_read_tlv_stream!(r, {
(12, payment_constraints, required),
(65536, payment_secret, required),
});
Ok(Self {
payment_secret: payment_secret.0.unwrap(),
payment_constraints: payment_constraints.0.unwrap()
})
}
}

impl<'a> Writeable for BlindedPaymentTlvsRef<'a> {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
// TODO: write padding
Expand Down Expand Up @@ -187,7 +172,7 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
}

/// `None` if underflow occurs.
fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option<u64> {
pub(crate) fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option<u64> {
let inbound_amt = inbound_amt_msat as u128;
let base = payment_relay.fee_base_msat as u128;
let prop = payment_relay.fee_proportional_millionths as u128;
Expand Down
329 changes: 326 additions & 3 deletions lightning/src/ln/blinded_payment_tests.rs

Large diffs are not rendered by default.

169 changes: 155 additions & 14 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

114 changes: 102 additions & 12 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParame
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, InboundOnionErr, NextPacketDetails};
use crate::ln::msgs;
use crate::ln::onion_utils;
use crate::ln::onion_utils::HTLCFailReason;
use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
#[cfg(test)]
use crate::ln::outbound_payment;
Expand Down Expand Up @@ -119,6 +119,8 @@ pub enum PendingHTLCRouting {
/// The SCID from the onion that we should forward to. This could be a real SCID or a fake one
/// generated using `get_fake_scid` from the scid_utils::fake_scid module.
short_channel_id: u64, // This should be NonZero<u64> eventually when we bump MSRV
/// Set if this HTLC is being forwarded within a blinded path.
blinded: Option<BlindedForward>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is public now, we should include a bit more details about why this is here and what its used for. Happy to just do that in #2762.

Copy link
Contributor Author

@valentinewallace valentinewallace Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, doing it in #2762 sgtm. FWIW, there are more detailed docs on the BlindedForward struct itself.

},
/// An HTLC paid to an invoice (supposedly) generated by us.
/// At this point, we have not checked that the invoice being paid was actually generated by us,
Expand Down Expand Up @@ -155,6 +157,28 @@ pub enum PendingHTLCRouting {
},
}

/// Information used to forward or fail this HTLC that is being forwarded within a blinded path.
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct BlindedForward {
/// The `blinding_point` that was set in the inbound [`msgs::UpdateAddHTLC`], or in the inbound
/// onion payload if we're the introduction node. Useful for calculating the next hop's
/// [`msgs::UpdateAddHTLC::blinding_point`].
pub inbound_blinding_point: PublicKey,
// Another field will be added here when we support forwarding as a non-intro node.
}

impl PendingHTLCRouting {
// Used to override the onion failure code and data if the HTLC is blinded.
fn blinded_failure(&self) -> Option<BlindedFailure> {
// TODO: needs update when we support receiving to multi-hop blinded paths
if let Self::Forward { blinded: Some(_), .. } = self {
Some(BlindedFailure::FromIntroductionNode)
} else {
None
}
}
}

/// Full details of an incoming HTLC, including routing info.
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
pub struct PendingHTLCInfo {
Expand Down Expand Up @@ -213,6 +237,13 @@ pub(super) enum HTLCForwardInfo {
},
}

// Used for failing blinded HTLCs backwards correctly.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
enum BlindedFailure {
FromIntroductionNode,
// Another variant will be added here for non-intro nodes.
}

/// Tracks the inbound corresponding to an outbound HTLC
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) struct HTLCPreviousHopData {
Expand All @@ -222,6 +253,7 @@ pub(crate) struct HTLCPreviousHopData {
htlc_id: u64,
incoming_packet_shared_secret: [u8; 32],
phantom_shared_secret: Option<[u8; 32]>,
blinded_failure: Option<BlindedFailure>,

// This field is consumed by `claim_funds_from_hop()` when updating a force-closed backwards
// channel with a preimage provided by the forward channel.
Expand Down Expand Up @@ -2945,14 +2977,24 @@ where
msg, &self.node_signer, &self.logger, &self.secp_ctx
)?;

let is_blinded = match next_hop {
onion_utils::Hop::Forward {
next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, ..
} => true,
_ => false, // TODO: update this when we support receiving to multi-hop blinded paths
};

macro_rules! return_err {
($msg: expr, $err_code: expr, $data: expr) => {
{
log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
let (err_code, err_data) = if is_blinded {
(INVALID_ONION_BLINDING, &[0; 32][..])
} else { ($err_code, $data) };
return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
reason: HTLCFailReason::reason($err_code, $data.to_vec())
reason: HTLCFailReason::reason(err_code, err_data.to_vec())
.get_encrypted_failure_packet(&shared_secret, &None),
}));
}
Expand Down Expand Up @@ -4013,8 +4055,10 @@ where
})?;

let routing = match payment.forward_info.routing {
PendingHTLCRouting::Forward { onion_packet, .. } => {
PendingHTLCRouting::Forward { onion_packet, short_channel_id: next_hop_scid }
PendingHTLCRouting::Forward { onion_packet, blinded, .. } => {
PendingHTLCRouting::Forward {
onion_packet, blinded, short_channel_id: next_hop_scid
}
},
_ => unreachable!() // Only `PendingHTLCRouting::Forward`s are intercepted
};
Expand Down Expand Up @@ -4058,6 +4102,7 @@ where
htlc_id: payment.prev_htlc_id,
incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret,
phantom_shared_secret: None,
blinded_failure: payment.forward_info.routing.blinded_failure(),
});

let failure_reason = HTLCFailReason::from_failure_code(0x4000 | 10);
Expand Down Expand Up @@ -4106,6 +4151,7 @@ where
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: incoming_shared_secret,
phantom_shared_secret: $phantom_ss,
blinded_failure: routing.blinded_failure(),
});

let reason = if $next_hop_unknown {
Expand Down Expand Up @@ -4135,7 +4181,7 @@ where
}
}
}
if let PendingHTLCRouting::Forward { onion_packet, .. } = routing {
if let PendingHTLCRouting::Forward { ref onion_packet, .. } = routing {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to handle the route blinding stuff here? If we have a forward to a phantom that takes a blinded path we should support overriding the error type, even though its not something we're gonna use right now its pretty trivial to do and we might as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will always fail to decode onion_packet if it's a phantom, because we use Recipient::Node instead of ::PhantomNode when decoding the encrypted TLVs. Not much we can do here until we add full phantom support, IIUC.

let phantom_pubkey_res = self.node_signer.get_node_id(Recipient::PhantomNode);
if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.chain_hash) {
let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
Expand Down Expand Up @@ -4210,7 +4256,9 @@ where
prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
forward_info: PendingHTLCInfo {
incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value,
routing: PendingHTLCRouting::Forward { onion_packet, .. }, skimmed_fee_msat, ..
routing: PendingHTLCRouting::Forward {
onion_packet, blinded, ..
}, skimmed_fee_msat, ..
},
}) => {
log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, &payment_hash, short_chan_id);
Expand All @@ -4222,10 +4270,19 @@ where
incoming_packet_shared_secret: incoming_shared_secret,
// Phantom payments are only PendingHTLCRouting::Receive.
phantom_shared_secret: None,
blinded_failure: blinded.map(|_| BlindedFailure::FromIntroductionNode),
});
let next_blinding_point = blinded.and_then(|b| {
let encrypted_tlvs_ss = self.node_signer.ecdh(
Recipient::Node, &b.inbound_blinding_point, None
).unwrap().secret_bytes();
onion_utils::next_hop_pubkey(
&self.secp_ctx, b.inbound_blinding_point, &encrypted_tlvs_ss
).ok()
});
if let Err(e) = chan.queue_add_htlc(outgoing_amt_msat,
payment_hash, outgoing_cltv_value, htlc_source.clone(),
onion_packet, skimmed_fee_msat, &self.fee_estimator,
onion_packet, skimmed_fee_msat, next_blinding_point, &self.fee_estimator,
&self.logger)
{
if let ChannelError::Ignore(msg) = e {
Expand Down Expand Up @@ -4276,6 +4333,7 @@ where
skimmed_fee_msat, ..
}
}) => {
let blinded_failure = routing.blinded_failure();
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => {
let _legacy_hop_data = Some(payment_data.clone());
Expand Down Expand Up @@ -4305,6 +4363,7 @@ where
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: incoming_shared_secret,
phantom_shared_secret,
blinded_failure,
},
// We differentiate the received value from the sender intended value
// if possible so that we don't prematurely mark MPP payments complete
Expand Down Expand Up @@ -4335,6 +4394,7 @@ where
htlc_id: $htlc.prev_hop.htlc_id,
incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret,
phantom_shared_secret,
blinded_failure: None,
}), payment_hash,
HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data),
HTLCDestination::FailedPayment { payment_hash: $payment_hash },
Expand Down Expand Up @@ -5098,9 +5158,23 @@ where
&self.pending_events, &self.logger)
{ self.push_pending_forwards_ev(); }
},
HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, ref phantom_shared_secret, ref outpoint, .. }) => {
log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards from us with {:?}", &payment_hash, onion_error);
let err_packet = onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret);
HTLCSource::PreviousHopData(HTLCPreviousHopData {
ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret,
ref phantom_shared_secret, ref outpoint, ref blinded_failure, ..
}) => {
log_trace!(self.logger, "Failing {}HTLC with payment_hash {} backwards from us: {:?}",
if blinded_failure.is_some() { "blinded " } else { "" }, &payment_hash, onion_error);
let err_packet = match blinded_failure {
Some(BlindedFailure::FromIntroductionNode) => {
let blinded_onion_error = HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]);
blinded_onion_error.get_encrypted_failure_packet(
incoming_packet_shared_secret, phantom_shared_secret
)
},
None => {
onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret)
}
};

let mut push_forward_ev = false;
let mut forward_htlcs = self.forward_htlcs.lock().unwrap();
Expand Down Expand Up @@ -6381,8 +6455,12 @@ where
// but if we've sent a shutdown and they haven't acknowledged it yet, we just
// want to reject the new HTLC and fail it backwards instead of forwarding.
match pending_forward_info {
PendingHTLCStatus::Forward(PendingHTLCInfo { ref incoming_shared_secret, .. }) => {
let reason = if (error_code & 0x1000) != 0 {
PendingHTLCStatus::Forward(PendingHTLCInfo {
ref incoming_shared_secret, ref routing, ..
}) => {
let reason = if routing.blinded_failure().is_some() {
HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32])
} else if (error_code & 0x1000) != 0 {
let (real_code, error_data) = self.get_htlc_inbound_temp_fail_err_and_data(error_code, chan);
HTLCFailReason::reason(real_code, error_data)
} else {
Expand Down Expand Up @@ -6584,6 +6662,7 @@ where
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: forward_info.incoming_shared_secret,
phantom_shared_secret: None,
blinded_failure: forward_info.routing.blinded_failure(),
});

failed_intercept_forwards.push((htlc_source, forward_info.payment_hash,
Expand Down Expand Up @@ -8180,6 +8259,7 @@ where
incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret,
phantom_shared_secret: None,
outpoint: htlc.prev_funding_outpoint,
blinded_failure: htlc.forward_info.routing.blinded_failure(),
});

let requested_forward_scid /* intercept scid */ = match htlc.forward_info.routing {
Expand Down Expand Up @@ -9143,9 +9223,14 @@ impl_writeable_tlv_based!(PhantomRouteHints, {
(6, real_node_pubkey, required),
});

impl_writeable_tlv_based!(BlindedForward, {
(0, inbound_blinding_point, required),
});

impl_writeable_tlv_based_enum!(PendingHTLCRouting,
(0, Forward) => {
(0, onion_packet, required),
(1, blinded, option),
(2, short_channel_id, required),
},
(1, Receive) => {
Expand Down Expand Up @@ -9247,10 +9332,15 @@ impl_writeable_tlv_based_enum!(PendingHTLCStatus, ;
(1, Fail),
);

impl_writeable_tlv_based_enum!(BlindedFailure,
(0, FromIntroductionNode) => {}, ;
);

impl_writeable_tlv_based!(HTLCPreviousHopData, {
(0, short_channel_id, required),
(1, phantom_shared_secret, option),
(2, outpoint, required),
(3, blinded_failure, option),
(4, htlc_id, required),
(6, incoming_packet_shared_secret, required),
(7, user_channel_id, option),
Expand Down
5 changes: 5 additions & 0 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,7 @@ fn test_fee_spike_violation_fails_htlc() {
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet,
skimmed_fee_msat: None,
blinding_point: None,
};

nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg);
Expand Down Expand Up @@ -1611,6 +1612,7 @@ fn test_chan_reserve_violation_inbound_htlc_outbound_channel() {
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet,
skimmed_fee_msat: None,
blinding_point: None,
};

nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &msg);
Expand Down Expand Up @@ -1789,6 +1791,7 @@ fn test_chan_reserve_violation_inbound_htlc_inbound_chan() {
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet,
skimmed_fee_msat: None,
blinding_point: None,
};

nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg);
Expand Down Expand Up @@ -3510,6 +3513,7 @@ fn fail_backward_pending_htlc_upon_channel_failure() {
cltv_expiry,
onion_routing_packet,
skimmed_fee_msat: None,
blinding_point: None,
};
nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add_htlc);
}
Expand Down Expand Up @@ -6481,6 +6485,7 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
cltv_expiry: htlc_cltv,
onion_routing_packet: onion_packet.clone(),
skimmed_fee_msat: None,
blinding_point: None,
};

for i in 0..50 {
Expand Down
Loading