Skip to content

Commit 221a2f3

Browse files
committed
Invoice request raw byte encoding and decoding
When reading an offer, an `invoice_request` message is sent over the wire. Implement Writeable for encoding the message and TryFrom for decoding it by defining in terms of TLV streams. These streams represent content for the payer metadata (0), reflected `offer` (1-79), `invoice_request` (80-159), and signature (240).
1 parent 0f60c81 commit 221a2f3

File tree

7 files changed

+260
-6
lines changed

7 files changed

+260
-6
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@
1212
use bitcoin::blockdata::constants::ChainHash;
1313
use bitcoin::secp256k1::PublicKey;
1414
use bitcoin::secp256k1::schnorr::Signature;
15+
use core::convert::TryFrom;
16+
use crate::io;
1517
use crate::ln::features::OfferFeatures;
16-
use crate::offers::offer::OfferContents;
17-
use crate::offers::payer::PayerContents;
18+
use crate::ln::msgs::DecodeError;
19+
use crate::offers::merkle::{SignatureTlvStream, self};
20+
use crate::offers::offer::{Amount, OfferContents, OfferTlvStream};
21+
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
22+
use crate::offers::payer::{PayerContents, PayerTlvStream};
23+
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
1824

1925
use crate::prelude::*;
2026

@@ -74,9 +80,9 @@ impl InvoiceRequest {
7480
self.contents.features.as_ref()
7581
}
7682

77-
/// The quantity of the offer's item conforming to [`Offer::supported_quantity`].
83+
/// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
7884
///
79-
/// [`Offer::supported_quantity`]: crate::offers::offer::Offer::supported_quantity
85+
/// [`Offer::is_valid_quantity`]: crate::offers::offer::Offer::is_valid_quantity
8086
pub fn quantity(&self) -> Option<u64> {
8187
self.contents.quantity
8288
}
@@ -98,3 +104,126 @@ impl InvoiceRequest {
98104
self.signature
99105
}
100106
}
107+
108+
impl Writeable for InvoiceRequest {
109+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
110+
WithoutLength(&self.bytes).write(writer)
111+
}
112+
}
113+
114+
impl TryFrom<Vec<u8>> for InvoiceRequest {
115+
type Error = ParseError;
116+
117+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
118+
let parsed_invoice_request = ParsedMessage::<FullInvoiceRequestTlvStream>::try_from(bytes)?;
119+
InvoiceRequest::try_from(parsed_invoice_request)
120+
}
121+
}
122+
123+
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
124+
(80, chain: ChainHash),
125+
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
126+
(84, features: OfferFeatures),
127+
(86, quantity: (u64, HighZeroBytesDroppedBigSize)),
128+
(88, payer_id: PublicKey),
129+
(89, payer_note: (String, WithoutLength)),
130+
});
131+
132+
type FullInvoiceRequestTlvStream =
133+
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
134+
135+
impl SeekReadable for FullInvoiceRequestTlvStream {
136+
fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
137+
let payer = SeekReadable::read(r)?;
138+
let offer = SeekReadable::read(r)?;
139+
let invoice_request = SeekReadable::read(r)?;
140+
let signature = SeekReadable::read(r)?;
141+
142+
Ok((payer, offer, invoice_request, signature))
143+
}
144+
}
145+
146+
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
147+
148+
impl TryFrom<ParsedMessage<FullInvoiceRequestTlvStream>> for InvoiceRequest {
149+
type Error = ParseError;
150+
151+
fn try_from(invoice_request: ParsedMessage<FullInvoiceRequestTlvStream>)
152+
-> Result<Self, Self::Error>
153+
{
154+
let ParsedMessage {bytes, tlv_stream } = invoice_request;
155+
let (
156+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
157+
SignatureTlvStream { signature },
158+
) = tlv_stream;
159+
let contents = InvoiceRequestContents::try_from(
160+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
161+
)?;
162+
163+
if let Some(signature) = &signature {
164+
let tag = concat!("lightning", "invoice_request", "signature");
165+
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
166+
}
167+
168+
Ok(InvoiceRequest { bytes, contents, signature })
169+
}
170+
}
171+
172+
impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
173+
type Error = SemanticError;
174+
175+
fn try_from(tlv_stream: PartialInvoiceRequestTlvStream) -> Result<Self, Self::Error> {
176+
let (
177+
PayerTlvStream { metadata },
178+
offer_tlv_stream,
179+
InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note },
180+
) = tlv_stream;
181+
182+
let payer = PayerContents(metadata);
183+
let offer = OfferContents::try_from(offer_tlv_stream)?;
184+
185+
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
186+
return Err(SemanticError::UnsupportedChain);
187+
}
188+
189+
let amount_msats = match (offer.amount(), amount) {
190+
(Some(_), None) => return Err(SemanticError::MissingAmount),
191+
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnsupportedCurrency),
192+
(_, amount_msats) => amount_msats,
193+
};
194+
195+
if let Some(features) = &features {
196+
if features.requires_unknown_bits() {
197+
return Err(SemanticError::UnknownRequiredFeatures);
198+
}
199+
}
200+
201+
let expects_quantity = offer.expects_quantity();
202+
let quantity = match quantity {
203+
None if expects_quantity => return Err(SemanticError::MissingQuantity),
204+
Some(_) if !expects_quantity => return Err(SemanticError::UnexpectedQuantity),
205+
Some(quantity) if !offer.is_valid_quantity(quantity) => {
206+
return Err(SemanticError::InvalidQuantity);
207+
}
208+
quantity => quantity,
209+
};
210+
211+
{
212+
let amount_msats = amount_msats.unwrap_or(offer.amount_msats());
213+
let quantity = quantity.unwrap_or(1);
214+
if amount_msats < offer.expected_invoice_amount_msats(quantity) {
215+
return Err(SemanticError::InsufficientAmount);
216+
}
217+
}
218+
219+
220+
let payer_id = match payer_id {
221+
None => return Err(SemanticError::MissingPayerId),
222+
Some(payer_id) => payer_id,
223+
};
224+
225+
Ok(InvoiceRequestContents {
226+
payer, offer, chain, amount_msats, features, quantity, payer_id, payer_note,
227+
})
228+
}
229+
}

