Skip to content

Commit 173d0ff

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent 6fbd477 commit 173d0ff

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,20 +8,189 @@
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

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

184353
if let Some(signature) = &signature {
185-
let tag = concat!("lightning", "invoice_request", "signature");
186-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
354+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
187355
}
188356

189357
Ok(InvoiceRequest { bytes, contents, signature })
@@ -203,25 +371,22 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
203371
let payer = PayerContents(payer_info.map(Into::into));
204372
let offer = OfferContents::try_from(offer_tlv_stream)?;
205373

206-
let chain = match chain {
207-
None => None,
208-
Some(chain) if chain == offer.chain() => Some(chain),
209-
Some(_) => return Err(SemanticError::UnsupportedChain),
210-
};
374+
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
375+
return Err(SemanticError::UnsupportedChain);
376+
}
211377

212378
// TODO: Determine whether quantity should be accounted for
213379
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
214380
// TODO: Handle currency case
215-
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
216381
(Some(_), None) => return Err(SemanticError::MissingAmount),
217-
(Some(Amount::Bitcoin { amount_msats: offer_amount_msats }), Some(amount_msats)) => {
218-
if amount_msats < *offer_amount_msats {
382+
(Some(_), Some(amount_msats)) => {
383+
if !offer.is_sufficient_amount(amount_msats) {
219384
return Err(SemanticError::InsufficientAmount);
220385
} else {
221386
Some(amount_msats)
222387
}
223388
},
224-
(_, amount_msats) => amount_msats,
389+
(None, amount_msats) => amount_msats,
225390
};
226391

227392
if let Some(features) = &features {

lightning/src/offers/merkle.rs

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

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

48+
fn message_digest(tag: &str, bytes: &[u8]) -> Message {
49+
let tag = sha256::Hash::hash(tag.as_bytes());
50+
let merkle_root = root_hash(bytes);
51+
Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap()
52+
}
53+
3454
fn root_hash(data: &[u8]) -> sha256::Hash {
3555
let mut tlv_stream = TlvStream::new(&data[..]).peekable();
3656
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");
@@ -627,6 +669,7 @@ mod tests {
627669
let offer = OfferBuilder::new("foo".into(), pubkey())
628670
.chain(Network::Bitcoin)
629671
.build();
672+
assert!(offer.supports_chain(block_hash));
630673
assert_eq!(offer.chain(), block_hash);
631674
assert_eq!(offer.as_tlv_stream().chains, Some((&vec![block_hash]).into()));
632675

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

0 commit comments

Comments
 (0)