Skip to content

Commit 0ccfe84

Browse files
committed
WIP: Invoice encoding
1 parent b325438 commit 0ccfe84

File tree

6 files changed

+305
-3
lines changed

6 files changed

+305
-3
lines changed

lightning/src/offers/invoice.rs

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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!(struct InvoiceTlvStream {
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.node_id();
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 = paths.map(Into::<Vec<_>>::into);
183+
let payinfo = payinfo.map(Into::<Vec<_>>::into);
184+
let (paths, payinfo) = match (paths, payinfo) {
185+
(None, _) => return Err(SemanticError::MissingPaths),
186+
(_, None) => return Err(SemanticError::InvalidPayInfo),
187+
(Some(paths), _) if paths.is_empty() => return Err(SemanticError::MissingPaths),
188+
(Some(paths), Some(payinfo)) if paths.len() != payinfo.len() => {
189+
return Err(SemanticError::InvalidPayInfo);
190+
},
191+
(paths, payinfo) => (paths, payinfo),
192+
};
193+
194+
let created_at = match created_at.map(Into::into) {
195+
None => return Err(SemanticError::MissingCreationTime),
196+
Some(timestamp) => Duration::from_secs(timestamp),
197+
};
198+
199+
let relative_expiry = relative_expiry
200+
.map(Into::<u32>::into)
201+
.map(Into::<u64>::into)
202+
.map(Duration::from_secs);
203+
204+
let payment_hash = match payment_hash {
205+
None => return Err(SemanticError::MissingPaymentHash),
206+
Some(payment_hash) => payment_hash,
207+
};
208+
209+
let amount_msats = match amount.map(Into::into) {
210+
None => return Err(SemanticError::MissingAmount),
211+
Some(amount) => amount,
212+
};
213+
214+
let fallbacks = match fallbacks.map(Into::<Vec<_>>::into) {
215+
None => None,
216+
Some(fallbacks) => {
217+
let mut addresses = Vec::with_capacity(fallbacks.len());
218+
for FallbackAddress { version, program } in fallbacks {
219+
addresses.push(Address {
220+
payload: Payload::WitnessProgram { version, program },
221+
network: invoice_request.network(),
222+
});
223+
}
224+
Some(addresses)
225+
},
226+
};
227+
228+
let code = code.map(Into::into);
229+
230+
Ok(InvoiceContents {
231+
invoice_request, paths, payinfo, created_at, relative_expiry, payment_hash,
232+
amount_msats, fallbacks, features, code,
233+
})
234+
}
235+
}

lightning/src/offers/invoice_request.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ pub struct InvoiceRequest {
203203
///
204204
pub(crate) struct InvoiceRequestContents {
205205
payer: PayerContents,
206-
offer: OfferContents,
206+
pub(super) offer: OfferContents,
207207
chain: Option<ChainHash>,
208208
amount_msats: Option<u64>,
209209
features: Option<OfferFeatures>,
@@ -220,7 +220,7 @@ impl InvoiceRequest {
220220

221221
///
222222
pub fn chain(&self) -> ChainHash {
223-
self.contents.chain.unwrap_or_else(|| self.contents.offer.chain())
223+
self.contents.chain()
224224
}
225225

226226
///
@@ -261,6 +261,25 @@ impl AsRef<[u8]> for InvoiceRequest {
261261
}
262262

263263
impl InvoiceRequestContents {
264+
pub fn chain(&self) -> ChainHash {
265+
self.chain.unwrap_or_else(|| self.offer.chain())
266+
}
267+
268+
pub fn network(&self) -> Network {
269+
let chain = self.chain();
270+
if chain == ChainHash::using_genesis_block(Network::Bitcoin) {
271+
Network::Bitcoin
272+
} else if chain == ChainHash::using_genesis_block(Network::Testnet) {
273+
Network::Testnet
274+
} else if chain == ChainHash::using_genesis_block(Network::Signet) {
275+
Network::Signet
276+
} else if chain == ChainHash::using_genesis_block(Network::Regtest) {
277+
Network::Regtest
278+
} else {
279+
unreachable!()
280+
}
281+
}
282+
264283
pub(super) fn as_tlv_stream(&self) -> ReferencedPartialInvoiceRequestTlvStream {
265284
let payer = payer::reference::PayerTlvStream {
266285
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
@@ -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
@@ -339,7 +339,7 @@ impl Offer {
339339

340340
/// The recipient's public key used to sign invoices.
341341
pub fn node_id(&self) -> PublicKey {
342-
self.contents.node_id.unwrap()
342+
self.contents.node_id()
343343
}
344344

345345
///
@@ -424,6 +424,10 @@ impl OfferContents {
424424
self.quantity_min.is_some() || self.quantity_max.is_some()
425425
}
426426

427+
pub fn node_id(&self) -> PublicKey {
428+
self.node_id.unwrap()
429+
}
430+
427431
pub(super) fn as_tlv_stream(&self) -> reference::OfferTlvStream {
428432
let (currency, amount) = match &self.amount {
429433
None => (None, None),

lightning/src/offers/parse.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ pub enum SemanticError {
9898
InvalidQuantity,
9999
///
100100
MissingPayerId,
101+
///
102+
InvalidPayInfo,
103+
///
104+
MissingCreationTime,
105+
///
106+
MissingPaymentHash,
101107
}
102108

103109
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;
@@ -983,6 +984,22 @@ macro_rules! impl_consensus_ser {
983984
impl_consensus_ser!(Transaction);
984985
impl_consensus_ser!(TxOut);
985986

987+
impl Writeable for WitnessVersion {
988+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
989+
self.to_num().write(w)
990+
}
991+
}
992+
993+
impl Readable for WitnessVersion {
994+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
995+
let num: u8 = Readable::read(r)?;
996+
match WitnessVersion::try_from(num) {
997+
Ok(version) => Ok(version),
998+
Err(_) => Err(DecodeError::InvalidValue),
999+
}
1000+
}
1001+
}
1002+
9861003
impl<T: Readable> Readable for Mutex<T> {
9871004
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
9881005
let t: T = Readable::read(r)?;
@@ -1043,6 +1060,26 @@ impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B
10431060
}
10441061
}
10451062

1063+
impl<A: Readable, B: Readable, C: Readable, D: Readable, E: Readable> Readable for (A, B, C, D, E) {
1064+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
1065+
let a: A = Readable::read(r)?;
1066+
let b: B = Readable::read(r)?;
1067+
let c: C = Readable::read(r)?;
1068+
let d: D = Readable::read(r)?;
1069+
let e: E = Readable::read(r)?;
1070+
Ok((a, b, c, d, e))
1071+
}
1072+
}
1073+
impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable, E: Writeable> Writeable for (A, B, C, D, E) {
1074+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
1075+
self.0.write(w)?;
1076+
self.1.write(w)?;
1077+
self.2.write(w)?;
1078+
self.3.write(w)?;
1079+
self.4.write(w)
1080+
}
1081+
}
1082+
10461083
impl Writeable for () {
10471084
fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
10481085
Ok(())

0 commit comments

Comments
 (0)