Skip to content

Commit 5e41425

Browse files
authored
Merge pull request #2935 from valentinewallace/2024-03-keysend-to-blinded
Support keysend to blinded paths
2 parents 8354e0c + 664abf2 commit 5e41425

File tree

5 files changed

+105
-10
lines changed

5 files changed

+105
-10
lines changed

lightning/src/ln/blinded_payment_tests.rs

+81
Original file line numberDiff line numberDiff line change
@@ -1183,3 +1183,84 @@ fn conditionally_round_fwd_amt() {
11831183
let expected_fee = pass_claimed_payment_along_route(args);
11841184
expect_payment_sent(&nodes[0], payment_preimage, Some(Some(expected_fee)), true, true);
11851185
}
1186+
1187+
#[test]
1188+
fn blinded_keysend() {
1189+
let mut mpp_keysend_config = test_default_channel_config();
1190+
mpp_keysend_config.accept_mpp_keysend = true;
1191+
let chanmon_cfgs = create_chanmon_cfgs(3);
1192+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1193+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, Some(mpp_keysend_config)]);
1194+
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1195+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1196+
let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
1197+
1198+
let amt_msat = 5000;
1199+
let (keysend_preimage, _, payment_secret) = get_payment_preimage_hash(&nodes[2], None, None);
1200+
let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1,
1201+
1_0000_0000,
1202+
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(),
1203+
&[&chan_upd_1_2], &chanmon_cfgs[2].keys_manager);
1204+
1205+
let payment_hash = nodes[0].node.send_spontaneous_payment_with_retry(Some(keysend_preimage), RecipientOnionFields::spontaneous_empty(), PaymentId(keysend_preimage.0), route_params, Retry::Attempts(0)).unwrap();
1206+
check_added_monitors(&nodes[0], 1);
1207+
1208+
let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]];
1209+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
1210+
assert_eq!(events.len(), 1);
1211+
1212+
let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
1213+
pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash, Some(payment_secret), ev.clone(), true, Some(keysend_preimage));
1214+
claim_payment_along_route(&nodes[0], expected_route, false, keysend_preimage);
1215+
}
1216+
1217+
#[test]
1218+
fn blinded_mpp_keysend() {
1219+
let mut mpp_keysend_config = test_default_channel_config();
1220+
mpp_keysend_config.accept_mpp_keysend = true;
1221+
let chanmon_cfgs = create_chanmon_cfgs(4);
1222+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
1223+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, Some(mpp_keysend_config)]);
1224+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
1225+
1226+
create_announced_chan_between_nodes(&nodes, 0, 1);
1227+
create_announced_chan_between_nodes(&nodes, 0, 2);
1228+
let chan_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3);
1229+
let chan_2_3 = create_announced_chan_between_nodes(&nodes, 2, 3);
1230+
1231+
let amt_msat = 15_000_000;
1232+
let (keysend_preimage, _, payment_secret) = get_payment_preimage_hash(&nodes[3], None, None);
1233+
let route_params = {
1234+
let pay_params = PaymentParameters::blinded(
1235+
vec![
1236+
blinded_payment_path(payment_secret, 1, 1_0000_0000,
1237+
vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_1_3.0.contents],
1238+
&chanmon_cfgs[3].keys_manager
1239+
),
1240+
blinded_payment_path(payment_secret, 1, 1_0000_0000,
1241+
vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_2_3.0.contents],
1242+
&chanmon_cfgs[3].keys_manager
1243+
),
1244+
]
1245+
)
1246+
.with_bolt12_features(channelmanager::provided_bolt12_invoice_features(&UserConfig::default()))
1247+
.unwrap();
1248+
RouteParameters::from_payment_params_and_value(pay_params, amt_msat)
1249+
};
1250+
1251+
let payment_hash = nodes[0].node.send_spontaneous_payment_with_retry(Some(keysend_preimage), RecipientOnionFields::spontaneous_empty(), PaymentId(keysend_preimage.0), route_params, Retry::Attempts(0)).unwrap();
1252+
check_added_monitors!(nodes[0], 2);
1253+
1254+
let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]];
1255+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
1256+
assert_eq!(events.len(), 2);
1257+
1258+
let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
1259+
pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash.clone(),
1260+
Some(payment_secret), ev.clone(), false, Some(keysend_preimage));
1261+
1262+
let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
1263+
pass_along_path(&nodes[0], expected_route[1], amt_msat, payment_hash.clone(),
1264+
Some(payment_secret), ev.clone(), true, Some(keysend_preimage));
1265+
claim_payment_along_route(&nodes[0], expected_route, false, keysend_preimage);
1266+
}

lightning/src/ln/channelmanager.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ pub enum PendingHTLCRouting {
200200
/// For HTLCs received by LDK, these will ultimately bubble back up as
201201
/// [`RecipientOnionFields::custom_tlvs`].
202202
custom_tlvs: Vec<(u64, Vec<u8>)>,
203+
/// Set if this HTLC is the final hop in a multi-hop blinded path.
204+
requires_blinded_error: bool,
203205
},
204206
}
205207

