Skip to content

Commit f562577

Browse files
f use a smaller offer
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
1 parent e147725 commit f562577

File tree

3 files changed

+94
-15
lines changed

3 files changed

+94
-15
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,31 @@ pub struct OptionalOfferPaymentParams {
732732
/// Contact secrets to include in the invoice request for BLIP-42 contact management.
733733
/// If provided, these secrets will be used to establish a contact relationship with the recipient.
734734
pub contact_secrects: Option<ContactSecrets>,
735+
/// A custom payer offer to include in the invoice request for BLIP-42 contact management.
736+
///
737+
/// If provided, this offer will be included in the invoice request, allowing the recipient to
738+
/// contact you back. If `None`, **no payer offer will be included** in the invoice request.
739+
///
740+
/// You can create custom offers using [`OffersMessageFlow::create_compact_offer_builder`]:
741+
/// - Pass `None` for no blinded path (smallest size, ~70 bytes)
742+
/// - Pass `Some(intro_node_id)` for a single blinded path (~200 bytes)
743+
///
744+
/// # Example
745+
/// ```rust,ignore
746+
/// // Include a compact offer with a single blinded path
747+
/// let payer_offer = flow.create_compact_offer_builder(
748+
/// &entropy_source,
749+
/// Some(trusted_peer_pubkey)
750+
/// )?.build()?;
751+
///
752+
/// let params = OptionalOfferPaymentParams {
753+
/// payer_offer: Some(payer_offer),
754+
/// ..Default::default()
755+
/// };
756+
/// ```
757+
///
758+
/// [`OffersMessageFlow::create_compact_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_compact_offer_builder
759+
pub payer_offer: Option<Offer>,
735760
}
736761

737762
impl Default for OptionalOfferPaymentParams {
@@ -744,6 +769,7 @@ impl Default for OptionalOfferPaymentParams {
744769
#[cfg(not(feature = "std"))]
745770
retry_strategy: Retry::Attempts(3),
746771
contact_secrects: None,
772+
payer_offer: None,
747773
}
748774
}
749775
}
@@ -12950,6 +12976,7 @@ where
1295012976
None,
1295112977
create_pending_payment_fn,
1295212978
optional_params.contact_secrects,
12979+
optional_params.payer_offer,
1295312980
)
1295412981
}
1295512982

@@ -12980,6 +13007,7 @@ where
1298013007
Some(offer.hrn),
1298113008
create_pending_payment_fn,
1298213009
optional_params.contact_secrects,
13010+
optional_params.payer_offer,
1298313011
)
1298413012
}
1298513013

@@ -13023,6 +13051,7 @@ where
1302313051
None,
1302413052
create_pending_payment_fn,
1302513053
optional_params.contact_secrects,
13054+
optional_params.payer_offer,
1302613055
)
1302713056
}
1302813057

@@ -13031,7 +13060,7 @@ where
1303113060
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
1303213061
payer_note: Option<String>, payment_id: PaymentId,
1303313062
human_readable_name: Option<HumanReadableName>, create_pending_payment: CPP,
13034-
contacts: Option<ContactSecrets>,
13063+
contacts: Option<ContactSecrets>, payer_offer: Option<Offer>,
1303513064
) -> Result<(), Bolt12SemanticError> {
1303613065
let entropy = &*self.entropy_source;
1303713066
let nonce = Nonce::from_entropy_source(entropy);
@@ -13062,11 +13091,14 @@ where
1306213091
} else {
1306313092
builder
1306413093
};
13065-
// Create a minimal offer for BLIP-42 contact exchange (just node_id, no description/paths)
13066-
// TODO: Create a better minimal offer with a single blinded path hop for privacy,
13067-
// while keeping the size small enough to fit in the onion packet.
13068-
let payer_offer = self.create_offer_builder()?.build()?;
13069-
let builder = builder.payer_offer(&payer_offer);
13094+
13095+
// Add payer offer only if provided by the user.
13096+
// If the user explicitly wants to include an offer, they should provide it via payer_offer parameter.
13097+
let builder = if let Some(offer) = payer_offer {
13098+
builder.payer_offer(&offer)
13099+
} else {
13100+
builder
13101+
};
1307013102

