Skip to content

Commit bfaa374

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent 9f4948a commit bfaa374

File tree

3 files changed

+179
-19
lines changed

3 files changed

+179
-19
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,122 @@
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};
2325
use util::ser::{Readable, WithoutLength, Writeable, Writer};
2426

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+
}
127+
25128
///
26129
pub struct InvoiceRequest {
27130
bytes: Vec<u8>,
@@ -169,8 +272,7 @@ impl FromStr for InvoiceRequest {
169272
let contents = InvoiceRequestContents::try_from(tlv_stream)?;
170273

171274
if let Some(signature) = &contents.signature {
172-
let tag = concat!("lightning", "invoice_request", "signature");
173-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
275+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
174276
}
175277

176278
Ok(InvoiceRequest { bytes, contents })
@@ -191,25 +293,22 @@ impl TryFrom<FullInvoiceRequestTlvStream> for InvoiceRequestContents {
191293
let payer = PayerContents(payer_info.map(Into::into));
192294
let offer = OfferContents::try_from(offer_tlv_stream)?;
193295

194-
let chain = match chain {
195-
None => None,
196-
Some(chain) if chain == offer.chain() => Some(chain),
197-
Some(_) => return Err(SemanticError::UnsupportedChain),
198-
};
296+
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
297+
return Err(SemanticError::UnsupportedChain);
298+
}
199299

200300
// TODO: Determine whether quantity should be accounted for
201301
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
202302
// TODO: Handle currency case
203-
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
204303
(Some(_), None) => return Err(SemanticError::MissingAmount),
205-
(Some(Amount::Bitcoin { amount_msats: offer_amount_msats }), Some(amount_msats)) => {
206-
if amount_msats < *offer_amount_msats {
304+
(Some(_), Some(amount_msats)) => {
305+
if !offer.is_sufficient_amount(amount_msats) {
207306
return Err(SemanticError::InsufficientAmount);
208307
} else {
209308
Some(amount_msats)
210309
}
211310
},
212-
(_, amount_msats) => amount_msats,
311+
(None, amount_msats) => amount_msats,
213312
};
214313

215314
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
@@ -248,6 +259,11 @@ impl Offer {
248259
self.contents.node_id.unwrap()
249260
}
250261

262+
///
263+
pub fn request_invoice(&self, payer_id: PublicKey) -> InvoiceRequestBuilder {
264+
InvoiceRequestBuilder::new(self, payer_id)
265+
}
266+
251267
#[cfg(test)]
252268
fn as_bytes(&self) -> &[u8] {
253269
&self.bytes
@@ -278,13 +294,34 @@ impl OfferContents {
278294
self.chains
279295
.as_ref()
280296
.and_then(|chains| chains.first().copied())
281-
.unwrap_or_else(|| genesis_block(Network::Bitcoin).block_hash())
297+
.unwrap_or_else(|| self.implied_chain())
298+
}
299+
300+
///
301+
pub fn supports_chain(&self, chain: BlockHash) -> bool {
302+
self.chains
303+
.as_ref()
304+
.map(|chains| chains.contains(&chain))
305+
.unwrap_or_else(||chain == self.implied_chain())
306+
}
307+
308+
pub(super) fn implied_chain(&self) -> BlockHash {
309+
genesis_block(Network::Bitcoin).block_hash()
282310
}
283311

284312
pub fn amount(&self) -> Option<&Amount> {
285313
self.amount.as_ref()
286314
}
287315

316+
pub fn is_sufficient_amount(&self, amount_msats: u64) -> bool {
317+
match self.amount {
318+
Some(Amount::Currency { .. }) => unimplemented!(),
319+
Some(Amount::Bitcoin { amount_msats: offer_amount_msats }) => {
320+
amount_msats >= offer_amount_msats
321+
},
322+
None => true,
323+
}
324+
}
288325
pub fn quantity_min(&self) -> u64 {
289326
self.quantity_min.unwrap_or(1)
290327
}
@@ -526,6 +563,7 @@ mod tests {
526563

527564
assert_eq!(offer.as_bytes(), &offer.to_bytes()[..]);
528565
assert_eq!(offer.chain(), genesis_block(Network::Bitcoin).block_hash());
566+
assert!(offer.supports_chain(genesis_block(Network::Bitcoin).block_hash()));
529567
assert_eq!(offer.metadata(), None);
530568
assert_eq!(offer.amount(), None);
531569
assert_eq!(offer.description(), "foo");
@@ -563,6 +601,7 @@ mod tests {
563601
let offer = OfferBuilder::new("foo".into(), pubkey())
564602
.chain(Network::Bitcoin)
565603
.build();
604+
assert!(offer.supports_chain(block_hash));
566605
assert_eq!(offer.chain(), block_hash);
567606
assert_eq!(offer.as_tlv_stream().chains, Some((&vec![block_hash]).into()));
568607

@@ -577,6 +616,8 @@ mod tests {
577616
.chain(Network::Bitcoin)
578617
.chain(Network::Testnet)
579618
.build();
619+
assert!(offer.supports_chain(block_hashes[0]));
620+
assert!(offer.supports_chain(block_hashes[1]));
580621
assert_eq!(offer.chain(), block_hashes[0]);
581622
assert_eq!(offer.as_tlv_stream().chains, Some((&block_hashes).into()));
582623
}

0 commit comments

Comments
 (0)