Skip to content

Commit 21684be

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent f73b923 commit 21684be

File tree

3 files changed

+274
-13
lines changed

3 files changed

+274
-13
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 237 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,215 @@
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 io;
1758
use ln::features::OfferFeatures;
18-
use offers::merkle::{SignatureTlvStream, self};
19-
use offers::offer::{Amount, OfferContents, OfferTlvStream};
59+
use offers::merkle::{SignatureTlvStream, SignatureTlvStreamRef, self};
60+
use offers::offer::{Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
2061
use offers::parse::{ParseError, SemanticError};
21-
use offers::payer::{PayerContents, PayerTlvStream};
62+
use offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
2263
use util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
2364

2465
use prelude::*;
2566

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

68262
/// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
@@ -104,12 +298,43 @@ impl InvoiceRequest {
104298
}
105299
}
106300

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+
107326
impl Writeable for InvoiceRequest {
108327
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
109328
WithoutLength(&self.bytes).write(writer)
110329
}
111330
}
112331

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+
113338
impl TryFrom<Vec<u8>> for InvoiceRequest {
114339
type Error = ParseError;
115340

@@ -135,6 +360,12 @@ type FullInvoiceRequestTlvStream =
135360

136361
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
137362

363+
type PartialInvoiceRequestTlvStreamRef<'a> = (
364+
PayerTlvStreamRef<'a>,
365+
OfferTlvStreamRef<'a>,
366+
InvoiceRequestTlvStreamRef<'a>,
367+
);
368+
138369
impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
139370
type Error = ParseError;
140371

@@ -149,8 +380,7 @@ impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
149380
)?;
150381

151382
if let Some(signature) = &signature {
152-
let tag = concat!("lightning", "invoice_request", "signature");
153-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
383+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
154384
}
155385

156386
Ok(InvoiceRequest { bytes, contents, signature })

lightning/src/offers/merkle.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,39 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, {
2323
(240, signature: Signature),
2424
});
2525

26+
pub(super) fn sign_message<F>(
27+
sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey,
28+
) -> Result<Signature, secp256k1::Error>
29+
where
30+
F: FnOnce(&Message) -> Signature
31+
{
32+
let digest = message_digest(tag, bytes);
33+
let signature = sign(&digest);
34+
35+
let pubkey = pubkey.into();
36+
let secp_ctx = Secp256k1::verification_only();
37+
secp_ctx.verify_schnorr(&signature, &digest, &pubkey)?;
38+
39+
Ok(signature)
40+
}
41+
2642
/// Verifies the signature with a pubkey over the given bytes using a tagged hash as the message
2743
/// digest.
2844
pub(super) fn verify_signature(
2945
signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
3046
) -> Result<(), secp256k1::Error> {
31-
let tag = sha256::Hash::hash(tag.as_bytes());
32-
let merkle_root = root_hash(bytes);
33-
let digest = Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap();
47+
let digest = message_digest(tag, bytes);
3448
let pubkey = pubkey.into();
3549
let secp_ctx = Secp256k1::verification_only();
3650
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
3751
}
3852

53+
fn message_digest(tag: &str, bytes: &[u8]) -> Message {
54+
let tag = sha256::Hash::hash(tag.as_bytes());
55+
let merkle_root = root_hash(bytes);
56+
Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap()
57+
}
58+
3959
/// Computes a merkle root hash for the given data, which must be a well-formed TLV stream
4060
/// containing at least one TLV record.
4161
fn root_hash(data: &[u8]) -> sha256::Hash {

lightning/src/offers/offer.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ use core::time::Duration;
7676
use io;
7777
use ln::features::OfferFeatures;
7878
use ln::msgs::MAX_VALUE_MSAT;
79+
use offers::invoice_request::InvoiceRequestBuilder;
7980
use offers::parse::{Bech32Encode, ParseError, SemanticError};
8081
use onion_message::BlindedPath;
8182
use util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
@@ -218,8 +219,8 @@ impl OfferBuilder {
218219
pub struct Offer {
219220
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
220221
// fields.
221-
bytes: Vec<u8>,
222-
contents: OfferContents,
222+
pub(super) bytes: Vec<u8>,
223+
pub(super) contents: OfferContents,
223224
}
224225

225226
/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
@@ -265,6 +266,11 @@ impl Offer {
265266
self.contents.amount()
266267
}
267268

269+
/// The minimum amount in msats required for a successful payment.
270+
pub fn amount_msats(&self) -> u64 {
271+
self.contents.amount_msats()
272+
}
273+
268274
/// Returns the minimum amount in msats required for the given quantity.
269275
pub fn expected_invoice_amount_msats(&self, quantity: u64) -> u64 {
270276
self.contents.expected_invoice_amount_msats(quantity)
@@ -334,6 +340,11 @@ impl Offer {
334340
self.contents.signing_pubkey.unwrap()
335341
}
336342

343+
///
344+
pub fn request_invoice(&self, payer_id: PublicKey) -> InvoiceRequestBuilder {
345+
InvoiceRequestBuilder::new(self, payer_id)
346+
}
347+
337348
#[cfg(test)]
338349
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
339350
self.contents.as_tlv_stream()
@@ -391,7 +402,7 @@ impl OfferContents {
391402
}
392403
}
393404

394-
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
405+
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
395406
let (currency, amount) = match &self.amount {
396407
None => (None, None),
397408
Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)),

0 commit comments

Comments
 (0)