Skip to content

Commit 9a45f9a

Browse files
committed
Expose Offer/InvoiceRequest methods in Invoice
Bolt12Invoice can either be for an Offer (via an InvoiceRequest) or a Refund. It wraps those types, so expose their methods on both Bolt12Invoice and UnsignedBolt12Invoice. Since Refund does not have all the Offer/InvoiceRequest methods, use an Option return type such that None can returned for refund-based invoices. For methods that are duplicated between Offer/InvoiceRequest and Bolt12Invoice, prefer the (non-Option, if applicable) method from Bolt12Invoice (e.g., amount_msats, signing_pubkey).
1 parent 5ed9c21 commit 9a45f9a

File tree

4 files changed

+342
-27
lines changed

4 files changed

+342
-27
lines changed

lightning/src/offers/invoice.rs

Lines changed: 286 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,12 @@ use core::time::Duration;
110110
use crate::io;
111111
use crate::blinded_path::BlindedPath;
112112
use crate::ln::PaymentHash;
113-
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
113+
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
114114
use crate::ln::inbound_payment::ExpandedKey;
115115
use crate::ln::msgs::DecodeError;
116116
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
117117
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
118-
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef};
118+
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
119119
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
120120
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
121121
use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents};
@@ -482,12 +482,139 @@ struct InvoiceFields {
482482
}
483483

484484
macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
485-
/// A complete description of the purpose of the originating offer or refund. Intended to be
486-
/// displayed to the user but with the caveat that it has not been verified in any way.
485+
/// The chains that may be used when paying a requested invoice.
486+
///
487+
/// From [`Offer::chains`]; `None` if the invoice was created in response to a [`Refund`].
488+
///
489+
/// [`Offer::chains`]: crate::offers::offer::Offer::chains
490+
pub fn chains(&$self) -> Option<Vec<ChainHash>> {
491+
$contents.chains()
492+
}
493+
494+
/// A chain that the originating offer or refund is valid for.
495+
///
496+
/// From [`InvoiceRequest::chain`] or [`Refund::chain`].
497+
///
498+
/// [`InvoiceRequest::chain`]: crate::offers::invoice_request::InvoiceRequest::chain
499+
pub fn chain(&$self) -> ChainHash {
500+
$contents.chain()
501+
}
502+
503+
/// Opaque bytes set by the originating [`Offer`].
504+
///
505+
/// From [`Offer::metadata`]; `None` if the invoice was created in response to a [`Refund`] or
506+
/// if the [`Offer`] did not set it.
507+
///
508+
/// [`Offer`]: crate::offers::offer::Offer
509+
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
510+
pub fn metadata(&$self) -> Option<&Vec<u8>> {
511+
$contents.metadata()
512+
}
513+
514+
/// The minimum amount required for a successful payment of a single item.
515+
///
516+
/// From [`Offer::amount`]; `None` if the invoice was created in response to a [`Refund`] or if
517+
/// the [`Offer`] did not set it.
518+
///
519+
/// [`Offer`]: crate::offers::offer::Offer
520+
/// [`Offer::amount`]: crate::offers::offer::Offer::amount
521+
pub fn amount(&$self) -> Option<&Amount> {
522+
$contents.amount()
523+
}
524+
525+
/// Features pertaining to the originating [`Offer`].
526+
///
527+
/// From [`Offer::offer_features`]; `None` if the invoice was created in response to a
528+
/// [`Refund`].
529+
///
530+
/// [`Offer`]: crate::offers::offer::Offer
531+
/// [`Offer::offer_features`]: crate::offers::offer::Offer::offer_features
532+
pub fn offer_features(&$self) -> Option<&OfferFeatures> {
533+
$contents.offer_features()
534+
}
535+
536+
/// A complete description of the purpose of the originating offer or refund.
537+
///
538+
/// From [`Offer::description`] or [`Refund::description`].
539+
///
540+
/// [`Offer::description`]: crate::offers::offer::Offer::description
487541
pub fn description(&$self) -> PrintableString {
488542
$contents.description()
489543
}
490544

