Skip to content

Commit c7f52f3

Browse files
committed
Support setting the new payment metadata field in invoices
1 parent 27671cc commit c7f52f3

File tree

2 files changed

+141
-25
lines changed

2 files changed

+141
-25
lines changed

lightning-invoice/src/lib.rs

Lines changed: 91 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,13 @@ fn check_system_time_bounds() {
225225
/// * `D`: exactly one `Description` or `DescriptionHash`
226226
/// * `H`: exactly one `PaymentHash`
227227
/// * `T`: the timestamp is set
228+
/// * `C`: the CLTV expiry is set
229+
/// * `S`: the payment secret is set
230+
/// * `M`: payment metadata is set
228231
///
229232
/// (C-not exported) as we likely need to manually select one set of boolean type parameters.
230233
#[derive(Eq, PartialEq, Debug, Clone)]
231-
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
234+
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
232235
currency: Currency,
233236
amount: Option<u64>,
234237
si_prefix: Option<SiPrefix>,
@@ -241,6 +244,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
241244
phantom_t: core::marker::PhantomData<T>,
242245
phantom_c: core::marker::PhantomData<C>,
243246
phantom_s: core::marker::PhantomData<S>,
247+
phantom_m: core::marker::PhantomData<M>,
244248
}
245249

246250
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@@ -499,7 +503,7 @@ pub mod constants {
499503
pub const TAG_FEATURES: u8 = 5;
500504
}
501505

502-
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
506+
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
503507
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
504508
/// `InvoiceBuilder::build(self)` becomes available.
505509
pub fn new(currrency: Currency) -> Self {
@@ -516,14 +520,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
516520
phantom_t: core::marker::PhantomData,
517521
phantom_c: core::marker::PhantomData,
518522
phantom_s: core::marker::PhantomData,
523+
phantom_m: core::marker::PhantomData,
519524
}
520525
}
521526
}
522527

523-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
528+
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> {
524529
/// Helper function to set the completeness flags.
525-
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> {
526-
InvoiceBuilder::<DN, HN, TN, CN, SN> {
530+
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> {
531+
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
527532
currency: self.currency,
528533
amount: self.amount,
529534
si_prefix: self.si_prefix,
@@ -536,6 +541,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
536541
phantom_t: core::marker::PhantomData,
537542
phantom_c: core::marker::PhantomData,
538543
phantom_s: core::marker::PhantomData,
544+
phantom_m: core::marker::PhantomData,
539545
}
540546
}
541547

@@ -582,7 +588,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
582588
}
583589
}
584590

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

@@ -615,9 +621,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
615621
}
616622
}
617623

618-
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
624+
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
619625
/// Set the description. This function is only available if no description (hash) was set.
620-
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
626+
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
621627
match Description::new(description) {
622628
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
623629
Err(e) => self.error = Some(e),
@@ -626,24 +632,24 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
626632
}
627633

628634
/// Set the description hash. This function is only available if no description (hash) was set.
629-
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S> {
635+
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
630636
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
631637
self.set_flags()
632638
}
633639
}
634640

635-
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
641+
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
636642
/// Set the payment hash. This function is only available if no payment hash was set.
637-
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
643+
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
638644
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
639645
self.set_flags()
640646
}
641647
}
642648

643-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
649+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
644650
/// Sets the timestamp to a specific [`SystemTime`].
645651
#[cfg(feature = "std")]
646-
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
652+
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
647653
match PositiveTimestamp::from_system_time(time) {
648654
Ok(t) => self.timestamp = Some(t),
649655
Err(e) => self.error = Some(e),
@@ -653,7 +659,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
653659
}
654660

655661
/// Sets the timestamp to a duration since the UNIX epoch.
656-
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
662+
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
657663
match PositiveTimestamp::from_duration_since_epoch(time) {
658664
Ok(t) => self.timestamp = Some(t),
659665
Err(e) => self.error = Some(e),
@@ -664,34 +670,94 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
664670

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

674-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
680+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
675681
/// Sets `min_final_cltv_expiry`.
676-
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
682+
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
677683
self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry)));
678684
self.set_flags()
679685
}
680686
}
681687

682-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
688+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
683689
/// Sets the payment secret and relevant features.
684-
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
685-
let features = InvoiceFeatures::empty()
686-
.set_variable_length_onion_required()
687-
.set_payment_secret_required();
690+
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
691+
let mut found_features = false;
692+
self.tagged_fields = self.tagged_fields
693+
.drain(..)
694+
.map(|field| match field {
695+
TaggedField::Features(f) => {
696+
found_features = true;
697+
TaggedField::Features(f
698+
.set_variable_length_onion_required()
699+
.set_payment_secret_required())
700+
},
701+
_ => field,
702+
})
703+
.collect();
688704
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
689-
self.tagged_fields.push(TaggedField::Features(features));
705+
if !found_features {
706+
let features = InvoiceFeatures::empty()
707+
.set_variable_length_onion_required()
708+
.set_payment_secret_required();
709+
self.tagged_fields.push(TaggedField::Features(features));
710+
}
690711
self.set_flags()
691712
}
692713
}
693714

694-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
715+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
716+
/// Sets the payment metadata.
717+
///
718+
/// By default features are set to *optionally* allow the sender to include the payment metadata.
719+
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
720+
/// they don't support payment metadata fields), you need to call
721+
/// [`InvoiceBuilder::require_payment_metadata`] after this.
722+
pub fn payment_metadata(mut self, payment_metadata: Vec<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
723+
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
724+
let mut found_features = false;
725+
self.tagged_fields = self.tagged_fields
726+
.drain(..)
727+
.map(|field| match field {
728+
TaggedField::Features(f) => {
729+
found_features = true;
730+
TaggedField::Features(f.set_payment_metadata_optional())
731+
},
732+
_ => field,
733+
})
734+
.collect();
735+
if !found_features {
736+
let features = InvoiceFeatures::empty()
737+
.set_payment_metadata_optional();
738+
self.tagged_fields.push(TaggedField::Features(features));
739+
}
740+
self.set_flags()
741+
}
742+
}
743+
744+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
745+
/// Sets the payment secret and relevant features.
746+
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
747+
self.tagged_fields = self.tagged_fields
748+
.drain(..)
749+
.map(|field| match field {
750+
TaggedField::Features(f) => {
751+
TaggedField::Features(f.set_payment_metadata_required())
752+
},
753+
_ => field,
754+
})
755+
.collect();
756+
self
757+
}
758+
}
759+
760+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
695761
/// Sets the `basic_mpp` feature as optional.
696762
pub fn basic_mpp(mut self) -> Self {
697763
self.tagged_fields = self.tagged_fields
@@ -705,7 +771,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
705771
}
706772
}
707773

708-
impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
774+
impl<M: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True, M> {
709775
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
710776
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
711777
/// 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+
.timestamp(UNIX_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+
.timestamp(UNIX_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)