Skip to content

Commit 4b65ffb

Browse files
blip42: Add ContactInfo to payment events for contact management
Implements BLIP-42 contact information propagation through payment events: - Add ContactInfo struct to events module with contact_secret and payer_offer - Update PaymentSent event to include optional ContactInfo - Update outbound_payment to track and propagate ContactInfo - Update test utilities to handle ContactInfo in payment events - Update async_payments_tests and payment_tests for new event fields This allows applications to establish contact relationships when BOLT12 offer payments complete. Signed-off-by: Vincenzo Palazzo <[email protected]>
1 parent 097e285 commit 4b65ffb

File tree

5 files changed

+150
-28
lines changed

5 files changed

+150
-28
lines changed

lightning/src/events/mod.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,55 @@ pub enum InboundChannelFunds {
727727
/// who is the channel opener in this case.
728728
DualFunded,
729729
}
730+
/// Contact information for BLIP-42 contact management, containing the contact secrets
731+
/// and payer offer that were used when paying a BOLT12 offer.
732+
///
733+
/// This information allows the payer to establish a contact relationship with the recipient,
734+
/// enabling future direct payments without needing a new offer.
735+
#[derive(Clone, Debug, PartialEq, Eq)]
736+
pub struct ContactInfo {
737+
/// The contact secrets that were generated and sent in the invoice request.
738+
pub contact_secrets: crate::offers::contacts::ContactSecrets,
739+
/// The payer's offer that was sent in the invoice request.
740+
pub payer_offer: crate::offers::offer::Offer,
741+
}
742+
743+
impl Writeable for ContactInfo {
744+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
745+
// Serialize ContactSecrets by writing its fields
746+
self.contact_secrets.primary_secret().write(writer)?;
747+
(self.contact_secrets.additional_remote_secrets().len() as u16).write(writer)?;
748+
for secret in self.contact_secrets.additional_remote_secrets() {
749+
secret.write(writer)?;
750+
}
751+
// Serialize Offer as bytes (as a length-prefixed Vec<u8>)
752+
self.payer_offer.as_ref().to_vec().write(writer)?;
753+
Ok(())
754+
}
755+
}
756+
757+
impl Readable for ContactInfo {
758+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, crate::ln::msgs::DecodeError> {
759+
// Deserialize ContactSecrets
760+
let primary_secret: crate::offers::contacts::ContactSecret = Readable::read(reader)?;
761+
let num_secrets: u16 = Readable::read(reader)?;
762+
let mut additional_remote_secrets = Vec::with_capacity(num_secrets as usize);
763+
for _ in 0..num_secrets {
764+
additional_remote_secrets.push(Readable::read(reader)?);
765+
}
766+
let contact_secrets = crate::offers::contacts::ContactSecrets::with_additional_secrets(
767+
primary_secret,
768+
additional_remote_secrets,
769+
);
770+
771+
// Deserialize Offer (as a length-prefixed Vec<u8>)
772+
let payer_offer_bytes: Vec<u8> = Readable::read(reader)?;
773+
let payer_offer = crate::offers::offer::Offer::try_from(payer_offer_bytes)
774+
.map_err(|_| crate::ln::msgs::DecodeError::InvalidValue)?;
775+
776+
Ok(ContactInfo { contact_secrets, payer_offer })
777+
}
778+
}
730779

