Skip to content

Commit 551ce8e

Browse files
Support forwarding blinded payments.
Error handling will be completed in later commit(s).
1 parent b2eb3e7 commit 551ce8e

File tree

2 files changed

+105
-15
lines changed

2 files changed

+105
-15
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ pub(super) enum PendingHTLCRouting {
105105
/// The SCID from the onion that we should forward to. This could be a real SCID or a fake one
106106
/// generated using `get_fake_scid` from the scid_utils::fake_scid module.
107107
short_channel_id: u64, // This should be NonZero<u64> eventually when we bump MSRV
108+
blinded: Option<BlindedForward>,
108109
},
109110
Receive {
110111
payment_data: msgs::FinalOnionHopData,
@@ -182,6 +183,12 @@ pub(super) enum HTLCForwardInfo {
182183
},
183184
}
184185

186+
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
187+
pub(super) struct BlindedForward {
188+
inbound_blinding_point: PublicKey,
189+
we_are_intro_node: bool,
190+
}
191+
185192
/// Tracks the inbound corresponding to an outbound HTLC
186193
#[derive(Clone, Hash, PartialEq, Eq)]
187194
pub(crate) struct HTLCPreviousHopData {
@@ -2733,22 +2740,45 @@ where
27332740
hmac: hop_hmac,
27342741
};
27352742

2736-
let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data {
2743+
let (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_pt)
2744+
= match hop_data
2745+
{
27372746
msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
2738-
(short_channel_id, amt_to_forward, outgoing_cltv_value),
2739-
msgs::InboundOnionPayload::Receive { .. } =>
2747+
(short_channel_id, amt_to_forward, outgoing_cltv_value, None),
2748+
msgs::InboundOnionPayload::BlindedForward {
2749+
short_channel_id, payment_relay, intro_node_blinding_point, ..
2750+
} => {
2751+
// We checked these values in [`Self::decode_update_add_htlc_onion`].
2752+
let amt_to_forward = msg.amount_msat - payment_relay.fee_base_msat as u64
2753+
- (msg.amount_msat * payment_relay.fee_proportional_millionths as u64 / 1_000_000);
2754+
let outgoing_cltv_value = msg.cltv_expiry - payment_relay.cltv_expiry_delta as u32;
2755+
(short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point)
2756+
},
2757+
msgs::InboundOnionPayload::Receive { .. } => {
27402758
return Err(InboundOnionErr {
27412759
msg: "Final Node OnionHopData provided for us as an intermediary node",
27422760
err_code: 0x4000 | 22,
27432761
err_data: Vec::new(),
2744-
}),
2745-
_ => todo!()
2762+
})
2763+
},
2764+
msgs::InboundOnionPayload::BlindedReceive { .. } => {
2765+
return Err(InboundOnionErr {
2766+
msg: "Final Node OnionHopData provided for us as an intermediary node",
2767+
err_code: INVALID_ONION_BLINDING,
2768+
err_data: Sha256::hash(&msg.onion_routing_packet.hop_data).into_inner().to_vec(),
2769+
})
2770+
},
27462771
};
27472772

27482773
Ok(PendingHTLCInfo {
27492774
routing: PendingHTLCRouting::Forward {
27502775
onion_packet: outgoing_packet,
27512776
short_channel_id,
2777+
blinded: intro_node_blinding_pt.or(msg.blinding_point)
2778+
.map(|bp| BlindedForward {
2779+
inbound_blinding_point: bp,
2780+
we_are_intro_node: intro_node_blinding_pt.is_some(),
2781+
}),
27522782
},
27532783
payment_hash: msg.payment_hash,
27542784
incoming_shared_secret: shared_secret,
@@ -2939,6 +2969,15 @@ where
29392969
}
29402970
}
29412971
}
2972+
macro_rules! return_blinded_htlc_err {
2973+
($msg: expr) => {
2974+
if msg.blinding_point.is_some() {
2975+
return_malformed_err!($msg, INVALID_ONION_BLINDING);
2976+
} else {
2977+
return_err!($msg, INVALID_ONION_BLINDING, [0; 32]);
2978+
}
2979+
}
2980+
}
29422981

