Skip to content

Commit 1b2e9be

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent b72ef1a commit 1b2e9be

File tree

3 files changed

+180
-20
lines changed

3 files changed

+180
-20
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,121 @@
99

1010
//! Data structures and encoding for `invoice_request` messages.
1111
12+
use bitcoin::blockdata::constants::genesis_block;
1213
use bitcoin::hash_types::BlockHash;
13-
use bitcoin::secp256k1::PublicKey;
14+
use bitcoin::network::constants::Network;
15+
use bitcoin::secp256k1::{Message, PublicKey, self};
1416
use bitcoin::secp256k1::schnorr::Signature;
1517
use core::convert::TryFrom;
1618
use core::str::FromStr;
1719
use io;
1820
use ln::features::OfferFeatures;
1921
use offers::merkle::{SignatureTlvStream, self};
20-
use offers::offer::{Amount, OfferContents, OfferTlvStream, self};
22+
use offers::offer::{Offer, OfferContents, OfferTlvStream, self};
2123
use offers::parse::{Bech32Encode, ParseError, SemanticError};
2224
use offers::payer::{PayerContents, PayerTlvStream, self};
23-
use util::ser::{Writeable, Writer};
25+
use util::ser::{WithoutLength, Writeable, Writer};
26+
27+
///
28+
const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
29+
30+
///
31+
pub struct InvoiceRequestBuilder<'a> {
32+
offer: &'a Offer,
33+
invoice_request: InvoiceRequestContents,
34+
}
35+
36+
impl<'a> InvoiceRequestBuilder<'a> {
37+
pub(super) fn new(offer: &'a Offer, payer_id: PublicKey) -> Self {
38+
Self {
39+
offer,
40+
invoice_request: InvoiceRequestContents {
41+
payer: PayerContents(None), offer: offer.contents.clone(), chain: None,
42+
amount_msats: None, features: None, quantity: None, payer_id, payer_note: None,
43+
signature: None,
44+
},
45+
}
46+
}
47+
48+
///
49+
pub fn payer_info(mut self, payer_info: Vec<u8>) -> Self {
50+
self.invoice_request.payer = PayerContents(Some(payer_info));
51+
self
52+
}
53+
54+
///
55+
pub fn chain(mut self, network: Network) -> Result<Self, SemanticError> {
56+
let block_hash = genesis_block(network).block_hash();
57+
if !self.offer.supports_chain(block_hash) {
58+
return Err(SemanticError::UnsupportedChain)
59+
}
60+
61+
self.invoice_request.chain = Some(block_hash);
62+
Ok(self)
63+
}
64+
65+
///
66+
pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, SemanticError> {
67+
if !self.offer.is_sufficient_amount(amount_msats) {
68+
return Err(SemanticError::InsufficientAmount);
69+
}
70+
71+
self.invoice_request.amount_msats = Some(amount_msats);
72+
Ok(self)
73+
}
74+
75+
///
76+
pub fn features(mut self, features: OfferFeatures) -> Self {
77+
self.invoice_request.features = Some(features);
78+
self
79+
}
80+
81+
///
82+
pub fn quantity(mut self, quantity: u64) -> Result<Self, SemanticError> {
83+
if !self.offer.is_valid_quantity(quantity) {
84+
return Err(SemanticError::InvalidQuantity);
85+
}
86+
87+
self.invoice_request.quantity = Some(quantity);
88+
Ok(self)
89+
}
90+
91+
///
92+
pub fn payer_note(mut self, payer_note: String) -> Self {
93+
self.invoice_request.payer_note = Some(payer_note);
94+
self
95+
}
96+
97+
///
98+
pub fn build_signed<F>(mut self, sign: F) -> Result<InvoiceRequest, secp256k1::Error>
99+
where F: FnOnce(&Message) -> Signature
100+
{
101+
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
102+
// unknown TLV records, which are not stored in `OfferContents`.
103+
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream, _) =
104+
self.invoice_request.as_tlv_stream();
105+
let offer_bytes = WithoutLength(&self.offer.bytes);
106+
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
107+
108+
let mut bytes = Vec::new();
109+
unsigned_tlv_stream.write(&mut bytes).unwrap();
110+
111+
let pubkey = self.invoice_request.payer_id;
112+
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
113+
self.invoice_request.signature = Some(signature);
114+
115+
// Append the signature TLV record to the bytes.
116+
let signature_tlv_stream = merkle::reference::SignatureTlvStream {
117+
signature: self.invoice_request.signature.as_ref(),
118+
};
119+
signature_tlv_stream.write(&mut bytes).unwrap();
120+
121+
Ok(InvoiceRequest {
122+
bytes,
123+
contents: self.invoice_request,
124+
})
125+
}
126+
}
24127

