Skip to content

Commit 8e2e0a2

Browse files
committed
WIP: Offer semantic checks
1 parent 87c8211 commit 8e2e0a2

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

lightning/src/offers/mod.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use bitcoin::bech32::FromBase32;
1515
use bitcoin::hash_types::BlockHash;
1616
use bitcoin::secp256k1::{PublicKey, XOnlyPublicKey};
1717
use bitcoin::secp256k1::schnorr::Signature;
18+
use core::convert::TryFrom;
1819
use core::str::FromStr;
1920
use core::time::Duration;
2021
use ln::PaymentHash;
@@ -188,6 +189,25 @@ pub enum ParseError {
188189
Bech32(bech32::Error),
189190
/// The bech32 decoded string could not be decoded as the expected message type.
190191
Decode(DecodeError),
192+
/// The parsed message has invalid semantics.
193+
InvalidSemantics(SemanticError),
194+
}
195+
196+
#[derive(Debug, PartialEq)]
197+
///
198+
pub enum SemanticError {
199+
///
200+
MissingDescription,
201+
///
202+
MissingDestination,
203+
///
204+
DuplicateDestination,
205+
///
206+
MissingPaths,
207+
///
208+
InvalidQuantity,
209+
///
210+
UnexpectedRefund,
191211
}
192212

193213
impl From<bech32::Error> for ParseError {
@@ -202,6 +222,69 @@ impl From<DecodeError> for ParseError {
202222
}
203223
}
204224

225+
impl From<SemanticError> for ParseError {
226+
fn from(error: SemanticError) -> Self {
227+
Self::InvalidSemantics(error)
228+
}
229+
}
230+
231+
impl FromStr for Offer {
232+
type Err = ParseError;
233+
234+
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
235+
let tlv_stream = OfferTlvStream::from_str(s)?;
236+
Ok(Offer::try_from(tlv_stream)?)
237+
}
238+
}
239+
240+
impl TryFrom<OfferTlvStream> for Offer {
241+
type Error = SemanticError;
242+
243+
fn try_from(tlv_stream: OfferTlvStream) -> Result<Self, Self::Error> {
244+
if tlv_stream.description.is_none() {
245+
return Err(SemanticError::MissingDescription);
246+
}
247+
248+
if tlv_stream.node_id.is_none() && tlv_stream.paths.is_none() {
249+
return Err(SemanticError::MissingDestination);
250+
}
251+
252+
if tlv_stream.node_id.is_some() && tlv_stream.paths.is_some() {
253+
return Err(SemanticError::DuplicateDestination);
254+
}
255+
256+
if let Some(WithoutLength(ref paths)) = tlv_stream.paths {
257+
if paths.is_empty() {
258+
return Err(SemanticError::MissingPaths);
259+
}
260+
}
261+
262+
if let Some(HighZeroBytesDroppedVarInt(quantity_min)) = tlv_stream.quantity_min {
263+
if quantity_min < 1 {
264+
return Err(SemanticError::InvalidQuantity);
265+
}
266+
267+
if let Some(HighZeroBytesDroppedVarInt(quantity_max)) = tlv_stream.quantity_max {
268+
if quantity_min > quantity_max {
269+
return Err(SemanticError::InvalidQuantity);
270+
}
271+
}
272+
}
273+
274+
if let Some(HighZeroBytesDroppedVarInt(quantity_max)) = tlv_stream.quantity_max {
275+
if quantity_max < 1 {
276+
return Err(SemanticError::InvalidQuantity);
277+
}
278+
}
279+
280+
if tlv_stream.refund_for.is_some() && tlv_stream.send_invoice.is_none() {
281+
return Err(SemanticError::UnexpectedRefund);
282+
}
283+
284+
Ok(Offer { tlv_stream })
285+
}
286+
}
287+
205288
const OFFER_BECH32_HRP: &str = "lno";
206289

207290
impl FromStr for OfferTlvStream {

0 commit comments

Comments
 (0)