Skip to content

Commit b325438

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent 78a6f8d commit b325438

File tree

3 files changed

+249
-19
lines changed

3 files changed

+249
-19
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 178 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,191 @@
88
// licenses.
99

1010
//! Data structures and encoding for `invoice_request` messages.
11+
//!
12+
//! An [`InvoiceRequest`] can be either built from a parsed [`Offer`] for the user-pays-merchant
13+
//! flow or built directly for the merchant-pays-user flow.
14+
//!
15+
//! ```
16+
//! extern crate bitcoin;
17+
//! extern crate lightning;
18+
//!
19+
//! use bitcoin::network::constants::Network;
20+
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
21+
//! use lightning::ln::features::OfferFeatures;
22+
//! use lightning::offers::Offer;
23+
//! use lightning::util::ser::Writeable;
24+
//!
25+
//! # fn parse() -> Result<(), lightning::offers::ParseError> {
26+
//! let secp_ctx = Secp256k1::new();
27+
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
28+
//! let pubkey = PublicKey::from(keys);
29+
//! let mut buffer = Vec::new();
30+
//!
31+
//! // User-pays-merchant flow
32+
//! "lno1qcp4256ypq"
33+
//! .parse::<Offer>()?
34+
//! .request_invoice(pubkey)
35+
//! .payer_info(vec![42; 64])
36+
//! .chain(Network::Testnet)?
37+
//! .amount_msats(1000)?
38+
//! .features(OfferFeatures::known())
39+
//! .quantity(5)?
40+
//! .payer_note("foo".to_string())
41+
//! .build()?
42+
//! .sign(|digest| secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))?
43+
//! .write(&mut buffer)
44+
//! .unwrap();
45+
//! # Ok(())
46+
//! # }
47+
//! ```
1148
1249
use bitcoin::blockdata::constants::ChainHash;
13-
use bitcoin::secp256k1::PublicKey;
50+
use bitcoin::network::constants::Network;
51+
use bitcoin::secp256k1::{Message, PublicKey, self};
1452
use bitcoin::secp256k1::schnorr::Signature;
1553
use core::convert::TryFrom;
1654
use core::str::FromStr;
1755
use io;
1856
use ln::features::OfferFeatures;
1957
use offers::merkle::{SignatureTlvStream, self};
20-
use offers::offer::{Amount, OfferContents, OfferTlvStream, self};
58+
use offers::offer::{Offer, OfferContents, OfferTlvStream, self};
2159
use offers::parse::{Bech32Encode, ParseError, SemanticError};
2260
use offers::payer::{PayerContents, PayerTlvStream, self};
2361
use util::ser::{Readable, WithoutLength, Writeable, Writer};
2462

2563
use prelude::*;
2664

