Skip to content

Commit 5a76cd5

Browse files
committed
WIP: Offer message format
1 parent ea2b210 commit 5a76cd5

File tree

4 files changed

+216
-1
lines changed

4 files changed

+216
-1
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
#[allow(unused)]
8384
mod onion_message; // To be exposed after sending/receiving OMs is supported in PeerManager.

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+
mod offer;
14+
15+
pub use self::offer::{Amount, BlindedPath, CurrencyCode, Offer};

lightning/src/offers/offer.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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::genesis_block;
13+
use bitcoin::hash_types::BlockHash;
14+
use bitcoin::hashes::sha256;
15+
use bitcoin::network::constants::Network;
16+
use bitcoin::secp256k1::PublicKey;
17+
use bitcoin::secp256k1::schnorr::Signature;
18+
use core::time::Duration;
19+
use ln::PaymentHash;
20+
use ln::features::OfferFeatures;
21+
use util::ser::WithLength;
22+
23+
use prelude::*;
24+
25+
#[cfg(feature = "std")]
26+
use std::time::SystemTime;
27+
28+
///
29+
#[derive(Clone, Debug)]
30+
pub struct Offer {
31+
id: sha256::Hash,
32+
chains: Option<Vec<BlockHash>>,
33+
amount: Option<Amount>,
34+
description: String,
35+
features: Option<OfferFeatures>,
36+
absolute_expiry: Option<Duration>,
37+
issuer: Option<String>,
38+
paths: Option<Vec<BlindedPath>>,
39+
quantity_min: Option<u64>,
40+
quantity_max: Option<u64>,
41+
node_id: Option<PublicKey>,
42+
send_invoice: Option<SendInvoice>,
43+
signature: Option<Signature>,
44+
}
45+
46+
impl Offer {
47+
///
48+
pub fn id(&self) -> sha256::Hash {
49+
self.id
50+
}
51+
52+
///
53+
pub fn chain(&self) -> BlockHash {
54+
// TODO: Update once spec is finalized
55+
self.chains
56+
.as_ref()
57+
.and_then(|chains| chains.first().copied())
58+
.unwrap_or_else(|| genesis_block(Network::Bitcoin).block_hash())
59+
}
60+
61+
///
62+
pub fn amount(&self) -> Option<&Amount> {
63+
self.amount.as_ref()
64+
}
65+
66+
///
67+
pub fn description(&self) -> &String {
68+
&self.description
69+
}
70+
71+
///
72+
pub fn features(&self) -> Option<&OfferFeatures> {
73+
self.features.as_ref()
74+
}
75+
76+
///
77+
pub fn absolute_expiry(&self) -> Option<Duration> {
78+
self.absolute_expiry
79+
}
80+
81+
///
82+
#[cfg(feature = "std")]
83+
pub fn is_expired(&self) -> bool {
84+
match self.absolute_expiry() {
85+
Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
86+
Ok(elapsed) => elapsed > seconds_from_epoch,
87+
Err(_) => false,
88+
},
89+
None => false,
90+
}
91+
}
92+
93+
///
94+
pub fn issuer(&self) -> Option<&String> {
95+
self.issuer.as_ref()
96+
}
97+
98+
///
99+
pub fn paths(&self) -> Option<&Vec<BlindedPath>> {
100+
self.paths.as_ref()
101+
}
102+
103+
///
104+
pub fn quantity_min(&self) -> u64 {
105+
self.quantity_min.unwrap_or(1)
106+
}
107+
108+
///
109+
pub fn quantity_max(&self) -> u64 {
110+
self.quantity_max.unwrap_or_else(||
111+
self.quantity_min.map_or(1, |_| u64::max_value()))
112+
}
113+
114+
///
115+
pub fn node_id(&self) -> PublicKey {
116+
self.node_id.unwrap_or_else(||
117+
self.paths.as_ref().unwrap().first().unwrap().path.0.last().unwrap().node_id)
118+
}
119+
120+
///
121+
pub fn send_invoice(&self) -> Option<&SendInvoice> {
122+
self.send_invoice.as_ref()
123+
}
124+
125+
///
126+
pub fn signature(&self) -> Option<&Signature> {
127+
self.signature.as_ref()
128+
}
129+
}
130+
131+
/// The amount required for an item in an [`Offer`] denominated in either bitcoin or another
132+
/// currency.
133+
#[derive(Clone, Debug)]
134+
pub enum Amount {
135+
/// An amount of bitcoin.
136+
Bitcoin {
137+
/// The amount in millisatoshi.
138+
amount_msats: u64,
139+
},
140+
/// An amount of currency specified using ISO 4712.
141+
Currency {
142+
/// The currency that the amount is denominated in.
143+
iso4217_code: CurrencyCode,
144+
/// The amount in the currency unit adjusted by the ISO 4712 exponent (e.g., USD cents).
145+
amount: u64,
146+
},
147+
}
148+
149+
/// An ISO 4712 three-letter currency code (e.g., USD).
150+
pub type CurrencyCode = [u8; 3];
151+
152+
///
153+
#[derive(Clone, Debug)]
154+
pub struct SendInvoice {
155+
refund_for: Option<PaymentHash>,
156+
}
157+
158+
#[derive(Clone, Debug)]
159+
///
160+
pub struct BlindedPath {
161+
blinding: PublicKey,
162+
path: WithLength<Vec<OnionMessagePath>, u8>,
163+
}
164+
165+
#[derive(Clone, Debug)]
166+
struct OnionMessagePath {
167+
node_id: PublicKey,
168+
encrypted_recipient_data: Vec<u8>,
169+
}

lightning/src/util/ser.rs

Lines changed: 31 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 HighZeroBytesDroppedVarInt<T>(pub T);
404405

405406
macro_rules! impl_writeable_primitive {
@@ -523,6 +524,35 @@ impl_array!(PUBLIC_KEY_SIZE); // for PublicKey
523524
impl_array!(COMPACT_SIGNATURE_SIZE); // for Signature
524525
impl_array!(1300); // for OnionPacket.hop_data
525526

527+
/// For variable-length values within TLV subtypes where the length cannot be inferred from the
528+
/// enclosing TLV record.
529+
///
530+
/// The length is encoded as an unsigned `U` type.
531+
#[derive(Clone, Debug)]
532+
pub(crate) struct WithLength<T, U>(pub T, pub core::marker::PhantomData<U>);
533+
534+
impl<T: Writeable> Writeable for WithLength<Vec<T>, u8> {
535+
#[inline]
536+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
537+
(self.0.len() as u8).write(w)?;
538+
for element in self.0.iter() {
539+
element.write(w)?;
540+
}
541+
Ok(())
542+
}
543+
}
544+
impl<T: Readable> Readable for WithLength<Vec<T>, u8> {
545+
#[inline]
546+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
547+
let len = <u8 as Readable>::read(r)? as usize;
548+
let mut result = Vec::with_capacity(len);
549+
for _ in 0..len {
550+
result.push(<T as Readable>::read(r)?);
551+
}
552+
Ok(Self(result, core::marker::PhantomData))
553+
}
554+
}
555+
526556
// HashMap
527557
impl<K, V> Writeable for HashMap<K, V>
528558
where K: Writeable + Eq + Hash,

0 commit comments

Comments
 (0)