Skip to content

Commit 7c32590

Browse files
committed
Add a payment_metadata map in blinded payment path contexts
Similar to how BOLT 11 payments can use a `payment_metadata` to provide arbitrary bytes in the invoice to be communicated back to them when receiving, its useful to be able to provide some bytes which are communicated back upon receiving a payment. Here we do so in the BOLT 12 blinded path contexts, offering a `BTreeMap<u64, Vec<u8>>` instead to enable more easily including multiple sets of data. Also note that a `Router` building a blinded path is allowed to modify the `payment_metadata` without breaking the payment. Tests by claude
1 parent 05135ae commit 7c32590

12 files changed

Lines changed: 368 additions & 32 deletions

File tree

fuzz/src/invoice_request_deser.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
104104
let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
105105
offer_id: OfferId([42; 32]),
106106
invoice_request: invoice_request_fields,
107+
payment_metadata: None,
107108
});
108109
let payee_tlvs = ReceiveTlvs {
109110
payment_secret: PaymentSecret([42; 32]),

fuzz/src/refund_deser.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
6969
) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
7070
let entropy_source = Randomness {};
7171
let receive_auth_key = ReceiveAuthKey([41; 32]);
72-
let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
72+
let payment_context =
73+
PaymentContext::Bolt12Refund(Bolt12RefundContext { payment_metadata: None });
7374
let payee_tlvs = ReceiveTlvs {
7475
payment_secret: PaymentSecret([42; 32]),
7576
payment_constraints: PaymentConstraints {

lightning/src/blinded_path/payment.rs

Lines changed: 102 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
//! Data structures and methods for constructing [`BlindedPaymentPath`]s to send a payment over.
1111
12+
use alloc::collections::BTreeMap;
13+
1214
use bitcoin::secp256k1::ecdh::SharedSecret;
1315
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1416

@@ -29,8 +31,8 @@ use crate::types::features::BlindedHopFeatures;
2931
use crate::types::payment::PaymentSecret;
3032
use crate::types::routing::RoutingFees;
3133
use crate::util::ser::{
32-
FixedLengthReader, HighZeroBytesDroppedBigSize, LengthReadableArgs, Readable, WithoutLength,
33-
Writeable, Writer,
34+
BigSizeKeyedMap, FixedLengthReader, HighZeroBytesDroppedBigSize, LengthReadableArgs, Readable,
35+
WithoutLength, Writeable, Writer,
3436
};
3537

3638
#[allow(unused_imports)]
@@ -572,6 +574,20 @@ pub enum PaymentContext {
572574
/// [`Refund`]: crate::offers::refund::Refund
573575
Bolt12Refund(Bolt12RefundContext),
574576
}
577+
impl PaymentContext {
578+
/// Returns the additional payment metadata stored alongside this payment context, if any.
579+
///
580+
/// Payment metadata is stored as a map from a numeric key to an arbitrary byte array value.
581+
/// This allows for several types of metadata to be stored attached to a single payment. In the
582+
/// future some optional features of LDK may use some keys.
583+
pub fn payment_metadata(&self) -> Option<&BTreeMap<u64, Vec<u8>>> {
584+
match self {
585+
Self::Bolt12Offer(Bolt12OfferContext { payment_metadata, .. })
586+
| Self::AsyncBolt12Offer(AsyncBolt12OfferContext { payment_metadata, .. })
587+
| Self::Bolt12Refund(Bolt12RefundContext { payment_metadata, .. }) => payment_metadata.as_ref(),
588+
}
589+
}
590+
}
575591

576592
// Used when writing PaymentContext in Event::PaymentClaimable to avoid cloning.
577593
pub(crate) enum PaymentContextRef<'a> {
@@ -594,6 +610,27 @@ pub struct Bolt12OfferContext {
594610
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
595611
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
596612
pub invoice_request: InvoiceRequestFields,
613+
614+
/// Additional data about this payment which is not used in LDK and can be used for any
615+
/// purpose.
616+
///
617+
/// This is analogous to the BOLT 11 [`RecipientOnionFields::payment_metadata`] (which is
618+
/// provided to payers via [`Bolt11Invoice::payment_metadata`]) and can be used any time data
619+
/// needs to be "stored" by a payment recipient for their own internal use, provided back to
620+
/// them with the payment.
621+
///
622+
/// Payment metadata is stored as a map from a numeric key to an arbitrary byte array value.
623+
/// This allows for several types of metadata to be stored attached to a single payment. In the
624+
/// future some optional features of LDK may use some keys. For the sake of conflict
625+
/// reduction, those features will attempt to use keys in the range 128-256.
626+
///
627+
/// Note that because this is included in the payment onion, its size must be tightly
628+
/// constrained. More than a few hundred bytes and the payment will be entirely unpayable (with
629+
/// limited routing options as size increases).
630+
///
631+
/// [`RecipientOnionFields::payment_metadata`]: crate::ln::outbound_payment::RecipientOnionFields::payment_metadata
632+
/// [`Bolt11Invoice::payment_metadata`]: lightning_invoice::Bolt11Invoice::payment_metadata
633+
pub payment_metadata: Option<BTreeMap<u64, Vec<u8>>>,
597634
}
598635

599636
/// The context of a payment made for a static invoice requested from a BOLT 12 [`Offer`].
@@ -606,13 +643,55 @@ pub struct AsyncBolt12OfferContext {
606643
///
607644
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
608645
pub offer_nonce: Nonce,
646+
647+
/// Additional data about this payment which is not used in LDK and can be used for any
648+
/// purpose.
649+
///
650+
/// This is analogous to the BOLT 11 [`RecipientOnionFields::payment_metadata`] (which is
651+
/// provided to payers via [`Bolt11Invoice::payment_metadata`]) and can be used any time data
652+
/// needs to be "stored" by a payment recipient for their own internal use, provided back to
653+
/// them with the payment.
654+
///
655+
/// Payment metadata is stored as a map from a numeric key to an arbitrary byte array value.
656+
/// This allows for several types of metadata to be stored attached to a single payment. In the
657+
/// future some optional features of LDK may use some keys. For the sake of conflict
658+
/// reduction, those features will attempt to use keys in the range 128-256.
659+
///
660+
/// Note that because this is included in the payment onion, its size must be tightly
661+
/// constrained. More than a few hundred bytes and the payment will be entirely unpayable (with
662+
/// limited routing options as size increases).
663+
///
664+
/// [`RecipientOnionFields::payment_metadata`]: crate::ln::outbound_payment::RecipientOnionFields::payment_metadata
665+
/// [`Bolt11Invoice::payment_metadata`]: lightning_invoice::Bolt11Invoice::payment_metadata
666+
pub payment_metadata: Option<BTreeMap<u64, Vec<u8>>>,
609667
}
610668

611669
/// The context of a payment made for an invoice sent for a BOLT 12 [`Refund`].
612670
///
613671
/// [`Refund`]: crate::offers::refund::Refund
614672
#[derive(Clone, Debug, Eq, PartialEq)]
615-
pub struct Bolt12RefundContext {}
673+
pub struct Bolt12RefundContext {
674+
/// Additional data about this payment which is not used in LDK and can be used for any
675+
/// purpose.
676+
///
677+
/// This is analogous to the BOLT 11 [`RecipientOnionFields::payment_metadata`] (which is
678+
/// provided to payers via [`Bolt11Invoice::payment_metadata`]) and can be used any time data
679+
/// needs to be "stored" by a payment recipient for their own internal use, provided back to
680+
/// them with the payment.
681+
///
682+
/// Payment metadata is stored as a map from a numeric key to an arbitrary byte array value.
683+
/// This allows for several types of metadata to be stored attached to a single payment. In the
684+
/// future some optional features of LDK may use some keys. For the sake of conflict
685+
/// reduction, those features will attempt to use keys in the range 128-256.
686+
///
687+
/// Note that because this is included in the payment onion, its size must be tightly
688+
/// constrained. More than a few hundred bytes and the payment will be entirely unpayable (with
689+
/// limited routing options as size increases).
690+
///
691+
/// [`RecipientOnionFields::payment_metadata`]: crate::ln::outbound_payment::RecipientOnionFields::payment_metadata
692+
/// [`Bolt11Invoice::payment_metadata`]: lightning_invoice::Bolt11Invoice::payment_metadata
693+
pub payment_metadata: Option<BTreeMap<u64, Vec<u8>>>,
694+
}
616695

617696
impl TryFrom<CounterpartyForwardingInfo> for PaymentRelay {
618697
type Error = ();
@@ -1031,14 +1110,18 @@ impl<'a> Writeable for PaymentContextRef<'a> {
10311110

10321111
impl_writeable_tlv_based!(Bolt12OfferContext, {
10331112
(0, offer_id, required),
1113+
(1, payment_metadata, (option, encoding: (BTreeMap<u64, Vec<u8>>, BigSizeKeyedMap))),
10341114
(2, invoice_request, required),
10351115
});
10361116

10371117
impl_writeable_tlv_based!(AsyncBolt12OfferContext, {
10381118
(0, offer_nonce, required),
1119+
(1, payment_metadata, (option, encoding: (BTreeMap<u64, Vec<u8>>, BigSizeKeyedMap))),
10391120
});
10401121

1041-
impl_writeable_tlv_based!(Bolt12RefundContext, {});
1122+
impl_writeable_tlv_based!(Bolt12RefundContext, {
1123+
(1, payment_metadata, (option, encoding: (BTreeMap<u64, Vec<u8>>, BigSizeKeyedMap))),
1124+
});
10421125

10431126
#[cfg(test)]
10441127
mod tests {
@@ -1097,7 +1180,9 @@ mod tests {
10971180
let recv_tlvs = ReceiveTlvs {
10981181
payment_secret: PaymentSecret([0; 32]),
10991182
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 },
1100-
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1183+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {
1184+
payment_metadata: None,
1185+
}),
11011186
};
11021187
let htlc_maximum_msat = 100_000;
11031188
let blinded_payinfo =
@@ -1115,7 +1200,9 @@ mod tests {
11151200
let recv_tlvs = ReceiveTlvs {
11161201
payment_secret: PaymentSecret([0; 32]),
11171202
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 },
1118-
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1203+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {
1204+
payment_metadata: None,
1205+
}),
11191206
};
11201207
let blinded_payinfo = super::compute_payinfo::<ForwardTlvs>(
11211208
&[],
@@ -1178,7 +1265,9 @@ mod tests {
11781265
let recv_tlvs = ReceiveTlvs {
11791266
payment_secret: PaymentSecret([0; 32]),
11801267
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 3 },
1181-
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1268+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {
1269+
payment_metadata: None,
1270+
}),
11821271
};
11831272
let htlc_maximum_msat = 100_000;
11841273
let blinded_payinfo = super::compute_payinfo(
@@ -1238,7 +1327,9 @@ mod tests {
12381327
let recv_tlvs = ReceiveTlvs {
12391328
payment_secret: PaymentSecret([0; 32]),
12401329
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 },
1241-
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1330+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {
1331+
payment_metadata: None,
1332+
}),
12421333
};
12431334
let htlc_minimum_msat = 3798;
12441335
assert!(super::compute_payinfo(
@@ -1309,7 +1400,9 @@ mod tests {
13091400
let recv_tlvs = ReceiveTlvs {
13101401
payment_secret: PaymentSecret([0; 32]),
13111402
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 },
1312-
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1403+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {
1404+
payment_metadata: None,
1405+
}),
13131406
};
13141407

13151408
let blinded_payinfo = super::compute_payinfo(

lightning/src/ln/async_payments_tests.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10+
use alloc::collections::BTreeMap;
11+
1012
use crate::blinded_path::message::{
1113
BlindedMessagePath, MessageContext, NextMessageHop, OffersContext,
1214
};
@@ -299,6 +301,7 @@ fn create_static_invoice_builder<'a>(
299301
relative_expiry_secs,
300302
recipient.node.list_usable_channels(),
301303
recipient.node.test_get_peers_for_blinded_path(),
304+
None,
302305
)
303306
.unwrap()
304307
}
@@ -1150,6 +1153,88 @@ fn async_receive_flow_success() {
11501153
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
11511154
}
11521155

1156+
#[test]
1157+
fn async_payment_delivers_payment_metadata() {
1158+
// Test that `payment_metadata` set in the `AsyncBolt12OfferContext` of a static invoice's
1159+
// blinded payment paths is surfaced via `Event::PaymentClaimable` when the async recipient
1160+
// receives the keysend payment.
1161+
let chanmon_cfgs = create_chanmon_cfgs(3);
1162+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1163+
1164+
let mut allow_priv_chan_fwds_cfg = test_default_channel_config();
1165+
allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true;
1166+
let node_chanmgrs =
1167+
create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]);
1168+
1169+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1170+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1171+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
1172+
1173+
let recipient_id = vec![42; 32];
1174+
let inv_server_paths =
1175+
nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
1176+
nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();
1177+
expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]);
1178+
1179+
// Configure the recipient's router to inject `payment_metadata` into the
1180+
// `AsyncBolt12OfferContext` of the static invoice's blinded payment paths. The
1181+
// `pass_static_invoice_server_messages` flow below builds the static invoice via this router,
1182+
// at which point the override is consumed.
1183+
let mut expected_metadata = BTreeMap::new();
1184+
expected_metadata.insert(0u64, vec![1, 2, 3, 4]);
1185+
expected_metadata.insert(7u64, vec![0xab, 0xcd]);
1186+
nodes[2].router.set_next_payment_context_metadata(expected_metadata.clone());
1187+
1188+
let invoice_flow_res =
1189+
pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone());
1190+
let static_invoice = invoice_flow_res.invoice;
1191+
let offer = nodes[2].node.get_async_receive_offer().unwrap();
1192+
let amt_msat = 5000;
1193+
let payment_id = PaymentId([1; 32]);
1194+
nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap();
1195+
let release_held_htlc_om = pass_async_payments_oms(
1196+
static_invoice.clone(),
1197+
&nodes[0],
1198+
&nodes[1],
1199+
&nodes[2],
1200+
recipient_id,
1201+
invoice_flow_res.invoice_request_path,
1202+
)
1203+
.1;
1204+
nodes[0]
1205+
.onion_messenger
1206+
.handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om);
1207+
1208+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
1209+
assert_eq!(events.len(), 1);
1210+
let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
1211+
let payment_hash = extract_payment_hash(&ev);
1212+
check_added_monitors(&nodes[0], 1);
1213+
1214+
let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]];
1215+
let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev)
1216+
.with_dummy_tlvs(&[DummyTlvs::default(); DEFAULT_PAYMENT_DUMMY_HOPS]);
1217+
let claimable_ev = do_pass_along_path(args).unwrap();
1218+
1219+
// Verify the `payment_metadata` we injected is surfaced via the `Bolt12OfferContext` of
1220+
// the `PaymentPurpose`. The recipient converts `AsyncBolt12OfferContext` to
1221+
// `Bolt12OfferContext` when constructing the `PaymentPurpose` for keysend payments.
1222+
match &claimable_ev {
1223+
Event::PaymentClaimable {
1224+
purpose: PaymentPurpose::Bolt12OfferPayment { payment_context, .. },
1225+
..
1226+
} => {
1227+
assert_eq!(payment_context.payment_metadata.as_ref(), Some(&expected_metadata));
1228+
},
1229+
_ => panic!("Unexpected event: {:?}", claimable_ev),
1230+
}
1231+
1232+
let keysend_preimage = extract_payment_preimage(&claimable_ev);
1233+
let (res, _) =
1234+
claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage));
1235+
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
1236+
}
1237+
11531238
#[cfg_attr(feature = "std", ignore)]
11541239
#[test]
11551240
fn expired_static_invoice_fail() {
@@ -1591,6 +1676,7 @@ fn reject_bad_payment_secret() {
15911676
PaymentContext::AsyncBolt12Offer(AsyncBolt12OfferContext {
15921677
// We don't reach the point of checking the invreq nonce due to the invalid payment secret
15931678
offer_nonce: Nonce([i; Nonce::LENGTH]),
1679+
payment_metadata: None,
15941680
}),
15951681
u32::MAX,
15961682
)
@@ -3123,7 +3209,10 @@ fn intercepted_hold_htlc() {
31233209
.unwrap();
31243210
let mut offer_nonce = Nonce([0; Nonce::LENGTH]);
31253211
offer_nonce.0.copy_from_slice(&hardcoded_random_bytes[..Nonce::LENGTH]);
3126-
let payment_context = PaymentContext::AsyncBolt12Offer(AsyncBolt12OfferContext { offer_nonce });
3212+
let payment_context = PaymentContext::AsyncBolt12Offer(AsyncBolt12OfferContext {
3213+
offer_nonce,
3214+
payment_metadata: None,
3215+
});
31273216
let blinded_payment_path_with_jit_channel_scid = recipient
31283217
.node
31293218
.flow

0 commit comments

Comments
 (0)