@@ -221,6 +223,7 @@ impl PendingHTLCRouting {
221223
match self {
222224
Self::Forward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure),
223225
Self::Receive { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode),
226+
Self::ReceiveKeysend { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode),
224227
_ => None,
225228
}
226229
}
@@ -4523,7 +4526,10 @@ where
45234526
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
45244527
Some(payment_data), phantom_shared_secret, onion_fields)
45254528
},
4526-
PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry, custom_tlvs } => {
4529+
PendingHTLCRouting::ReceiveKeysend {
4530+
payment_data, payment_preimage, payment_metadata,
4531+
incoming_cltv_expiry, custom_tlvs, requires_blinded_error: _
4532+
} => {
45274533
let onion_fields = RecipientOnionFields {
45284534
payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
45294535
payment_metadata,
@@ -9783,6 +9789,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
97839789
},
97849790
(2, ReceiveKeysend) => {
97859791
(0, payment_preimage, required),
9792+
(1, requires_blinded_error, (default_value, false)),
97869793
(2, incoming_cltv_expiry, required),
97879794
(3, payment_metadata, option),
97889795
(4, payment_data, option), // Added in 0.0.116

lightning/src/ln/msgs.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,7 @@ mod fuzzy_internal_msgs {
17001700
payment_secret: PaymentSecret,
17011701
payment_constraints: PaymentConstraints,
17021702
intro_node_blinding_point: Option<PublicKey>,
1703+
keysend_preimage: Option<PaymentPreimage>,
17031704
}
17041705
}
17051706

@@ -1728,6 +1729,7 @@ mod fuzzy_internal_msgs {
17281729
cltv_expiry_height: u32,
17291730
encrypted_tlvs: Vec<u8>,
17301731
intro_node_blinding_point: Option<PublicKey>, // Set if the introduction node of the blinded path is the final node
1732+
keysend_preimage: Option<PaymentPreimage>,
17311733
}
17321734
}
17331735

@@ -2515,14 +2517,15 @@ impl Writeable for OutboundOnionPayload {
25152517
},
25162518
Self::BlindedReceive {
25172519
sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, encrypted_tlvs,
2518-
intro_node_blinding_point,
2520+
intro_node_blinding_point, keysend_preimage,
25192521
} => {
25202522
_encode_varint_length_prefixed_tlv!(w, {
25212523
(2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required),
25222524
(4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required),
25232525
(10, *encrypted_tlvs, required_vec),
25242526
(12, intro_node_blinding_point, option),
2525-
(18, HighZeroBytesDroppedBigSize(*total_msat), required)
2527+
(18, HighZeroBytesDroppedBigSize(*total_msat), required),
2528+
(5482373484, keysend_preimage, option)
25262529
});
25272530
},
25282531
}
@@ -2572,9 +2575,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
25722575
}
25732576

25742577
if let Some(blinding_point) = intro_node_blinding_point.or(update_add_blinding_point) {
2575-
if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() ||
2576-
keysend_preimage.is_some()
2577-
{
2578+
if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() {
25782579
return Err(DecodeError::InvalidValue)
25792580
}
25802581
let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0;
@@ -2587,7 +2588,9 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
25872588
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs {
25882589
short_channel_id, payment_relay, payment_constraints, features
25892590
})} => {
2590-
if amt.is_some() || cltv_value.is_some() || total_msat.is_some() {
2591+
if amt.is_some() || cltv_value.is_some() || total_msat.is_some() ||
2592+
keysend_preimage.is_some()
2593+
{
25912594
return Err(DecodeError::InvalidValue)
25922595
}
25932596
Ok(Self::BlindedForward {
@@ -2609,6 +2612,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
26092612
payment_secret,
26102613
payment_constraints,
26112614
intro_node_blinding_point,
2615+
keysend_preimage,
26122616
})
26132617
},
26142618
}

lightning/src/ln/onion_payment.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ pub(super) fn create_recv_pending_htlc_info(
139139
cltv_expiry_height, payment_metadata, false),
140140
msgs::InboundOnionPayload::BlindedReceive {
141141
sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret,
142-
intro_node_blinding_point, payment_constraints, ..
142+
intro_node_blinding_point, payment_constraints, keysend_preimage, ..
143143
} => {
144144
check_blinded_payment_constraints(
145145
sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints
@@ -152,8 +152,9 @@ pub(super) fn create_recv_pending_htlc_info(
152152
}
153153
})?;
154154
let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
155-
(Some(payment_data), None, Vec::new(), sender_intended_htlc_amt_msat, cltv_expiry_height,
156-
None, intro_node_blinding_point.is_none())
155+
(Some(payment_data), keysend_preimage, Vec::new(),
156+
sender_intended_htlc_amt_msat, cltv_expiry_height, None,
157+
intro_node_blinding_point.is_none())
157158
}
158159
msgs::InboundOnionPayload::Forward { .. } => {
159160
return Err(InboundHTLCErr {
@@ -232,6 +233,7 @@ pub(super) fn create_recv_pending_htlc_info(
232233
payment_metadata,
233234
incoming_cltv_expiry: onion_cltv_expiry,
234235
custom_tlvs,
236+
requires_blinded_error,
235237
}
236238
} else if let Some(data) = payment_data {
237239
PendingHTLCRouting::Receive {

lightning/src/ln/onion_utils.rs

+1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ pub(super) fn build_onion_payloads(
213213
cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
214214
encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
215215
intro_node_blinding_point: blinding_point.take(),
216+
keysend_preimage: *keysend_preimage,
216217
});
217218
} else {
218219
res.push(msgs::OutboundOnionPayload::BlindedForward {

0 commit comments

Comments
 (0)