1use bitcoin::secp256k1;
13use crate::io;
14use crate::ln::msgs::DecodeError;
15use crate::util::ser::CursorReadable;
16use bech32::primitives::decode::CheckedHrpstringError;
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 bech32::{EncodeError, Hrp, NoChecksum, encode_to_fmt};
29 use bech32::primitives::decode::CheckedHrpstring;
30 use core::fmt;
31 use super::Bolt12ParseError;
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 for chunk in s.split('+') {
47 let chunk = chunk.trim_start();
48 if chunk.is_empty() || chunk.contains(char::is_whitespace) {
49 return Err(Bolt12ParseError::InvalidContinuation);
50 }
51 }
52
53 let s: String = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect();
54 Bech32String::Owned(s)
55 },
56 None => Bech32String::Borrowed(s),
57 };
58
59 let parsed = CheckedHrpstring::new::<NoChecksum>(encoded.as_ref())?;
60 let hrp = parsed.hrp();
61 if hrp.lowercase_char_iter().ne(Self::BECH32_HRP.chars()) {
63 return Err(Bolt12ParseError::InvalidBech32Hrp);
64 }
65
66 let data = parsed.byte_iter().collect::<Vec<u8>>();
67 Self::try_from(data)
68 }
69
70 fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
72 encode_to_fmt::<NoChecksum, _>(f, Hrp::parse(Self::BECH32_HRP).unwrap(), self.as_ref())
73 .map_err(|e| match e {
74 EncodeError::Fmt(e) => e,
75 _ => fmt::Error {},
76 })
77 }
78 }
79
80 enum Bech32String<'a> {
82 Borrowed(&'a str),
83 Owned(String),
84 }
85
86 impl<'a> AsRef<str> for Bech32String<'a> {
87 fn as_ref(&self) -> &str {
88 match self {
89 Bech32String::Borrowed(s) => s,
90 Bech32String::Owned(s) => s,
91 }
92 }
93 }
94}
95
96pub(super) struct ParsedMessage<T: CursorReadable> {
99 pub bytes: Vec<u8>,
100 pub tlv_stream: T,
101}
102
103impl<T: CursorReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
104 type Error = DecodeError;
105
106 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
107 let mut cursor = io::Cursor::new(bytes);
108 let tlv_stream: T = CursorReadable::read(&mut cursor)?;
109
110 if cursor.position() < cursor.get_ref().len() as u64 {
112 return Err(DecodeError::InvalidValue);
113 }
114
115 let bytes = cursor.into_inner();
116 Ok(Self { bytes, tlv_stream })
117 }
118}
119
120#[derive(Clone, Debug, PartialEq)]
122pub enum Bolt12ParseError {
123 InvalidContinuation,
126 InvalidBech32Hrp,
129 Bech32(CheckedHrpstringError),
131 Decode(DecodeError),
133 InvalidSemantics(Bolt12SemanticError),
135 InvalidSignature(secp256k1::Error),
137}
138
139#[derive(Clone, Debug, PartialEq)]
141pub enum Bolt12SemanticError {
142 AlreadyExpired,
144 UnsupportedChain,
146 UnexpectedChain,
148 MissingAmount,
150 InvalidAmount,
152 InsufficientAmount,
154 UnexpectedAmount,
156 UnsupportedCurrency,
158 UnknownRequiredFeatures,
160 UnexpectedFeatures,
162 MissingDescription,
164 MissingIssuerSigningPubkey,
166 UnexpectedIssuerSigningPubkey,
168 MissingQuantity,
170 InvalidQuantity,
172 UnexpectedQuantity,
174 InvalidMetadata,
176 UnexpectedMetadata,
178 MissingPayerMetadata,
180 MissingPayerSigningPubkey,
182 DuplicatePaymentId,
184 MissingPaths,
186 UnexpectedPaths,
188 InvalidPayInfo,
190 MissingCreationTime,
192 MissingPaymentHash,
194 UnexpectedPaymentHash,
196 MissingSigningPubkey,
198 InvalidSigningPubkey,
200 MissingSignature,
202 UnexpectedHumanReadableName,
207}
208
209impl From<CheckedHrpstringError> for Bolt12ParseError {
210 fn from(error: CheckedHrpstringError) -> Self {
211 Self::Bech32(error)
212 }
213}
214
215impl From<DecodeError> for Bolt12ParseError {
216 fn from(error: DecodeError) -> Self {
217 Self::Decode(error)
218 }
219}
220
221impl From<Bolt12SemanticError> for Bolt12ParseError {
222 fn from(error: Bolt12SemanticError) -> Self {
223 Self::InvalidSemantics(error)
224 }
225}
226
227impl From<secp256k1::Error> for Bolt12ParseError {
228 fn from(error: secp256k1::Error) -> Self {
229 Self::InvalidSignature(error)
230 }
231}
232
233#[cfg(test)]
234mod bolt12_tests {
235 use super::Bolt12ParseError;
236 use crate::offers::offer::Offer;
237 use bech32::primitives::decode::{CheckedHrpstringError, UncheckedHrpstringError, CharError};
238
239 #[test]
240 fn encodes_offer_as_bech32_without_checksum() {
241 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
242 let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
243 let reencoded_offer = offer.to_string();
244 dbg!(reencoded_offer.parse::<Offer>().unwrap());
245 assert_eq!(reencoded_offer, encoded_offer);
246 }
247
248 #[test]
249 fn parses_bech32_encoded_offers() {
250 let offers = [
251 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
253
254 "LNO1PQPS7SJQPGTYZM3QV4UXZMTSD3JJQER9WD3HY6TSW35K7MSJZFPY7NZ5YQCNYGRFDEJ82UM5WF5K2UCKYYPWA3EYT44H6TXTXQUQH7LZ5DJGE4AFGFJN7K4RGRKUAG0JSD5XVXG",
256
257 "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
259
260 "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
262
263 "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
265 ];
266 for encoded_offer in &offers {
267 if let Err(e) = encoded_offer.parse::<Offer>() {
268 panic!("Invalid offer ({:?}): {}", e, encoded_offer);
269 }
270 }
271 }
272
273 #[test]
274 fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
275 let offers = [
276 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
278 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
279 "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
280 "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
281 "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
282 ];
283 for encoded_offer in &offers {
284 match encoded_offer.parse::<Offer>() {
285 Ok(_) => panic!("Valid offer: {}", encoded_offer),
286 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
287 }
288 }
289 }
290
291 #[test]
292 fn fails_parsing_bech32_encoded_offers_with_mixed_casing() {
293 let mixed_case_offer = "LnO1PqPs7sJqPgTyZm3qV4UxZmTsD3JjQeR9Wd3hY6TsW35k7mSjZfPy7nZ5YqCnYgRfDeJ82uM5Wf5k2uCkYyPwA3EyT44h6tXtXqUqH7Lz5dJgE4AfGfJn7k4rGrKuAg0jSd5xVxG";
295 match mixed_case_offer.parse::<Offer>() {
296 Ok(_) => panic!("Valid offer: {}", mixed_case_offer),
297 Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MixedCase)))),
298 }
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::Bolt12ParseError;
305 use crate::ln::msgs::DecodeError;
306 use crate::offers::offer::Offer;
307 use bech32::primitives::decode::{CharError, CheckedHrpstringError, UncheckedHrpstringError};
308
309 #[test]
310 fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
311 let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
312 match encoded_offer.parse::<Offer>() {
313 Ok(_) => panic!("Valid offer: {}", encoded_offer),
314 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
315 }
316 }
317
318 #[test]
319 fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
320 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
321 match encoded_offer.parse::<Offer>() {
322 Ok(_) => panic!("Valid offer: {}", encoded_offer),
323 Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::InvalidChar('o'))))),
324 }
325 }
326
327 #[test]
328 fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
329 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
330 match encoded_offer.parse::<Offer>() {
331 Ok(_) => panic!("Valid offer: {}", encoded_offer),
332 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
333 }
334 }
335}