1307113103
let invoice_request = builder.build_and_sign()?;
1307213104
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
@@ -15669,7 +15701,7 @@ where
1566915701
self.pending_outbound_payments
1567015702
.received_offer(payment_id, Some(retryable_invoice_request))
1567115703
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
15672-
}, None);
15704+
}, None, None);
1567315705
if offer_pay_res.is_err() {
1567415706
// The offer we tried to pay is the canonical current offer for the name we
1567515707
// wanted to pay. If we can't pay it, there's no way to recover so fail the

lightning/src/ln/offers_tests.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,9 +2575,13 @@ fn pay_offer_and_add_contacts_info_blip42() {
25752575
.private_key;
25762576
let bob_contact_secrets = compute_contact_secret(&bob_private_key, &offer);
25772577

2578+
// Create a compact payer offer for Bob to include in the invoice request
2579+
let bob_payer_offer = bob.node.flow.create_compact_offer_builder(&*bob.node.entropy_source, None).unwrap().build().unwrap();
2580+
25782581
let payment_id = PaymentId([1; 32]);
25792582
bob.node.pay_for_offer(&offer, None, payment_id, OptionalOfferPaymentParams {
25802583
contact_secrects: Some(bob_contact_secrets),
2584+
payer_offer: Some(bob_payer_offer),
25812585
..Default::default()
25822586
}).unwrap();
25832587

@@ -2640,9 +2644,13 @@ fn pay_offer_and_add_contacts_info_blip42() {
26402644
let alice_contact_secret = alice_invoice_request_fields.contact_secret.unwrap();
26412645
let alice_payer_offer = alice_invoice_request_fields.payer_offer.unwrap();
26422646

2647+
// Create a compact payer offer for Alice to include in the invoice request
2648+
let alice_payer_offer_for_bob = alice.node.flow.create_compact_offer_builder(&*alice.node.entropy_source, None).unwrap().build().unwrap();
2649+
26432650
let payment_id = PaymentId([1; 32]);
26442651
alice.node.pay_for_offer(&alice_payer_offer, Some(5_000_000), payment_id, OptionalOfferPaymentParams{
26452652
contact_secrects: Some(ContactSecrets::new(alice_contact_secret)),
2653+
payer_offer: Some(alice_payer_offer_for_bob),
26462654
..Default::default()
26472655
}).unwrap();
26482656

@@ -2659,27 +2667,27 @@ fn pay_offer_and_add_contacts_info_blip42() {
26592667
assert!(invoice_request.payer_offer().is_some());
26602668

26612669
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
2662-
bob.onion_messenger.handle_onion_message(bob_id, &onion_message);
2670+
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
26632671

26642672
let (invoice, _reply_path) = extract_invoice(alice, &onion_message);
2665-
assert_eq!(invoice.amount_msats(), 10_000_000);
2673+
assert_eq!(invoice.amount_msats(), 5_000_000);
26662674
assert_ne!(invoice.signing_pubkey(), alice_id);
26672675
assert!(!invoice.payment_paths().is_empty());
26682676

2669-
route_bolt12_payment(bob, &[alice], &invoice);
2670-
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
2677+
route_bolt12_payment(alice, &[bob], &invoice);
2678+
expect_recent_payment!(alice, RecentPaymentDetails::Pending, payment_id);
26712679

2672-
let (_, alice_payment_preimage) = match get_event!(alice, Event::PaymentClaimable) {
2680+
let (_, bob_payment_preimage) = match get_event!(bob, Event::PaymentClaimable) {
26732681
Event::PaymentClaimable { purpose, .. } => {
26742682
let preimage = match purpose.preimage() {
26752683
Some(p) => p,
26762684
None => panic!("No preimage in PaymentClaimable"),
26772685
};
26782686
(purpose, preimage)
26792687
},
2680-
_ => panic!("No Event::PaymentClaimable for Alice"),
2688+
_ => panic!("No Event::PaymentClaimable for Bob"),
26812689
};
26822690

2683-
let (_, _) = claim_payment(bob, &[alice], alice_payment_preimage);
2684-
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
2691+
let (_, _) = claim_payment(alice, &[bob], bob_payment_preimage);
2692+
expect_recent_payment!(alice, RecentPaymentDetails::Fulfilled, payment_id);
26852693
}

lightning/src/offers/flow.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,45 @@ where
574574
Ok((builder.into(), nonce))
575575
}
576576

577+
/// Creates a minimal [`OfferBuilder`] with derived metadata and an optional blinded path.
578+
///
579+
/// If `intro_node_id` is `None`, creates an offer with no blinded paths (~70 bytes) suitable
580+
/// for scenarios like BLIP-42 where the payer intentionally shares their contact info.
581+
///
582+
/// If `intro_node_id` is `Some`, creates an offer with a single blinded path (~200 bytes)
583+
/// providing privacy/routability for unannounced nodes. The intro node must be a public
584+
/// peer (routable via gossip) with an outbound channel.
585+
///
586+
/// # Privacy
587+
///
588+
/// - `None`: Exposes the derived signing pubkey directly without blinded path privacy
589+
/// - `Some`: Intro node learns payer identity (choose trusted/routable peer)
590+
///
591+
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
592+
pub fn create_compact_offer_builder<ES: Deref>(
593+
&self, entropy_source: ES, intro_node_id: Option<PublicKey>,
594+
) -> Result<OfferBuilder<'_, DerivedMetadata, secp256k1::All>, Bolt12SemanticError>
595+
where
596+
ES::Target: EntropySource,
597+
{
598+
match intro_node_id {
599+
None => {
600+
// Use the internal builder but don't add any paths
601+
self.create_offer_builder_intern(&*entropy_source, |_, _, _| {
602+
Ok(core::iter::empty())
603+
})
604+
.map(|(builder, _)| builder)
605+
},
606+
Some(node_id) => {
607+
// Delegate to create_offer_builder with a single-peer list to reuse the router logic
608+
self.create_offer_builder(
609+
entropy_source,
610+
vec![MessageForwardNode { node_id, short_channel_id: None }],
611+
)
612+
},
613+
}
614+
}
615+
577616
/// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
578617
/// [`OffersMessageFlow`], and any corresponding [`InvoiceRequest`] can be verified using
579618
/// [`Self::verify_invoice_request`]. The offer will expire at `absolute_expiry` if `Some`,

0 commit comments

Comments
 (0)