731780
/// An Event which you should probably take some action in response to.
732781
///
@@ -1064,6 +1113,13 @@ pub enum Event {
10641113
///
10651114
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
10661115
bolt12_invoice: Option<PaidBolt12Invoice>,
1116+
/// Contact information for BLIP-42 contact management.
1117+
///
1118+
/// This is `Some` when paying a BOLT12 offer with contact information enabled,
1119+
/// containing the contact secrets and payer offer that were sent in the invoice request.
1120+
///
1121+
/// This allows the payer to establish a contact relationship with the recipient.
1122+
contact_info: Option<ContactInfo>,
10671123
},
10681124
/// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events
10691125
/// provide failure information for each path attempt in the payment, including retries.
@@ -1951,6 +2007,7 @@ impl Writeable for Event {
19512007
ref amount_msat,
19522008
ref fee_paid_msat,
19532009
ref bolt12_invoice,
2010+
ref contact_info,
19542011
} => {
19552012
2u8.write(writer)?;
19562013
write_tlv_fields!(writer, {
@@ -1960,6 +2017,7 @@ impl Writeable for Event {
19602017
(5, fee_paid_msat, option),
19612018
(7, amount_msat, option),
19622019
(9, bolt12_invoice, option),
2020+
(11, contact_info, option),
19632021
});
19642022
},
19652023
&Event::PaymentPathFailed {
@@ -2422,13 +2480,15 @@ impl MaybeReadable for Event {
24222480
let mut amount_msat = None;
24232481
let mut fee_paid_msat = None;
24242482
let mut bolt12_invoice = None;
2483+
let mut contact_info = None;
24252484
read_tlv_fields!(reader, {
24262485
(0, payment_preimage, required),
24272486
(1, payment_hash, option),
24282487
(3, payment_id, option),
24292488
(5, fee_paid_msat, option),
24302489
(7, amount_msat, option),
24312490
(9, bolt12_invoice, option),
2491+
(11, contact_info, option),
24322492
});
24332493
if payment_hash.is_none() {
24342494
payment_hash = Some(PaymentHash(
@@ -2442,6 +2502,7 @@ impl MaybeReadable for Event {
24422502
amount_msat,
24432503
fee_paid_msat,
24442504
bolt12_invoice,
2505+
contact_info,
24452506
}))
24462507
};
24472508
f()

lightning/src/ln/async_payments_tests.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,7 @@ fn ignore_duplicate_invoice() {
988988
let args = PassAlongPathArgs::new(sender, route[0], amt_msat, payment_hash, ev);
989989
let claimable_ev = do_pass_along_path(args).unwrap();
990990
let keysend_preimage = extract_payment_preimage(&claimable_ev);
991-
let (res, _) =
991+
let (res, _, _) =
992992
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
993993
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice.clone())));
994994

@@ -1073,7 +1073,7 @@ fn ignore_duplicate_invoice() {
10731073
};
10741074

10751075
// After paying invoice, check that static invoice is ignored.
1076-
let res = claim_payment(sender, route[0], payment_preimage);
1076+
let (res, _) = claim_payment(sender, route[0], payment_preimage);
10771077
assert_eq!(res, Some(PaidBolt12Invoice::Bolt12Invoice(invoice)));
10781078

10791079
sender.onion_messenger.handle_onion_message(always_online_node_id, &static_invoice_om);
@@ -1142,7 +1142,7 @@ fn async_receive_flow_success() {
11421142
let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev);
11431143
let claimable_ev = do_pass_along_path(args).unwrap();
11441144
let keysend_preimage = extract_payment_preimage(&claimable_ev);
1145-
let (res, _) =
1145+
let (res, _, _) =
11461146
claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage));
11471147
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
11481148
}
@@ -2942,7 +2942,7 @@ fn async_payment_e2e() {
29422942

29432943
let route: &[&[&Node]] = &[&[sender_lsp, invoice_server, recipient]];
29442944
let keysend_preimage = extract_payment_preimage(&claimable_ev);
2945-
let (res, _) =
2945+
let (res, _, _) =
29462946
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
29472947
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
29482948
}
@@ -3180,7 +3180,7 @@ fn intercepted_hold_htlc() {
31803180

31813181
let route: &[&[&Node]] = &[&[lsp, recipient]];
31823182
let keysend_preimage = extract_payment_preimage(&claimable_ev);
3183-
let (res, _) =
3183+
let (res, _, _) =
31843184
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
31853185
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
31863186
}
@@ -3427,7 +3427,7 @@ fn release_htlc_races_htlc_onion_decode() {
34273427

34283428
let route: &[&[&Node]] = &[&[sender_lsp, invoice_server, recipient]];
34293429
let keysend_preimage = extract_payment_preimage(&claimable_ev);
3430-
let (res, _) =
3430+
let (res, _, _) =
34313431
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
34323432
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
34333433
}

