Skip to content

Commit 3685b47

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent 221a2f3 commit 3685b47

File tree

3 files changed

+320
-18
lines changed

3 files changed

+320
-18
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 278 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,220 @@
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+
//! extern crate bitcoin;
21+
//! extern crate lightning;
22+
//!
23+
//! use bitcoin::network::constants::Network;
24+
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
25+
//! use lightning::ln::features::OfferFeatures;
26+
//! use lightning::offers::offer::Offer;
27+
//! use lightning::util::ser::Writeable;
28+
//!
29+
//! # fn parse() -> Result<(), lightning::offers::parse::ParseError> {
30+
//! let secp_ctx = Secp256k1::new();
31+
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
32+
//! let pubkey = PublicKey::from(keys);
33+
//! let mut buffer = Vec::new();
34+
//!
35+
//! // "offer to be paid" flow
36+
//! "lno1qcp4256ypq"
37+
//! .parse::<Offer>()?
38+
//! .request_invoice(pubkey)
39+
//! .metadata(vec![42; 64])
40+
//! .chain(Network::Testnet)?
41+
//! .amount_msats(1000)
42+
//! .quantity(5)?
43+
//! .payer_note("foo".to_string())
44+
//! .build()?
45+
//! .sign(|digest| secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))?
46+
//! .write(&mut buffer)
47+
//! .unwrap();
48+
//! # Ok(())
49+
//! # }
50+
//! ```
1151
1252
use bitcoin::blockdata::constants::ChainHash;
13-
use bitcoin::secp256k1::PublicKey;
53+
use bitcoin::network::constants::Network;
54+
use bitcoin::secp256k1::{Message, PublicKey, self};
1455
use bitcoin::secp256k1::schnorr::Signature;
1556
use core::convert::TryFrom;
1657
use crate::io;
1758
use crate::ln::features::OfferFeatures;
1859
use crate::ln::msgs::DecodeError;
19-
use crate::offers::merkle::{SignatureTlvStream, self};
20-
use crate::offers::offer::{Amount, OfferContents, OfferTlvStream};
60+
use crate::offers::merkle::{SignatureTlvStream, SignatureTlvStreamRef, self};
61+
use crate::offers::offer::{Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
2162
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
22-
use crate::offers::payer::{PayerContents, PayerTlvStream};
63+
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
2364
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
2465

2566
use crate::prelude::*;
2667

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

62260
/// A chain from [`Offer::chains`] that the offer is valid for.
63-
///
64-
/// [`Offer::chains`]: crate::offers::offer::Offer::chains
65261
pub fn chain(&self) -> ChainHash {
66-
self.contents.chain.unwrap_or_else(|| self.contents.offer.implied_chain())
262+
self.contents.chain()
67263
}
68264

69265
/// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
70266
/// must be greater than or equal to [`Offer::amount`], converted if necessary.
71267
///
72268
/// [`chain`]: Self::chain
73-
/// [`Offer::amount`]: crate::offers::offer::Offer::amount
74269
pub fn amount_msats(&self) -> Option<u64> {
75270
self.contents.amount_msats
76271
}
@@ -81,8 +276,6 @@ impl InvoiceRequest {
81276
}
82277

83278
/// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
84-
///
85-
/// [`Offer::is_valid_quantity`]: crate::offers::offer::Offer::is_valid_quantity
86279
pub fn quantity(&self) -> Option<u64> {
87280
self.contents.quantity
88281
}
@@ -105,12 +298,43 @@ impl InvoiceRequest {
105298
}
106299
}
107300

301+
impl InvoiceRequestContents {
302+
fn chain(&self) -> ChainHash {
303+
self.chain.unwrap_or_else(|| self.offer.implied_chain())
304+
}
305+
306+
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
307+
let payer = PayerTlvStreamRef {
308+
metadata: self.payer.0.as_ref(),
309+
};
310+
311+
let offer = self.offer.as_tlv_stream();
312+
313+
let invoice_request = InvoiceRequestTlvStreamRef {
314+
chain: self.chain.as_ref(),
315+
amount: self.amount_msats,
316+
features: self.features.as_ref(),
317+
quantity: self.quantity,
318+
payer_id: Some(&self.payer_id),
319+
payer_note: self.payer_note.as_ref(),
320+
};
321+
322+
(payer, offer, invoice_request)
323+
}
324+
}
325+
108326
impl Writeable for InvoiceRequest {
109327
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
110328
WithoutLength(&self.bytes).write(writer)
111329
}
112330
}
113331

332+
impl Writeable for InvoiceRequestContents {
333+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
334+
self.as_tlv_stream().write(writer)
335+
}
336+
}
337+
114338
impl TryFrom<Vec<u8>> for InvoiceRequest {
115339
type Error = ParseError;
116340

@@ -145,6 +369,12 @@ impl SeekReadable for FullInvoiceRequestTlvStream {
145369

146370
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
147371

372+
type PartialInvoiceRequestTlvStreamRef<'a> = (
373+
PayerTlvStreamRef<'a>,
374+
OfferTlvStreamRef<'a>,
375+
InvoiceRequestTlvStreamRef<'a>,
376+
);
377+
148378
impl TryFrom<ParsedMessage<FullInvoiceRequestTlvStream>> for InvoiceRequest {
149379
type Error = ParseError;
150380

@@ -161,8 +391,7 @@ impl TryFrom<ParsedMessage<FullInvoiceRequestTlvStream>> for InvoiceRequest {
161391
)?;
162392

163393
if let Some(signature) = &signature {
164-
let tag = concat!("lightning", "invoice_request", "signature");
165-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
394+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
166395
}
167396

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

0 commit comments

Comments
 (0)