Skip to content

Commit cfc8b06

Browse files
committed
WIP: Offer semantic checks
1 parent 531a111 commit cfc8b06

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;
@@ -189,6 +190,25 @@ pub enum ParseError {
189190
Bech32(bech32::Error),
190191
/// The bech32 decoded string could not be decoded as the expected message type.
191192
Decode(DecodeError),
193+
/// The parsed message has invalid semantics.
194+
InvalidSemantics(SemanticError),
195+
}
196+
197+
#[derive(Debug, PartialEq)]
198+
///
199+
pub enum SemanticError {
200+
///
201+
MissingDescription,
202+
///
203+
MissingDestination,
204+
///
205+
DuplicateDestination,
206+
///
207+
MissingPaths,
208+
///
209+
InvalidQuantity,
210+
///
211+
UnexpectedRefund,
192212
}
193213

194214
impl From<bech32::Error> for ParseError {
@@ -203,6 +223,69 @@ impl From<DecodeError> for ParseError {
203223
}
204224
}
205225

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

208291
impl FromStr for OfferTlvStream {

0 commit comments

Comments
 (0)