Skip to content

Commit d4487f0

Browse files
committed
Support setting the new payment metadata field in invoices
1 parent d11f57b commit d4487f0

File tree

2 files changed

+116
-24
lines changed

2 files changed

+116
-24
lines changed

lightning-invoice/src/lib.rs

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,13 @@ pub fn check_platform() {
175175
/// * `D`: exactly one `Description` or `DescriptionHash`
176176
/// * `H`: exactly one `PaymentHash`
177177
/// * `T`: the timestamp is set
178+
/// * `C`: XXX
179+
/// * `S`: the payment secret is set
180+
/// * `M`: payment metadata is set
178181
///
179182
/// (C-not exported) as we likely need to manually select one set of boolean type parameters.
180183
#[derive(Eq, PartialEq, Debug, Clone)]
181-
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
184+
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
182185
currency: Currency,
183186
amount: Option<u64>,
184187
si_prefix: Option<SiPrefix>,
@@ -191,6 +194,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
191194
phantom_t: std::marker::PhantomData<T>,
192195
phantom_c: std::marker::PhantomData<C>,
193196
phantom_s: std::marker::PhantomData<S>,
197+
phantom_m: std::marker::PhantomData<M>,
194198
}
195199

196200
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@@ -449,7 +453,7 @@ pub mod constants {
449453
pub const TAG_FEATURES: u8 = 5;
450454
}
451455

452-
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
456+
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
453457
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
454458
/// `InvoiceBuilder::build(self)` becomes available.
455459
pub fn new(currrency: Currency) -> Self {
@@ -466,14 +470,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
466470
phantom_t: std::marker::PhantomData,
467471
phantom_c: std::marker::PhantomData,
468472
phantom_s: std::marker::PhantomData,
473+
phantom_m: std::marker::PhantomData,
469474
}
470475
}
471476
}
472477

473-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
478+
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> {
474479
/// Helper function to set the completeness flags.
475-
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> {
476-
InvoiceBuilder::<DN, HN, TN, CN, SN> {
480+
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> {
481+
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
477482
currency: self.currency,
478483
amount: self.amount,
479484
si_prefix: self.si_prefix,
@@ -486,6 +491,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
486491
phantom_t: std::marker::PhantomData,
487492
phantom_c: std::marker::PhantomData,
488493
phantom_s: std::marker::PhantomData,
494+
phantom_m: std::marker::PhantomData,
489495
}
490496
}
491497

@@ -532,7 +538,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
532538
}
533539
}
534540

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

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

568-
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
574+
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
569575
/// Set the description. This function is only available if no description (hash) was set.
570-
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
576+
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
571577
match Description::new(description) {
572578
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
573579
Err(e) => self.error = Some(e),
@@ -576,23 +582,23 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
576582
}
577583

578584
/// Set the description hash. This function is only available if no description (hash) was set.
579-
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S> {
585+
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
580586
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
581587
self.set_flags()
582588
}
583589
}
584590

585-
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
591+
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
586592
/// Set the payment hash. This function is only available if no payment hash was set.
587-
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
593+
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
588594
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
589595
self.set_flags()
590596
}
591597
}
592598

593-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
599+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
594600
/// Sets the timestamp.
595-
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
601+
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
596602
match PositiveTimestamp::from_system_time(time) {
597603
Ok(t) => self.timestamp = Some(t),
598604
Err(e) => self.error = Some(e),
@@ -602,34 +608,94 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
602608
}
603609

604610
/// Sets the timestamp to the current UNIX timestamp.
605-
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
611+
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
606612
let now = PositiveTimestamp::from_system_time(SystemTime::now());
607613
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
608614
self.set_flags()
609615
}
610616
}
611617

612-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
618+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
613619
/// Sets `min_final_cltv_expiry`.
614-
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
620+
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
615621
self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry)));
616622
self.set_flags()
617623
}
618624
}
619625

620-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
626+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
621627
/// Sets the payment secret and relevant features.
622-
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
623-
let features = InvoiceFeatures::empty()
624-
.set_variable_length_onion_required()
625-
.set_payment_secret_required();
628+
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
629+
let mut found_features = false;
630+
self.tagged_fields = self.tagged_fields
631+
.drain(..)
632+
.map(|field| match field {
633+
TaggedField::Features(f) => {
634+
found_features = true;
635+
TaggedField::Features(f
636+
.set_variable_length_onion_required()
637+
.set_payment_secret_required())
638+
},
639+
_ => field,
640+
})
641+
.collect();
626642
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
627-
self.tagged_fields.push(TaggedField::Features(features));
643+
if !found_features {
644+
let features = InvoiceFeatures::empty()
645+
.set_variable_length_onion_required()
646+
.set_payment_secret_required();
647+
self.tagged_fields.push(TaggedField::Features(features));
648+
}
628649
self.set_flags()
629650
}
630651
}
631652

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

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

lightning-invoice/tests/ser_de.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,32 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> {
331331
true, // Different features than set in InvoiceBuilder
332332
true, // Some unknown fields
333333
),
334+
(
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+
),
334360
]
335361
}
336362

0 commit comments

Comments
 (0)