Invoice request message interface and data format
authorJeffrey Czyz <jkczyz@gmail.com>
Mon, 19 Sep 2022 21:57:46 +0000 (16:57 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 9 Dec 2022 14:53:33 +0000 (08:53 -0600)
Define an interface for BOLT 12 `invoice_request` messages. The
underlying format consists of the original bytes and the parsed
contents.

The bytes are later needed when constructing an `invoice` message. This
is because it must mirror all the `offer` and `invoice_request` TLV
records, including unknown ones, which aren't represented in the
contents.

The contents will be used in `invoice` messages to avoid duplication.
Some fields while required in a typical user-pays-merchant flow may not
be necessary in the merchant-pays-user flow (e.g., refund, ATM).

lightning/src/ln/features.rs
lightning/src/offers/invoice_request.rs [new file with mode: 0644]
lightning/src/offers/mod.rs
lightning/src/offers/offer.rs
lightning/src/offers/payer.rs [new file with mode: 0644]

index 77d0fa4529fb2ea526682079229b81fa9165ddf6..1f455471a9f4fa2bf3b30b9a1ade5ac54638bb72 100644 (file)
@@ -158,6 +158,7 @@ mod sealed {
                BasicMPP,
        ]);
        define_context!(OfferContext, []);
+       define_context!(InvoiceRequestContext, []);
        // This isn't a "real" feature context, and is only used in the channel_type field in an
        // `OpenChannel` message.
        define_context!(ChannelTypeContext, [
@@ -367,7 +368,8 @@ mod sealed {
                supports_keysend, requires_keysend);
 
        #[cfg(test)]
-       define_feature!(123456789, UnknownFeature, [NodeContext, ChannelContext, InvoiceContext, OfferContext],
+       define_feature!(123456789, UnknownFeature,
+               [NodeContext, ChannelContext, InvoiceContext, OfferContext, InvoiceRequestContext],
                "Feature flags for an unknown feature used in testing.", set_unknown_feature_optional,
                set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature);
 }
@@ -426,8 +428,10 @@ pub type NodeFeatures = Features<sealed::NodeContext>;
 pub type ChannelFeatures = Features<sealed::ChannelContext>;
 /// Features used within an invoice.
 pub type InvoiceFeatures = Features<sealed::InvoiceContext>;
-/// Features used within an offer.
+/// Features used within an `offer`.
 pub type OfferFeatures = Features<sealed::OfferContext>;
+/// Features used within an `invoice_request`.
+pub type InvoiceRequestFeatures = Features<sealed::InvoiceRequestContext>;
 
 /// Features used within the channel_type field in an OpenChannel message.
 ///
@@ -735,6 +739,7 @@ macro_rules! impl_feature_tlv_write {
 
 impl_feature_tlv_write!(ChannelTypeFeatures);
 impl_feature_tlv_write!(OfferFeatures);
+impl_feature_tlv_write!(InvoiceRequestFeatures);
 
 #[cfg(test)]
 mod tests {
diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs
new file mode 100644 (file)
index 0000000..43ae18d
--- /dev/null
@@ -0,0 +1,101 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Data structures and encoding for `invoice_request` messages.
+
+use bitcoin::blockdata::constants::ChainHash;
+use bitcoin::secp256k1::PublicKey;
+use bitcoin::secp256k1::schnorr::Signature;
+use crate::ln::features::InvoiceRequestFeatures;
+use crate::offers::offer::OfferContents;
+use crate::offers::payer::PayerContents;
+use crate::util::string::PrintableString;
+
+use crate::prelude::*;
+
+/// An `InvoiceRequest` is a request for an `Invoice` formulated from an [`Offer`].
+///
+/// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request
+/// specifies these such that its recipient can send an invoice for payment.
+///
+/// [`Offer`]: crate::offers::offer::Offer
+#[derive(Clone, Debug)]
+pub struct InvoiceRequest {
+       bytes: Vec<u8>,
+       contents: InvoiceRequestContents,
+       signature: Option<Signature>,
+}
+
+/// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`.
+#[derive(Clone, Debug)]
+pub(crate) struct InvoiceRequestContents {
+       payer: PayerContents,
+       offer: OfferContents,
+       chain: Option<ChainHash>,
+       amount_msats: Option<u64>,
+       features: InvoiceRequestFeatures,
+       quantity: Option<u64>,
+       payer_id: PublicKey,
+       payer_note: Option<String>,
+}
+
+impl InvoiceRequest {
+       /// An unpredictable series of bytes, typically containing information about the derivation of
+       /// [`payer_id`].
+       ///
+       /// [`payer_id`]: Self::payer_id
+       pub fn metadata(&self) -> &[u8] {
+               &self.contents.payer.0[..]
+       }
+
+       /// A chain from [`Offer::chains`] that the offer is valid for.
+       ///
+       /// [`Offer::chains`]: crate::offers::offer::Offer::chains
+       pub fn chain(&self) -> ChainHash {
+               self.contents.chain.unwrap_or_else(|| self.contents.offer.implied_chain())
+       }
+
+       /// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
+       /// must be greater than or equal to [`Offer::amount`], converted if necessary.
+       ///
+       /// [`chain`]: Self::chain
+       /// [`Offer::amount`]: crate::offers::offer::Offer::amount
+       pub fn amount_msats(&self) -> Option<u64> {
+               self.contents.amount_msats
+       }
+
+       /// Features for paying the invoice.
+       pub fn features(&self) -> &InvoiceRequestFeatures {
+               &self.contents.features
+       }
+
+       /// The quantity of the offer's item conforming to [`Offer::supported_quantity`].
+       ///
+       /// [`Offer::supported_quantity`]: crate::offers::offer::Offer::supported_quantity
+       pub fn quantity(&self) -> Option<u64> {
+               self.contents.quantity
+       }
+
+       /// A possibly transient pubkey used to sign the invoice request.
+       pub fn payer_id(&self) -> PublicKey {
+               self.contents.payer_id
+       }
+
+       /// Payer provided note to include in the invoice.
+       pub fn payer_note(&self) -> Option<PrintableString> {
+               self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
+       }
+
+       /// Signature of the invoice request using [`payer_id`].
+       ///
+       /// [`payer_id`]: Self::payer_id
+       pub fn signature(&self) -> Option<Signature> {
+               self.signature
+       }
+}
index 273650285c6e4b9f084d1ed5bb260dafdadd8562..a58903f70dccf9dfecda34f1a973419167221f84 100644 (file)
@@ -12,5 +12,7 @@
 //!
 //! Offers are a flexible protocol for Lightning payments.
 
+pub mod invoice_request;
 pub mod offer;
 pub mod parse;
+mod payer;
index c31ca9bb9c5ac5ee4a7d8debff728309957e9b4a..1ac8b0bdebf26377d4cb9440f208595b1f4d2c83 100644 (file)
@@ -224,7 +224,7 @@ impl OfferBuilder {
 
 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
 ///
-/// An offer is a precursor to an `InvoiceRequest`. A merchant publishes an offer from which a
+/// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
 /// customer may request an `Invoice` for a specific quantity and using an amount sufficient to
 /// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
 ///
@@ -232,6 +232,8 @@ impl OfferBuilder {
 /// latter.
 ///
 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
+///
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
 #[derive(Clone, Debug)]
 pub struct Offer {
        // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
@@ -240,7 +242,9 @@ pub struct Offer {
        contents: OfferContents,
 }
 
-/// The contents of an [`Offer`], which may be shared with an `InvoiceRequest` or an `Invoice`.
+/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
+///
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
 #[derive(Clone, Debug)]
 pub(super) struct OfferContents {
        chains: Option<Vec<ChainHash>>,
@@ -263,10 +267,7 @@ impl Offer {
        /// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
        /// for the selected chain.
        pub fn chains(&self) -> Vec<ChainHash> {
-               self.contents.chains
-                       .as_ref()
-                       .cloned()
-                       .unwrap_or_else(|| vec![self.contents.implied_chain()])
+               self.contents.chains()
        }
 
        // TODO: Link to corresponding method in `InvoiceRequest`.
@@ -346,6 +347,10 @@ impl AsRef<[u8]> for Offer {
 }
 
 impl OfferContents {
+       pub fn chains(&self) -> Vec<ChainHash> {
+               self.chains.as_ref().cloned().unwrap_or_else(|| vec![self.implied_chain()])
+       }
+
        pub fn implied_chain(&self) -> ChainHash {
                ChainHash::using_genesis_block(Network::Bitcoin)
        }
diff --git a/lightning/src/offers/payer.rs b/lightning/src/offers/payer.rs
new file mode 100644 (file)
index 0000000..1705be8
--- /dev/null
@@ -0,0 +1,19 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Data structures and encoding for `invoice_request_metadata` records.
+
+use crate::prelude::*;
+
+/// An unpredictable sequence of bytes typically containing information needed to derive
+/// [`InvoiceRequest::payer_id`].
+///
+/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
+#[derive(Clone, Debug)]
+pub(crate) struct PayerContents(pub Vec<u8>);