]> git.bitcoin.ninja Git - rust-lightning/blob - lightning/src/offers/offer.rs
Offer message interface and data format
[rust-lightning] / lightning / src / offers / offer.rs
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 `offer` messages.
11
12 use bitcoin::blockdata::constants::ChainHash;
13 use bitcoin::network::constants::Network;
14 use bitcoin::secp256k1::PublicKey;
15 use core::num::NonZeroU64;
16 use core::time::Duration;
17 use crate::ln::features::OfferFeatures;
18 use crate::onion_message::BlindedPath;
19 use crate::util::string::PrintableString;
20
21 use crate::prelude::*;
22
23 #[cfg(feature = "std")]
24 use std::time::SystemTime;
25
26 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
27 ///
28 /// An offer is a precursor to an `InvoiceRequest`. A merchant publishes an offer from which a
29 /// customer may request an `Invoice` for a specific quantity and using an amount sufficient to
30 /// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
31 ///
32 /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
33 /// latter.
34 ///
35 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
36 #[derive(Clone, Debug)]
37 pub struct Offer {
38         // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
39         // fields.
40         bytes: Vec<u8>,
41         contents: OfferContents,
42 }
43
44 /// The contents of an [`Offer`], which may be shared with an `InvoiceRequest` or an `Invoice`.
45 #[derive(Clone, Debug)]
46 pub(crate) struct OfferContents {
47         chains: Option<Vec<ChainHash>>,
48         metadata: Option<Vec<u8>>,
49         amount: Option<Amount>,
50         description: String,
51         features: OfferFeatures,
52         absolute_expiry: Option<Duration>,
53         issuer: Option<String>,
54         paths: Option<Vec<BlindedPath>>,
55         quantity_max: Option<u64>,
56         signing_pubkey: Option<PublicKey>,
57 }
58
59 impl Offer {
60         // TODO: Return a slice once ChainHash has constants.
61         // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1283
62         // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1286
63         /// The chains that may be used when paying a requested invoice (e.g., bitcoin mainnet).
64         /// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
65         /// for the selected chain.
66         pub fn chains(&self) -> Vec<ChainHash> {
67                 self.contents.chains
68                         .as_ref()
69                         .cloned()
70                         .unwrap_or_else(|| vec![ChainHash::using_genesis_block(Network::Bitcoin)])
71         }
72
73         // TODO: Link to corresponding method in `InvoiceRequest`.
74         /// Opaque bytes set by the originator. Useful for authentication and validating fields since it
75         /// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
76         pub fn metadata(&self) -> Option<&Vec<u8>> {
77                 self.contents.metadata.as_ref()
78         }
79
80         /// The minimum amount required for a successful payment of a single item.
81         pub fn amount(&self) -> Option<&Amount> {
82                 self.contents.amount.as_ref()
83         }
84
85         /// A complete description of the purpose of the payment. Intended to be displayed to the user
86         /// but with the caveat that it has not been verified in any way.
87         pub fn description(&self) -> PrintableString {
88                 PrintableString(&self.contents.description)
89         }
90
91         /// Features pertaining to the offer.
92         pub fn features(&self) -> &OfferFeatures {
93                 &self.contents.features
94         }
95
96         /// Duration since the Unix epoch when an invoice should no longer be requested.
97         ///
98         /// If `None`, the offer does not expire.
99         pub fn absolute_expiry(&self) -> Option<Duration> {
100                 self.contents.absolute_expiry
101         }
102
103         /// Whether the offer has expired.
104         #[cfg(feature = "std")]
105         pub fn is_expired(&self) -> bool {
106                 match self.absolute_expiry() {
107                         Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
108                                 Ok(elapsed) => elapsed > seconds_from_epoch,
109                                 Err(_) => false,
110                         },
111                         None => false,
112                 }
113         }
114
115         /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
116         /// displayed to the user but with the caveat that it has not been verified in any way.
117         pub fn issuer(&self) -> Option<PrintableString> {
118                 self.contents.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
119         }
120
121         /// Paths to the recipient originating from publicly reachable nodes. Blinded paths provide
122         /// recipient privacy by obfuscating its node id.
123         pub fn paths(&self) -> &[BlindedPath] {
124                 self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
125         }
126
127         /// The quantity of items supported.
128         pub fn supported_quantity(&self) -> Quantity {
129                 match self.contents.quantity_max {
130                         Some(0) => Quantity::Unbounded,
131                         Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
132                         None => Quantity::Bounded(NonZeroU64::new(1).unwrap()),
133                 }
134         }
135
136         /// The public key used by the recipient to sign invoices.
137         pub fn signing_pubkey(&self) -> PublicKey {
138                 self.contents.signing_pubkey.unwrap()
139         }
140 }
141
142 /// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or
143 /// another currency.
144 #[derive(Clone, Debug)]
145 pub enum Amount {
146         /// An amount of bitcoin.
147         Bitcoin {
148                 /// The amount in millisatoshi.
149                 amount_msats: u64,
150         },
151         /// An amount of currency specified using ISO 4712.
152         Currency {
153                 /// The currency that the amount is denominated in.
154                 iso4217_code: CurrencyCode,
155                 /// The amount in the currency unit adjusted by the ISO 4712 exponent (e.g., USD cents).
156                 amount: u64,
157         },
158 }
159
160 /// An ISO 4712 three-letter currency code (e.g., USD).
161 pub type CurrencyCode = [u8; 3];
162
163 /// Quantity of items supported by an [`Offer`].
164 pub enum Quantity {
165         /// Up to a specific number of items (inclusive).
166         Bounded(NonZeroU64),
167         /// One or more items.
168         Unbounded,
169 }