Default to requiring `payment_metadata` when building BOLT 11s
What changed, and why it matters
This commit changes how Lightning invoices are built in the LDK library. Previously, payment metadata was optional by default, and developers had to explicitly call a method to require it. Now, payment metadata is required by default, with an opt-out method for legacy compatibility. This is a defensive change to align invoice behavior with LDK's internal payment handling, reducing the risk of payment failures or ambiguity when metadata is omitted.
Review downstream usage of `payment_metadata` and `require_payment_metadata` in dependent projects. Update code that relied on optional-by-default behavior to explicitly call `optional_payment_metadata` if legacy sender support is needed. No immediate security patch is required; this is a hardening/API-alignment change.
Security signals we found
Default behavior changed from optional to required for payment metadata in BOLT 11 invoices
Old `require_payment_metadata` method removed; new `optional_payment_metadata` opt-out added
LDK internally commits to payment metadata via payment secret, so defaulting to required improves consistency
Potential compatibility impact for legacy senders that do not support payment metadata
Evidence from the diff
The patch renames payment_metadata to optional_payment_metadata and introduces a new payment_metadata method that sets the metadata and marks it as required in the invoice features. The old require_payment_metadata method is removed. The change reflects that LDK commits to payment metadata in its payment secret, making it effectively required. Test cases are updated to remove now-redundant require_payment_metadata calls.
Changed components
lightning-invoice/src/lib.rslightning-invoice/tests/ser_de.rsInspect captured patch +16 / −16
diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs
index 4ee9acb..6c18e60 100644
--- a/lightning-invoice/src/lib.rs
+++ b/lightning-invoice/src/lib.rs
@@ -880,11 +880,10 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool>
{
/// Sets the payment metadata.
///
- /// By default features are set to *optionally* allow the sender to include the payment metadata.
- /// If you wish to require that the sender include the metadata (and fail to parse the invoice if
- /// they don't support payment metadata fields), you need to call
- /// [`InvoiceBuilder::require_payment_metadata`] after this.
- pub fn payment_metadata(
+ /// This marks the payment metadata as optional, allowing a legacy sender that doesn't
+ /// understand payment metadata to ignore it. Note that LDK by default commits to the payment
+ /// metadata in its payment secret, implicitly making it required.
+ pub fn optional_payment_metadata(
mut self, payment_metadata: Vec<u8>,
) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
@@ -902,20 +901,23 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool>
}
self.set_flags()
}
-}
-impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool>
- InvoiceBuilder<D, H, T, C, S, tb::True>
-{
- /// Sets forwarding of payment metadata as required. A reader of the invoice which does not
- /// support sending payment metadata will fail to read the invoice.
- pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
- for field in self.tagged_fields.iter_mut() {
+ /// Sets the payment metadata.
+ ///
+ /// By default features are set to *require* the sender to include the payment metadata.
+ /// If you wish to support legacy senders that ignore the metadata, you can call
+ /// [`InvoiceBuilder::optional_payment_metadata`] instead. Note that LDK by default commits to
+ /// the payment metadata in its payment secret, implicitly making it required.
+ pub fn payment_metadata(
+ self, payment_metadata: Vec<u8>,
+ ) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
+ let mut res = self.optional_payment_metadata(payment_metadata);
+ for field in res.tagged_fields.iter_mut() {
if let TaggedField::Features(f) = field {
f.set_payment_metadata_required();
}
}
- self
+ res
}
}
diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs
index 353878a..be17391 100644
--- a/lightning-invoice/tests/ser_de.rs
+++ b/lightning-invoice/tests/ser_de.rs
@@ -418,7 +418,6 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> {
))
.description("payment metadata inside".to_owned())
.payment_metadata(<Vec<u8>>::from_hex("01fafaf0").unwrap())
- .require_payment_metadata()
.payee_pub_key(PublicKey::from_slice(&<Vec<u8>>::from_hex(
"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"
).unwrap()).unwrap())
@@ -450,7 +449,6 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> {
))
.description("payment metadata inside".to_owned())
.payment_metadata(<Vec<u8>>::from_hex("01fafaf0").unwrap())
- .require_payment_metadata()
.payment_secret(PaymentSecret([0x11; 32]))
.build_raw()
.unwrap()
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.