Skip to content

Commit 78a6f8d

Browse files
committed
WIP: invoice_request with reflected offer
1 parent 4de6818 commit 78a6f8d

File tree

8 files changed

+373
-10
lines changed

8 files changed

+373
-10
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Data structures and encoding for `invoice_request` messages.
11+
12+
use bitcoin::blockdata::constants::ChainHash;
13+
use bitcoin::secp256k1::PublicKey;
14+
use bitcoin::secp256k1::schnorr::Signature;
15+
use core::convert::TryFrom;
16+
use core::str::FromStr;
17+
use io;
18+
use ln::features::OfferFeatures;
19+
use offers::merkle::{SignatureTlvStream, self};
20+
use offers::offer::{Amount, OfferContents, OfferTlvStream, self};
21+
use offers::parse::{Bech32Encode, ParseError, SemanticError};
22+
use offers::payer::{PayerContents, PayerTlvStream, self};
23+
use util::ser::{Readable, WithoutLength, Writeable, Writer};
24+
25+
use prelude::*;
26+
27+
///
28+
pub struct InvoiceRequest {
29+
bytes: Vec<u8>,
30+
contents: InvoiceRequestContents,
31+
signature: Option<Signature>,
32+
}
33+
34+
///
35+
pub(crate) struct InvoiceRequestContents {
36+
payer: PayerContents,
37+
offer: OfferContents,
38+
chain: Option<ChainHash>,
39+
amount_msats: Option<u64>,
40+
features: Option<OfferFeatures>,
41+
quantity: Option<u64>,
42+
payer_id: PublicKey,
43+
payer_note: Option<String>,
44+
}
45+
46+
impl InvoiceRequest {
47+
///
48+
pub fn payer_info(&self) -> Option<&Vec<u8>> {
49+
self.contents.payer.0.as_ref()
50+
}
51+
52+
///
53+
pub fn chain(&self) -> ChainHash {
54+
self.contents.chain.unwrap_or_else(|| self.contents.offer.chain())
55+
}
56+
57+
///
58+
pub fn amount_msats(&self) -> Option<u64> {
59+
self.contents.amount_msats
60+
}
61+
62+
///
63+
pub fn features(&self) -> Option<&OfferFeatures> {
64+
self.contents.features.as_ref()
65+
}
66+
67+
///
68+
pub fn quantity(&self) -> Option<u64> {
69+
self.contents.quantity
70+
}
71+
72+
///
73+
pub fn payer_id(&self) -> PublicKey {
74+
self.contents.payer_id
75+
}
76+
77+
///
78+
pub fn payer_note(&self) -> Option<&String> {
79+
self.contents.payer_note.as_ref()
80+
}
81+
82+
///
83+
pub fn signature(&self) -> Option<Signature> {
84+
self.signature
85+
}
86+
}
87+
88+
impl AsRef<[u8]> for InvoiceRequest {
89+
fn as_ref(&self) -> &[u8] {
90+
&self.bytes
91+
}
92+
}
93+
94+
impl InvoiceRequestContents {
95+
pub(super) fn as_tlv_stream(&self) -> ReferencedPartialInvoiceRequestTlvStream {
96+
let payer = payer::reference::PayerTlvStream {
97+
payer_info: self.payer.0.as_ref().map(Into::into),
98+
};
99+
100+
let offer = self.offer.as_tlv_stream();
101+
102+
let invoice_request = reference::InvoiceRequestTlvStream {
103+
chain: self.chain.as_ref(),
104+
amount: self.amount_msats.map(Into::into),
105+
features: self.features.as_ref(),
106+
quantity: self.quantity.map(Into::into),
107+
payer_id: Some(&self.payer_id),
108+
payer_note: self.payer_note.as_ref().map(Into::into),
109+
};
110+
111+
(payer, offer, invoice_request)
112+
}
113+
}
114+
115+
impl Writeable for InvoiceRequest {
116+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
117+
WithoutLength(&self.bytes).write(writer)
118+
}
119+
}
120+
121+
impl Writeable for InvoiceRequestContents {
122+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
123+
self.as_tlv_stream().write(writer)
124+
}
125+
}
126+
127+
impl TryFrom<Vec<u8>> for InvoiceRequest {
128+
type Error = ParseError;
129+
130+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
131+
let tlv_stream: FullInvoiceRequestTlvStream = Readable::read(&mut &bytes[..])?;
132+
InvoiceRequest::try_from((bytes, tlv_stream))
133+
}
134+
}
135+
136+
tlv_stream!(struct InvoiceRequestTlvStream {
137+
(80, chain: ChainHash),
138+
(82, amount: u64),
139+
(84, features: OfferFeatures),
140+
(86, quantity: u64),
141+
(88, payer_id: PublicKey),
142+
(89, payer_note: String),
143+
});
144+
145+
impl Bech32Encode for InvoiceRequest {
146+
type TlvStream = FullInvoiceRequestTlvStream;
147+
148+
const BECH32_HRP: &'static str = "lnr";
149+
}
150+
151+
type ParsedInvoiceRequest = (Vec<u8>, FullInvoiceRequestTlvStream);
152+
153+
type FullInvoiceRequestTlvStream =
154+
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
155+
156+
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
157+
158+
type ReferencedPartialInvoiceRequestTlvStream<'a> = (
159+
payer::reference::PayerTlvStream<'a>,
160+
offer::reference::OfferTlvStream<'a>,
161+
reference::InvoiceRequestTlvStream<'a>,
162+
);
163+
164+
impl FromStr for InvoiceRequest {
165+
type Err = ParseError;
166+
167+
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
168+
let (tlv_stream, bytes) = InvoiceRequest::from_bech32_str(s)?;
169+
InvoiceRequest::try_from((bytes, tlv_stream))
170+
}
171+
}
172+
173+
impl TryFrom<ParsedInvoiceRequest> for InvoiceRequest {
174+
type Error = ParseError;
175+
176+
fn try_from(invoice_request: ParsedInvoiceRequest) -> Result<Self, Self::Error> {
177+
let (bytes, tlv_stream) = invoice_request;
178+
let (
179+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
180+
SignatureTlvStream { signature },
181+
) = tlv_stream;
182+
let contents = InvoiceRequestContents::try_from(
183+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
184+
)?;
185+
186+
if let Some(signature) = &signature {
187+
let tag = concat!("lightning", "invoice_request", "signature");
188+
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
189+
}
190+
191+
Ok(InvoiceRequest { bytes, contents, signature })
192+
}
193+
}
194+
195+
impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
196+
type Error = SemanticError;
197+
198+
fn try_from(tlv_stream: PartialInvoiceRequestTlvStream) -> Result<Self, Self::Error> {
199+
let (
200+
PayerTlvStream { payer_info },
201+
offer_tlv_stream,
202+
InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note },
203+
) = tlv_stream;
204+
205+
let payer = PayerContents(payer_info.map(Into::into));
206+
let offer = OfferContents::try_from(offer_tlv_stream)?;
207+
208+
let chain = match chain {
209+
None => None,
210+
Some(chain) if chain == offer.chain() => Some(chain),
211+
Some(_) => return Err(SemanticError::UnsupportedChain),
212+
};
213+
214+
// TODO: Determine whether quantity should be accounted for
215+
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
216+
// TODO: Handle currency case
217+
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
218+
(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 {
221+
return Err(SemanticError::InsufficientAmount);
222+
} else {
223+
Some(amount_msats)
224+
}
225+
},
226+
(_, amount_msats) => amount_msats,
227+
};
228+
229+
if let Some(features) = &features {
230+
if features.requires_unknown_bits() {
231+
return Err(SemanticError::UnknownRequiredFeatures);
232+
}
233+
}
234+
235+
let quantity = match quantity.map(Into::into) {
236+
None if !offer.expects_quantity() => None,
237+
Some(quantity) if offer.is_valid_quantity(quantity) => Some(quantity),
238+
_ => return Err(SemanticError::InvalidQuantity),
239+
};
240+
241+
let payer_id = match payer_id {
242+
None => return Err(SemanticError::MissingPayerId),
243+
Some(payer_id) => payer_id,
244+
};
245+
246+
let payer_note = payer_note.map(Into::into);
247+
248+
Ok(InvoiceRequestContents {
249+
payer, offer, chain, amount_msats, features, quantity, payer_id, payer_note,
250+
})
251+
}
252+
}
253+
254+
impl core::fmt::Display for InvoiceRequest {
255+
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
256+
self.fmt_bech32_str(f)
257+
}
258+
}

