Skip to content

Commit 9c60c5a

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent 3ee6506 commit 9c60c5a

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.base_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
@@ -106,12 +300,43 @@ impl InvoiceRequest {
106300
}
107301
}
108302

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

334+
impl Writeable for InvoiceRequestContents {
335+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
336+
self.as_tlv_stream().write(writer)
337+
}
338+
}
339+
115340
impl TryFrom<Vec<u8>> for InvoiceRequest {
116341
type Error = ParseError;
117342

@@ -137,6 +362,12 @@ type FullInvoiceRequestTlvStream =
137362

138363
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
139364

365+
type PartialInvoiceRequestTlvStreamRef<'a> = (
366+
PayerTlvStreamRef<'a>,
367+
OfferTlvStreamRef<'a>,
368+
InvoiceRequestTlvStreamRef<'a>,
369+
);
370+
140371
impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
141372
type Error = ParseError;
142373

@@ -151,8 +382,7 @@ impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
151382
)?;
152383

153384
if let Some(signature) = &signature {
154-
let tag = concat!("lightning", "invoice_request", "signature");
155-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
385+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
156386
}
157387

158388
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
@@ -78,6 +78,7 @@ use core::time::Duration;
7878
use io;
7979
use ln::features::OfferFeatures;
8080
use ln::msgs::MAX_VALUE_MSAT;
81+
use offers::invoice_request::InvoiceRequestBuilder;
8182
use offers::parse::{Bech32Encode, ParseError, SemanticError};
8283
use onion_message::BlindedPath;
8384
use util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
@@ -263,8 +264,8 @@ impl OfferBuilder {
263264
pub struct Offer {
264265
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
265266
// fields.
266-
bytes: Vec<u8>,
267-
contents: OfferContents,
267+
pub(super) bytes: Vec<u8>,
268+
pub(super) contents: OfferContents,
268269
}
269270

270271
/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
@@ -311,6 +312,11 @@ impl Offer {
311312
self.contents.amount()
312313
}
313314

315+
/// The minimum amount in msats required for a successful payment.
316+
pub fn amount_msats(&self) -> u64 {
317+
self.contents.amount_msats()
318+
}
319+
314320
/// Returns the minimum amount in msats required for the given quantity.
315321
pub fn base_invoice_amount_msats(&self, quantity: u64) -> u64 {
316322
self.contents.base_invoice_amount_msats(quantity)
@@ -384,6 +390,11 @@ impl Offer {
384390
self.contents.signing_pubkey.unwrap()
385391
}
386392

393+
///
394+
pub fn request_invoice(&self, payer_id: PublicKey) -> InvoiceRequestBuilder {
395+
InvoiceRequestBuilder::new(self, payer_id)
396+
}
397+
387398
#[cfg(test)]
388399
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
389400
self.contents.as_tlv_stream()
@@ -440,7 +451,7 @@ impl OfferContents {
440451
self.quantity_min.is_some() || self.quantity_max.is_some()
441452
}
442453

443-
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
454+
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
444455
let (currency, amount) = match &self.amount {
445456
None => (None, None),
446457
Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)),

0 commit comments

Comments
 (0)