65+
///
66+
const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
67+
68+
/// Builds an [`InvoiceRequest`] from an [`Offer`] for the user-pays-merchant flow.
69+
///
70+
/// See [module-level documentation] for usage.
71+
///
72+
/// [module-level documentation]: self
73+
pub struct InvoiceRequestBuilder<'a> {
74+
offer: &'a Offer,
75+
invoice_request: InvoiceRequestContents,
76+
}
77+
78+
impl<'a> InvoiceRequestBuilder<'a> {
79+
pub(super) fn new(offer: &'a Offer, payer_id: PublicKey) -> Self {
80+
Self {
81+
offer,
82+
invoice_request: InvoiceRequestContents {
83+
payer: PayerContents(None), offer: offer.contents.clone(), chain: None,
84+
amount_msats: None, features: None, quantity: None, payer_id, payer_note: None,
85+
},
86+
}
87+
}
88+
89+
///
90+
pub fn payer_info(mut self, payer_info: Vec<u8>) -> Self {
91+
self.invoice_request.payer = PayerContents(Some(payer_info));
92+
self
93+
}
94+
95+
///
96+
pub fn chain(mut self, network: Network) -> Result<Self, SemanticError> {
97+
let chain_hash = ChainHash::using_genesis_block(network);
98+
if !self.offer.supports_chain(chain_hash) {
99+
return Err(SemanticError::UnsupportedChain)
100+
}
101+
102+
self.invoice_request.chain = Some(chain_hash);
103+
Ok(self)
104+
}
105+
106+
///
107+
pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, SemanticError> {
108+
if !self.offer.is_sufficient_amount(amount_msats) {
109+
return Err(SemanticError::InsufficientAmount);
110+
}
111+
112+
self.invoice_request.amount_msats = Some(amount_msats);
113+
Ok(self)
114+
}
115+
116+
///
117+
pub fn features(mut self, features: OfferFeatures) -> Self {
118+
self.invoice_request.features = Some(features);
119+
self
120+
}
121+
122+
///
123+
pub fn quantity(mut self, quantity: u64) -> Result<Self, SemanticError> {
124+
if !self.offer.is_valid_quantity(quantity) {
125+
return Err(SemanticError::InvalidQuantity);
126+
}
127+
128+
self.invoice_request.quantity = Some(quantity);
129+
Ok(self)
130+
}
131+
132+
///
133+
pub fn payer_note(mut self, payer_note: String) -> Self {
134+
self.invoice_request.payer_note = Some(payer_note);
135+
self
136+
}
137+
138+
/// Builds the [`InvoiceRequest`] after checking for valid semantics.
139+
pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
140+
let chain = self.invoice_request.chain.unwrap_or_else(|| self.offer.implied_chain());
141+
if !self.offer.supports_chain(chain) {
142+
return Err(SemanticError::UnsupportedChain);
143+
}
144+
145+
if self.offer.amount().is_some() && self.invoice_request.amount_msats.is_none() {
146+
return Err(SemanticError::MissingAmount);
147+
}
148+
149+
if self.offer.expects_quantity() && self.invoice_request.quantity.is_none() {
150+
return Err(SemanticError::InvalidQuantity);
151+
}
152+
153+
let InvoiceRequestBuilder { offer, invoice_request } = self;
154+
Ok(UnsignedInvoiceRequest { offer, invoice_request })
155+
}
156+
}
157+
158+
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
159+
pub struct UnsignedInvoiceRequest<'a> {
160+
offer: &'a Offer,
161+
invoice_request: InvoiceRequestContents,
162+
}
163+
164+
impl<'a> UnsignedInvoiceRequest<'a> {
165+
/// Signs the invoice request using the given function.
166+
pub fn sign<F>(self, sign: F) -> Result<InvoiceRequest, secp256k1::Error>
167+
where F: FnOnce(&Message) -> Signature
168+
{
169+
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
170+
// unknown TLV records, which are not stored in `OfferContents`.
171+
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
172+
self.invoice_request.as_tlv_stream();
173+
let offer_bytes = WithoutLength(&self.offer.bytes);
174+
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
175+
176+
let mut bytes = Vec::new();
177+
unsigned_tlv_stream.write(&mut bytes).unwrap();
178+
179+
let pubkey = self.invoice_request.payer_id;
180+
let signature = Some(merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?);
181+
182+
// Append the signature TLV record to the bytes.
183+
let signature_tlv_stream = merkle::reference::SignatureTlvStream {
184+
signature: signature.as_ref(),
185+
};
186+
signature_tlv_stream.write(&mut bytes).unwrap();
187+
188+
Ok(InvoiceRequest {
189+
bytes,
190+
contents: self.invoice_request,
191+
signature,
192+
})
193+
}
194+
}
195+
27196
///
28197
pub struct InvoiceRequest {
29198
bytes: Vec<u8>,
@@ -184,8 +353,7 @@ impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
184353
)?;
185354

186355
if let Some(signature) = &signature {
187-
let tag = concat!("lightning", "invoice_request", "signature");
188-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
356+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
189357
}
190358

