1use crate::io;
13use crate::ln::msgs::DecodeError;
14use crate::util::ser::CursorReadable;
15use bech32::primitives::decode::CheckedHrpstringError;
16use bitcoin::secp256k1;
17
18#[allow(unused_imports)]
19use crate::prelude::*;
20
21#[cfg(not(fuzzing))]
22pub(super) use sealed::Bech32Encode;
23
24#[cfg(fuzzing)]
25pub use sealed::Bech32Encode;
26
27mod sealed {
28 use super::Bolt12ParseError;
29 use bech32::primitives::decode::CheckedHrpstring;
30 use bech32::{encode_to_fmt, EncodeError, Hrp, NoChecksum};
31 use core::fmt;
32
33 #[allow(unused_imports)]
34 use crate::prelude::*;
35
36 pub trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error = Bolt12ParseError> {
38 const BECH32_HRP: &'static str;
40
41 fn from_bech32_str(s: &str) -> Result<Self, Bolt12ParseError> {
43 let encoded = match s.split('+').skip(1).next() {
45 Some(_) => {
46 let mut chunks = s.split('+');
47
48 if let Some(first_chunk) = chunks.next() {
50 if first_chunk.contains(char::is_whitespace) {
51 return Err(Bolt12ParseError::InvalidLeadingWhitespace);
52 }
53 if first_chunk.is_empty() {
54 return Err(Bolt12ParseError::InvalidContinuation);
55 }
56 }
57
58 for chunk in chunks {
60 let chunk = chunk.trim_start();
61 if chunk.is_empty() || chunk.contains(char::is_whitespace) {
62 return Err(Bolt12ParseError::InvalidContinuation);
63 }
64 }
65
66 let s: String = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect();
67 Bech32String::Owned(s)
68 },
69 None => Bech32String::Borrowed(s),
70 };
71
72 let parsed = CheckedHrpstring::new::<NoChecksum>(encoded.as_ref())?;
73 let hrp = parsed.hrp();
74 if hrp.lowercase_char_iter().ne(Self::BECH32_HRP.chars()) {
76 return Err(Bolt12ParseError::InvalidBech32Hrp);
77 }
78
79 let data = parsed.byte_iter().collect::<Vec<u8>>();
80 Self::try_from(data)
81 }
82
83 fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
85 encode_to_fmt::<NoChecksum, _>(f, Hrp::parse(Self::BECH32_HRP).unwrap(), self.as_ref())
86 .map_err(|e| match e {
87 EncodeError::Fmt(e) => e,
88 _ => fmt::Error {},
89 })
90 }
91 }
92
93 enum Bech32String<'a> {
95 Borrowed(&'a str),
96 Owned(String),
97 }
98
99 impl<'a> AsRef<str> for Bech32String<'a> {
100 fn as_ref(&self) -> &str {
101 match self {
102 Bech32String::Borrowed(s) => s,
103 Bech32String::Owned(s) => s,
104 }
105 }
106 }
107}
108
109pub(super) struct ParsedMessage<T: CursorReadable> {
112 pub bytes: Vec<u8>,
113 pub tlv_stream: T,
114}
115
116impl<T: CursorReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
117 type Error = DecodeError;
118
119 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
120 let mut cursor = io::Cursor::new(bytes);
121 let tlv_stream: T = CursorReadable::read(&mut cursor)?;
122
123 if cursor.position() < cursor.get_ref().len() as u64 {
125 return Err(DecodeError::InvalidValue);
126 }
127
128 let bytes = cursor.into_inner();
129 Ok(Self { bytes, tlv_stream })
130 }
131}
132
133#[derive(Clone, Debug, PartialEq)]
135pub enum Bolt12ParseError {
136 InvalidContinuation,
139 InvalidLeadingWhitespace,
141 InvalidBech32Hrp,
144 Bech32(
146 CheckedHrpstringError,
148 ),
149 Decode(DecodeError),
151 InvalidSemantics(Bolt12SemanticError),
153 InvalidSignature(secp256k1::Error),
155}
156
157#[derive(Clone, Debug, PartialEq)]
159pub enum Bolt12SemanticError {
160 AlreadyExpired,
162 UnsupportedChain,
164 UnexpectedChain,
166 MissingAmount,
168 InvalidAmount,
170 InvalidCurrencyCode,
172 InsufficientAmount,
174 UnexpectedAmount,
176 UnsupportedCurrency,
178 UnknownRequiredFeatures,
180 UnexpectedFeatures,
182 MissingDescription,
184 MissingIssuerSigningPubkey,
186 UnexpectedIssuerSigningPubkey,
188 MissingQuantity,
190 InvalidQuantity,
192 UnexpectedQuantity,
194 InvalidMetadata,
196 UnexpectedMetadata,
198 MissingPayerMetadata,
200 MissingPayerSigningPubkey,
202 DuplicatePaymentId,
204 MissingPaths,
206 UnexpectedPaths,
208 InvalidPayInfo,
210 MissingCreationTime,
212 MissingPaymentHash,
214 UnexpectedPaymentHash,
216 MissingSigningPubkey,
218 InvalidSigningPubkey,
220 MissingSignature,
222 UnexpectedHumanReadableName,
227}
228
229impl From<CheckedHrpstringError> for Bolt12ParseError {
230 fn from(error: CheckedHrpstringError) -> Self {
231 Self::Bech32(error)
232 }
233}
234
235impl From<DecodeError> for Bolt12ParseError {
236 fn from(error: DecodeError) -> Self {
237 Self::Decode(error)
238 }
239}
240
241impl From<Bolt12SemanticError> for Bolt12ParseError {
242 fn from(error: Bolt12SemanticError) -> Self {
243 Self::InvalidSemantics(error)
244 }
245}
246
247impl From<secp256k1::Error> for Bolt12ParseError {
248 fn from(error: secp256k1::Error) -> Self {
249 Self::InvalidSignature(error)
250 }
251}
252
253#[cfg(test)]
254mod bolt12_tests {
255 use super::Bolt12ParseError;
256 use crate::offers::offer::Offer;
257 use bech32::primitives::decode::{CharError, CheckedHrpstringError, UncheckedHrpstringError};
258
259 #[test]
260 fn encodes_offer_as_bech32_without_checksum() {
261 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
262 let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
263 let reencoded_offer = offer.to_string();
264 dbg!(reencoded_offer.parse::<Offer>().unwrap());
265 assert_eq!(reencoded_offer, encoded_offer);
266 }
267
268 #[test]
269 fn parses_bech32_encoded_offers() {
270 let offers = [
271 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
273
274 "LNO1PQPS7SJQPGTYZM3QV4UXZMTSD3JJQER9WD3HY6TSW35K7MSJZFPY7NZ5YQCNYGRFDEJ82UM5WF5K2UCKYYPWA3EYT44H6TXTXQUQH7LZ5DJGE4AFGFJN7K4RGRKUAG0JSD5XVXG",
276
277 "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
279
280 "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
282
283 "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
285 ];
286 for encoded_offer in &offers {
287 if let Err(e) = encoded_offer.parse::<Offer>() {
288 panic!("Invalid offer ({:?}): {}", e, encoded_offer);
289 }
290 }
291 }
292
293 #[test]
294 fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
295 let offers = [
296 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
298 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
299 "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
300 "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
301 "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
302 ];
303 for encoded_offer in &offers {
304 match encoded_offer.parse::<Offer>() {
305 Ok(_) => panic!("Valid offer: {}", encoded_offer),
306 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
307 }
308 }
309 }
310
311 #[test]
312 fn fails_parsing_bech32_encoded_offers_with_mixed_casing() {
313 let mixed_case_offer = "LnO1PqPs7sJqPgTyZm3qV4UxZmTsD3JjQeR9Wd3hY6TsW35k7mSjZfPy7nZ5YqCnYgRfDeJ82uM5Wf5k2uCkYyPwA3EyT44h6tXtXqUqH7Lz5dJgE4AfGfJn7k4rGrKuAg0jSd5xVxG";
315 match mixed_case_offer.parse::<Offer>() {
316 Ok(_) => panic!("Valid offer: {}", mixed_case_offer),
317 Err(e) => assert_eq!(
318 e,
319 Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(
320 UncheckedHrpstringError::Char(CharError::MixedCase)
321 ))
322 ),
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::Bolt12ParseError;
330 use crate::ln::msgs::DecodeError;
331 use crate::offers::offer::Offer;
332 use bech32::primitives::decode::{CharError, CheckedHrpstringError, UncheckedHrpstringError};
333
334 #[test]
335 fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
336 let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
337 match encoded_offer.parse::<Offer>() {
338 Ok(_) => panic!("Valid offer: {}", encoded_offer),
339 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
340 }
341 }
342
343 #[test]
344 fn fails_parsing_bech32_encoded_offer_with_leading_whitespace() {
345 let encoded_offer = "\u{b}lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah+\u{b}\u{b}\u{b}\u{b}82ru5rdpnpj";
346 match encoded_offer.parse::<Offer>() {
347 Ok(_) => panic!("Valid offer: {}", encoded_offer),
348 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidLeadingWhitespace),
349 }
350 }
351
352 #[test]
353 fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
354 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
355 match encoded_offer.parse::<Offer>() {
356 Ok(_) => panic!("Valid offer: {}", encoded_offer),
357 Err(e) => assert_eq!(
358 e,
359 Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(
360 UncheckedHrpstringError::Char(CharError::InvalidChar('o'))
361 ))
362 ),
363 }
364 }
365
366 #[test]
367 fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
368 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
369 match encoded_offer.parse::<Offer>() {
370 Ok(_) => panic!("Valid offer: {}", encoded_offer),
371 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
372 }
373 }
374}