Skip to content

Commit 40cf6a5

Browse files
committed
Builder for creating invoice requests
Add a builder for creating invoice requests for an offer given a payer_id. Other settings may be optional depending on the offer and duplicative settings will override previous settings. Building produces a semantically valid `invoice_request` message for the offer, which then can be signed for the payer_id.
1 parent bebe2d6 commit 40cf6a5

File tree

4 files changed

+336
-18
lines changed

4 files changed

+336
-18
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 288 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,227 @@
88
// licenses.
99

1010
//! Data structures and encoding for `invoice_request` messages.
11+
//!
12+
//! An [`InvoiceRequest`] can be either built from a parsed [`Offer`] as an "offer to be paid" or
13+
//! built directly as an "offer for money" (e.g., refund, ATM withdrawal). In the former case, it is
14+
//! typically constructed by a customer and sent to the merchant who had published the corresponding
15+
//! offer. In the latter case, an offer doesn't exist as a precursor to the request. Rather the
16+
//! merchant would typically construct the invoice request and presents it to the customer.
17+
//!
18+
//! The recipient of the request responds with an `Invoice`.
19+
//!
20+
//! ```ignore
21+
//! extern crate bitcoin;
22+
//! extern crate lightning;
23+
//!
24+
//! use bitcoin::network::constants::Network;
25+
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
26+
//! use lightning::ln::features::OfferFeatures;
27+
//! use lightning::offers::offer::Offer;
28+
//! use lightning::util::ser::Writeable;
29+
//!
30+
//! # fn parse() -> Result<(), lightning::offers::parse::ParseError> {
31+
//! let secp_ctx = Secp256k1::new();
32+
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
33+
//! let pubkey = PublicKey::from(keys);
34+
//! let mut buffer = Vec::new();
35+
//!
36+
//! // "offer to be paid" flow
37+
//! "lno1qcp4256ypq"
38+
//! .parse::<Offer>()?
39+
//! .request_invoice(pubkey)
40+
//! .metadata(vec![42; 64])
41+
//! .chain(Network::Testnet)
42+
//! .amount_msats(1000)
43+
//! .quantity(5)
44+
//! .payer_note("foo".to_string())
45+
//! .build()?
46+
//! .sign(|digest| secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))?
47+
//! .write(&mut buffer)
48+
//! .unwrap();
49+
//! # Ok(())
50+
//! # }
51+
//! ```
1152
1253
use bitcoin::blockdata::constants::ChainHash;
13-
use bitcoin::secp256k1::PublicKey;
54+
use bitcoin::network::constants::Network;
55+
use bitcoin::secp256k1::{Message, PublicKey, self};
1456
use bitcoin::secp256k1::schnorr::Signature;
1557
use core::convert::TryFrom;
1658
use crate::io;
1759
use crate::ln::features::InvoiceRequestFeatures;
1860
use crate::ln::msgs::DecodeError;
19-
use crate::offers::merkle::{SignatureTlvStream, self};
20-
use crate::offers::offer::{Amount, OfferContents, OfferTlvStream};
61+
use crate::offers::merkle::{SignatureTlvStream, SignatureTlvStreamRef, self};
62+
use crate::offers::offer::{Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
2163
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
22-
use crate::offers::payer::{PayerContents, PayerTlvStream};
64+
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
2365
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
2466
use crate::util::string::PrintableString;
2567

2668
use crate::prelude::*;
2769

70+
const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
71+
72+
/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow.
73+
///
74+
/// See [module-level documentation] for usage.
75+
///
76+
/// [module-level documentation]: self
77+
pub struct InvoiceRequestBuilder<'a> {
78+
offer: &'a Offer,
79+
invoice_request: InvoiceRequestContents,
80+
}
81+
82+
impl<'a> InvoiceRequestBuilder<'a> {
83+
pub(super) fn new(offer: &'a Offer, payer_id: PublicKey) -> Self {
84+
Self {
85+
offer,
86+
invoice_request: InvoiceRequestContents {
87+
payer: PayerContents(None), offer: offer.contents.clone(), chain: None,
88+
amount_msats: None, features: InvoiceRequestFeatures::empty(), quantity: None,
89+
payer_id, payer_note: None,
90+
},
91+
}
92+
}
93+
94+
/// Sets the metadata for the invoice request. Useful for containing information about the
95+
/// derivation of [`InvoiceRequest::payer_id`]. This should not leak any information such as
96+
/// using a simple BIP-32 derivation path.
97+
///
98+
/// Successive calls to this method will override the previous setting.
99+
pub fn metadata(mut self, metadata: Vec<u8>) -> Self {
100+
self.invoice_request.payer = PayerContents(Some(metadata));
101+
self
102+
}
103+
104+
/// Sets the chain hash of the given [`Network`] for paying an invoice. If not called,
105+
/// [`Network::Bitcoin`] is assumed. Must be supported by the offer.
106+
///
107+
/// Successive calls to this method will override the previous setting.
108+
pub fn chain(mut self, network: Network) -> Self {
109+
self.invoice_request.chain = Some(ChainHash::using_genesis_block(network));
110+
self
111+
}
112+
113+
/// Sets the amount for paying an invoice. Must be at least the base invoice amount (i.e.,
114+
/// [`Offer::amount`] times [`quantity`]).
115+
///
116+
/// Successive calls to this method will override the previous setting.
117+
///
118+
/// [`quantity`]: Self::quantity
119+
pub fn amount_msats(mut self, amount_msats: u64) -> Self {
120+
self.invoice_request.amount_msats = Some(amount_msats);
121+
self
122+
}
123+
124+
/// Sets the features for the invoice request.
125+
///
126+
/// Successive calls to this method will override the previous setting.
127+
#[cfg(test)]
128+
pub fn features(mut self, features: InvoiceRequestFeatures) -> Self {
129+
self.invoice_request.features = features;
130+
self
131+
}
132+
133+
/// Sets a quantity of items for the invoice request. If not set, `1` is assumed. Must conform
134+
/// to [`Offer::is_valid_quantity`].
135+
///
136+
/// Successive calls to this method will override the previous setting.
137+
pub fn quantity(mut self, quantity: u64) -> Self {
138+
self.invoice_request.quantity = Some(quantity);
139+
self
140+
}
141+
142+
/// Sets a note for the invoice request.
143+
///
144+
/// Successive calls to this method will override the previous setting.
145+
pub fn payer_note(mut self, payer_note: String) -> Self {
146+
self.invoice_request.payer_note = Some(payer_note);
147+
self
148+
}
149+
150+
/// Builds an [`InvoiceRequest`] after checking for valid semantics.
151+
pub fn build(mut self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
152+
let chain = self.invoice_request.chain();
153+
if !self.offer.supports_chain(chain) {
154+
return Err(SemanticError::UnsupportedChain);
155+
}
156+
157+
if chain == self.offer.implied_chain() {
158+
self.invoice_request.chain = None;
159+
}
160+
161+
if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() {
162+
return Err(SemanticError::MissingAmount);
163+
}
164+
165+
if let Some(Amount::Currency { .. }) = self.offer.amount() {
166+
return Err(SemanticError::UnsupportedCurrency);
167+
}
168+
169+
let expects_quantity = self.offer.expects_quantity();
170+
match self.invoice_request.quantity {
171+
None if expects_quantity => return Err(SemanticError::MissingQuantity),
172+
Some(_) if !expects_quantity => return Err(SemanticError::UnexpectedQuantity),
173+
Some(quantity) if !self.offer.is_valid_quantity(quantity) => {
174+
return Err(SemanticError::InvalidQuantity);
175+
},
176+
_ => {},
177+
}
178+
179+
if self.offer.features().requires_unknown_bits() {
180+
return Err(SemanticError::UnknownRequiredFeatures);
181+
}
182+
183+
let amount_msats = self.invoice_request.amount_msats.unwrap_or(self.offer.amount_msats());
184+
let quantity = self.invoice_request.quantity.unwrap_or(1);
185+
if amount_msats < self.offer.expected_invoice_amount_msats(quantity) {
186+
return Err(SemanticError::InsufficientAmount);
187+
}
188+
189+
let InvoiceRequestBuilder { offer, invoice_request } = self;
190+
Ok(UnsignedInvoiceRequest { offer, invoice_request })
191+
}
192+
}
193+
194+
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
195+
pub struct UnsignedInvoiceRequest<'a> {
196+
offer: &'a Offer,
197+
invoice_request: InvoiceRequestContents,
198+
}
199+
200+
impl<'a> UnsignedInvoiceRequest<'a> {
201+
/// Signs the invoice request using the given function.
202+
pub fn sign<F>(self, sign: F) -> Result<InvoiceRequest, secp256k1::Error>
203+
where F: FnOnce(&Message) -> Signature
204+
{
205+
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
206+
// unknown TLV records, which are not stored in `OfferContents`.
207+
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
208+
self.invoice_request.as_tlv_stream();
209+
let offer_bytes = WithoutLength(&self.offer.bytes);
210+
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
211+
212+
let mut bytes = Vec::new();
213+
unsigned_tlv_stream.write(&mut bytes).unwrap();
214+
215+
let pubkey = self.invoice_request.payer_id;
216+
let signature = Some(merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?);
217+
218+
// Append the signature TLV record to the bytes.
219+
let signature_tlv_stream = SignatureTlvStreamRef {
220+
signature: signature.as_ref(),
221+
};
222+
signature_tlv_stream.write(&mut bytes).unwrap();
223+
224+
Ok(InvoiceRequest {
225+
bytes,
226+
contents: self.invoice_request,
227+
signature,
228+
})
229+
}
230+
}
231+
28232
/// An `InvoiceRequest` is a request for an `Invoice` formulated from an [`Offer`].
29233
///
30234
/// An offer may provided choices such as quantity, amount, chain, features, etc. An invoice request
@@ -61,17 +265,14 @@ impl InvoiceRequest {
61265
}
62266

