Skip to content

Commit 8d8f554

Browse files
committed
WIP: Invoice encoding
1 parent c1f70ff commit 8d8f554

File tree

6 files changed

+296
-3
lines changed

6 files changed

+296
-3
lines changed

lightning/src/offers/invoice.rs

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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::secp256k1::schnorr::Signature;
13+
use bitcoin::util::address::{Address, Payload, WitnessVersion};
14+
use core::convert::TryFrom;
15+
use core::time::Duration;
16+
use io;
17+
use ln::PaymentHash;
18+
use ln::features::OfferFeatures;
19+
use offers::BlindedPath;
20+
use offers::merkle::{SignatureTlvStream, self};
21+
use offers::offer::OfferTlvStream;
22+
use offers::parse::{ParseError, SemanticError};
23+
use offers::payer::PayerTlvStream;
24+
use offers::invoice_request::{InvoiceRequestContents, InvoiceRequestTlvStream};
25+
use util::ser::{Readable, WithoutLength, Writeable, Writer};
26+
27+
///
28+
const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
29+
30+
///
31+
pub struct Invoice {
32+
bytes: Vec<u8>,
33+
contents: InvoiceContents,
34+
signature: Option<Signature>,
35+
}
36+
37+
///
38+
pub(crate) struct InvoiceContents {
39+
invoice_request: InvoiceRequestContents,
40+
paths: Option<Vec<BlindedPath>>,
41+
payinfo: Option<Vec<BlindedPayInfo>>,
42+
created_at: Duration,
43+
payment_hash: PaymentHash,
44+
relative_expiry: Option<Duration>,
45+
fallbacks: Option<Vec<Address>>,
46+
features: Option<OfferFeatures>,
47+
amount_msats: u64,
48+
code: Option<String>,
49+
}
50+
51+
impl Invoice {
52+
///
53+
pub fn fallbacks(&self) -> Vec<&Address> {
54+
self.contents.fallbacks
55+
.as_ref()
56+
.map(|fallbacks| fallbacks.iter().filter(|address| address.is_standard()).collect())
57+
.unwrap_or_else(Vec::new)
58+
}
59+
}
60+
61+
impl Writeable for Invoice {
62+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
63+
WithoutLength(&self.bytes).write(writer)
64+
}
65+
}
66+
67+
impl TryFrom<Vec<u8>> for Invoice {
68+
type Error = ParseError;
69+
70+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
71+
let tlv_stream: FullInvoiceTlvStream = Readable::read(&mut &bytes[..])?;
72+
Invoice::try_from((bytes, tlv_stream))
73+
}
74+
}
75+
76+
tlv_stream!(struct InvoiceTlvStream {
77+
(160, paths: Vec<BlindedPath>),
78+
(162, payinfo: Vec<BlindedPayInfo>),
79+
(164, created_at: u64),
80+
(168, payment_hash: PaymentHash),
81+
(170, relative_expiry: u32),
82+
(172, fallbacks: Vec<FallbackAddress>),
83+
(174, features: OfferFeatures),
84+
(176, amount: u64),
85+
(178, code: String),
86+
});
87+
88+
///
89+
#[derive(Debug)]
90+
pub struct BlindedPayInfo {
91+
fee_base_msat: u32,
92+
fee_proportional_millionths: u32,
93+
cltv_expiry_delta: u16,
94+
htlc_minimum_msat: u64,
95+
htlc_maximum_msat: u64,
96+
features_len: u16,
97+
features: OfferFeatures,
98+
}
99+
100+
impl_writeable!(BlindedPayInfo, {
101+
fee_base_msat,
102+
fee_proportional_millionths,
103+
cltv_expiry_delta,
104+
htlc_minimum_msat,
105+
htlc_maximum_msat,
106+
features_len,
107+
features
108+
});
109+
110+
///
111+
#[derive(Debug)]
112+
pub struct FallbackAddress {
113+
version: WitnessVersion,
114+
program: Vec<u8>,
115+
}
116+
117+
impl_writeable!(FallbackAddress, { version, program });
118+
119+
type ParsedInvoice = (Vec<u8>, FullInvoiceTlvStream);
120+
121+
type FullInvoiceTlvStream =
122+
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
123+
124+
type PartialInvoiceTlvStream =
125+
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream);
126+
127+
impl TryFrom<ParsedInvoice> for Invoice {
128+
type Error = ParseError;
129+
130+
fn try_from(invoice: ParsedInvoice) -> Result<Self, Self::Error> {
131+
let (bytes, tlv_stream) = invoice;
132+
let (
133+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
134+
SignatureTlvStream { signature },
135+
) = tlv_stream;
136+
let contents = InvoiceContents::try_from(
137+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
138+
)?;
139+
140+
if let Some(signature) = &signature {
141+
let pubkey = contents.invoice_request.offer.node_id();
142+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, pubkey)?;
143+
}
144+
145+
Ok(Invoice { bytes, contents, signature })
146+
}
147+
}
148+
149+
impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
150+
type Error = SemanticError;
151+
152+
fn try_from(tlv_stream: PartialInvoiceTlvStream) -> Result<Self, Self::Error> {
153+
let (
154+
payer_tlv_stream,
155+
offer_tlv_stream,
156+
invoice_request_tlv_stream,
157+
InvoiceTlvStream {
158+
paths, payinfo, created_at, payment_hash, relative_expiry, fallbacks, features,
159+
amount, code,
160+
},
161+
) = tlv_stream;
162+
163+
let invoice_request = InvoiceRequestContents::try_from(
164+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
165+
)?;
166+
167+
let paths = paths.map(Into::<Vec<_>>::into);
168+
let payinfo = payinfo.map(Into::<Vec<_>>::into);
169+
let (paths, payinfo) = match (paths, payinfo) {
170+
(None, _) => return Err(SemanticError::MissingPaths),
171+
(_, None) => return Err(SemanticError::InvalidPayInfo),
172+
(Some(paths), _) if paths.is_empty() => return Err(SemanticError::MissingPaths),
173+
(Some(paths), Some(payinfo)) if paths.len() != payinfo.len() => {
174+
return Err(SemanticError::InvalidPayInfo);
175+
},
176+
(paths, payinfo) => (paths, payinfo),
177+
};
178+
179+
let created_at = match created_at.map(Into::into) {
180+
None => return Err(SemanticError::MissingCreationTime),
181+
Some(timestamp) => Duration::from_secs(timestamp),
182+
};
183+
184+
let payment_hash = match payment_hash {
185+
None => return Err(SemanticError::MissingPaymentHash),
186+
Some(payment_hash) => payment_hash,
187+
};
188+
189+
let relative_expiry = relative_expiry
190+
.map(Into::<u32>::into)
191+
.map(Into::<u64>::into)
192+
.map(Duration::from_secs);
193+
194+
let fallbacks = match fallbacks.map(Into::<Vec<_>>::into) {
195+
None => None,
196+
Some(fallbacks) => {
197+
let mut addresses = Vec::with_capacity(fallbacks.len());
198+
for FallbackAddress { version, program } in fallbacks {
199+
if program.len() < 2 || program.len() > 40 {
200+
return Err(SemanticError::InvalidFallbackAddress);
201+
}
202+
203+
addresses.push(Address {
204+
payload: Payload::WitnessProgram { version, program },
205+
network: invoice_request.network(),
206+
});
207+
}
208+
Some(addresses)
209+
},
210+
};
211+
212+
let amount_msats = match amount.map(Into::into) {
213+
None => return Err(SemanticError::MissingAmount),
214+
Some(amount) => amount,
215+
};
216+
217+
let code = code.map(Into::into);
218+
219+
Ok(InvoiceContents {
220+
invoice_request, paths, payinfo, created_at, payment_hash, relative_expiry, fallbacks,
221+
features, amount_msats, code,
222+
})
223+
}
224+
}

