Skip to content

Commit 2cfa80b

Browse files
committed
Support setting the new payment metadata field in invoices
1 parent c8e2635 commit 2cfa80b

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
@@ -163,10 +163,13 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18;
163163
/// * `D`: exactly one `Description` or `DescriptionHash`
164164
/// * `H`: exactly one `PaymentHash`
165165
/// * `T`: the timestamp is set
166+
/// * `C`: the CLTV expiry is set
167+
/// * `S`: the payment secret is set
168+
/// * `M`: payment metadata is set
166169
///
167170
/// (C-not exported) as we likely need to manually select one set of boolean type parameters.
168171
#[derive(Eq, PartialEq, Debug, Clone)]
169-
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
172+
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
170173
currency: Currency,
171174
amount: Option<u64>,
172175
si_prefix: Option<SiPrefix>,
@@ -179,6 +182,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
179182
phantom_t: core::marker::PhantomData<T>,
180183
phantom_c: core::marker::PhantomData<C>,
181184
phantom_s: core::marker::PhantomData<S>,
185+
phantom_m: core::marker::PhantomData<M>,
182186
}
183187

184188
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@@ -432,7 +436,7 @@ pub mod constants {
432436
pub const TAG_FEATURES: u8 = 5;
433437
}
434438

435-
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
439+
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
436440
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
437441
/// `InvoiceBuilder::build(self)` becomes available.
438442
pub fn new(currrency: Currency) -> Self {
@@ -449,14 +453,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
449453
phantom_t: core::marker::PhantomData,
450454
phantom_c: core::marker::PhantomData,
451455
phantom_s: core::marker::PhantomData,
456+
phantom_m: core::marker::PhantomData,
452457
}
453458
}
454459
}
455460

456-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
461+
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> {
457462
/// Helper function to set the completeness flags.
458-
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> {
459-
InvoiceBuilder::<DN, HN, TN, CN, SN> {
463+
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> {
464+
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
460465
currency: self.currency,
461466
amount: self.amount,
462467
si_prefix: self.si_prefix,
@@ -469,6 +474,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
469474
phantom_t: core::marker::PhantomData,
470475
phantom_c: core::marker::PhantomData,
471476
phantom_s: core::marker::PhantomData,
477+
phantom_m: core::marker::PhantomData,
472478
}
473479
}
474480

@@ -512,7 +518,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
512518
}
513519
}
514520

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

@@ -545,9 +551,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
545551
}
546552
}
547553

548-
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
554+
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
549555
/// Set the description. This function is only available if no description (hash) was set.
550-
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
556+
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
551557
match Description::new(description) {
552558
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
553559
Err(e) => self.error = Some(e),
@@ -556,24 +562,24 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
556562
}
557563

558564
/// Set the description hash. This function is only available if no description (hash) was set.
559-
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S> {
565+
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
560566
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
561567
self.set_flags()
562568
}
563569
}
564570

565-
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
571+
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
566572
/// Set the payment hash. This function is only available if no payment hash was set.
567-
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
573+
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
568574
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
569575
self.set_flags()
570576
}
571577
}
572578

573-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
579+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
574580
/// Sets the timestamp to a specific [`SystemTime`].
575581
#[cfg(feature = "std")]
576-
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
582+
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
577583
match PositiveTimestamp::from_system_time(time) {
578584
Ok(t) => self.timestamp = Some(t),
579585
Err(e) => self.error = Some(e),
@@ -583,7 +589,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
583589
}
584590

585591
/// Sets the timestamp to a duration since the Unix epoch.
586-
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
592+
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
587593
match PositiveTimestamp::from_duration_since_epoch(time) {
588594
Ok(t) => self.timestamp = Some(t),
589595
Err(e) => self.error = Some(e),
@@ -594,34 +600,94 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
594600

595601
/// Sets the timestamp to the current system time.
596602
#[cfg(feature = "std")]
597-
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
603+
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S. M> {
598604
let now = PositiveTimestamp::from_system_time(SystemTime::now());
599605
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
600606
self.set_flags()
601607
}
602608
}
603609

604-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
610+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
605611
/// Sets `min_final_cltv_expiry`.
606-
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
612+
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
607613
self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry)));
608614
self.set_flags()
609615
}
610616
}
611617

612-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
618+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
613619
/// Sets the payment secret and relevant features.
614-
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
615-
let features = InvoiceFeatures::empty()
616-
.set_variable_length_onion_required()
617-
.set_payment_secret_required();
620+
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
621+
let mut found_features = false;
622+
self.tagged_fields = self.tagged_fields
623+
.drain(..)
624+
.map(|field| match field {
625+
TaggedField::Features(f) => {
626+
found_features = true;
627+
TaggedField::Features(f
628+
.set_variable_length_onion_required()
629+
.set_payment_secret_required())
630+
},
631+
_ => field,
632+
})
633+
.collect();
618634
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
619-
self.tagged_fields.push(TaggedField::Features(features));
635+
if !found_features {
636+
let features = InvoiceFeatures::empty()
637+
.set_variable_length_onion_required()
638+
.set_payment_secret_required();
639+
self.tagged_fields.push(TaggedField::Features(features));
640+
}
620641
self.set_flags()
621642
}
622643
}
623644

624-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
645+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
646+
/// Sets the payment metadata.
647+
///
648+
/// By default features are set to *optionally* allow the sender to include the payment metadata.
649+
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
650+
/// they don't support payment metadata fields), you need to call
651+
/// [`InvoiceBuilder::require_payment_metadata`] after this.
652+
pub fn payment_metadata(mut self, payment_metadata: Vec<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
653+
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
654+
let mut found_features = false;
655+
self.tagged_fields = self.tagged_fields
656+
.drain(..)
657+
.map(|field| match field {
658+
TaggedField::Features(f) => {
659+
found_features = true;
660+
TaggedField::Features(f.set_payment_metadata_optional())
661+
},
662+
_ => field,
663+
})
664+
.collect();
665+
if !found_features {
666+
let features = InvoiceFeatures::empty()
667+
.set_payment_metadata_optional();
668+
self.tagged_fields.push(TaggedField::Features(features));
669+
}
670+
self.set_flags()
671+
}
672+
}
673+
674+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
675+
/// Sets the payment secret and relevant features.
676+
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
677+
self.tagged_fields = self.tagged_fields
678+
.drain(..)
679+
.map(|field| match field {
680+
TaggedField::Features(f) => {
681+
TaggedField::Features(f.set_payment_metadata_required())
682+
},
683+
_ => field,
684+
})
685+
.collect();
686+
self
687+
}
688+
}
689+
690+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
625691
/// Sets the `basic_mpp` feature as optional.
626692
pub fn basic_mpp(mut self) -> Self {
627693
self.tagged_fields = self.tagged_fields
@@ -635,7 +701,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
635701
}
636702
}
637703

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