63267
/// A chain from [`Offer::chains`] that the offer is valid for.
64-
///
65-
/// [`Offer::chains`]: crate::offers::offer::Offer::chains
66268
pub fn chain(&self) -> ChainHash {
67-
self.contents.chain.unwrap_or_else(|| self.contents.offer.implied_chain())
269+
self.contents.chain()
68270
}
69271

70272
/// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
71273
/// must be greater than or equal to [`Offer::amount`], converted if necessary.
72274
///
73275
/// [`chain`]: Self::chain
74-
/// [`Offer::amount`]: crate::offers::offer::Offer::amount
75276
pub fn amount_msats(&self) -> Option<u64> {
76277
self.contents.amount_msats
77278
}
@@ -82,8 +283,6 @@ impl InvoiceRequest {
82283
}
83284

84285
/// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
85-
///
86-
/// [`Offer::is_valid_quantity`]: crate::offers::offer::Offer::is_valid_quantity
87286
pub fn quantity(&self) -> Option<u64> {
88287
self.contents.quantity
89288
}
@@ -106,12 +305,48 @@ impl InvoiceRequest {
106305
}
107306
}
108307

308+
impl InvoiceRequestContents {
309+
fn chain(&self) -> ChainHash {
310+
self.chain.unwrap_or_else(|| self.offer.implied_chain())
311+
}
312+
313+
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
314+
let payer = PayerTlvStreamRef {
315+
metadata: self.payer.0.as_ref(),
316+
};
317+
318+
let offer = self.offer.as_tlv_stream();
319+
320+
let features = {
321+
if self.features == InvoiceRequestFeatures::empty() { None }
322+
else { Some(&self.features) }
323+
};
324+
325+
let invoice_request = InvoiceRequestTlvStreamRef {
326+
chain: self.chain.as_ref(),
327+
amount: self.amount_msats,
328+
features,
329+
quantity: self.quantity,
330+
payer_id: Some(&self.payer_id),
331+
payer_note: self.payer_note.as_ref(),
332+
};
333+
334+
(payer, offer, invoice_request)
335+
}
336+
}
337+
109338
impl Writeable for InvoiceRequest {
110339
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
111340
WithoutLength(&self.bytes).write(writer)
112341
}
113342
}
114343