545+
/// Duration since the Unix epoch when an invoice should no longer be requested.
546+
///
547+
/// From [`Offer::absolute_expiry`] or [`Refund::absolute_expiry`].
548+
///
549+
/// [`Offer::absolute_expiry`]: crate::offers::offer::Offer::absolute_expiry
550+
pub fn absolute_expiry(&$self) -> Option<Duration> {
551+
$contents.absolute_expiry()
552+
}
553+
554+
/// The issuer of the offer or refund.
555+
///
556+
/// From [`Offer::issuer`] or [`Refund::issuer`].
557+
///
558+
/// [`Offer::issuer`]: crate::offers::offer::Offer::issuer
559+
pub fn issuer(&$self) -> Option<PrintableString> {
560+
$contents.issuer()
561+
}
562+
563+
/// Paths to the recipient originating from publicly reachable nodes.
564+
///
565+
/// From [`Offer::paths`] or [`Refund::paths`].
566+
///
567+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
568+
pub fn paths(&$self) -> &[BlindedPath] {
569+
$contents.paths()
570+
}
571+
572+
/// The quantity of items supported.
573+
///
574+
/// From [`Offer::supported_quantity`]; `None` if the invoice was created in response to a
575+
/// [`Refund`].
576+
///
577+
/// [`Offer::supported_quantity`]: crate::offers::offer::Offer::supported_quantity
578+
pub fn supported_quantity(&$self) -> Option<Quantity> {
579+
$contents.supported_quantity()
580+
}
581+
582+
/// An unpredictable series of bytes from the payer.
583+
///
584+
/// From [`InvoiceRequest::payer_metadata`] or [`Refund::payer_metadata`].
585+
pub fn payer_metadata(&$self) -> &[u8] {
586+
$contents.payer_metadata()
587+
}
588+
589+
/// Features pertaining to requesting an invoice.
590+
///
591+
/// From [`InvoiceRequest::invoice_request_features`] or [`Refund::features`].
592+
pub fn invoice_request_features(&$self) -> &InvoiceRequestFeatures {
593+
&$contents.invoice_request_features()
594+
}
595+
596+
/// The quantity of items requested or refunded for.
597+
///
598+
/// From [`InvoiceRequest::quantity`] or [`Refund::quantity`].
599+
pub fn quantity(&$self) -> Option<u64> {
600+
$contents.quantity()
601+
}
602+
603+
/// A possibly transient pubkey used to sign the invoice request or to send an invoice for a
604+
/// refund in case there are no [`paths`].
605+
///
606+
/// [`paths`]: Self::paths
607+
pub fn payer_id(&$self) -> PublicKey {
608+
$contents.payer_id()
609+
}
610+
611+
/// A payer-provided note reflected back in the invoice.
612+
///
613+
/// From [`InvoiceRequest::payer_note`] or [`Refund::payer_note`].
614+
pub fn payer_note(&$self) -> Option<PrintableString> {
615+
$contents.payer_note()
616+
}
617+
491618
/// Paths to the recipient originating from publicly reachable nodes, including information
492619
/// needed for routing payments across them.
493620
///
@@ -591,13 +718,37 @@ impl InvoiceContents {
591718
}
592719
}
593720

721+
fn chains(&self) -> Option<Vec<ChainHash>> {
722+
match self {
723+
InvoiceContents::ForOffer { invoice_request, .. } =>
724+
Some(invoice_request.inner.offer.chains()),
725+
InvoiceContents::ForRefund { .. } => None,
726+
}
727+
}
728+
594729
fn chain(&self) -> ChainHash {
595730
match self {
596731
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.chain(),
597732
InvoiceContents::ForRefund { refund, .. } => refund.chain(),
598733
}
599734
}
600735