lightning/src/offers/merkle.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,30 @@
1010
//! Tagged hashes for use in signature calculation and verification.
1111
1212
use bitcoin::hashes::{Hash, HashEngine, sha256};
13+
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
14+
use bitcoin::secp256k1::schnorr::Signature;
1315
use util::ser::{BigSize, Readable};
1416

1517
use prelude::*;
1618

1719
const SIGNATURE_TYPES: core::ops::RangeInclusive<u64> = 240..=1000;
1820

19-
pub(super) fn root_hash(data: &[u8]) -> sha256::Hash {
21+
tlv_stream!(struct SignatureTlvStream {
22+
(240, signature: Signature),
23+
});
24+
25+
pub(super) fn verify_signature(
26+
signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
27+
) -> 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();
31+
let pubkey = pubkey.into();
32+
let secp_ctx = Secp256k1::verification_only();
33+
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
34+
}
35+
36+
fn root_hash(data: &[u8]) -> sha256::Hash {
2037
let mut tlv_stream = TlvStream::new(&data[..]).peekable();
2138
let nonce_tag = tagged_hash_engine(sha256::Hash::from_engine({
2239
let mut engine = sha256::Hash::engine();
@@ -52,7 +69,7 @@ pub(super) fn root_hash(data: &[u8]) -> sha256::Hash {
5269
*leaves.first().unwrap()
5370
}
5471

55-
pub(super) fn tagged_hash<T: AsRef<[u8]>>(tag: sha256::Hash, msg: T) -> sha256::Hash {
72+
fn tagged_hash<T: AsRef<[u8]>>(tag: sha256::Hash, msg: T) -> sha256::Hash {
5673
let engine = tagged_hash_engine(tag);
5774
tagged_hash_from_engine(engine, msg)
5875
}

lightning/src/offers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
//!
1313
//! Offers are a flexible protocol for Lightning payments.
1414
15+
pub mod invoice_request;
1516
mod merkle;
1617
pub mod offer;
1718
pub mod parse;
19+
mod payer;

lightning/src/offers/offer.rs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,7 @@ pub(crate) struct OfferContents {
250250
impl Offer {
251251
/// The chain used for paying the invoice.
252252
pub fn chain(&self) -> ChainHash {
253-
// TODO: Update once spec is finalized
254-
self.contents.chains
255-
.as_ref()
256-
.and_then(|chains| chains.first().copied())
257-
.unwrap_or_else(|| ChainHash::using_genesis_block(Network::Bitcoin))
253+
self.contents.chain()
258254
}
259255

260256
/// Metadata set by the originator, useful for authentication and validating fields.
@@ -264,7 +260,7 @@ impl Offer {
264260

265261
/// The minimum amount required for a successful payment.
266262
pub fn amount(&self) -> Option<&Amount> {
267-
self.contents.amount.as_ref()
263+
self.contents.amount()
268264
}
269265

270266
/// A complete description of the purpose of the payment.
@@ -316,6 +312,16 @@ impl Offer {
316312
self.contents.quantity_max()
317313
}
318314

315+
///
316+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
317+
self.contents.is_valid_quantity(quantity)
318+
}
319+
320+
///
321+
pub fn expects_quantity(&self) -> bool {
322+
self.contents.expects_quantity()
323+
}
324+
319325
/// The recipient's public key used to sign invoices.
320326
pub fn node_id(&self) -> PublicKey {
321327
self.contents.node_id.unwrap()
@@ -346,6 +352,18 @@ impl AsRef<[u8]> for Offer {
346352
}
347353

348354
impl OfferContents {
355+
pub fn chain(&self) -> ChainHash {
356+
// TODO: Update once spec is finalized
357+
self.chains
358+
.as_ref()
359+
.and_then(|chains| chains.first().copied())
360+
.unwrap_or_else(|| ChainHash::using_genesis_block(Network::Bitcoin))
361+
}
362+
363+
pub fn amount(&self) -> Option<&Amount> {
364+
self.amount.as_ref()
365+
}
366+
349367
pub fn quantity_min(&self) -> u64 {
350368
self.quantity_min.unwrap_or(1)
351369
}
@@ -355,7 +373,17 @@ impl OfferContents {
355373
self.quantity_min.map_or(1, |_| u64::max_value()))
356374
}
357375

358-
fn as_tlv_stream(&self) -> reference::OfferTlvStream {
376+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
377+
self.expects_quantity()
378+
&& quantity >= self.quantity_min()
379+
&& quantity <= self.quantity_max()
380+
}
381+
382+
pub fn expects_quantity(&self) -> bool {
383+
self.quantity_min.is_some() || self.quantity_max.is_some()
384+
}
385+
386+
pub(super) fn as_tlv_stream(&self) -> reference::OfferTlvStream {
359387
let (currency, amount) = match &self.amount {
360388
None => (None, None),
361389
Some(Amount::Bitcoin { amount_msats }) => (None, Some(amount_msats.into())),

0 commit comments

Comments
 (0)