Skip to content

Commit f4e17b4

Browse files
committed
f - invoice_request semantic and signature checks
1 parent 0bcf220 commit f4e17b4

File tree

3 files changed

+92
-9
lines changed

3 files changed

+92
-9
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
//! Data structures and encoding for `invoice_request` messages.
1111
1212
use bitcoin::hash_types::BlockHash;
13-
use bitcoin::secp256k1::PublicKey;
13+
use bitcoin::hashes::{Hash, sha256};
14+
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
1415
use bitcoin::secp256k1::schnorr::Signature;
1516
use core::convert::TryFrom;
1617
use core::str::FromStr;
1718
use ln::features::OfferFeatures;
1819
use offers::PayerTlvStream;
19-
use offers::merkle::SignatureTlvStream;
20-
use offers::offer::{OfferContents, OfferTlvStream};
20+
use offers::merkle::{SignatureTlvStream, self};
21+
use offers::offer::{Amount, OfferContents, OfferTlvStream};
2122
use offers::parse::{Bech32Encode, ParseError, SemanticError};
2223

2324
///
@@ -30,15 +31,33 @@ pub struct InvoiceRequest {
3031
pub(crate) struct InvoiceRequestContents {
3132
offer: OfferContents,
3233
chain: Option<BlockHash>,
33-
amount_msat: Option<u64>,
34+
amount_msats: Option<u64>,
3435
features: Option<OfferFeatures>,
3536
quantity: Option<u64>,
36-
payer_id: Option<PublicKey>,
37+
payer_id: PublicKey,
3738
payer_note: Option<String>,
3839
payer_info: Option<Vec<u8>>,
3940
signature: Option<Signature>,
4041
}
4142

43+
impl InvoiceRequestContents {
44+
pub fn verify_signature(&self, bytes: &[u8]) -> Result<(), secp256k1::Error> {
45+
if let Some(signature) = &self.signature {
46+
let tag = sha256::Hash::hash(
47+
concat!("lightning", "invoice_request", "signature").as_bytes()
48+
);
49+
let merkle_root = merkle::root_hash(bytes);
50+
let digest = Message::from_slice(&merkle::tagged_hash(tag, merkle_root)).unwrap();
51+
let pubkey = self.payer_id.into();
52+
let secp_ctx = Secp256k1::verification_only();
53+
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
54+
} else {
55+
Ok(())
56+
}
57+
}
58+
59+
}
60+
4261
impl AsRef<[u8]> for InvoiceRequest {
4362
fn as_ref(&self) -> &[u8] {
4463
&self.bytes
@@ -69,6 +88,8 @@ impl FromStr for InvoiceRequest {
6988
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
7089
let (tlv_stream, bytes) = InvoiceRequest::from_bech32_str(s)?;
7190
let contents = InvoiceRequestContents::try_from(tlv_stream)?;
91+
contents.verify_signature(&bytes)?;
92+
7293
Ok(InvoiceRequest { bytes, contents })
7394
}
7495
}
@@ -93,16 +114,44 @@ impl TryFrom<FullInvoiceRequestTlvStream> for InvoiceRequestContents {
93114
};
94115

95116
// TODO: Check remaining fields against the reflected offer
96-
let amount_msat = amount.map(Into::into);
97117

98-
let quantity = quantity.map(Into::into);
118+
// TODO: Determine whether quantity should be accounted for
119+
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
120+
// TODO: Handle currency case
121+
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
122+
(Some(_), None) => return Err(SemanticError::MissingAmount),
123+
(Some(Amount::Bitcoin { amount_msats: offer_amount_msats }), Some(amount_msats)) => {
124+
if amount_msats < *offer_amount_msats {
125+
return Err(SemanticError::InsufficientAmount);
126+
} else {
127+
Some(amount_msats)
128+
}
129+
},
130+
(_, amount_msats) => amount_msats,
131+
};
132+
133+
if let Some(features) = &features {
134+
if features.requires_unknown_bits() {
135+
return Err(SemanticError::UnknownRequiredFeatures);
136+
}
137+
}
138+
139+
let quantity = match quantity.map(Into::into) {
140+
Some(quantity) if offer.is_valid_quantity(quantity) => Some(quantity),
141+
_ => return Err(SemanticError::InvalidQuantity),
142+
};
143+
144+
let payer_id = match payer_id {
145+
None => return Err(SemanticError::MissingPayerId),
146+
Some(payer_id) => payer_id,
147+
};
99148

100149
let payer_note = payer_note.map(Into::into);
101150

102151
let payer_info = payer_info.map(Into::into);
103152

104153
Ok(InvoiceRequestContents {
105-
offer, chain, amount_msat, features, quantity, payer_id, payer_note, payer_info,
154+
offer, chain, amount_msats, features, quantity, payer_id, payer_note, payer_info,
106155
signature,
107156
})
108157
}