736+
fn metadata(&self) -> Option<&Vec<u8>> {
737+
match self {
738+
InvoiceContents::ForOffer { invoice_request, .. } =>
739+
invoice_request.inner.offer.metadata(),
740+
InvoiceContents::ForRefund { .. } => None,
741+
}
742+
}
743+
744+
fn amount(&self) -> Option<&Amount> {
745+
match self {
746+
InvoiceContents::ForOffer { invoice_request, .. } =>
747+
invoice_request.inner.offer.amount(),
748+
InvoiceContents::ForRefund { .. } => None,
749+
}
750+
}
751+
601752
fn description(&self) -> PrintableString {
602753
match self {
603754
InvoiceContents::ForOffer { invoice_request, .. } => {
@@ -607,6 +758,86 @@ impl InvoiceContents {
607758
}
608759
}
609760

761+
fn offer_features(&self) -> Option<&OfferFeatures> {
762+
match self {
763+
InvoiceContents::ForOffer { invoice_request, .. } => {
764+
Some(invoice_request.inner.offer.features())
765+
},
766+
InvoiceContents::ForRefund { .. } => None,
767+
}
768+
}
769+
770+
fn absolute_expiry(&self) -> Option<Duration> {
771+
match self {
772+
InvoiceContents::ForOffer { invoice_request, .. } => {
773+
invoice_request.inner.offer.absolute_expiry()
774+
},
775+
InvoiceContents::ForRefund { refund, .. } => refund.absolute_expiry(),
776+
}
777+
}
778+
779+
fn issuer(&self) -> Option<PrintableString> {
780+
match self {
781+
InvoiceContents::ForOffer { invoice_request, .. } => {
782+
invoice_request.inner.offer.issuer()
783+
},
784+
InvoiceContents::ForRefund { refund, .. } => refund.issuer(),
785+
}
786+
}
787+
788+
fn paths(&self) -> &[BlindedPath] {
789+
match self {
790+
InvoiceContents::ForOffer { invoice_request, .. } => {
791+
invoice_request.inner.offer.paths()
792+
},
793+
InvoiceContents::ForRefund { refund, .. } => refund.paths(),
794+
}
795+
}
796+
797+
fn supported_quantity(&self) -> Option<Quantity> {
798+
match self {
799+
InvoiceContents::ForOffer { invoice_request, .. } => {
800+
Some(invoice_request.inner.offer.supported_quantity())
801+
},
802+
InvoiceContents::ForRefund { .. } => None,
803+
}
804+
}
805+
806+
fn payer_metadata(&self) -> &[u8] {
807+
match self {
808+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.metadata(),
809+
InvoiceContents::ForRefund { refund, .. } => refund.metadata(),
810+
}
811+
}
812+
813+
fn invoice_request_features(&self) -> &InvoiceRequestFeatures {
814+
match self {
815+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.features(),
816+
InvoiceContents::ForRefund { refund, .. } => refund.features(),
817+
}
818+
}
819+
820+
fn quantity(&self) -> Option<u64> {
821+
match self {
822+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.quantity(),
823+
InvoiceContents::ForRefund { refund, .. } => refund.quantity(),
824+
}
825+
}
826+
827+
fn payer_id(&self) -> PublicKey {
828+
match self {
829+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.payer_id(),
830+
InvoiceContents::ForRefund { refund, .. } => refund.payer_id(),
831+
}
832+
}
833+
834+
fn payer_note(&self) -> Option<PrintableString> {
835+
match self {
836+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.payer_note(),
837+
InvoiceContents::ForRefund { refund, .. } => refund.payer_note(),
838+
}
839+
}
840+
610841
fn payment_paths(&self) -> &[(BlindedPayInfo, BlindedPath)] {
611842
&self.fields().payment_paths[..]
612843
}
@@ -1040,6 +1271,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
10401271
mod tests {
10411272
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
10421273

1274+
use bitcoin::blockdata::constants::ChainHash;
10431275
use bitcoin::blockdata::script::Script;
10441276
use bitcoin::hashes::Hash;
10451277
use bitcoin::network::constants::Network;
@@ -1050,12 +1282,12 @@ mod tests {
10501282
use core::time::Duration;
10511283
use crate::blinded_path::{BlindedHop, BlindedPath};
10521284
use crate::sign::KeyMaterial;
1053-
use crate::ln::features::Bolt12InvoiceFeatures;
1285+
use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
10541286
use crate::ln::inbound_payment::ExpandedKey;
10551287
use crate::ln::msgs::DecodeError;
10561288
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
10571289
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
1058-
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
1290+
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
10591291
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
10601292
use crate::offers::payer::PayerTlvStreamRef;
10611293
use crate::offers::refund::RefundBuilder;
@@ -1097,7 +1329,23 @@ mod tests {
10971329
unsigned_invoice.write(&mut buffer).unwrap();
10981330

10991331
assert_eq!(unsigned_invoice.bytes, buffer.as_slice());
1332+
assert_eq!(unsigned_invoice.payer_metadata(), &[1; 32]);
1333+
assert_eq!(unsigned_invoice.chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
1334+
assert_eq!(unsigned_invoice.metadata(), None);
1335+
assert_eq!(unsigned_invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
11001336
assert_eq!(unsigned_invoice.description(), PrintableString("foo"));
1337+
assert_eq!(unsigned_invoice.offer_features(), Some(&OfferFeatures::empty()));
1338+
assert_eq!(unsigned_invoice.absolute_expiry(), None);
1339+
assert_eq!(unsigned_invoice.paths(), &[]);
1340+
assert_eq!(unsigned_invoice.issuer(), None);
1341+
assert_eq!(unsigned_invoice.supported_quantity(), Some(Quantity::One));
1342+
assert_eq!(unsigned_invoice.signing_pubkey(), recipient_pubkey());
1343+
assert_eq!(unsigned_invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1344+
assert_eq!(unsigned_invoice.amount_msats(), 1000);
1345+
assert_eq!(unsigned_invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1346+
assert_eq!(unsigned_invoice.quantity(), None);
1347+
assert_eq!(unsigned_invoice.payer_id(), payer_pubkey());
1348+
assert_eq!(unsigned_invoice.payer_note(), None);
11011349
assert_eq!(unsigned_invoice.payment_paths(), payment_paths.as_slice());
11021350
assert_eq!(unsigned_invoice.created_at(), now);
11031351
assert_eq!(unsigned_invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1123,7 +1371,23 @@ mod tests {
11231371
invoice.write(&mut buffer).unwrap();
11241372

11251373
assert_eq!(invoice.bytes, buffer.as_slice());
1374+
assert_eq!(invoice.payer_metadata(), &[1; 32]);
1375+
assert_eq!(invoice.chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
1376+
assert_eq!(invoice.metadata(), None);
1377+
assert_eq!(invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
11261378
assert_eq!(invoice.description(), PrintableString("foo"));
1379+
assert_eq!(invoice.offer_features(), Some(&OfferFeatures::empty()));
1380+
assert_eq!(invoice.absolute_expiry(), None);
1381+
assert_eq!(invoice.paths(), &[]);
1382+
assert_eq!(invoice.issuer(), None);
1383+
assert_eq!(invoice.supported_quantity(), Some(Quantity::One));
1384+
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
1385+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1386+
assert_eq!(invoice.amount_msats(), 1000);
1387+
assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1388+
assert_eq!(invoice.quantity(), None);
1389+
assert_eq!(invoice.payer_id(), payer_pubkey());
1390+
assert_eq!(invoice.payer_note(), None);
11271391
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
11281392
assert_eq!(invoice.created_at(), now);
11291393
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1206,7 +1470,23 @@ mod tests {
12061470
invoice.write(&mut buffer).unwrap();
12071471

12081472
assert_eq!(invoice.bytes, buffer.as_slice());
1473+
assert_eq!(invoice.payer_metadata(), &[1; 32]);
1474+
assert_eq!(invoice.chains(), None);
1475+
assert_eq!(invoice.metadata(), None);
1476+
assert_eq!(invoice.amount(), None);
12091477
assert_eq!(invoice.description(), PrintableString("foo"));
1478+
assert_eq!(invoice.offer_features(), None);
1479+
assert_eq!(invoice.absolute_expiry(), None);
1480+
assert_eq!(invoice.paths(), &[]);
1481+
assert_eq!(invoice.issuer(), None);
1482+
assert_eq!(invoice.supported_quantity(), None);
1483+
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
1484+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1485+
assert_eq!(invoice.amount_msats(), 1000);
1486+
assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1487+
assert_eq!(invoice.quantity(), None);
1488+
assert_eq!(invoice.payer_id(), payer_pubkey());
1489+
assert_eq!(invoice.payer_note(), None);
12101490
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
12111491
assert_eq!(invoice.created_at(), now);
12121492
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);

0 commit comments

Comments
 (0)