Skip to content

Commit fa76a49

Browse files
committed
f - invoice_request semantic and signature checks
1 parent 492704c commit fa76a49

File tree

4 files changed

+89
-11
lines changed

4 files changed

+89
-11
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use core::convert::TryFrom;
1616
use core::str::FromStr;
1717
use ln::features::OfferFeatures;
1818
use offers::PayerTlvStream;
19-
use offers::merkle::SignatureTlvStream;
20-
use offers::offer::{OfferContents, OfferTlvStream};
19+
use offers::merkle::{SignatureTlvStream, self};
20+
use offers::offer::{Amount, OfferContents, OfferTlvStream};
2121
use offers::parse::{Bech32Encode, ParseError, SemanticError};
2222

2323
///
@@ -30,10 +30,10 @@ pub struct InvoiceRequest {
3030
pub(crate) struct InvoiceRequestContents {
3131
offer: OfferContents,
3232
chain: Option<BlockHash>,
33-
amount_msat: Option<u64>,
33+
amount_msats: Option<u64>,
3434
features: Option<OfferFeatures>,
3535
quantity: Option<u64>,
36-
payer_id: Option<PublicKey>,
36+
payer_id: PublicKey,
3737
payer_note: Option<String>,
3838
payer_info: Option<Vec<u8>>,
3939
signature: Option<Signature>,
@@ -69,6 +69,12 @@ impl FromStr for InvoiceRequest {
6969
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
7070
let (tlv_stream, bytes) = InvoiceRequest::from_bech32_str(s)?;
7171
let contents = InvoiceRequestContents::try_from(tlv_stream)?;
72+
73+
if let Some(signature) = &contents.signature {
74+
let tag = concat!("lightning", "invoice_request", "signature");
75+
merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
76+
}
77+
7278
Ok(InvoiceRequest { bytes, contents })
7379
}
7480
}
@@ -92,17 +98,43 @@ impl TryFrom<FullInvoiceRequestTlvStream> for InvoiceRequestContents {
9298
Some(_) => return Err(SemanticError::UnsupportedChain),
9399
};
94100

95-
// TODO: Check remaining fields against the reflected offer
96-
let amount_msat = amount.map(Into::into);
101+
// TODO: Determine whether quantity should be accounted for
102+
let amount_msats = match (offer.amount(), amount.map(Into::into)) {
103+
// TODO: Handle currency case
104+
(Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnexpectedCurrency),
105+
(Some(_), None) => return Err(SemanticError::MissingAmount),
106+
(Some(Amount::Bitcoin { amount_msats: offer_amount_msats }), Some(amount_msats)) => {
107+
if amount_msats < *offer_amount_msats {
108+
return Err(SemanticError::InsufficientAmount);
109+
} else {
110+
Some(amount_msats)
111+
}
112+
},
113+
(_, amount_msats) => amount_msats,
114+
};
115+
116+
if let Some(features) = &features {
117+
if features.requires_unknown_bits() {
118+
return Err(SemanticError::UnknownRequiredFeatures);
119+
}
120+
}
97121

98-
let quantity = quantity.map(Into::into);
122+
let quantity = match quantity.map(Into::into) {
123+
Some(quantity) if offer.is_valid_quantity(quantity) => Some(quantity),
124+
_ => return Err(SemanticError::InvalidQuantity),
125+
};
126+
127+
let payer_id = match payer_id {
128+
None => return Err(SemanticError::MissingPayerId),
129+
Some(payer_id) => payer_id,
130+
};
99131

100132
let payer_note = payer_note.map(Into::into);
101133

102134
let payer_info = payer_info.map(Into::into);
103135

104136
Ok(InvoiceRequestContents {
105-
offer, chain, amount_msat, features, quantity, payer_id, payer_note, payer_info,
137+
offer, chain, amount_msats, features, quantity, payer_id, payer_note, payer_info,
106138
signature,
107139
})
108140
}

lightning/src/offers/merkle.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
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};
1314
use bitcoin::secp256k1::schnorr::Signature;
1415
use util::ser::{BigSize, Readable};
1516

@@ -19,7 +20,18 @@ tlv_stream!(struct SignatureTlvStream {
1920
(240, signature: Signature),
2021
});
2122

22-
pub(super) fn root_hash(data: &[u8]) -> sha256::Hash {
23+
pub(super) fn verify_signature(
24+
signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
25+
) -> 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();
29+
let pubkey = pubkey.into();
30+
let secp_ctx = Secp256k1::verification_only();
31+
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
32+
}
33+
34+
fn root_hash(data: &[u8]) -> sha256::Hash {
2335
let mut tlv_stream = TlvStream::new(&data[..]).peekable();
2436
let nonce_tag = tagged_hash_engine(sha256::Hash::from_engine({
2537
let mut engine = sha256::Hash::engine();
@@ -55,7 +67,7 @@ pub(super) fn root_hash(data: &[u8]) -> sha256::Hash {
5567
*leaves.first().unwrap()
5668
}
5769

58-
pub(super) fn tagged_hash<T: AsRef<[u8]>>(tag: sha256::Hash, msg: T) -> sha256::Hash {
70+
fn tagged_hash<T: AsRef<[u8]>>(tag: sha256::Hash, msg: T) -> sha256::Hash {
5971
let engine = tagged_hash_engine(tag);
6072
tagged_hash_from_engine(engine, msg)
6173
}

lightning/src/offers/offer.rs

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

184184
///
185185
pub fn amount(&self) -> Option<&Amount> {
186-
self.contents.amount.as_ref()
186+
self.contents.amount()
187187
}
188188

189189
///
@@ -233,6 +233,11 @@ impl Offer {
233233
self.contents.quantity_max()
234234
}
235235

236+
///
237+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
238+
self.contents.is_valid_quantity(quantity)
239+
}
240+
236241
///
237242
pub fn node_id(&self) -> PublicKey {
238243
self.contents.node_id.unwrap()
@@ -271,6 +276,10 @@ impl OfferContents {
271276
.unwrap_or_else(|| genesis_block(Network::Bitcoin).block_hash())
272277
}
273278

279+
pub fn amount(&self) -> Option<&Amount> {
280+
self.amount.as_ref()
281+
}
282+
274283
pub fn quantity_min(&self) -> u64 {
275284
self.quantity_min.unwrap_or(1)
276285
}
@@ -280,6 +289,14 @@ impl OfferContents {
280289
self.quantity_min.map_or(1, |_| u64::max_value()))
281290
}
282291

292+
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
293+
if self.quantity_min.is_none() && self.quantity_max.is_none() {
294+
false
295+
} else {
296+
quantity >= self.quantity_min() && quantity <= self.quantity_max()
297+
}
298+
}
299+
283300
fn as_tlv_stream(&self) -> reference::OfferTlvStream {
284301
let (currency, amount) = match &self.amount {
285302
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
/// A node id 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)