Skip to content

Commit 9fed1d0

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 92d5d83 commit 9fed1d0

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
/// (C-not exported) 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,24 +615,24 @@ 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
}
616622
}
617623

618-
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
624+
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
619625
/// Set the payment hash. This function is only available if no payment hash was set.
620-
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
626+
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
621627
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
622628
self.set_flags()
623629
}
624630
}
625631

626-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
632+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
627633
/// Sets the timestamp to a specific [`SystemTime`].
628634
#[cfg(feature = "std")]
629-
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
635+
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
630636
match PositiveTimestamp::from_system_time(time) {
631637
Ok(t) => self.timestamp = Some(t),
632638
Err(e) => self.error = Some(e),
@@ -637,7 +643,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
637643

638644
/// Sets the timestamp to a duration since the Unix epoch, dropping the subsecond part (which
639645
/// is not representable in BOLT 11 invoices).
640-
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
646+
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
641647
match PositiveTimestamp::from_duration_since_epoch(time) {
642648
Ok(t) => self.timestamp = Some(t),
643649
Err(e) => self.error = Some(e),
@@ -648,34 +654,81 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
648654

649655
/// Sets the timestamp to the current system time.
650656
#[cfg(feature = "std")]
651-
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
657+
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
652658
let now = PositiveTimestamp::from_system_time(SystemTime::now());
653659
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
654660
self.set_flags()
655661
}
656662
}
657663

658-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
664+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
659665
/// Sets `min_final_cltv_expiry_delta`.
660-
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
666+
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
661667
self.tagged_fields.push(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta(min_final_cltv_expiry_delta)));
662668
self.set_flags()
663669
}
664670
}
665671

666-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
672+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
667673
/// Sets the payment secret and relevant features.
668-
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
669-
let mut features = InvoiceFeatures::empty();
670-
features.set_variable_length_onion_required();
671-
features.set_payment_secret_required();
674+
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
675+
let mut found_features = false;
676+
for field in self.tagged_fields.iter_mut() {
677+
if let TaggedField::Features(f) = field {
678+
found_features = true;
679+
f.set_variable_length_onion_required();
680+
f.set_payment_secret_required();
681+
}
682+
}
672683
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
673-
self.tagged_fields.push(TaggedField::Features(features));
684+
if !found_features {
685+
let mut features = InvoiceFeatures::empty();
686+
features.set_variable_length_onion_required();
687+
features.set_payment_secret_required();
688+
self.tagged_fields.push(TaggedField::Features(features));
689+
}
690+
self.set_flags()
691+
}
692+
}
693+
694+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
695+
/// Sets the payment metadata.
696+
///
697+
/// By default features are set to *optionally* allow the sender to include the payment metadata.
698+
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
699+
/// they don't support payment metadata fields), you need to call
700+
/// [`InvoiceBuilder::require_payment_metadata`] after this.
701+
pub fn payment_metadata(mut self, payment_metadata: Vec<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
702+
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
703+
let mut found_features = false;
704+
for field in self.tagged_fields.iter_mut() {
705+
if let TaggedField::Features(f) = field {
706+
found_features = true;
707+
f.set_payment_metadata_optional();
708+
}
709+
}
710+
if !found_features {
711+
let mut features = InvoiceFeatures::empty();
712+
features.set_payment_metadata_optional();
713+
self.tagged_fields.push(TaggedField::Features(features));
714+
}
674715
self.set_flags()
675716
}
676717
}
677718

678-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
719+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
720+
/// Sets the payment secret and relevant features.
721+
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
722+
for field in self.tagged_fields.iter_mut() {
723+
if let TaggedField::Features(f) = field {
724+
f.set_payment_metadata_required();
725+
}
726+
}
727+
self
728+
}
729+
}
730+
731+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
679732
/// Sets the `basic_mpp` feature as optional.
680733
pub fn basic_mpp(mut self) -> Self {
681734
for field in self.tagged_fields.iter_mut() {
@@ -687,7 +740,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
687740
}
688741
}
689742

690-
impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
743+
impl<M: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True, M> {
691744
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
692745
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
693746
/// 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)