Skip to content

Commit 4df2f6b

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 5a0c4ba commit 4df2f6b

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};
@@ -476,12 +476,139 @@ struct InvoiceFields {
476476
}
477477

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

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

715+
fn chains(&self) -> Option<Vec<ChainHash>> {
716+
match self {
717+
InvoiceContents::ForOffer { invoice_request, .. } =>
718+
Some(invoice_request.inner.offer.chains()),
719+
InvoiceContents::ForRefund { .. } => None,
720+
}
721+
}
722+
588723
fn chain(&self) -> ChainHash {
589724
match self {
590725
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.chain(),
591726
InvoiceContents::ForRefund { refund, .. } => refund.chain(),
592727
}
593728
}
594729

730+
fn metadata(&self) -> Option<&Vec<u8>> {
731+
match self {
732+
InvoiceContents::ForOffer { invoice_request, .. } =>
733+
invoice_request.inner.offer.metadata(),
734+
InvoiceContents::ForRefund { .. } => None,
735+
}
736+
}
737+
738+
fn amount(&self) -> Option<&Amount> {
739+
match self {
740+
InvoiceContents::ForOffer { invoice_request, .. } =>
741+
invoice_request.inner.offer.amount(),
742+
InvoiceContents::ForRefund { .. } => None,
743+
}
744+
}
745+
595746
fn description(&self) -> PrintableString {
596747
match self {
597748
InvoiceContents::ForOffer { invoice_request, .. } => {
@@ -601,6 +752,86 @@ impl InvoiceContents {
601752
}
602753
}
603754

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

1268+
use bitcoin::blockdata::constants::ChainHash;
10371269
use bitcoin::blockdata::script::Script;
10381270
use bitcoin::hashes::Hash;
10391271
use bitcoin::network::constants::Network;
@@ -1044,12 +1276,12 @@ mod tests {
10441276
use core::time::Duration;
10451277
use crate::blinded_path::{BlindedHop, BlindedPath};
10461278
use crate::sign::KeyMaterial;
1047-
use crate::ln::features::Bolt12InvoiceFeatures;
1279+
use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
10481280
use crate::ln::inbound_payment::ExpandedKey;
10491281
use crate::ln::msgs::DecodeError;
10501282
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
10511283
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
1052-
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
1284+
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
10531285
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
10541286
use crate::offers::payer::PayerTlvStreamRef;
10551287
use crate::offers::refund::RefundBuilder;
@@ -1091,7 +1323,23 @@ mod tests {
10911323
unsigned_invoice.write(&mut buffer).unwrap();
10921324

10931325
assert_eq!(unsigned_invoice.bytes, buffer.as_slice());
1326+
assert_eq!(unsigned_invoice.payer_metadata(), &[1; 32]);
1327+
assert_eq!(unsigned_invoice.chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
1328+
assert_eq!(unsigned_invoice.metadata(), None);
1329+
assert_eq!(unsigned_invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
10941330
assert_eq!(unsigned_invoice.description(), PrintableString("foo"));
1331+
assert_eq!(unsigned_invoice.offer_features(), Some(&OfferFeatures::empty()));
1332+
assert_eq!(unsigned_invoice.absolute_expiry(), None);
1333+
assert_eq!(unsigned_invoice.paths(), &[]);
1334+
assert_eq!(unsigned_invoice.issuer(), None);
1335+
assert_eq!(unsigned_invoice.supported_quantity(), Some(Quantity::One));
1336+
assert_eq!(unsigned_invoice.signing_pubkey(), recipient_pubkey());
1337+
assert_eq!(unsigned_invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1338+
assert_eq!(unsigned_invoice.amount_msats(), 1000);
1339+
assert_eq!(unsigned_invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1340+
assert_eq!(unsigned_invoice.quantity(), None);
1341+
assert_eq!(unsigned_invoice.payer_id(), payer_pubkey());
1342+
assert_eq!(unsigned_invoice.payer_note(), None);
10951343
assert_eq!(unsigned_invoice.payment_paths(), payment_paths.as_slice());
10961344
assert_eq!(unsigned_invoice.created_at(), now);
10971345
assert_eq!(unsigned_invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1117,7 +1365,23 @@ mod tests {
11171365
invoice.write(&mut buffer).unwrap();
11181366

11191367
assert_eq!(invoice.bytes, buffer.as_slice());
1368+
assert_eq!(invoice.payer_metadata(), &[1; 32]);
1369+
assert_eq!(invoice.chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
1370+
assert_eq!(invoice.metadata(), None);
1371+
assert_eq!(invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
11201372
assert_eq!(invoice.description(), PrintableString("foo"));
1373+
assert_eq!(invoice.offer_features(), Some(&OfferFeatures::empty()));
1374+
assert_eq!(invoice.absolute_expiry(), None);
1375+
assert_eq!(invoice.paths(), &[]);
1376+
assert_eq!(invoice.issuer(), None);
1377+
assert_eq!(invoice.supported_quantity(), Some(Quantity::One));
1378+
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
1379+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1380+
assert_eq!(invoice.amount_msats(), 1000);
1381+
assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1382+
assert_eq!(invoice.quantity(), None);
1383+
assert_eq!(invoice.payer_id(), payer_pubkey());
1384+
assert_eq!(invoice.payer_note(), None);
11211385
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
11221386
assert_eq!(invoice.created_at(), now);
11231387
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1200,7 +1464,23 @@ mod tests {
12001464
invoice.write(&mut buffer).unwrap();
12011465

12021466
assert_eq!(invoice.bytes, buffer.as_slice());
1467+
assert_eq!(invoice.payer_metadata(), &[1; 32]);
1468+
assert_eq!(invoice.chains(), None);
1469+
assert_eq!(invoice.metadata(), None);
1470+
assert_eq!(invoice.amount(), None);
12031471
assert_eq!(invoice.description(), PrintableString("foo"));
1472+
assert_eq!(invoice.offer_features(), None);
1473+
assert_eq!(invoice.absolute_expiry(), None);
1474+
assert_eq!(invoice.paths(), &[]);
1475+
assert_eq!(invoice.issuer(), None);
1476+
assert_eq!(invoice.supported_quantity(), None);
1477+
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
1478+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1479+
assert_eq!(invoice.amount_msats(), 1000);
1480+
assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1481+
assert_eq!(invoice.quantity(), None);
1482+
assert_eq!(invoice.payer_id(), payer_pubkey());
1483+
assert_eq!(invoice.payer_note(), None);
12041484
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
12051485
assert_eq!(invoice.created_at(), now);
12061486
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);

0 commit comments

Comments
 (0)