Skip to content

Commit d6ed847

Browse files
TheBlueMattandozw
authored andcommitted
Support setting the new payment metadata field in invoices
1 parent b7f00f7 commit d6ed847

File tree

2 files changed

+143
-25
lines changed

2 files changed

+143
-25
lines changed

lightning-invoice/src/lib.rs

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,13 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18;
205205
/// * `D`: exactly one `Description` or `DescriptionHash`
206206
/// * `H`: exactly one `PaymentHash`
207207
/// * `T`: the timestamp is set
208+
/// * `C`: the CLTV expiry is set
209+
/// * `S`: the payment secret is set
210+
/// * `M`: payment metadata is set
208211
///
209212
/// (C-not exported) as we likely need to manually select one set of boolean type parameters.
210213
#[derive(Eq, PartialEq, Debug, Clone)]
211-
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
214+
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
212215
currency: Currency,
213216
amount: Option<u64>,
214217
si_prefix: Option<SiPrefix>,
@@ -221,6 +224,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
221224
phantom_t: core::marker::PhantomData<T>,
222225
phantom_c: core::marker::PhantomData<C>,
223226
phantom_s: core::marker::PhantomData<S>,
227+
phantom_m: core::marker::PhantomData<M>,
224228
}
225229

226230
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@@ -474,7 +478,7 @@ pub mod constants {
474478
pub const TAG_FEATURES: u8 = 5;
475479
}
476480

477-
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
481+
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
478482
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
479483
/// `InvoiceBuilder::build(self)` becomes available.
480484
pub fn new(currrency: Currency) -> Self {
@@ -491,14 +495,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
491495
phantom_t: core::marker::PhantomData,
492496
phantom_c: core::marker::PhantomData,
493497
phantom_s: core::marker::PhantomData,
498+
phantom_m: core::marker::PhantomData,
494499
}
495500
}
496501
}
497502

498-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
503+
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> {
499504
/// Helper function to set the completeness flags.
500-
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> {
501-
InvoiceBuilder::<DN, HN, TN, CN, SN> {
505+
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> {
506+
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
502507
currency: self.currency,
503508
amount: self.amount,
504509
si_prefix: self.si_prefix,
@@ -511,6 +516,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
511516
phantom_t: core::marker::PhantomData,
512517
phantom_c: core::marker::PhantomData,
513518
phantom_s: core::marker::PhantomData,
519+
phantom_m: core::marker::PhantomData,
514520
}
515521
}
516522

@@ -554,7 +560,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
554560
}
555561
}
556562

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

@@ -587,9 +593,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
587593
}
588594
}
589595

590-
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
596+
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
591597
/// Set the description. This function is only available if no description (hash) was set.
592-
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
598+
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
593599
match Description::new(description) {
594600
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
595601
Err(e) => self.error = Some(e),
@@ -598,24 +604,24 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
598604
}
599605

600606
/// Set the description hash. This function is only available if no description (hash) was set.
601-
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S> {
607+
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
602608
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
603609
self.set_flags()
604610
}
605611
}
606612

607-
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
613+
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
608614
/// Set the payment hash. This function is only available if no payment hash was set.
609-
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
615+
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
610616
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
611617
self.set_flags()
612618
}
613619
}
614620

615-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
621+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
616622
/// Sets the timestamp to a specific [`SystemTime`].
617623
#[cfg(feature = "std")]
618-
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
624+
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
619625
match PositiveTimestamp::from_system_time(time) {
620626
Ok(t) => self.timestamp = Some(t),
621627
Err(e) => self.error = Some(e),
@@ -625,7 +631,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
625631
}
626632

627633
/// Sets the timestamp to a duration since the Unix epoch.
628-
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
634+
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
629635
match PositiveTimestamp::from_duration_since_epoch(time) {
630636
Ok(t) => self.timestamp = Some(t),
631637
Err(e) => self.error = Some(e),
@@ -636,34 +642,96 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
636642

637643
/// Sets the timestamp to the current system time.
638644
#[cfg(feature = "std")]
639-
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
645+
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
640646
let now = PositiveTimestamp::from_system_time(SystemTime::now());
641647
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
642648
self.set_flags()
643649
}
644650
}
645651

646-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
652+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
647653
/// Sets `min_final_cltv_expiry`.
648-
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
654+
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
649655
self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry)));
650656
self.set_flags()
651657
}
652658
}
653659

654-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
660+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
655661
/// Sets the payment secret and relevant features.
656-
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
657-
let mut features = InvoiceFeatures::empty();
658-
features.set_variable_length_onion_required();
659-
features.set_payment_secret_required();
662+
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
663+
let mut found_features = false;
664+
self.tagged_fields = self.tagged_fields
665+
.drain(..)
666+
.map(|field| match field {
667+
TaggedField::Features(mut f) => {
668+
found_features = true;
669+
f.set_variable_length_onion_required();
670+
f.set_payment_secret_required();
671+
TaggedField::Features(f)
672+
},
673+
_ => field,
674+
})
675+
.collect();
660676
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
661-
self.tagged_fields.push(TaggedField::Features(features));
677+
if !found_features {
678+
let mut features = InvoiceFeatures::empty();
679+
features.set_variable_length_onion_required();
680+
features.set_payment_secret_required();
681+
self.tagged_fields.push(TaggedField::Features(features));
682+
}
662683
self.set_flags()
663684
}
664685
}
665686

666-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
687+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
688+
/// Sets the payment metadata.
689+
///
690+
/// By default features are set to *optionally* allow the sender to include the payment metadata.
691+
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
692+
/// they don't support payment metadata fields), you need to call
693+
/// [`InvoiceBuilder::require_payment_metadata`] after this.
694+
pub fn payment_metadata(mut self, payment_metadata: Vec<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
695+
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
696+
let mut found_features = false;
697+
self.tagged_fields = self.tagged_fields
698+
.drain(..)
699+
.map(|field| match field {
700+
TaggedField::Features(mut f) => {
701+
found_features = true;
702+
f.set_payment_metadata_optional();
703+
TaggedField::Features(f)
704+
},
705+
_ => field,
706+
})
707+
.collect();
708+
if !found_features {
709+
let mut features = InvoiceFeatures::empty();
710+
features.set_payment_metadata_optional();
711+
self.tagged_fields.push(TaggedField::Features(features));
712+
}
713+
self.set_flags()
714+
}
715+
}
716+
717+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
718+
/// Sets the payment secret and relevant features.
719+
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
720+
self.tagged_fields = self.tagged_fields
721+
.drain(..)
722+
.map(|field| match field {
723+
TaggedField::Features(mut f) => {
724+
f.set_payment_metadata_required();
725+
TaggedField::Features(f)
726+
},
727+
_ => field,
728+
})
729+
.collect();
730+
self
731+
}
732+
}
733+
734+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
667735
/// Sets the `basic_mpp` feature as optional.
668736
pub fn basic_mpp(mut self) -> Self {
669737
for field in self.tagged_fields.iter_mut() {
@@ -675,7 +743,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
675743
}
676744
}
677745

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