Skip to content

Commit e39da9a

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent 1e1ec45 commit e39da9a

File tree

3 files changed

+283
-18
lines changed

3 files changed

+283
-18
lines changed

lightning/src/offers/invoice_request.rs

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

2465
use crate::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 [`InvoiceRequest::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 conform
135+
/// to [`Offer::is_valid_quantity`].
136+
///
137+
/// Successive calls to this method will override the previous setting.
138+
pub fn quantity(mut self, quantity: u64) -> Result<Self, SemanticError> {
139+
if !self.offer.is_valid_quantity(quantity) {
140+
return Err(SemanticError::InvalidQuantity);
141+
}
142+
143+
self.invoice_request.quantity = Some(quantity);
144+
Ok(self)
145+
}
146+
147+
/// Sets a note for the invoice request.
148+
///
149+
/// Successive calls to this method will override the previous setting.
150+
pub fn payer_note(mut self, payer_note: String) -> Self {
151+
self.invoice_request.payer_note = Some(payer_note);
152+
self
153+
}
154+
155+
/// Builds an [`InvoiceRequest`] after checking for valid semantics.
156+
pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
157+
if !self.offer.supports_chain(self.invoice_request.chain()) {
158+
return Err(SemanticError::UnsupportedChain);
159+
}
160+
161+
if let Some(amount) = self.offer.amount() {
162+
if self.invoice_request.amount_msats.is_none() {
163+
return Err(SemanticError::MissingAmount);
164+
}
165+
166+
if let Amount::Currency { .. } = amount {
167+
return Err(SemanticError::UnsupportedCurrency);
168+
}
169+
}
170+
171+
if self.offer.expects_quantity() && self.invoice_request.quantity.is_none() {
172+
return Err(SemanticError::InvalidQuantity);
173+
}
174+
175+
let amount_msats = self.invoice_request.amount_msats.unwrap_or(self.offer.amount_msats());
176+
let quantity = self.invoice_request.quantity.unwrap_or(1);
177+
if amount_msats < self.offer.expected_invoice_amount_msats(quantity) {
178+
return Err(SemanticError::InsufficientAmount);
179+
}
180+
181+
let InvoiceRequestBuilder { offer, invoice_request } = self;
182+
Ok(UnsignedInvoiceRequest { offer, invoice_request })
183+
}
184+
}
185+
186+
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
187+
pub struct UnsignedInvoiceRequest<'a> {
188+
offer: &'a Offer,
189+
invoice_request: InvoiceRequestContents,
190+
}
191+
192+
impl<'a> UnsignedInvoiceRequest<'a> {
193+
/// Signs the invoice request using the given function.
194+
pub fn sign<F>(self, sign: F) -> Result<InvoiceRequest, secp256k1::Error>
195+
where F: FnOnce(&Message) -> Signature
196+
{
197+
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
198+
// unknown TLV records, which are not stored in `OfferContents`.
199+
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
200+
self.invoice_request.as_tlv_stream();
201+
let offer_bytes = WithoutLength(&self.offer.bytes);
202+
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
203+
204+
let mut bytes = Vec::new();
205+
unsigned_tlv_stream.write(&mut bytes).unwrap();
206+
207+
let pubkey = self.invoice_request.payer_id;
208+
let signature = Some(merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?);
209+
210+
// Append the signature TLV record to the bytes.
211+
let signature_tlv_stream = SignatureTlvStreamRef {
212+
signature: signature.as_ref(),
213+
};
214+
signature_tlv_stream.write(&mut bytes).unwrap();
215+
216+
Ok(InvoiceRequest {
217+
bytes,
218+
contents: self.invoice_request,
219+
signature,
220+
})
221+
}
222+
}
223+
26224
/// An `InvoiceRequest` is a request for an `Invoice` formulated from an [`Offer`].
27225
///
28226
/// An offer may provided choices such as quantity, amount, chain, features, etc. An invoice request
@@ -59,17 +257,14 @@ impl InvoiceRequest {
59257
}
60258

61259
/// A chain from [`Offer::chains`] that the offer is valid for.
62-
///
63-
/// [`Offer::chains`]: crate::offers::offer::Offer::chains
64260
pub fn chain(&self) -> ChainHash {
65-
self.contents.chain.unwrap_or_else(|| self.contents.offer.implied_chain())
261+
self.contents.chain()
66262
}
67263

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

82277
/// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
83-
///
84-
/// [`Offer::is_valid_quantity`]: crate::offers::offer::Offer::is_valid_quantity
85278
pub fn quantity(&self) -> Option<u64> {
86279
self.contents.quantity
87280
}
@@ -104,12 +297,43 @@ impl InvoiceRequest {
104297
}
105298
}
106299

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

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

@@ -135,6 +359,12 @@ type FullInvoiceRequestTlvStream =
135359

136360
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
137361

362+
type PartialInvoiceRequestTlvStreamRef<'a> = (
363+
PayerTlvStreamRef<'a>,
364+
OfferTlvStreamRef<'a>,
365+
InvoiceRequestTlvStreamRef<'a>,
366+
);
367+
138368
impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
139369
type Error = ParseError;
140370

@@ -149,8 +379,7 @@ impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
149379
)?;
150380

151381
if let Some(signature) = &signature {
152-
let tag = concat!("lightning", "invoice_request", "signature");
153-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
382+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
154383
}
155384

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

lightning/src/offers/merkle.rs

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

27+
pub(super) fn sign_message<F>(
28+
sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey,
29+
) -> Result<Signature, secp256k1::Error>
30+
where
31+
F: FnOnce(&Message) -> Signature
32+
{
33+
let digest = message_digest(tag, bytes);
34+
let signature = sign(&digest);
35+
36+
let pubkey = pubkey.into();
37+
let secp_ctx = Secp256k1::verification_only();
38+
secp_ctx.verify_schnorr(&signature, &digest, &pubkey)?;
39+
40+
Ok(signature)
41+
}
42+
2743
/// Verifies the signature with a pubkey over the given bytes using a tagged hash as the message
2844
/// digest.
2945
pub(super) fn verify_signature(
3046
signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
3147
) -> 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();
48+
let digest = message_digest(tag, bytes);
3549
let pubkey = pubkey.into();
3650
let secp_ctx = Secp256k1::verification_only();
3751
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
3852
}
3953

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

0 commit comments

Comments
 (0)