lightning/src/offers/merkle.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//! Tagged hashes for use in signature calculation and verification.
1111

1212
use bitcoin::hashes::{Hash, HashEngine, sha256};
13+
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
14+
use bitcoin::secp256k1::schnorr::Signature;
1315
use crate::io;
1416
use crate::util::ser::{BigSize, Readable};
1517

@@ -18,6 +20,23 @@ use crate::prelude::*;
1820
/// Valid type range for signature TLV records.
1921
const SIGNATURE_TYPES: core::ops::RangeInclusive<u64> = 240..=1000;
2022

23+
tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, {
24+
(240, signature: Signature),
25+
});
26+
27+
/// Verifies the signature with a pubkey over the given bytes using a tagged hash as the message
28+
/// digest.
29+
pub(super) fn verify_signature(
30+
signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
31+
) -> Result<(), secp256k1::Error> {
32+
let tag = sha256::Hash::hash(tag.as_bytes());
33+
let merkle_root = root_hash(bytes);
34+
let digest = Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap();
35+
let pubkey = pubkey.into();
36+
let secp_ctx = Secp256k1::verification_only();
37+
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
38+
}
39+
2140
/// Computes a merkle root hash for the given data, which must be a well-formed TLV stream
2241
/// containing at least one TLV record.
2342
fn root_hash(data: &[u8]) -> sha256::Hash {

lightning/src/offers/offer.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ impl Offer {
269269
self.contents.chains()
270270
}
271271

272+
/// Returns whether the given chain is supported by the offer.
273+
pub fn supports_chain(&self, chain: ChainHash) -> bool {
274+
self.contents.supports_chain(chain)
275+
}
276+
272277
// TODO: Link to corresponding method in `InvoiceRequest`.
273278
/// Opaque bytes set by the originator. Useful for authentication and validating fields since it
274279
/// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
@@ -278,7 +283,7 @@ impl Offer {
278283

279284
/// The minimum amount required for a successful payment of a single item.
280285
pub fn amount(&self) -> Option<&Amount> {
281-
self.contents.amount.as_ref()
286+
self.contents.amount()
282287
}
283288

284289
/// A complete description of the purpose of the payment. Intended to be displayed to the user
@@ -328,6 +333,18 @@ impl Offer {
328333
self.contents.supported_quantity()
329334
}
330335

336+
/// Returns whether the given quantity is valid for the offer.
337+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
338+
self.contents.is_valid_quantity(quantity)
339+
}
340+
341+
/// Returns whether a quantity is expected in an [`InvoiceRequest`] for the offer.
342+
///
343+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
344+
pub fn expects_quantity(&self) -> bool {
345+
self.contents.expects_quantity()
346+
}
347+
331348
/// The public key used by the recipient to sign invoices.
332349
pub fn signing_pubkey(&self) -> PublicKey {
333350
self.contents.signing_pubkey.unwrap()
@@ -354,10 +371,48 @@ impl OfferContents {
354371
ChainHash::using_genesis_block(Network::Bitcoin)
355372
}
356373

374+
pub fn supports_chain(&self, chain: ChainHash) -> bool {
375+
self.chains().contains(&chain)
376+
}
377+
378+
pub fn amount(&self) -> Option<&Amount> {
379+
self.amount.as_ref()
380+
}
381+
382+
pub fn amount_msats(&self) -> u64 {
383+
match self.amount() {
384+
None => 0,
385+
Some(&Amount::Bitcoin { amount_msats }) => amount_msats,
386+
Some(&Amount::Currency { .. }) => unreachable!(),
387+
}
388+
}
389+
390+
pub fn expected_invoice_amount_msats(&self, quantity: u64) -> u64 {
391+
self.amount_msats() * quantity
392+
}
393+
357394
pub fn supported_quantity(&self) -> Quantity {
358395
self.supported_quantity
359396
}
360397

398+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
399+
match self.supported_quantity {
400+
Quantity::Bounded(n) => {
401+
let n = n.get();
402+
if n == 1 { false }
403+
else { quantity > 0 && quantity <= n }
404+
},
405+
Quantity::Unbounded => quantity > 0,
406+
}
407+
}
408+
409+
pub fn expects_quantity(&self) -> bool {
410+
match self.supported_quantity {
411+
Quantity::Bounded(n) => n.get() != 1,
412+
Quantity::Unbounded => true,
413+
}
414+
}
415+
361416
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
362417
let (currency, amount) = match &self.amount {
363418
None => (None, None),
@@ -587,6 +642,7 @@ mod tests {
587642

588643
assert_eq!(offer.bytes, buffer.as_slice());
589644
assert_eq!(offer.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
645+
assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin)));
590646
assert_eq!(offer.metadata(), None);
591647
assert_eq!(offer.amount(), None);
592648
assert_eq!(offer.description(), PrintableString("foo"));
@@ -625,13 +681,15 @@ mod tests {
625681
.chain(Network::Bitcoin)
626682
.build()
627683
.unwrap();
684+
assert!(offer.supports_chain(mainnet));
628685
assert_eq!(offer.chains(), vec![mainnet]);
629686
assert_eq!(offer.as_tlv_stream().chains, None);
630687

631688
let offer = OfferBuilder::new("foo".into(), pubkey(42))
632689
.chain(Network::Testnet)
633690
.build()
634691
.unwrap();
692+
assert!(offer.supports_chain(testnet));
635693
assert_eq!(offer.chains(), vec![testnet]);
636694
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
637695

@@ -640,6 +698,7 @@ mod tests {
640698
.chain(Network::Testnet)
641699
.build()
642700
.unwrap();
701+
assert!(offer.supports_chain(testnet));
643702
assert_eq!(offer.chains(), vec![testnet]);
644703
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
645704

@@ -648,6 +707,8 @@ mod tests {
648707
.chain(Network::Testnet)
649708
.build()
650709
.unwrap();
710+
assert!(offer.supports_chain(mainnet));
711+
assert!(offer.supports_chain(testnet));
651712
assert_eq!(offer.chains(), vec![mainnet, testnet]);
652713
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![mainnet, testnet]));
653714
}

lightning/src/offers/parse.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use bitcoin::bech32;
1313
use bitcoin::bech32::{FromBase32, ToBase32};
14+
use bitcoin::secp256k1;
1415
use core::convert::TryFrom;
1516
use core::fmt;
1617
use crate::io;
@@ -92,25 +93,39 @@ pub enum ParseError {
9293
Decode(DecodeError),
9394
/// The parsed message has invalid semantics.
9495
InvalidSemantics(SemanticError),
96+
/// The parsed message has an invalid signature.
97+
InvalidSignature(secp256k1::Error),
9598
}
9699

97100
/// Error when interpreting a TLV stream as a specific type.
98101
#[derive(Debug, PartialEq)]
99102
pub enum SemanticError {
103+
/// The provided chain hash does not correspond to a supported chain.
104+
UnsupportedChain,
100105
/// An amount was expected but was missing.
101106
MissingAmount,
102107
/// An amount exceeded the maximum number of bitcoin.
103108
InvalidAmount,
109+
/// An amount was provided but was not sufficient in value.
110+
InsufficientAmount,
104111
/// A currency was provided that is not supported.
105112
UnsupportedCurrency,
113+
/// A feature was required but is unknown.
114+
UnknownRequiredFeatures,
106115
/// A required description was not provided.
107116
MissingDescription,
108117
/// A node id was not provided.
109118
MissingNodeId,
110119
/// An empty set of blinded paths was provided.
111120
MissingPaths,
121+
/// A quantity was not provided.
122+
MissingQuantity,
112123
/// An unsupported quantity was provided.
113124
InvalidQuantity,
125+
/// A quantity or quantity bounds was provided but was not expected.
126+
UnexpectedQuantity,
127+
/// A payer id was expected but was missing.
128+
MissingPayerId,
114129
}
115130

116131
impl From<bech32::Error> for ParseError {
@@ -130,3 +145,9 @@ impl From<SemanticError> for ParseError {
130145
Self::InvalidSemantics(error)
131146
}
132147
}
148+
149+
impl From<secp256k1::Error> for ParseError {
150+
fn from(error: secp256k1::Error) -> Self {
151+
Self::InvalidSignature(error)
152+
}
153+
}

lightning/src/offers/payer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
//! Data structures and encoding for `invoice_request_metadata` records.
1111

12+
use crate::util::ser::WithoutLength;
13+
1214
use crate::prelude::*;
1315

1416
/// An unpredictable sequence of bytes typically containing information needed to derive
@@ -17,3 +19,7 @@ use crate::prelude::*;
1719
/// [`InvoiceRequestContents::payer_id`]: invoice_request::InvoiceRequestContents::payer_id
1820
#[derive(Clone, Debug)]
1921
pub(crate) struct PayerContents(pub Option<Vec<u8>>);
22+
23+
tlv_stream!(PayerTlvStream, PayerTlvStreamRef, 0..1, {
24+
(0, metadata: (Vec<u8>, WithoutLength)),
25+
});

0 commit comments

Comments
 (0)