Skip to content

Commit 2f5ffe4

Browse files
committed
Support setting the new payment metadata field in invoices
This adds support for setting the new payment metadata field in BOLT11 invoices, using a new type flag on the builder to enforce transition correctness. We allow users to set the payment metadata as either optional or required, defaulting to optional so that invoice parsing does not fail if the sender does not support payment metadata fields.
1 parent fd80114 commit 2f5ffe4

File tree

2 files changed

+128
-25
lines changed

2 files changed

+128
-25
lines changed

lightning-invoice/src/lib.rs

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,13 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;
215215
/// * `D`: exactly one `Description` or `DescriptionHash`
216216
/// * `H`: exactly one `PaymentHash`
217217
/// * `T`: the timestamp is set
218+
/// * `C`: the CLTV expiry is set
219+
/// * `S`: the payment secret is set
220+
/// * `M`: payment metadata is set
218221
///
219222
/// This is not exported to bindings users as we likely need to manually select one set of boolean type parameters.
220223
#[derive(Eq, PartialEq, Debug, Clone)]
221-
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
224+
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
222225
currency: Currency,
223226
amount: Option<u64>,
224227
si_prefix: Option<SiPrefix>,
@@ -231,6 +234,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
231234
phantom_t: core::marker::PhantomData<T>,
232235
phantom_c: core::marker::PhantomData<C>,
233236
phantom_s: core::marker::PhantomData<S>,
237+
phantom_m: core::marker::PhantomData<M>,
234238
}
235239

236240
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@@ -484,7 +488,7 @@ pub mod constants {
484488
pub const TAG_FEATURES: u8 = 5;
485489
}
486490

487-
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
491+
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
488492
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
489493
/// `InvoiceBuilder::build(self)` becomes available.
490494
pub fn new(currrency: Currency) -> Self {
@@ -501,14 +505,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
501505
phantom_t: core::marker::PhantomData,
502506
phantom_c: core::marker::PhantomData,
503507
phantom_s: core::marker::PhantomData,
508+
phantom_m: core::marker::PhantomData,
504509
}
505510
}
506511
}
507512

508-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
513+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, S, M> {
509514
/// Helper function to set the completeness flags.
510-
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN> {
511-
InvoiceBuilder::<DN, HN, TN, CN, SN> {
515+
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool, MN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN, MN> {
516+
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
512517
currency: self.currency,
513518
amount: self.amount,
514519
si_prefix: self.si_prefix,
@@ -521,6 +526,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
521526
phantom_t: core::marker::PhantomData,
522527
phantom_c: core::marker::PhantomData,
523528
phantom_s: core::marker::PhantomData,
529+
phantom_m: core::marker::PhantomData,
524530
}
525531
}
526532

@@ -565,7 +571,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
565571
}
566572
}
567573

568-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S> {
574+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S, M> {
569575
/// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields.
570576
pub fn build_raw(self) -> Result<RawInvoice, CreationError> {
571577

@@ -598,9 +604,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
598604
}
599605
}
600606

601-
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
607+
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
602608
/// Set the description. This function is only available if no description (hash) was set.
603-
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
609+
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
604610
match Description::new(description) {
605611
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
606612
Err(e) => self.error = Some(e),
@@ -609,7 +615,7 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
609615
}
610616

611617
/// Set the description hash. This function is only available if no description (hash) was set.
612-
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S> {
618+
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
613619
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
614620
self.set_flags()
615621
}
@@ -627,18 +633,18 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
627633
}
628634
}
629635

630-
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
636+
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
631637
/// Set the payment hash. This function is only available if no payment hash was set.
632-
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
638+
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
633639
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
634640
self.set_flags()
635641
}
636642
}
637643

638-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
644+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
639645
/// Sets the timestamp to a specific [`SystemTime`].
640646
#[cfg(feature = "std")]
641-
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
647+
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
642648
match PositiveTimestamp::from_system_time(time) {
643649
Ok(t) => self.timestamp = Some(t),
644650
Err(e) => self.error = Some(e),
@@ -649,7 +655,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
649655

650656
/// Sets the timestamp to a duration since the Unix epoch, dropping the subsecond part (which
651657
/// is not representable in BOLT 11 invoices).
652-
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
658+
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
653659
match PositiveTimestamp::from_duration_since_epoch(time) {
654660
Ok(t) => self.timestamp = Some(t),
655661
Err(e) => self.error = Some(e),
@@ -660,34 +666,81 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
660666

661667
/// Sets the timestamp to the current system time.
662668
#[cfg(feature = "std")]
663-
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
669+
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
664670
let now = PositiveTimestamp::from_system_time(SystemTime::now());
665671
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
666672
self.set_flags()
667673
}
668674
}
669675

670-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
676+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
671677
/// Sets `min_final_cltv_expiry_delta`.
672-
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
678+
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
673679
self.tagged_fields.push(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta(min_final_cltv_expiry_delta)));
674680
self.set_flags()
675681
}
676682
}
677683

