472e44f6220f1aed6b2c77c71252a49a1b7aff3b
[rust-lightning] / lightning / src / offers / parse.rs
1 // This file is Copyright its original authors, visible in version control
2 // history.
3 //
4 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7 // You may not use this file except in accordance with one or both of these
8 // licenses.
9
10 //! Parsing and formatting for bech32 message encoding.
11
12 use bitcoin::secp256k1;
13 use crate::io;
14 use crate::ln::msgs::DecodeError;
15 use crate::util::ser::SeekReadable;
16
17 #[allow(unused_imports)]
18 use crate::prelude::*;
19
20 #[cfg(not(fuzzing))]
21 pub(super) use sealed::Bech32Encode;
22
23 #[cfg(fuzzing)]
24 pub use sealed::Bech32Encode;
25
26 mod sealed {
27         use bech32::{FromBase32, ToBase32};
28         use core::fmt;
29         use super::Bolt12ParseError;
30
31         #[allow(unused_imports)]
32         use crate::prelude::*;
33
34         /// Indicates a message can be encoded using bech32.
35         pub trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error=Bolt12ParseError> {
36                 /// Human readable part of the message's bech32 encoding.
37                 const BECH32_HRP: &'static str;
38
39                 /// Parses a bech32-encoded message into a TLV stream.
40                 fn from_bech32_str(s: &str) -> Result<Self, Bolt12ParseError> {
41                         // Offer encoding may be split by '+' followed by optional whitespace.
42                         let encoded = match s.split('+').skip(1).next() {
43                                 Some(_) => {
44                                         for chunk in s.split('+') {
45                                                 let chunk = chunk.trim_start();
46                                                 if chunk.is_empty() || chunk.contains(char::is_whitespace) {
47                                                         return Err(Bolt12ParseError::InvalidContinuation);
48                                                 }
49                                         }
50
51                                         let s: String = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect();
52                                         Bech32String::Owned(s)
53                                 },
54                                 None => Bech32String::Borrowed(s),
55                         };
56
57                         let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;
58
59                         if hrp != Self::BECH32_HRP {
60                                 return Err(Bolt12ParseError::InvalidBech32Hrp);
61                         }
62
63                         let data = Vec::<u8>::from_base32(&data)?;
64                         Self::try_from(data)
65                 }
66
67                 /// Formats the message using bech32-encoding.
68                 fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
69                         bech32::encode_without_checksum_to_fmt(f, Self::BECH32_HRP, self.as_ref().to_base32())
70                                 .expect("HRP is invalid").unwrap();
71
72                         Ok(())
73                 }
74         }
75
76         // Used to avoid copying a bech32 string not containing the continuation character (+).
77         enum Bech32String<'a> {
78                 Borrowed(&'a str),
79                 Owned(String),
80         }
81
82         impl<'a> AsRef<str> for Bech32String<'a> {
83                 fn as_ref(&self) -> &str {
84                         match self {
85                                 Bech32String::Borrowed(s) => s,
86                                 Bech32String::Owned(s) => s,
87                         }
88                 }
89         }
90 }
91
92 /// A wrapper for reading a message as a TLV stream `T` from a byte sequence, while still
93 /// maintaining ownership of the bytes for later use.
94 pub(super) struct ParsedMessage<T: SeekReadable> {
95         pub bytes: Vec<u8>,
96         pub tlv_stream: T,
97 }
98
99 impl<T: SeekReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
100         type Error = DecodeError;
101
102         fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
103                 let mut cursor = io::Cursor::new(bytes);
104                 let tlv_stream: T = SeekReadable::read(&mut cursor)?;
105
106                 // Ensure that there are no more TLV records left to parse.
107                 if cursor.position() < cursor.get_ref().len() as u64 {
108                         return Err(DecodeError::InvalidValue);
109                 }
110
111                 let bytes = cursor.into_inner();
112                 Ok(Self { bytes, tlv_stream })
113         }
114 }
115
116 /// Error when parsing a bech32 encoded message using [`str::parse`].
117 #[derive(Clone, Debug, PartialEq)]
118 pub enum Bolt12ParseError {
119         /// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
120         /// across multiple parts (i.e., '+' followed by whitespace).
121         InvalidContinuation,
122         /// The bech32 encoding's human-readable part does not match what was expected for the message
123         /// being parsed.
124         InvalidBech32Hrp,
125         /// The string could not be bech32 decoded.
126         Bech32(bech32::Error),
127         /// The bech32 decoded string could not be decoded as the expected message type.
128         Decode(DecodeError),
129         /// The parsed message has invalid semantics.
130         InvalidSemantics(Bolt12SemanticError),
131         /// The parsed message has an invalid signature.
132         InvalidSignature(secp256k1::Error),
133 }
134
135 /// Error when interpreting a TLV stream as a specific type.
136 #[derive(Clone, Debug, PartialEq)]
137 pub enum Bolt12SemanticError {
138         /// The current [`std::time::SystemTime`] is past the offer or invoice's expiration.
139         AlreadyExpired,
140         /// The provided chain hash does not correspond to a supported chain.
141         UnsupportedChain,
142         /// A chain was provided but was not expected.
143         UnexpectedChain,
144         /// An amount was expected but was missing.
145         MissingAmount,
146         /// The amount exceeded the total bitcoin supply.
147         InvalidAmount,
148         /// An amount was provided but was not sufficient in value.
149         InsufficientAmount,
150         /// An amount was provided but was not expected.
151         UnexpectedAmount,
152         /// A currency was provided that is not supported.
153         UnsupportedCurrency,
154         /// A feature was required but is unknown.
155         UnknownRequiredFeatures,
156         /// Features were provided but were not expected.
157         UnexpectedFeatures,
158         /// A required description was not provided.
159         MissingDescription,
160         /// A signing pubkey was not provided.
161         MissingSigningPubkey,
162         /// A signing pubkey was provided but a different one was expected.
163         InvalidSigningPubkey,
164         /// A signing pubkey was provided but was not expected.
165         UnexpectedSigningPubkey,
166         /// A quantity was expected but was missing.
167         MissingQuantity,
168         /// An unsupported quantity was provided.
169         InvalidQuantity,
170         /// A quantity or quantity bounds was provided but was not expected.
171         UnexpectedQuantity,
172         /// Metadata could not be used to verify the offers message.
173         InvalidMetadata,
174         /// Metadata was provided but was not expected.
175         UnexpectedMetadata,
176         /// Payer metadata was expected but was missing.
177         MissingPayerMetadata,
178         /// A payer id was expected but was missing.
179         MissingPayerId,
180         /// The payment id for a refund or request is already in use.
181         DuplicatePaymentId,
182         /// Blinded paths were expected but were missing.
183         MissingPaths,
184         /// Blinded paths were provided but were not expected.
185         UnexpectedPaths,
186         /// The blinded payinfo given does not match the number of blinded path hops.
187         InvalidPayInfo,
188         /// An invoice creation time was expected but was missing.
189         MissingCreationTime,
190         /// An invoice payment hash was expected but was missing.
191         MissingPaymentHash,
192         /// A signature was expected but was missing.
193         MissingSignature,
194 }
195
196 impl From<bech32::Error> for Bolt12ParseError {
197         fn from(error: bech32::Error) -> Self {
198                 Self::Bech32(error)
199         }
200 }
201
202 impl From<DecodeError> for Bolt12ParseError {
203         fn from(error: DecodeError) -> Self {
204                 Self::Decode(error)
205         }
206 }
207
208 impl From<Bolt12SemanticError> for Bolt12ParseError {
209         fn from(error: Bolt12SemanticError) -> Self {
210                 Self::InvalidSemantics(error)
211         }
212 }
213
214 impl From<secp256k1::Error> for Bolt12ParseError {
215         fn from(error: secp256k1::Error) -> Self {
216                 Self::InvalidSignature(error)
217         }
218 }
219
220 #[cfg(test)]
221 mod bolt12_tests {
222         use super::Bolt12ParseError;
223         use crate::offers::offer::Offer;
224
225         #[test]
226         fn encodes_offer_as_bech32_without_checksum() {
227                 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
228                 let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
229                 let reencoded_offer = offer.to_string();
230                 dbg!(reencoded_offer.parse::<Offer>().unwrap());
231                 assert_eq!(reencoded_offer, encoded_offer);
232         }
233
234         #[test]
235         fn parses_bech32_encoded_offers() {
236                 let offers = [
237                         // A complete string is valid
238                         "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
239
240                         // + can join anywhere
241                         "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
242
243                         // Multiple + can join
244                         "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
245
246                         // + can be followed by whitespace
247                         "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+  5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
248                 ];
249                 for encoded_offer in &offers {
250                         if let Err(e) = encoded_offer.parse::<Offer>() {
251                                 panic!("Invalid offer ({:?}): {}", e, encoded_offer);
252                         }
253                 }
254         }
255
256         #[test]
257         fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
258                 let offers = [
259                         // + must be surrounded by bech32 characters
260                         "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
261                         "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
262                         "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
263                         "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
264                         "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
265                 ];
266                 for encoded_offer in &offers {
267                         match encoded_offer.parse::<Offer>() {
268                                 Ok(_) => panic!("Valid offer: {}", encoded_offer),
269                                 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
270                         }
271                 }
272         }
273 }
274
275 #[cfg(test)]
276 mod tests {
277         use super::Bolt12ParseError;
278         use crate::ln::msgs::DecodeError;
279         use crate::offers::offer::Offer;
280
281         #[test]
282         fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
283                 let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
284                 match encoded_offer.parse::<Offer>() {
285                         Ok(_) => panic!("Valid offer: {}", encoded_offer),
286                         Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
287                 }
288         }
289
290         #[test]
291         fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
292                 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
293                 match encoded_offer.parse::<Offer>() {
294                         Ok(_) => panic!("Valid offer: {}", encoded_offer),
295                         Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
296                 }
297         }
298
299         #[test]
300         fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
301                 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
302                 match encoded_offer.parse::<Offer>() {
303                         Ok(_) => panic!("Valid offer: {}", encoded_offer),
304                         Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
305                 }
306         }
307 }