Skip to content

Commit 7f641da

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 57e62da commit 7f641da

File tree

4 files changed

+344
-27
lines changed

4 files changed

+344
-27
lines changed

lightning/src/offers/invoice.rs

+288-6
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,141 @@ 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 offer_chains(&$self) -> Option<Vec<ChainHash>> {
491+
$contents.offer_chains()
492+
}
493+
494+
/// The chain that must be used when paying the invoice; selected from [`offer_chains`] if the
495+
/// invoice originated from an offer.
496+
///
497+
/// From [`InvoiceRequest::chain`] or [`Refund::chain`].
498+
///
499+
/// [`offer_chains`]: Self::offer_chains
500+
/// [`InvoiceRequest::chain`]: crate::offers::invoice_request::InvoiceRequest::chain
501+
pub fn chain(&$self) -> ChainHash {
502+
$contents.chain()
503+
}
504+
505+
/// Opaque bytes set by the originating [`Offer`].
506+
///
507+
/// From [`Offer::metadata`]; `None` if the invoice was created in response to a [`Refund`] or
508+
/// if the [`Offer`] did not set it.
509+
///
510+
/// [`Offer`]: crate::offers::offer::Offer
511+
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
512+
pub fn metadata(&$self) -> Option<&Vec<u8>> {
513+
$contents.metadata()
514+
}
515+
516+
/// The minimum amount required for a successful payment of a single item.
517+
///
518+
/// From [`Offer::amount`]; `None` if the invoice was created in response to a [`Refund`] or if
519+
/// the [`Offer`] did not set it.
520+
///
521+
/// [`Offer`]: crate::offers::offer::Offer
522+
/// [`Offer::amount`]: crate::offers::offer::Offer::amount
523+
pub fn amount(&$self) -> Option<&Amount> {
524+
$contents.amount()
525+
}
526+
527+
/// Features pertaining to the originating [`Offer`].
528+
///
529+
/// From [`Offer::offer_features`]; `None` if the invoice was created in response to a
530+
/// [`Refund`].
531+
///
532+
/// [`Offer`]: crate::offers::offer::Offer
533+
/// [`Offer::offer_features`]: crate::offers::offer::Offer::offer_features
534+
pub fn offer_features(&$self) -> Option<&OfferFeatures> {
535+
$contents.offer_features()
536+
}
537+
538+
/// A complete description of the purpose of the originating offer or refund.
539+
///
540+
/// From [`Offer::description`] or [`Refund::description`].
541+
///
542+
/// [`Offer::description`]: crate::offers::offer::Offer::description
487543
pub fn description(&$self) -> PrintableString {
488544
$contents.description()
489545
}
490546

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

723+
fn offer_chains(&self) -> Option<Vec<ChainHash>> {
724+
match self {
725+
InvoiceContents::ForOffer { invoice_request, .. } =>
726+
Some(invoice_request.inner.offer.chains()),
727+
InvoiceContents::ForRefund { .. } => None,
728+
}
729+
}
730+
594731
fn chain(&self) -> ChainHash {
595732
match self {
596733
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.chain(),
597734
InvoiceContents::ForRefund { refund, .. } => refund.chain(),
598735
}
599736
}
600737