lightning/src/ln/functional_test_utils.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,7 +3033,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM = CM>>(
30333033
node: &H, expected_payment_preimage: PaymentPreimage,
30343034
expected_fee_msat_opt: Option<Option<u64>>, expect_per_path_claims: bool,
30353035
expect_post_ev_mon_update: bool,
3036-
) -> (Option<PaidBolt12Invoice>, Vec<Event>) {
3036+
) -> (Option<PaidBolt12Invoice>, Vec<Event>, Option<crate::events::ContactInfo>) {
30373037
if expect_post_ev_mon_update {
30383038
check_added_monitors(node, 0);
30393039
}
@@ -3051,6 +3051,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM = CM>>(
30513051
}
30523052
// We return the invoice because some test may want to check the invoice details.
30533053
let invoice;
3054+
let contact_info_result;
30543055
let mut path_events = Vec::new();
30553056
let expected_payment_id = match events[0] {
30563057
Event::PaymentSent {
@@ -3060,6 +3061,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM = CM>>(
30603061
ref amount_msat,
30613062
ref fee_paid_msat,
30623063
ref bolt12_invoice,
3064+
ref contact_info,
30633065
} => {
30643066
assert_eq!(expected_payment_preimage, *payment_preimage);
30653067
assert_eq!(expected_payment_hash, *payment_hash);
@@ -3070,6 +3072,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM = CM>>(
30703072
assert!(fee_paid_msat.is_some());
30713073
}
30723074
invoice = bolt12_invoice.clone();
3075+
contact_info_result = contact_info.clone();
30733076
payment_id.unwrap()
30743077
},
30753078
_ => panic!("Unexpected event"),
@@ -3087,7 +3090,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM = CM>>(
30873090
}
30883091
}
30893092
}
3090-
(invoice, path_events)
3093+
(invoice, path_events, contact_info_result)
30913094
}
30923095

30933096
#[macro_export]
@@ -4119,28 +4122,28 @@ pub fn pass_claimed_payment_along_route(args: ClaimAlongRouteArgs) -> u64 {
41194122
}
41204123
pub fn claim_payment_along_route(
41214124
args: ClaimAlongRouteArgs,
4122-
) -> (Option<PaidBolt12Invoice>, Vec<Event>) {
4125+
) -> (Option<PaidBolt12Invoice>, Vec<Event>, Option<crate::events::ContactInfo>) {
41234126
let origin_node = args.origin_node;
41244127
let payment_preimage = args.payment_preimage;
41254128
let skip_last = args.skip_last;
41264129
let expected_total_fee_msat = do_claim_payment_along_route(args);
41274130
if !skip_last {
41284131
expect_payment_sent!(origin_node, payment_preimage, Some(expected_total_fee_msat))
41294132
} else {
4130-
(None, Vec::new())
4133+
(None, Vec::new(), None)
41314134
}
41324135
}
41334136

41344137
pub fn claim_payment<'a, 'b, 'c>(
41354138
origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>],
41364139
our_payment_preimage: PaymentPreimage,
4137-
) -> Option<PaidBolt12Invoice> {
4138-
claim_payment_along_route(ClaimAlongRouteArgs::new(
4140+
) -> (Option<PaidBolt12Invoice>, Option<crate::events::ContactInfo>) {
4141+
let result = claim_payment_along_route(ClaimAlongRouteArgs::new(
41394142
origin_node,
41404143
&[expected_route],
41414144
our_payment_preimage,
4142-
))
4143-
.0
4145+
));
4146+
(result.0, result.2)
41444147
}
41454148

41464149
pub const TEST_FINAL_CLTV: u32 = 70;

0 commit comments

Comments
 (0)