Skip to content

Commit b408e26

Browse files
committed
WIP: InvoiceRequestBuilder
1 parent 467e37e commit b408e26

File tree

3 files changed

+177
-19
lines changed

3 files changed

+177
-19
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 110 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,119 @@
1010
//! Data structures and encoding for `invoice_request` messages.
1111
1212
use bitcoin::blockdata::constants::ChainHash;
13-
use bitcoin::secp256k1::PublicKey;
13+
use bitcoin::network::constants::Network;
14+
use bitcoin::secp256k1::{Message, PublicKey, self};
1415
use bitcoin::secp256k1::schnorr::Signature;
1516
use core::convert::TryFrom;
1617
use core::str::FromStr;
1718
use io;
1819
use ln::features::OfferFeatures;
1920
use offers::merkle::{SignatureTlvStream, self};
20-
use offers::offer::{Amount, OfferContents, OfferTlvStream, self};
21+
use offers::offer::{Offer, OfferContents, OfferTlvStream, self};
2122
use offers::parse::{Bech32Encode, ParseError, SemanticError};
2223
use offers::payer::{PayerContents, PayerTlvStream, self};
2324
use util::ser::{Readable, WithoutLength, Writeable, Writer};
2425

26+
///
27+
const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
28+
29+
///
30+
pub struct InvoiceRequestBuilder<'a> {
31+
offer: &'a Offer,
32+
invoice_request: InvoiceRequestContents,
33+
}
34+
35+
impl<'a> InvoiceRequestBuilder<'a> {
36+
pub(super) fn new(offer: &'a Offer, payer_id: PublicKey) -> Self {
37+
Self {
38+
offer,
39+
invoice_request: InvoiceRequestContents {
40+
payer: PayerContents(None), offer: offer.contents.clone(), chain: None,
41+
amount_msats: None, features: None, quantity: None, payer_id, payer_note: None,
42+
},
43+
}
44+
}
45+
46+
///
47+
pub fn payer_info(mut self, payer_info: Vec<u8>) -> Self {
48+
self.invoice_request.payer = PayerContents(Some(payer_info));
49+
self
50+
}
51+
52+
///
53+
pub fn chain(mut self, network: Network) -> Result<Self, SemanticError> {
54+
let chain_hash = ChainHash::using_genesis_block(network);
55+
if !self.offer.supports_chain(chain_hash) {
56+
return Err(SemanticError::UnsupportedChain)
57+
}
58+
59+
self.invoice_request.chain = Some(chain_hash);
60+
Ok(self)
61+
}
62+
63+
///
64+
pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, SemanticError> {
65+
if !self.offer.is_sufficient_amount(amount_msats) {
66+
return Err(SemanticError::InsufficientAmount);
67+
}
68+
69+
self.invoice_request.amount_msats = Some(amount_msats);
70+
Ok(self)
71+
}
72+
73+
///
74+
pub fn features(mut self, features: OfferFeatures) -> Self {
75+
self.invoice_request.features = Some(features);
76+
self
77+
}
78+
79+
///
80+
pub fn quantity(mut self, quantity: u64) -> Result<Self, SemanticError> {
81+
if !self.offer.is_valid_quantity(quantity) {
82+
return Err(SemanticError::InvalidQuantity);
83+
}
84+
85+
self.invoice_request.quantity = Some(quantity);
86+
Ok(self)
87+
}
88+
89+
///
90+
pub fn payer_note(mut self, payer_note: String) -> Self {
91+
self.invoice_request.payer_note = Some(payer_note);
92+
self
93+
}
94+
95+
///
96+
pub fn build_signed<F>(self, sign: F) -> Result<InvoiceRequest, secp256k1::Error>
97+
where F: FnOnce(&Message) -> Signature
98+
{
99+
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
100+
// unknown TLV records, which are not stored in `OfferContents`.
101+
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
102+
self.invoice_request.as_tlv_stream();
103+
let offer_bytes = WithoutLength(&self.offer.bytes);
104+
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
105+
106+
let mut bytes = Vec::new();
107+
unsigned_tlv_stream.write(&mut bytes).unwrap();
108+
109+
let pubkey = self.invoice_request.payer_id;
110+
let signature = Some(merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?);
111+
112+
// Append the signature TLV record to the bytes.
113+
let signature_tlv_stream = merkle::reference::SignatureTlvStream {
114+
signature: signature.as_ref(),
115+
};
116+
signature_tlv_stream.write(&mut bytes).unwrap();
117+
118+
Ok(InvoiceRequest {
119+
bytes,
120+
contents: self.invoice_request,
121+
signature,
122+
})
123+
}
124+
}
125+
25126
///
26127
pub struct InvoiceRequest {
27128
bytes: Vec<u8>,
@@ -182,8 +283,7 @@ impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
182283
)?;
183284