25128
///
26129
pub struct InvoiceRequest {
@@ -111,8 +214,7 @@ impl FromStr for InvoiceRequest {
111214
let contents = InvoiceRequestContents::try_from(tlv_stream)?;
112215

113216
if let Some(signature) = &contents.signature {
114-
let tag = concat!("lightning", "invoice_request", "signature");
115-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
217+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
116218
}
117219

118220
Ok(InvoiceRequest { bytes, contents })
@@ -133,25 +235,22 @@ impl TryFrom<FullInvoiceRequestTlvStream> for InvoiceRequestContents {
133235
let payer = PayerContents(payer_info.map(Into::into));
134236
let offer = OfferContents::try_from(offer_tlv_stream)?;
135237

136-
let chain = match chain {
137-
None => None,
138-
Some(chain) if chain == offer.chain() => Some(chain),
139-
Some(_) => return Err(SemanticError::UnsupportedChain),
140-
};
238+
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
239+
return Err(SemanticError::UnsupportedChain);
240+
}
141241

142242
// TODO: Determine whether quantity should be accounted for
143243
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
144244
// TODO: Handle currency case
145-
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
146245
(Some(_), None) => return Err(SemanticError::MissingAmount),
147-
(Some(Amount::Bitcoin { amount_msats: offer_amount_msats }), Some(amount_msats)) => {
148-
if amount_msats < *offer_amount_msats {
246+
(Some(_), Some(amount_msats)) => {
247+
if !offer.is_sufficient_amount(amount_msats) {
149248
return Err(SemanticError::InsufficientAmount);
150249
} else {
151250
Some(amount_msats)
152251
}
153252
},
154-
(_, amount_msats) => amount_msats,
253+
(None, amount_msats) => amount_msats,
155254
};
156255

157256
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: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use core::str::FromStr;
2020
use core::time::Duration;
2121
use io;
2222
use ln::features::OfferFeatures;
23+
use offers::invoice_request::InvoiceRequestBuilder;
2324
use offers::parse::{Bech32Encode, ParseError, SemanticError};
2425
use util::ser::{WithLength, Writeable, Writer};
2526

@@ -150,8 +151,8 @@ impl OfferBuilder {
150151
///
151152
#[derive(Clone, Debug)]
152153
pub struct Offer {
153-
bytes: Vec<u8>,
154-
contents: OfferContents,
154+
pub(super) bytes: Vec<u8>,
155+
pub(super) contents: OfferContents,
155156
}
156157

157158
///
@@ -176,6 +177,11 @@ impl Offer {
176177
self.contents.chain()
177178
}
178179

180+
///
181+
pub fn supports_chain(&self, chain: BlockHash) -> bool {
182+
self.contents.supports_chain(chain)
183+
}
184+
179185
///
180186
pub fn metadata(&self) -> Option<&Vec<u8>> {
181187
self.contents.metadata.as_ref()
@@ -186,6 +192,11 @@ impl Offer {
186192
self.contents.amount()
187193
}
188194

195+
///
196+
pub fn is_sufficient_amount(&self, amount_msats: u64) -> bool {
197+
self.contents.is_sufficient_amount(amount_msats)
198+
}
199+
189200
///
190201
pub fn description(&self) -> &String {
191202
&self.contents.description
@@ -243,6 +254,11 @@ impl Offer {
243254
self.contents.node_id.unwrap()
244255
}
245256

257+
///
258+
pub fn request_invoice(&self, payer_id: PublicKey) -> InvoiceRequestBuilder {
259+
InvoiceRequestBuilder::new(self, payer_id)
260+
}
261+
246262
#[cfg(test)]
247263
fn as_bytes(&self) -> &[u8] {
248264
&self.bytes
@@ -273,13 +289,34 @@ impl OfferContents {
273289
self.chains
274290
.as_ref()
275291
.and_then(|chains| chains.first().copied())
276-
.unwrap_or_else(|| genesis_block(Network::Bitcoin).block_hash())
292+
.unwrap_or_else(|| self.implied_chain())
293+
}
294+
295+
///
296+
pub fn supports_chain(&self, chain: BlockHash) -> bool {
297+
self.chains
298+
.as_ref()
299+
.map(|chains| chains.contains(&chain))
300+
.unwrap_or_else(||chain == self.implied_chain())
301+
}
302+
303+
pub(super) fn implied_chain(&self) -> BlockHash {
304+
genesis_block(Network::Bitcoin).block_hash()
277305
}
278306

279307
pub fn amount(&self) -> Option<&Amount> {
280308
self.amount.as_ref()
281309
}
282310

311+
pub fn is_sufficient_amount(&self, amount_msats: u64) -> bool {
312+
match self.amount {
313+
Some(Amount::Currency { .. }) => unimplemented!(),
314+
Some(Amount::Bitcoin { amount_msats: offer_amount_msats }) => {
315+
amount_msats >= offer_amount_msats
316+
},
317+
None => true,
318+
}
319+
}
283320
pub fn quantity_min(&self) -> u64 {
284321
self.quantity_min.unwrap_or(1)
285322
}
@@ -519,6 +556,7 @@ mod tests {
519556

520557
assert_eq!(offer.as_bytes(), &offer.to_bytes()[..]);
521558
assert_eq!(offer.chain(), genesis_block(Network::Bitcoin).block_hash());
559+
assert!(offer.supports_chain(genesis_block(Network::Bitcoin).block_hash()));
522560
assert_eq!(offer.metadata(), None);
523561
assert_eq!(offer.amount(), None);
524562
assert_eq!(offer.description(), "foo");
@@ -556,6 +594,7 @@ mod tests {
556594
let offer = OfferBuilder::new("foo".into(), pubkey())
557595
.chain(Network::Bitcoin)
558596
.build();
597+
assert!(offer.supports_chain(block_hash));
559598
assert_eq!(offer.chain(), block_hash);
560599
assert_eq!(offer.as_tlv_stream().chains, Some((&vec![block_hash]).into()));
561600

@@ -570,6 +609,8 @@ mod tests {
570609
.chain(Network::Bitcoin)
571610
.chain(Network::Testnet)
572611
.build();
612+
assert!(offer.supports_chain(block_hashes[0]));
613+
assert!(offer.supports_chain(block_hashes[1]));
573614
assert_eq!(offer.chain(), block_hashes[0]);
574615
assert_eq!(offer.as_tlv_stream().chains, Some((&block_hashes).into()));
575616
}

0 commit comments

Comments
 (0)