Skip to content

Commit d232a69

Browse files
committed
Offer message interface and data format
Define an interface for BOLT 12 `offer` messages. The underlying format consists of the original bytes and the parsed contents. The bytes are later needed when constructing an `invoice_request` message. This is because it must mirror all the `offer` TLV records, including unknown ones, which aren't represented in the contents. The contents will be used in `invoice_request` 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 (i.e., refund).
1 parent 8a85be4 commit d232a69

File tree

6 files changed

+178
-3
lines changed

6 files changed

+178
-3
lines changed

lightning/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ extern crate core;
7878
pub mod util;
7979
pub mod chain;
8080
pub mod ln;
81+
pub mod offers;
8182
pub mod routing;
8283
pub mod onion_message;
8384

lightning/src/offers/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
//! Implementation of Lightning Offers
11+
//! ([BOLT 12](https://github.com/lightning/bolts/blob/master/12-offer-encoding.md)).
12+
//!
13+
//! Offers are a flexible protocol for Lightning payments.
14+
15+
pub mod offer;

lightning/src/offers/offer.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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::time::Duration;
16+
use ln::features::OfferFeatures;
17+
use onion_message::BlindedPath;
18+
19+
use prelude::*;
20+
21+
#[cfg(feature = "std")]
22+
use std::time::SystemTime;
23+
24+
25+
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
26+
///
27+
/// An offer is precursor to an `InvoiceRequest`. A merchant publishes an offer from which a
28+
/// customer may request an `Invoice` for a specific quantity and using a specific amount. Offers
29+
/// may be denominated in currency other than bitcoin but are ultimately paid using the latter.
30+
///
31+
/// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
32+
#[derive(Clone, Debug)]
33+
pub struct Offer {
34+
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
35+
// fields.
36+
bytes: Vec<u8>,
37+
contents: OfferContents,
38+
}
39+
40+
/// The contents of an [`Offer`], which may be shared with an `InvoiceRequest` or an `Invoice`.
41+
#[derive(Clone, Debug)]
42+
pub(crate) struct OfferContents {
43+
chains: Option<Vec<ChainHash>>,
44+
metadata: Option<Vec<u8>>,
45+
amount: Option<Amount>,
46+
description: String,
47+
features: Option<OfferFeatures>,
48+
absolute_expiry: Option<Duration>,
49+
issuer: Option<String>,
50+
paths: Option<Vec<BlindedPath>>,
51+
quantity_min: Option<u64>,
52+
quantity_max: Option<u64>,
53+
node_id: Option<PublicKey>,
54+
}
55+
56+
impl Offer {
57+
/// The chain used for paying the invoice.
58+
pub fn chain(&self) -> ChainHash {
59+
// TODO: Update once spec is finalized
60+
self.contents.chains
61+
.as_ref()
62+
.and_then(|chains| chains.first().copied())
63+
.unwrap_or_else(|| ChainHash::using_genesis_block(Network::Bitcoin))
64+
}
65+
66+
/// Metadata set by the originator, useful for authentication and validating fields.
67+
pub fn metadata(&self) -> Option<&Vec<u8>> {
68+
self.contents.metadata.as_ref()
69+
}
70+
71+
/// The minimum amount required for a successful payment.
72+
pub fn amount(&self) -> Option<&Amount> {
73+
self.contents.amount.as_ref()
74+
}
75+
76+
/// A complete description of the purpose of the payment.
77+
pub fn description(&self) -> &String {
78+
&self.contents.description
79+
}
80+
81+
/// Features for paying the invoice.
82+
pub fn features(&self) -> Option<&OfferFeatures> {
83+
self.contents.features.as_ref()
84+
}
85+
86+
/// Duration since the Unix epoch when an invoice should no longer be requested.
87+
///
88+
/// If `None`, the offer does not expire.
89+
pub fn absolute_expiry(&self) -> Option<Duration> {
90+
self.contents.absolute_expiry
91+
}
92+
93+
/// Whether the offer has expired.
94+
#[cfg(feature = "std")]
95+
pub fn is_expired(&self) -> bool {
96+
match self.absolute_expiry() {
97+
Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
98+
Ok(elapsed) => elapsed > seconds_from_epoch,
99+
Err(_) => false,
100+
},
101+
None => false,
102+
}
103+
}
104+
105+
/// The issuer of the offer, possibly beginning with `user@domain` or `domain`.
106+
pub fn issuer(&self) -> Option<&String> {
107+
self.contents.issuer.as_ref()
108+
}
109+
110+
/// Paths to the recipient originating from publicly reachable nodes.
111+
pub fn paths(&self) -> Option<&Vec<BlindedPath>> {
112+
self.contents.paths.as_ref()
113+
}
114+
115+
/// The minimum quantity of items supported.
116+
pub fn quantity_min(&self) -> u64 {
117+
self.contents.quantity_min.unwrap_or(1)
118+
}
119+
120+
/// The maximum quantity of items supported.
121+
pub fn quantity_max(&self) -> u64 {
122+
self.contents.quantity_max.unwrap_or_else(||
123+
self.contents.quantity_min.map_or(1, |_| u64::max_value()))
124+
}
125+
126+
/// The recipient's public key used to sign invoices.
127+
pub fn node_id(&self) -> PublicKey {
128+
self.contents.node_id.unwrap_or_else(||
129+
self.contents.paths.as_ref().unwrap().first().unwrap()
130+
.blinded_hops.last().unwrap()
131+
.blinded_node_id
132+
)
133+
}
134+
}
135+
136+
/// The amount required for an item in an [`Offer`] denominated in either bitcoin or another
137+
/// currency.
138+
#[derive(Clone, Debug)]
139+
pub enum Amount {
140+
/// An amount of bitcoin.
141+
Bitcoin {
142+
/// The amount in millisatoshi.
143+
amount_msats: u64,
144+
},
145+
/// An amount of currency specified using ISO 4712.
146+
Currency {
147+
/// The currency that the amount is denominated in.
148+
iso4217_code: CurrencyCode,
149+
/// The amount in the currency unit adjusted by the ISO 4712 exponent (e.g., USD cents).
150+
amount: u64,
151+
},
152+
}
153+
154+
/// An ISO 4712 three-letter currency code (e.g., USD).
155+
pub type CurrencyCode = [u8; 3];

