Skip to content

Commit 062dd30

Browse files
committed
WIP: Invoice encoding
1 parent e411844 commit 062dd30

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::{HighZeroBytesDroppedBigSize, 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>, WithoutLength)),
93+
(162, payinfo: (Vec<BlindedPayInfo>, WithoutLength)),
94+
(164, created_at: (u64, HighZeroBytesDroppedBigSize)),
95+
(166, relative_expiry: (u32, HighZeroBytesDroppedBigSize)),
96+
(168, payment_hash: PaymentHash),
97+
(170, amount: (u64, HighZeroBytesDroppedBigSize)),
98+
(172, fallbacks: (Vec<FallbackAddress>, WithoutLength)),
99+
(174, features: OfferFeatures),
100+
(176, code: (String, WithoutLength)),
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
@@ -236,7 +236,7 @@ pub struct InvoiceRequest {
236236
#[derive(Clone, Debug)]
237237
pub(crate) struct InvoiceRequestContents {
238238
payer: PayerContents,
239-
offer: OfferContents,
239+
pub(super) offer: OfferContents,
240240
chain: Option<ChainHash>,
241241
amount_msats: Option<u64>,
242242
features: Option<OfferFeatures>,
@@ -305,6 +305,21 @@ impl InvoiceRequestContents {
305305
self.chain.unwrap_or_else(|| self.offer.implied_chain())
306306
}
307307

308+
pub fn network(&self) -> Network {
309+
let chain = self.chain();
310+
if chain == ChainHash::using_genesis_block(Network::Bitcoin) {
311+
Network::Bitcoin
312+
} else if chain == ChainHash::using_genesis_block(Network::Testnet) {
313+
Network::Testnet
314+
} else if chain == ChainHash::using_genesis_block(Network::Signet) {
315+
Network::Signet
316+
} else if chain == ChainHash::using_genesis_block(Network::Regtest) {
317+
Network::Regtest
318+
} else {
319+
unreachable!()
320+
}
321+
}
322+
308323
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
309324
let payer = PayerTlvStreamRef {
310325
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
@@ -337,7 +337,7 @@ impl Offer {
337337

338338
/// The public key used by the recipient to sign invoices.
339339
pub fn signing_pubkey(&self) -> PublicKey {
340-
self.contents.signing_pubkey.unwrap()
340+
self.contents.signing_pubkey()
341341
}
342342

343343
///
@@ -402,6 +402,10 @@ impl OfferContents {
402402
}
403403
}
404404

405+
pub fn signing_pubkey(&self) -> PublicKey {
406+
self.signing_pubkey.unwrap()
407+
}
408+
405409
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
406410
let (currency, amount) = match &self.amount {
407411
None => (None, None),

lightning/src/offers/parse.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ pub enum SemanticError {
108108
UnexpectedQuantity,
109109
/// A payer id was expected but was missing.
110110
MissingPayerId,
111+
///
112+
InvalidPayInfo,
113+
///
114+
MissingCreationTime,
115+
///
116+
MissingPaymentHash,
111117
}
112118

113119
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;
@@ -979,6 +980,22 @@ macro_rules! impl_consensus_ser {
979980
impl_consensus_ser!(Transaction);
980981
impl_consensus_ser!(TxOut);
981982

983+
impl Writeable for WitnessVersion {
984+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
985+
self.to_num().write(w)
986+
}
987+
}
988+
989+
impl Readable for WitnessVersion {
990+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
991+
let num: u8 = Readable::read(r)?;
992+
match WitnessVersion::try_from(num) {
993+
Ok(version) => Ok(version),
994+
Err(_) => Err(DecodeError::InvalidValue),
995+
}
996+
}
997+
}
998+
982999
impl<T: Readable> Readable for Mutex<T> {
9831000
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
9841001
let t: T = Readable::read(r)?;
@@ -1039,6 +1056,26 @@ impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B
10391056
}
10401057
}
10411058

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

0 commit comments

Comments
 (0)