184285
if let Some(signature) = &signature {
185-
let tag = concat!("lightning", "invoice_request", "signature");
186-
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
286+
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
187287
}
188288

189289
Ok(InvoiceRequest { bytes, contents, signature })
@@ -203,25 +303,22 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
203303
let payer = PayerContents(payer_info.map(Into::into));
204304
let offer = OfferContents::try_from(offer_tlv_stream)?;
205305

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

212310
// TODO: Determine whether quantity should be accounted for
213311
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
214312
// TODO: Handle currency case
215-
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
216313
(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 {
314+
(Some(_), Some(amount_msats)) => {
315+
if !offer.is_sufficient_amount(amount_msats) {
219316
return Err(SemanticError::InsufficientAmount);
220317
} else {
221318
Some(amount_msats)
222319
}
223320
},
224-
(_, amount_msats) => amount_msats,
321+
(None, amount_msats) => amount_msats,
225322
};
226323

227324
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
@@ -19,6 +19,7 @@ use core::str::FromStr;
1919
use core::time::Duration;
2020
use io;
2121
use ln::features::OfferFeatures;
22+
use offers::invoice_request::InvoiceRequestBuilder;
2223
use offers::parse::{Bech32Encode, ParseError, SemanticError};
2324
use util::ser::{WithLength, Writeable, Writer};
2425

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

156157
///
@@ -175,6 +176,11 @@ impl Offer {
175176
self.contents.chain()
176177
}
177178

179+
///
180+
pub fn supports_chain(&self, chain: ChainHash) -> bool {
181+
self.contents.supports_chain(chain)
182+
}
183+
178184
///
179185
pub fn metadata(&self) -> Option<&Vec<u8>> {
180186
self.contents.metadata.as_ref()
@@ -185,6 +191,11 @@ impl Offer {
185191
self.contents.amount()
186192
}
187193

194+
///
195+
pub fn is_sufficient_amount(&self, amount_msats: u64) -> bool {
196+
self.contents.is_sufficient_amount(amount_msats)
197+
}
198+
188199
///
189200
pub fn description(&self) -> &String {
190201
&self.contents.description
@@ -247,6 +258,11 @@ impl Offer {
247258
self.contents.node_id.unwrap()
248259
}
249260

261+
///
262+
pub fn request_invoice(&self, payer_id: PublicKey) -> InvoiceRequestBuilder {
263+
InvoiceRequestBuilder::new(self, payer_id)
264+
}
265+
250266
#[cfg(test)]
251267
fn as_bytes(&self) -> &[u8] {
252268
&self.bytes
@@ -277,13 +293,34 @@ impl OfferContents {
277293
self.chains
278294
.as_ref()
279295
.and_then(|chains| chains.first().copied())
280-
.unwrap_or_else(|| ChainHash::using_genesis_block(Network::Bitcoin))
296+
.unwrap_or_else(|| self.implied_chain())
297+
}
298+
299+
///
300+
pub fn supports_chain(&self, chain: ChainHash) -> bool {
301+
self.chains
302+
.as_ref()
303+
.map(|chains| chains.contains(&chain))
304+
.unwrap_or_else(||chain == self.implied_chain())
305+
}
306+
307+
pub(super) fn implied_chain(&self) -> ChainHash {
308+
ChainHash::using_genesis_block(Network::Bitcoin)
281309
}
282310

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

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

526563
assert_eq!(offer.as_bytes(), &offer.to_bytes()[..]);
527564
assert_eq!(offer.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
565+
assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin)));
528566
assert_eq!(offer.metadata(), None);
529567
assert_eq!(offer.amount(), None);
530568
assert_eq!(offer.description(), "foo");
@@ -562,6 +600,7 @@ mod tests {
562600
let offer = OfferBuilder::new("foo".into(), pubkey())
563601
.chain(Network::Bitcoin)
564602
.build();
603+
assert!(offer.supports_chain(block_hash));
565604
assert_eq!(offer.chain(), block_hash);
566605
assert_eq!(offer.as_tlv_stream().chains, Some((&vec![block_hash]).into()));
567606

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

0 commit comments

Comments
 (0)