29432982
let next_hop = match onion_utils::decode_next_payment_hop(shared_secret,
29442983
&msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash,
@@ -2962,6 +3001,39 @@ where
29623001
msg.onion_routing_packet.public_key.unwrap(), &shared_secret);
29633002
(short_channel_id, amt_to_forward, outgoing_cltv_value, Some(next_packet_pk))
29643003
},
3004+
onion_utils::Hop::Forward {
3005+
next_hop_data: msgs::InboundOnionPayload::BlindedForward {
3006+
short_channel_id, ref payment_relay, ref payment_constraints,
3007+
..
3008+
}, ..
3009+
} => {
3010+
let amt_to_forward =
3011+
match msg.amount_msat.checked_mul(payment_relay.fee_proportional_millionths as u64)
3012+
.map(|prop_fee| prop_fee / 1_000_000)
3013+
.and_then(|fee| msg.amount_msat.checked_sub(fee))
3014+
.and_then(|amt| amt.checked_sub(payment_relay.fee_base_msat as u64))
3015+
{
3016+
Some(a) => a,
3017+
None => {
3018+
return_blinded_htlc_err!("Over or underflow computing amt_to_forward for blinded forward");
3019+
}
3020+
};
3021+
let outgoing_cltv_value =
3022+
match msg.cltv_expiry.checked_sub(payment_relay.cltv_expiry_delta as u32) {
3023+
Some(v) => v,
3024+
None => {
3025+
return_blinded_htlc_err!("Underflow computing cltv value for blinded forward");
3026+
}
3027+
};
3028+
if amt_to_forward < payment_constraints.htlc_minimum_msat ||
3029+
outgoing_cltv_value > payment_constraints.max_cltv_expiry
3030+
{
3031+
return_blinded_htlc_err!("amt_to_forward did not meet htlc_minimum_msat or outgoing_cltv_value exceeded max_cltv_expiry");
3032+
}
3033+
let next_packet_pk = onion_utils::next_hop_pubkey(&self.secp_ctx,
3034+
msg.onion_routing_packet.public_key.unwrap(), &shared_secret);
3035+
(short_channel_id, amt_to_forward, outgoing_cltv_value, Some(next_packet_pk))
3036+
},
29653037
// We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the
29663038
// inbound channel's state.
29673039
onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
@@ -3808,8 +3880,8 @@ where
38083880
})?;
38093881

38103882
let routing = match payment.forward_info.routing {
3811-
PendingHTLCRouting::Forward { onion_packet, .. } => {
3812-
PendingHTLCRouting::Forward { onion_packet, short_channel_id: next_hop_scid }
3883+
PendingHTLCRouting::Forward { onion_packet, blinded, .. } => {
3884+
PendingHTLCRouting::Forward { short_channel_id: next_hop_scid, onion_packet, blinded, }
38133885
},
38143886
_ => unreachable!() // Only `PendingHTLCRouting::Forward`s are intercepted
38153887
};
@@ -4008,7 +4080,8 @@ where
40084080
prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
40094081
forward_info: PendingHTLCInfo {
40104082
incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value,
4011-
routing: PendingHTLCRouting::Forward { onion_packet, .. }, skimmed_fee_msat, ..
4083+
routing: PendingHTLCRouting::Forward { onion_packet, blinded, .. },
4084+
skimmed_fee_msat, ..
40124085
},
40134086
}) => {
40144087
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);
@@ -4021,9 +4094,24 @@ where
40214094
// Phantom payments are only PendingHTLCRouting::Receive.
40224095
phantom_shared_secret: None,
40234096
});
4024-
if let Err(e) = chan.get_mut().queue_add_htlc(outgoing_amt_msat,
4025-
payment_hash, outgoing_cltv_value, htlc_source.clone(),
4026-
onion_packet, skimmed_fee_msat, None, &self.fee_estimator,
4097+
let next_blinding_point = if let Some(b) = blinded {
4098+
let encrypted_tlvs_ss = self.node_signer.ecdh(
4099+
Recipient::Node, &b.inbound_blinding_point, None
4100+
).unwrap().secret_bytes();
4101+
match onion_utils::next_hop_pubkey(&self.secp_ctx, b.inbound_blinding_point, &encrypted_tlvs_ss) {
4102+
Ok(pk) => Some(pk),
4103+
Err(_) => {
4104+
failed_forwards.push((htlc_source, payment_hash,
4105+
HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]),
4106+
HTLCDestination::FailedPayment { payment_hash }
4107+
));
4108+
continue;
4109+
}
4110+
}
4111+
} else { None };
4112+
if let Err(e) = chan.get_mut().queue_add_htlc(outgoing_amt_msat, payment_hash,
4113+
outgoing_cltv_value, htlc_source.clone(), onion_packet, skimmed_fee_msat,
4114+
next_blinding_point, &self.fee_estimator,
40274115
&self.logger)
40284116
{
40294117
if let ChannelError::Ignore(msg) = e {
@@ -7946,6 +8034,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
79468034
(0, Forward) => {
79478035
(0, onion_packet, required),
79488036
(2, short_channel_id, required),
8037+
(4, blinded, option),
79498038
},
79508039
(1, Receive) => {
79518040
(0, payment_data, required),
@@ -7965,6 +8054,11 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
79658054
},
79668055
;);
79678056

8057+
impl_writeable_tlv_based!(BlindedForward, {
8058+
(0, inbound_blinding_point, required),
8059+
(2, we_are_intro_node, required),
8060+
});
8061+
79688062
impl_writeable_tlv_based!(PendingHTLCInfo, {
79698063
(0, routing, required),
79708064
(2, incoming_shared_secret, required),

lightning/src/ln/outbound_payment.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,10 +1075,6 @@ impl OutboundPayments {
10751075
path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()}));
10761076
continue 'path_check;
10771077
}
1078-
if path.blinded_tail.is_some() {
1079-
path_errs.push(Err(APIError::InvalidRoute{err: "Sending to blinded paths isn't supported yet".to_owned()}));
1080-
continue 'path_check;
1081-
}
10821078
let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 {
10831079
usize::max_value() } else { path.hops.len() - 1 };
10841080
for (idx, hop) in path.hops.iter().enumerate() {

0 commit comments

Comments
 (0)