lightning/src/onion_message/blinded_route.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use prelude::*;
2222

2323
/// Onion messages can be sent and received to blinded routes, which serve to hide the identity of
2424
/// the recipient.
25+
#[derive(Clone, Debug)]
2526
pub struct BlindedRoute {
2627
/// To send to a blinded route, the sender first finds a route to the unblinded
2728
/// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion
@@ -35,14 +36,15 @@ pub struct BlindedRoute {
3536
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
3637
pub(super) blinding_point: PublicKey,
3738
/// The hops composing the blinded route.
38-
pub(super) blinded_hops: Vec<BlindedHop>,
39+
pub(crate) blinded_hops: Vec<BlindedHop>,
3940
}
4041

4142
/// Used to construct the blinded hops portion of a blinded route. These hops cannot be identified
4243
/// by outside observers and thus can be used to hide the identity of the recipient.
44+
#[derive(Clone, Debug)]
4345
pub struct BlindedHop {
4446
/// The blinded node id of this hop in a blinded route.
45-
pub(super) blinded_node_id: PublicKey,
47+
pub(crate) blinded_node_id: PublicKey,
4648
/// The encrypted payload intended for this hop in a blinded route.
4749
// The node sending to this blinded route will later encode this payload into the onion packet for
4850
// this hop.

lightning/src/onion_message/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ mod functional_tests;
3030
// Re-export structs so they can be imported with just the `onion_message::` module prefix.
3131
pub use self::blinded_route::{BlindedRoute, BlindedHop};
3232
pub use self::messenger::{Destination, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
33+
pub use self::blinded_route::BlindedRoute as BlindedPath;
3334
pub(crate) use self::packet::Packet;

lightning/src/util/ser.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ impl Readable for BigSize {
399399
/// In TLV we occasionally send fields which only consist of, or potentially end with, a
400400
/// variable-length integer which is simply truncated by skipping high zero bytes. This type
401401
/// encapsulates such integers implementing Readable/Writeable for them.
402-
#[cfg_attr(test, derive(PartialEq, Debug))]
402+
#[cfg_attr(test, derive(PartialEq))]
403+
#[derive(Clone, Debug)]
403404
pub(crate) struct HighZeroBytesDroppedBigSize<T>(pub T);
404405

405406
macro_rules! impl_writeable_primitive {

0 commit comments

Comments
 (0)