Skip to content

Commit ce43cd9

Browse files
committed
WIP: Invoice encoding
1 parent 173d0ff commit ce43cd9

File tree

6 files changed

+303
-3
lines changed

6 files changed

+303
-3
lines changed

lightning/src/offers/invoice.rs

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

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::Signet) {
273+
Network::Signet
274+
} else if chain == ChainHash::using_genesis_block(Network::Regtest) {
275+
Network::Regtest
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
@@ -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
@@ -96,6 +96,12 @@ pub enum SemanticError {
9696
InvalidQuantity,
9797
///
9898
MissingPayerId,
99+
///
100+
InvalidPayInfo,
101+
///
102+
MissingCreationTime,
103+
///
104+
MissingPaymentHash,
99105
}
100106

101107
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)