191359
Ok(InvoiceRequest { bytes, contents, signature })
@@ -205,25 +373,22 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
205373
let payer = PayerContents(payer_info.map(Into::into));
206374
let offer = OfferContents::try_from(offer_tlv_stream)?;
207375

208-
let chain = match chain {
209-
None => None,
210-
Some(chain) if chain == offer.chain() => Some(chain),
211-
Some(_) => return Err(SemanticError::UnsupportedChain),
212-
};
376+
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
377+
return Err(SemanticError::UnsupportedChain);
378+
}
213379

214380
// TODO: Determine whether quantity should be accounted for
215381
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
216382
// TODO: Handle currency case
217-
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
218383
(Some(_), None) => return Err(SemanticError::MissingAmount),
219-
(Some(Amount::Bitcoin { amount_msats: offer_amount_msats }), Some(amount_msats)) => {
220-
if amount_msats < *offer_amount_msats {
384+
(Some(_), Some(amount_msats)) => {
385+
if !offer.is_sufficient_amount(amount_msats) {
221386
return Err(SemanticError::InsufficientAmount);
222387
} else {
223388
Some(amount_msats)
224389
}
225390
},
226-
(_, amount_msats) => amount_msats,
391+
(None, amount_msats) => amount_msats,
227392
};
228393

229394
if let Some(features) = &features {

lightning/src/offers/merkle.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,37 @@ tlv_stream!(struct SignatureTlvStream {
2222
(240, signature: Signature),
2323
});
2424

25+
pub(super) fn sign_message<F>(
26+
sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey,
27+
) -> Result<Signature, secp256k1::Error>
28+
where
29+
F: FnOnce(&Message) -> Signature
30+
{
31+
let digest = message_digest(tag, bytes);
32+
let signature = sign(&digest);
33+
34+
let pubkey = pubkey.into();
35+
let secp_ctx = Secp256k1::verification_only();
36+
secp_ctx.verify_schnorr(&signature, &digest, &pubkey)?;
37+
38+
Ok(signature)
39+
}
40+
2541
pub(super) fn verify_signature(
2642
signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
2743
) -> Result<(), secp256k1::Error> {
28-
let tag = sha256::Hash::hash(tag.as_bytes());
29-
let merkle_root = root_hash(bytes);
30-
let digest = Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap();
44+
let digest = message_digest(tag, bytes);
3145
let pubkey = pubkey.into();
3246
let secp_ctx = Secp256k1::verification_only();
3347
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
3448
}
3549

50+
fn message_digest(tag: &str, bytes: &[u8]) -> Message {
51+
let tag = sha256::Hash::hash(tag.as_bytes());
52+
let merkle_root = root_hash(bytes);
53+
Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap()
54+
}
55+
3656
fn root_hash(data: &[u8]) -> sha256::Hash {
3757
let mut tlv_stream = TlvStream::new(&data[..]).peekable();
3858
let nonce_tag = tagged_hash_engine(sha256::Hash::from_engine({

lightning/src/offers/offer.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ use core::str::FromStr;
6161
use core::time::Duration;
6262
use io;
6363
use ln::features::OfferFeatures;
64+
use offers::invoice_request::InvoiceRequestBuilder;
6465
use offers::parse::{Bech32Encode, ParseError, SemanticError};
6566
use onion_message::BlindedPath;
6667
use util::ser::{Writeable, Writer};
@@ -227,8 +228,8 @@ impl OfferBuilder {
227228
pub struct Offer {
228229
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
229230
// fields.
230-
bytes: Vec<u8>,
231-
contents: OfferContents,
231+
pub(super) bytes: Vec<u8>,
232+
pub(super) contents: OfferContents,
232233
}
233234

234235
/// The contents of an [`Offer`], which may be shared with an `InvoiceRequest` or an `Invoice`.
@@ -253,6 +254,15 @@ impl Offer {
253254
self.contents.chain()
254255
}
255256

257+
///
258+
pub fn supports_chain(&self, chain: ChainHash) -> bool {
259+
self.contents.supports_chain(chain)
260+
}
261+
262+
pub(super) fn implied_chain(&self) -> ChainHash {
263+
self.contents.implied_chain()
264+
}
265+
256266
/// Metadata set by the originator, useful for authentication and validating fields.
257267
pub fn metadata(&self) -> Option<&Vec<u8>> {
258268
self.contents.metadata.as_ref()
@@ -263,6 +273,11 @@ impl Offer {
263273
self.contents.amount()
264274
}
265275

276+
///
277+
pub fn is_sufficient_amount(&self, amount_msats: u64) -> bool {
278+
self.contents.is_sufficient_amount(amount_msats)
279+
}
280+
266281
/// A complete description of the purpose of the payment.
267282
pub fn description(&self) -> &String {
268283
&self.contents.description
@@ -327,6 +342,11 @@ impl Offer {
327342
self.contents.node_id.unwrap()
328343
}
329344

345+
///
346+
pub fn request_invoice(&self, payer_id: PublicKey) -> InvoiceRequestBuilder {
347+
InvoiceRequestBuilder::new(self, payer_id)
348+
}
349+
330350
#[cfg(test)]
331351
fn as_bytes(&self) -> &[u8] {
332352
&self.bytes
@@ -357,13 +377,34 @@ impl OfferContents {
357377
self.chains
358378
.as_ref()
359379
.and_then(|chains| chains.first().copied())
360-
.unwrap_or_else(|| ChainHash::using_genesis_block(Network::Bitcoin))
380+
.unwrap_or_else(|| self.implied_chain())
381+
}
382+
383+
///
384+
pub fn supports_chain(&self, chain: ChainHash) -> bool {
385+
self.chains
386+
.as_ref()
387+
.map(|chains| chains.contains(&chain))
388+
.unwrap_or_else(||chain == self.implied_chain())
389+
}
390+
391+
pub(super) fn implied_chain(&self) -> ChainHash {
392+
ChainHash::using_genesis_block(Network::Bitcoin)
361393
}
362394

363395
pub fn amount(&self) -> Option<&Amount> {
364396
self.amount.as_ref()
365397
}
366398

399+
pub fn is_sufficient_amount(&self, amount_msats: u64) -> bool {
400+
match self.amount {
401+
Some(Amount::Currency { .. }) => unimplemented!(),
402+
Some(Amount::Bitcoin { amount_msats: offer_amount_msats }) => {
403+
amount_msats >= offer_amount_msats
404+
},
405+
None => true,
406+
}
407+
}
367408
pub fn quantity_min(&self) -> u64 {
368409
self.quantity_min.unwrap_or(1)
369410
}
@@ -590,6 +631,7 @@ mod tests {
590631

591632
assert_eq!(offer.as_bytes(), &offer.to_bytes()[..]);
592633
assert_eq!(offer.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
634+
assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin)));
593635
assert_eq!(offer.metadata(), None);
594636
assert_eq!(offer.amount(), None);
595637
assert_eq!(offer.description(), "foo");
@@ -628,6 +670,7 @@ mod tests {
628670
let offer = OfferBuilder::new("foo".into(), pubkey())
629671
.chain(Network::Bitcoin)
630672
.build();
673+
assert!(offer.supports_chain(block_hash));
631674
assert_eq!(offer.chain(), block_hash);
632675
assert_eq!(offer.as_tlv_stream().chains, Some((&vec![block_hash]).into()));
633676

@@ -642,6 +685,8 @@ mod tests {
642685
.chain(Network::Bitcoin)
643686
.chain(Network::Testnet)
644687
.build();
688+
assert!(offer.supports_chain(block_hashes[0]));
689+
assert!(offer.supports_chain(block_hashes[1]));
645690
assert_eq!(offer.chain(), block_hashes[0]);
646691
assert_eq!(offer.as_tlv_stream().chains, Some((&block_hashes).into()));
647692
}

0 commit comments

Comments
 (0)