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