lightning/src/offers/offer.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl Offer {
202202

203203
///
204204
pub fn amount(&self) -> Option<&Amount> {
205-
self.contents.amount.as_ref()
205+
self.contents.amount()
206206
}
207207

208208
///
@@ -252,6 +252,11 @@ impl Offer {
252252
self.contents.quantity_max()
253253
}
254254

255+
///
256+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
257+
self.contents.is_valid_quantity(quantity)
258+
}
259+
255260
///
256261
pub fn node_id(&self) -> PublicKey {
257262
Self::node_id_from_parts(self.contents.node_id, self.paths())
@@ -302,6 +307,10 @@ impl OfferContents {
302307
.unwrap_or_else(|| genesis_block(Network::Bitcoin).block_hash())
303308
}
304309

310+
pub fn amount(&self) -> Option<&Amount> {
311+
self.amount.as_ref()
312+
}
313+
305314
pub fn quantity_min(&self) -> u64 {
306315
self.quantity_min.unwrap_or(1)
307316
}
@@ -311,6 +320,14 @@ impl OfferContents {
311320
self.quantity_min.map_or(1, |_| u64::max_value()))
312321
}
313322

323+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
324+
if self.quantity_min.is_none() && self.quantity_max.is_none() {
325+
false
326+
} else {
327+
quantity >= self.quantity_min() && quantity <= self.quantity_max()
328+
}
329+
}
330+
314331
fn as_tlv_stream(&self) -> reference::OfferTlvStream {
315332
let (currency, amount) = match &self.amount {
316333
None => (None, None),

lightning/src/offers/parse.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
1212
use bitcoin::bech32;
1313
use bitcoin::bech32::{FromBase32, ToBase32};
14+
use bitcoin::secp256k1;
1415
use core::fmt;
1516
use ln::msgs::DecodeError;
1617
use util::ser::Readable;
@@ -68,6 +69,8 @@ pub enum ParseError {
6869
Decode(DecodeError),
6970
/// The parsed message has invalid semantics.
7071
InvalidSemantics(SemanticError),
72+
/// The parsed message has an invalid signature.
73+
InvalidSignature(secp256k1::Error),
7174
}
7275

7376
/// Error when interpreting a TLV stream as a specific type.
@@ -77,6 +80,12 @@ pub enum SemanticError {
7780
UnsupportedChain,
7881
/// A currency was provided without an amount.
7982
UnexpectedCurrency,
83+
///
84+
MissingAmount,
85+
///
86+
InsufficientAmount,
87+
///
88+
UnknownRequiredFeatures,
8089
/// A required description was not provided.
8190
MissingDescription,
8291
/// Either a node id or a blinded path was not provided.
@@ -85,6 +94,8 @@ pub enum SemanticError {
8594
MissingPaths,
8695
/// A quantity representing an empty range or that was outside of a valid range was provided.
8796
InvalidQuantity,
97+
///
98+
MissingPayerId,
8899
}
89100

90101
impl From<bech32::Error> for ParseError {
@@ -104,3 +115,9 @@ impl From<SemanticError> for ParseError {
104115
Self::InvalidSemantics(error)
105116
}
106117
}
118+
119+
impl From<secp256k1::Error> for ParseError {
120+
fn from(error: secp256k1::Error) -> Self {
121+
Self::InvalidSignature(error)
122+
}
123+
}

0 commit comments

Comments
 (0)