Skip to content

Commit cd8cafb

Browse files
committed
WIP: Invoice encoding
1 parent b8601f4 commit cd8cafb

File tree

6 files changed

+306
-2
lines changed

6 files changed

+306
-2
lines changed

lightning/src/offers/invoice.rs

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

lightning/src/offers/invoice_request.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ pub struct InvoiceRequest {
240240
#[derive(Clone, Debug)]
241241
pub(crate) struct InvoiceRequestContents {
242242
payer: PayerContents,
243-
offer: OfferContents,
243+
pub(super) offer: OfferContents,
244244
chain: Option<ChainHash>,
245245
amount_msats: Option<u64>,
246246
features: Option<OfferFeatures>,
@@ -304,6 +304,21 @@ impl InvoiceRequestContents {
304304
self.chain.unwrap_or_else(|| self.offer.implied_chain())
305305
}
306306

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

359359
/// The public key used by the recipient to sign invoices.
360360
pub fn signing_pubkey(&self) -> PublicKey {
361-
self.contents.signing_pubkey.unwrap()
361+
self.contents.signing_pubkey()
362362
}
363363

364364
///
@@ -429,6 +429,10 @@ impl OfferContents {
429429
}
430430
}
431431

432+
pub fn signing_pubkey(&self) -> PublicKey {
433+
self.signing_pubkey.unwrap()
434+
}
435+
432436
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
433437
let (currency, amount) = match &self.amount {
434438
None => (None, None),

lightning/src/offers/parse.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ pub enum SemanticError {
132132
UnexpectedQuantity,
133133
/// A payer id was expected but was missing.
134134
MissingPayerId,
135+
///
136+
InvalidPayInfo,
137+
///
138+
MissingCreationTime,
139+
///
140+
MissingPaymentHash,
135141
}
136142

137143
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 crate::ln::msgs::DecodeError;
@@ -970,6 +971,22 @@ macro_rules! impl_consensus_ser {
970971
impl_consensus_ser!(Transaction);
971972
impl_consensus_ser!(TxOut);
972973

974+
impl Writeable for WitnessVersion {
975+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
976+
self.to_num().write(w)
977+
}
978+
}
979+
980+
impl Readable for WitnessVersion {
981+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
982+
let num: u8 = Readable::read(r)?;
983+
match WitnessVersion::try_from(num) {
984+
Ok(version) => Ok(version),
985+
Err(_) => Err(DecodeError::InvalidValue),
986+
}
987+
}
988+
}
989+
973990
impl<T: Readable> Readable for Mutex<T> {
974991
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
975992
let t: T = Readable::read(r)?;
@@ -1030,6 +1047,26 @@ impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B
10301047
}
10311048
}
10321049

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

0 commit comments

Comments
 (0)