738+
fn metadata(&self) -> Option<&Vec<u8>> {
739+
match self {
740+
InvoiceContents::ForOffer { invoice_request, .. } =>
741+
invoice_request.inner.offer.metadata(),
742+
InvoiceContents::ForRefund { .. } => None,
743+
}
744+
}
745+
746+
fn amount(&self) -> Option<&Amount> {
747+
match self {
748+
InvoiceContents::ForOffer { invoice_request, .. } =>
749+
invoice_request.inner.offer.amount(),
750+
InvoiceContents::ForRefund { .. } => None,
751+
}
752+
}
753+
601754
fn description(&self) -> PrintableString {
602755
match self {
603756
InvoiceContents::ForOffer { invoice_request, .. } => {
@@ -607,6 +760,86 @@ impl InvoiceContents {
607760
}
608761
}
609762

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

1276+
use bitcoin::blockdata::constants::ChainHash;
10431277
use bitcoin::blockdata::script::Script;
10441278
use bitcoin::hashes::Hash;
10451279
use bitcoin::network::constants::Network;
@@ -1050,12 +1284,12 @@ mod tests {
10501284
use core::time::Duration;
10511285
use crate::blinded_path::{BlindedHop, BlindedPath};
10521286
use crate::sign::KeyMaterial;
1053-
use crate::ln::features::Bolt12InvoiceFeatures;
1287+
use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
10541288
use crate::ln::inbound_payment::ExpandedKey;
10551289
use crate::ln::msgs::DecodeError;
10561290
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
10571291
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
1058-
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
1292+
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
10591293
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
10601294
use crate::offers::payer::PayerTlvStreamRef;
10611295
use crate::offers::refund::RefundBuilder;
@@ -1097,7 +1331,23 @@ mod tests {
10971331
unsigned_invoice.write(&mut buffer).unwrap();
10981332

10991333
assert_eq!(unsigned_invoice.bytes, buffer.as_slice());
1334+
assert_eq!(unsigned_invoice.payer_metadata(), &[1; 32]);
1335+
assert_eq!(unsigned_invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
1336+
assert_eq!(unsigned_invoice.metadata(), None);
1337+
assert_eq!(unsigned_invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
11001338
assert_eq!(unsigned_invoice.description(), PrintableString("foo"));
1339+
assert_eq!(unsigned_invoice.offer_features(), Some(&OfferFeatures::empty()));
1340+
assert_eq!(unsigned_invoice.absolute_expiry(), None);
1341+
assert_eq!(unsigned_invoice.message_paths(), &[]);
1342+
assert_eq!(unsigned_invoice.issuer(), None);
1343+
assert_eq!(unsigned_invoice.supported_quantity(), Some(Quantity::One));
1344+
assert_eq!(unsigned_invoice.signing_pubkey(), recipient_pubkey());
1345+
assert_eq!(unsigned_invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1346+
assert_eq!(unsigned_invoice.amount_msats(), 1000);
1347+
assert_eq!(unsigned_invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1348+
assert_eq!(unsigned_invoice.quantity(), None);
1349+
assert_eq!(unsigned_invoice.payer_id(), payer_pubkey());
1350+
assert_eq!(unsigned_invoice.payer_note(), None);
11011351
assert_eq!(unsigned_invoice.payment_paths(), payment_paths.as_slice());
11021352
assert_eq!(unsigned_invoice.created_at(), now);
11031353
assert_eq!(unsigned_invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1123,7 +1373,23 @@ mod tests {
11231373
invoice.write(&mut buffer).unwrap();
11241374

11251375
assert_eq!(invoice.bytes, buffer.as_slice());
1376+
assert_eq!(invoice.payer_metadata(), &[1; 32]);
1377+
assert_eq!(invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
1378+
assert_eq!(invoice.metadata(), None);
1379+
assert_eq!(invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
11261380
assert_eq!(invoice.description(), PrintableString("foo"));
1381+
assert_eq!(invoice.offer_features(), Some(&OfferFeatures::empty()));
1382+
assert_eq!(invoice.absolute_expiry(), None);
1383+
assert_eq!(invoice.message_paths(), &[]);
1384+
assert_eq!(invoice.issuer(), None);
1385+
assert_eq!(invoice.supported_quantity(), Some(Quantity::One));
1386+
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
1387+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1388+
assert_eq!(invoice.amount_msats(), 1000);
1389+
assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1390+
assert_eq!(invoice.quantity(), None);
1391+
assert_eq!(invoice.payer_id(), payer_pubkey());
1392+
assert_eq!(invoice.payer_note(), None);
11271393
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
11281394
assert_eq!(invoice.created_at(), now);
11291395
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1206,7 +1472,23 @@ mod tests {
12061472
invoice.write(&mut buffer).unwrap();
12071473

12081474
assert_eq!(invoice.bytes, buffer.as_slice());
1475+
assert_eq!(invoice.payer_metadata(), &[1; 32]);
1476+
assert_eq!(invoice.offer_chains(), None);
1477+
assert_eq!(invoice.metadata(), None);
1478+
assert_eq!(invoice.amount(), None);
12091479
assert_eq!(invoice.description(), PrintableString("foo"));
1480+
assert_eq!(invoice.offer_features(), None);
1481+
assert_eq!(invoice.absolute_expiry(), None);
1482+
assert_eq!(invoice.message_paths(), &[]);
1483+
assert_eq!(invoice.issuer(), None);
1484+
assert_eq!(invoice.supported_quantity(), None);
1485+
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
1486+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
1487+
assert_eq!(invoice.amount_msats(), 1000);
1488+
assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
1489+
assert_eq!(invoice.quantity(), None);
1490+
assert_eq!(invoice.payer_id(), payer_pubkey());
1491+
assert_eq!(invoice.payer_note(), None);
12101492
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
12111493
assert_eq!(invoice.created_at(), now);
12121494
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);

0 commit comments

Comments
 (0)