344+
impl Writeable for InvoiceRequestContents {
345+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
346+
self.as_tlv_stream().write(writer)
347+
}
348+
}
349+
115350
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
116351
(80, chain: ChainHash),
117352
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
@@ -137,6 +372,12 @@ impl SeekReadable for FullInvoiceRequestTlvStream {
137372

138373
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
139374

375+
type PartialInvoiceRequestTlvStreamRef<'a> = (
376+
PayerTlvStreamRef<'a>,
377+
OfferTlvStreamRef<'a>,
378+
InvoiceRequestTlvStreamRef<'a>,
379+
);
380+
140381
impl TryFrom<Vec<u8>> for InvoiceRequest {
141382
type Error = ParseError;
142383

@@ -152,8 +393,7 @@ impl TryFrom<Vec<u8>> for InvoiceRequest {
152393
)?;
153394

154395
if let Some(signature) = &signature {
155-
let tag = concat!("lightning", "invoice_request", "signature");
156-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
396+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
157397
}
158398

159399
Ok(InvoiceRequest { bytes, contents, signature })
@@ -213,3 +453,39 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
213453
})
214454
}
215455
}
456+
457+
#[cfg(test)]
458+
mod tests {
459+
use super::InvoiceRequest;
460+
461+
use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey};
462+
use core::convert::TryFrom;
463+
use crate::ln::msgs::DecodeError;
464+
use crate::offers::offer::OfferBuilder;
465+
use crate::offers::parse::ParseError;
466+
use crate::util::ser::{BigSize, Writeable};
467+
468+
#[test]
469+
fn fails_parsing_invoice_request_with_extra_tlv_records() {
470+
let secp_ctx = Secp256k1::new();
471+
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
472+
let invoice_request = OfferBuilder::new("foo".into(), keys.public_key())
473+
.amount_msats(1000)
474+
.build()
475+
.unwrap()
476+
.request_invoice(keys.public_key())
477+
.build().unwrap()
478+
.sign(|digest| secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)).unwrap();
479+
480+
let mut encoded_invoice_request = Vec::new();
481+
invoice_request.write(&mut encoded_invoice_request).unwrap();
482+
BigSize(1002).write(&mut encoded_invoice_request).unwrap();
483+
BigSize(32).write(&mut encoded_invoice_request).unwrap();
484+
[42u8; 32].write(&mut encoded_invoice_request).unwrap();
485+
486+
match InvoiceRequest::try_from(encoded_invoice_request) {
487+
Ok(_) => panic!("expected error"),
488+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
489+
}
490+
}
491+
}

0 commit comments

Comments
 (0)