678-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
684+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
679685
/// Sets the payment secret and relevant features.
680-
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
681-
let mut features = InvoiceFeatures::empty();
682-
features.set_variable_length_onion_required();
683-
features.set_payment_secret_required();
686+
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
687+
let mut found_features = false;
688+
for field in self.tagged_fields.iter_mut() {
689+
if let TaggedField::Features(f) = field {
690+
found_features = true;
691+
f.set_variable_length_onion_required();
692+
f.set_payment_secret_required();
693+
}
694+
}
684695
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
685-
self.tagged_fields.push(TaggedField::Features(features));
696+
if !found_features {
697+
let mut features = InvoiceFeatures::empty();
698+
features.set_variable_length_onion_required();
699+
features.set_payment_secret_required();
700+
self.tagged_fields.push(TaggedField::Features(features));
701+
}
702+
self.set_flags()
703+
}
704+
}
705+
706+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
707+
/// Sets the payment metadata.
708+
///
709+
/// By default features are set to *optionally* allow the sender to include the payment metadata.
710+
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
711+
/// they don't support payment metadata fields), you need to call
712+
/// [`InvoiceBuilder::require_payment_metadata`] after this.
713+
pub fn payment_metadata(mut self, payment_metadata: Vec<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
714+
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
715+
let mut found_features = false;
716+
for field in self.tagged_fields.iter_mut() {
717+
if let TaggedField::Features(f) = field {
718+
found_features = true;
719+
f.set_payment_metadata_optional();
720+
}
721+
}
722+
if !found_features {
723+
let mut features = InvoiceFeatures::empty();
724+
features.set_payment_metadata_optional();
725+
self.tagged_fields.push(TaggedField::Features(features));
726+
}
686727
self.set_flags()
687728
}
688729
}
689730

690-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
731+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
732+
/// Sets the payment secret and relevant features.
733+
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
734+
for field in self.tagged_fields.iter_mut() {
735+
if let TaggedField::Features(f) = field {
736+
f.set_payment_metadata_required();
737+
}
738+
}
739+
self
740+
}
741+
}
742+
743+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
691744
/// Sets the `basic_mpp` feature as optional.
692745
pub fn basic_mpp(mut self) -> Self {
693746
for field in self.tagged_fields.iter_mut() {
@@ -699,7 +752,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
699752
}
700753
}
701754

702-
impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
755+
impl<M: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True, M> {
703756
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
704757
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
705758
/// the included payee public key.

lightning-invoice/tests/ser_de.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,56 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> {
331331
true, // Different features than set in InvoiceBuilder
332332
true, // Some unknown fields
333333
),
334+
( // Older version of the payment metadata test with a payment_pubkey set
335+
"lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgqy9gw6ymamd20jumvdgpfphkhp8fzhhdhycw36egcmla5vlrtrmhs9t7psfy3hkkdqzm9eq64fjg558znccds5nhsfmxveha5xe0dykgpspdha0".to_owned(),
336+
InvoiceBuilder::new(Currency::Bitcoin)
337+
.amount_milli_satoshis(1_000_000_000)
338+
.duration_since_epoch(Duration::from_secs(1496314658))
339+
.payment_hash(sha256::Hash::from_hex(
340+
"0001020304050607080900010203040506070809000102030405060708090102"
341+
).unwrap())
342+
.description("payment metadata inside".to_owned())
343+
.payment_metadata(hex::decode("01fafaf0").unwrap())
344+
.require_payment_metadata()
345+
.payee_pub_key(PublicKey::from_slice(&hex::decode(
346+
"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"
347+
).unwrap()).unwrap())
348+
.payment_secret(PaymentSecret([0x11; 32]))
349+
.build_raw()
350+
.unwrap()
351+
.sign(|_| {
352+
RecoverableSignature::from_compact(
353+
&hex::decode("2150ed137ddb54f9736c6a0290ded709d22bddb7261d1d6518dffb467c6b1eef02afc182491bdacd00b65c83554c914a1c53c61b0a4ef04eccccdfb4365ed259").unwrap(),
354+
RecoveryId::from_i32(1).unwrap()
355+
)
356+
}).unwrap(),
357+
false, // Different features than set in InvoiceBuilder
358+
true, // Some unknown fields
359+
),
360+
(
361+
"lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc".to_owned(),
362+
InvoiceBuilder::new(Currency::Bitcoin)
363+
.amount_milli_satoshis(1_000_000_000)
364+
.duration_since_epoch(Duration::from_secs(1496314658))
365+
.payment_hash(sha256::Hash::from_hex(
366+
"0001020304050607080900010203040506070809000102030405060708090102"
367+
).unwrap())
368+
.description("payment metadata inside".to_owned())
369+
.payment_metadata(hex::decode("01fafaf0").unwrap())
370+
.require_payment_metadata()
371+
.payment_secret(PaymentSecret([0x11; 32]))
372+
.build_raw()
373+
.unwrap()
374+
.sign(|_| {
375+
RecoverableSignature::from_compact(
376+
&hex::decode("f5d27be7d9c27d3aa521bc35d77cabd6bda18f1f61716445b19e27e4e17a887508ea8de5a8e1d94f561248f65434e61a221160dac1f1991b9c0f1057b269d898").unwrap(),
377+
RecoveryId::from_i32(1).unwrap()
378+
)
379+
}).unwrap(),
380+
false, // Different features than set in InvoiceBuilder
381+
true, // Some unknown fields
382+
),
383+
334384
]
335385
}
336386

0 commit comments

Comments
 (0)