lightning/src/offers/invoice_request.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ pub struct InvoiceRequest {
201201
///
202202
pub(crate) struct InvoiceRequestContents {
203203
payer: PayerContents,
204-
offer: OfferContents,
204+
pub(super) offer: OfferContents,
205205
chain: Option<ChainHash>,
206206
amount_msats: Option<u64>,
207207
features: Option<OfferFeatures>,
@@ -218,7 +218,7 @@ impl InvoiceRequest {
218218

219219
///
220220
pub fn chain(&self) -> ChainHash {
221-
self.contents.chain.unwrap_or_else(|| self.contents.offer.chain())
221+
self.contents.chain()
222222
}
223223

224224
///
@@ -259,6 +259,25 @@ impl AsRef<[u8]> for InvoiceRequest {
259259
}
260260

261261
impl InvoiceRequestContents {
262+
pub fn chain(&self) -> ChainHash {
263+
self.chain.unwrap_or_else(|| self.offer.chain())
264+
}
265+
266+
pub fn network(&self) -> Network {
267+
let chain = self.chain();
268+
if chain == ChainHash::using_genesis_block(Network::Bitcoin) {
269+
Network::Bitcoin
270+
} else if chain == ChainHash::using_genesis_block(Network::Testnet) {
271+
Network::Testnet
272+
} else if chain == ChainHash::using_genesis_block(Network::Regtest) {
273+
Network::Regtest
274+
} else if chain == ChainHash::using_genesis_block(Network::Signet) {
275+
Network::Signet
276+
} else {
277+
unreachable!()
278+
}
279+
}
280+
262281
pub(super) fn as_tlv_stream(&self) -> ReferencedPartialInvoiceRequestTlvStream {
263282
let payer = payer::reference::PayerTlvStream {
264283
payer_info: self.payer.0.as_ref().map(Into::into),

lightning/src/offers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! Implementation of Lightning Offers
1111
//! ([BOLT 12](https://github.com/lightning/bolts/blob/master/12-offer-encoding.md)).
1212
13+
pub mod invoice;
1314
pub mod invoice_request;
1415
mod merkle;
1516
mod offer;

lightning/src/offers/offer.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ impl Offer {
259259

260260
///
261261
pub fn node_id(&self) -> PublicKey {
262-
self.contents.node_id.unwrap()
262+
self.contents.node_id()
263263
}
264264

265265
///
@@ -344,6 +344,10 @@ impl OfferContents {
344344
self.quantity_min.is_some() || self.quantity_max.is_some()
345345
}
346346

347+
pub fn node_id(&self) -> PublicKey {
348+
self.node_id.unwrap()
349+
}
350+
347351
pub(super) fn as_tlv_stream(&self) -> reference::OfferTlvStream {
348352
let (currency, amount) = match &self.amount {
349353
None => (None, None),

lightning/src/offers/parse.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ pub enum SemanticError {
9696
InvalidQuantity,
9797
///
9898
MissingPayerId,
99+
///
100+
InvalidPayInfo,
101+
///
102+
MissingCreationTime,
103+
///
104+
MissingPaymentHash,
105+
///
106+
InvalidFallbackAddress,
99107
}
100108

101109
impl From<bech32::Error> for ParseError {

lightning/src/util/ser.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use bitcoin::consensus;
3030
use bitcoin::consensus::Encodable;
3131
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
3232
use bitcoin::hash_types::{Txid, BlockHash};
33+
use bitcoin::util::address::WitnessVersion;
3334
use core::marker::Sized;
3435
use core::time::Duration;
3536
use ln::msgs::DecodeError;
@@ -1015,6 +1016,22 @@ macro_rules! impl_consensus_ser {
10151016
impl_consensus_ser!(Transaction);
10161017
impl_consensus_ser!(TxOut);
10171018

1019+
impl Writeable for WitnessVersion {
1020+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
1021+
self.to_num().write(w)
1022+
}
1023+
}
1024+
1025+
impl Readable for WitnessVersion {
1026+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
1027+
let num: u8 = Readable::read(r)?;
1028+
match WitnessVersion::try_from(num) {
1029+
Ok(version) => Ok(version),
1030+
Err(_) => Err(DecodeError::InvalidValue),
1031+
}
1032+
}
1033+
}
1034+
10181035
impl<T: Readable> Readable for Mutex<T> {
10191036
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
10201037
let t: T = Readable::read(r)?;
@@ -1075,6 +1092,26 @@ impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B
10751092
}
10761093
}
10771094

1095+
impl<A: Readable, B: Readable, C: Readable, D: Readable, E: Readable> Readable for (A, B, C, D, E) {
1096+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
1097+
let a: A = Readable::read(r)?;
1098+
let b: B = Readable::read(r)?;
1099+
let c: C = Readable::read(r)?;
1100+
let d: D = Readable::read(r)?;
1101+
let e: E = Readable::read(r)?;
1102+
Ok((a, b, c, d, e))
1103+
}
1104+
}
1105+
impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable, E: Writeable> Writeable for (A, B, C, D, E) {
1106+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
1107+
self.0.write(w)?;
1108+
self.1.write(w)?;
1109+
self.2.write(w)?;
1110+
self.3.write(w)?;
1111+
self.4.write(w)
1112+
}
1113+
}
1114+
10781115
impl Writeable for () {
10791116
fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
10801117
Ok(())

0 commit comments

Comments
 (0)