Skip to content

Commit ce1ec1c

Browse files
committed
Invoice request message interface and data format
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).
1 parent 2e852de commit ce1ec1c

File tree

5 files changed

+140
-8
lines changed

5 files changed

+140
-8
lines changed

lightning/src/ln/features.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ mod sealed {
158158
BasicMPP,
159159
]);
160160
define_context!(OfferContext, []);
161+
define_context!(InvoiceRequestContext, []);
161162
// This isn't a "real" feature context, and is only used in the channel_type field in an
162163
// `OpenChannel` message.
163164
define_context!(ChannelTypeContext, [
@@ -367,7 +368,8 @@ mod sealed {
367368
supports_keysend, requires_keysend);
368369

369370
#[cfg(test)]
370-
define_feature!(123456789, UnknownFeature, [NodeContext, ChannelContext, InvoiceContext, OfferContext],
371+
define_feature!(123456789, UnknownFeature,
372+
[NodeContext, ChannelContext, InvoiceContext, OfferContext, InvoiceRequestContext],
371373
"Feature flags for an unknown feature used in testing.", set_unknown_feature_optional,
372374
set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature);
373375
}
@@ -426,8 +428,10 @@ pub type NodeFeatures = Features<sealed::NodeContext>;
426428
pub type ChannelFeatures = Features<sealed::ChannelContext>;
427429
/// Features used within an invoice.
428430
pub type InvoiceFeatures = Features<sealed::InvoiceContext>;
429-
/// Features used within an offer.
431+
/// Features used within an `offer`.
430432
pub type OfferFeatures = Features<sealed::OfferContext>;
433+
/// Features used within an `invoice_request`.
434+
pub type InvoiceRequestFeatures = Features<sealed::InvoiceRequestContext>;
431435

432436
/// Features used within the channel_type field in an OpenChannel message.
433437
///
@@ -735,6 +739,7 @@ macro_rules! impl_feature_tlv_write {
735739

736740
impl_feature_tlv_write!(ChannelTypeFeatures);
737741
impl_feature_tlv_write!(OfferFeatures);
742+
impl_feature_tlv_write!(InvoiceRequestFeatures);
738743

739744
#[cfg(test)]
740745
mod tests {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Data structures and encoding for `invoice_request` messages.
11+
12+
use bitcoin::blockdata::constants::ChainHash;
13+
use bitcoin::secp256k1::PublicKey;
14+
use bitcoin::secp256k1::schnorr::Signature;
15+
use crate::ln::features::InvoiceRequestFeatures;
16+
use crate::offers::offer::OfferContents;
17+
use crate::offers::payer::PayerContents;
18+
use crate::util::string::PrintableString;
19+
20+
use crate::prelude::*;
21+
22+
/// An `InvoiceRequest` is a request for an `Invoice` formulated from an [`Offer`].
23+
///
24+
/// An offer may provided choices such as quantity, amount, chain, features, etc. An invoice request
25+
/// specifies these such that the recipient can send an invoice for payment.
26+
///
27+
/// [`Offer`]: crate::offers::offer::Offer
28+
#[derive(Clone, Debug)]
29+
pub struct InvoiceRequest {
30+
bytes: Vec<u8>,
31+
contents: InvoiceRequestContents,
32+
signature: Option<Signature>,
33+
}
34+
35+
/// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`.
36+
#[derive(Clone, Debug)]
37+
pub(crate) struct InvoiceRequestContents {
38+
payer: PayerContents,
39+
offer: OfferContents,
40+
chain: Option<ChainHash>,
41+
amount_msats: Option<u64>,
42+
features: InvoiceRequestFeatures,
43+
quantity: Option<u64>,
44+
payer_id: PublicKey,
45+
payer_note: Option<String>,
46+
}
47+
48+
impl InvoiceRequest {
49+
/// An unpredictable series of bytes, typically containing information about the derivation of
50+
/// [`payer_id`].
51+
///
52+
/// [`payer_id`]: Self::payer_id
53+
pub fn metadata(&self) -> Option<&Vec<u8>> {
54+
self.contents.payer.0.as_ref()
55+
}
56+
57+
/// A chain from [`Offer::chains`] that the offer is valid for.
58+
///
59+
/// [`Offer::chains`]: crate::offers::offer::Offer::chains
60+
pub fn chain(&self) -> ChainHash {
61+
self.contents.chain.unwrap_or_else(|| self.contents.offer.implied_chain())
62+
}
63+
64+
/// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
65+
/// must be greater than or equal to [`Offer::amount`], converted if necessary.
66+
///
67+
/// [`chain`]: Self::chain
68+
/// [`Offer::amount`]: crate::offers::offer::Offer::amount
69+
pub fn amount_msats(&self) -> Option<u64> {
70+
self.contents.amount_msats
71+
}
72+
73+
/// Features for paying the invoice.
74+
pub fn features(&self) -> &InvoiceRequestFeatures {
75+
&self.contents.features
76+
}
77+
78+
/// The quantity of the offer's item conforming to [`Offer::supported_quantity`].
79+
///
80+
/// [`Offer::supported_quantity`]: crate::offers::offer::Offer::supported_quantity
81+
pub fn quantity(&self) -> Option<u64> {
82+
self.contents.quantity
83+
}
84+
85+
/// A transient pubkey used to sign the invoice request.
86+
pub fn payer_id(&self) -> PublicKey {
87+
self.contents.payer_id
88+
}
89+
90+
/// Payer provided note to include in the invoice.
91+
pub fn payer_note(&self) -> Option<PrintableString> {
92+
self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
93+
}
94+
95+
/// Signature of the invoice request using [`payer_id`].
96+
///
97+
/// [`payer_id`]: Self::payer_id
98+
pub fn signature(&self) -> Option<Signature> {
99+
self.signature
100+
}
101+
}

lightning/src/offers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@
1212
//!
1313
//! Offers are a flexible protocol for Lightning payments.
1414
15+
pub mod invoice_request;
1516
pub mod offer;
1617
pub mod parse;
18+
mod payer;

lightning/src/offers/offer.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,16 @@ impl OfferBuilder {
224224

225225
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
226226
///
227-
/// An offer is a precursor to an `InvoiceRequest`. A merchant publishes an offer from which a
227+
/// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
228228
/// customer may request an `Invoice` for a specific quantity and using an amount sufficient to
229229
/// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
230230
///
231231
/// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
232232
/// latter.
233233
///
234234
/// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
235+
///
236+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
235237
#[derive(Clone, Debug)]
236238
pub struct Offer {
237239
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
@@ -240,7 +242,9 @@ pub struct Offer {
240242
contents: OfferContents,
241243
}
242244

243-
/// The contents of an [`Offer`], which may be shared with an `InvoiceRequest` or an `Invoice`.
245+
/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
246+
///
247+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
244248
#[derive(Clone, Debug)]
245249
pub(crate) struct OfferContents {
246250
chains: Option<Vec<ChainHash>>,
@@ -263,10 +267,7 @@ impl Offer {
263267
/// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
264268
/// for the selected chain.
265269
pub fn chains(&self) -> Vec<ChainHash> {
266-
self.contents.chains
267-
.as_ref()
268-
.cloned()
269-
.unwrap_or_else(|| vec![self.contents.implied_chain()])
270+
self.contents.chains()
270271
}
271272

272273
// TODO: Link to corresponding method in `InvoiceRequest`.
@@ -346,6 +347,10 @@ impl AsRef<[u8]> for Offer {
346347
}
347348

348349
impl OfferContents {
350+
pub fn chains(&self) -> Vec<ChainHash> {
351+
self.chains.as_ref().cloned().unwrap_or_else(|| vec![self.implied_chain()])
352+
}
353+
349354
pub fn implied_chain(&self) -> ChainHash {
350355
ChainHash::using_genesis_block(Network::Bitcoin)
351356
}

lightning/src/offers/payer.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Data structures and encoding for `invoice_request_metadata` records.
11+
12+
use crate::prelude::*;
13+
14+
/// An unpredictable sequence of bytes typically containing information needed to derive
15+
/// [`InvoiceRequestContents::payer_id`].
16+
///
17+
/// [`InvoiceRequestContents::payer_id`]: crate::offers::invoice_request::InvoiceRequestContents::payer_id
18+
#[derive(Clone, Debug)]
19+
pub(crate) struct PayerContents(pub Option<Vec<u8>>);

0 commit comments

Comments
 (0)