Skip to content

Commit

Permalink
Merge pull request #2540 from valentinewallace/2023-08-blinded-errors
Browse files Browse the repository at this point in the history
Route blinding: support forwarding as the intro node
  • Loading branch information
valentinewallace authored Dec 1, 2023
2 parents f07f4b9 + 6af786a commit 74bc9e2
Show file tree
Hide file tree
Showing 10 changed files with 757 additions and 70 deletions.
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>,
},
/// 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 {
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

0 comments on commit 74bc9e2

Please sign in to comment.