Skip to content

Commit 74f0102

Browse files
committed
WIP: Invoice encoding
1 parent 0f5e8fc commit 74f0102

File tree

6 files changed

+295
-2
lines changed

6 files changed

+295
-2
lines changed

lightning/src/offers/invoice.rs

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

lightning/src/offers/invoice_request.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ pub struct InvoiceRequest {
235235
#[derive(Clone, Debug)]
236236
pub(crate) struct InvoiceRequestContents {
237237
payer: PayerContents,
238-
offer: OfferContents,
238+
pub(super) offer: OfferContents,
239239
chain: Option<ChainHash>,
240240
amount_msats: Option<u64>,
241241
features: Option<OfferFeatures>,
@@ -306,6 +306,21 @@ impl InvoiceRequestContents {
306306
self.chain.unwrap_or_else(|| self.offer.implied_chain())
307307
}
308308

309+
pub fn network(&self) -> Network {
310+
let chain = self.chain();
311+
if chain == ChainHash::using_genesis_block(Network::Bitcoin) {
312+
Network::Bitcoin
313+
} else if chain == ChainHash::using_genesis_block(Network::Testnet) {
314+
Network::Testnet
315+
} else if chain == ChainHash::using_genesis_block(Network::Signet) {
316+
Network::Signet
317+
} else if chain == ChainHash::using_genesis_block(Network::Regtest) {
318+
Network::Regtest
319+
} else {
320+
unreachable!()
321+
}
322+
}
323+
309324
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
310325
let payer = PayerTlvStreamRef {
311326
metadata: self.payer.0.as_ref(),

lightning/src/offers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//!
1313
//! Offers are a flexible protocol for Lightning payments.
1414
15+
pub mod invoice;
1516
pub mod invoice_request;
1617
mod merkle;
1718
pub mod offer;

lightning/src/offers/offer.rs

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

378378
/// The public key used by the recipient to sign invoices.
379379
pub fn signing_pubkey(&self) -> PublicKey {
380-
self.contents.signing_pubkey.unwrap()
380+
self.contents.signing_pubkey()
381381
}
382382

383383
///
@@ -441,6 +441,10 @@ impl OfferContents {
441441
self.quantity_min.is_some() || self.quantity_max.is_some()
442442
}
443443

444+
pub fn signing_pubkey(&self) -> PublicKey {
445+
self.signing_pubkey.unwrap()
446+
}
447+
444448
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
445449
let (currency, amount) = match &self.amount {
446450
None => (None, None),

lightning/src/offers/parse.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ pub enum SemanticError {
111111
UnexpectedQuantity,
112112
/// A payer id was expected but was missing.
113113
MissingPayerId,
114+
///
115+
InvalidPayInfo,
116+
///
117+
MissingCreationTime,
118+
///
119+
MissingPaymentHash,
114120
}
115121

116122
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;
@@ -966,6 +967,22 @@ macro_rules! impl_consensus_ser {
966967
impl_consensus_ser!(Transaction);
967968
impl_consensus_ser!(TxOut);
968969

970+
impl Writeable for WitnessVersion {
971+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
972+
self.to_num().write(w)
973+
}
974+
}
975+
976+
impl Readable for WitnessVersion {
977+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
978+
let num: u8 = Readable::read(r)?;
979+
match WitnessVersion::try_from(num) {
980+
Ok(version) => Ok(version),
981+
Err(_) => Err(DecodeError::InvalidValue),
982+
}
983+
}
984+
}
985+
969986
impl<T: Readable> Readable for Mutex<T> {
970987
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
971988
let t: T = Readable::read(r)?;
@@ -1026,6 +1043,26 @@ impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B
10261043
}
10271044
}
10281045

1046+
impl<A: Readable, B: Readable, C: Readable, D: Readable, E: Readable> Readable for (A, B, C, D, E) {
1047+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
1048+
let a: A = Readable::read(r)?;
1049+
let b: B = Readable::read(r)?;
1050+
let c: C = Readable::read(r)?;
1051+
let d: D = Readable::read(r)?;
1052+
let e: E = Readable::read(r)?;
1053+
Ok((a, b, c, d, e))
1054+
}
1055+
}
1056+
impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable, E: Writeable> Writeable for (A, B, C, D, E) {
1057+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
1058+
self.0.write(w)?;
1059+
self.1.write(w)?;
1060+
self.2.write(w)?;
1061+
self.3.write(w)?;
1062+
self.4.write(w)
1063+
}
1064+
}
1065+
10291066
impl Writeable for () {
10301067
fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
10311068
Ok(())

0 commit comments

Comments
 (0)