1 // This file is Copyright its original authors, visible in version control
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
10 //! Parsing and formatting for bech32 message encoding.
12 use bitcoin::secp256k1;
14 use crate::ln::msgs::DecodeError;
15 use crate::util::ser::SeekReadable;
17 #[allow(unused_imports)]
18 use crate::prelude::*;
21 pub(super) use sealed::Bech32Encode;
24 pub use sealed::Bech32Encode;
27 use bech32::{FromBase32, ToBase32};
29 use super::Bolt12ParseError;
31 #[allow(unused_imports)]
32 use crate::prelude::*;
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;
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() {
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);
51 let s: String = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect();
52 Bech32String::Owned(s)
54 None => Bech32String::Borrowed(s),
57 let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;
59 if hrp != Self::BECH32_HRP {
60 return Err(Bolt12ParseError::InvalidBech32Hrp);
63 let data = Vec::<u8>::from_base32(&data)?;
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();
76 // Used to avoid copying a bech32 string not containing the continuation character (+).
77 enum Bech32String<'a> {
82 impl<'a> AsRef<str> for Bech32String<'a> {
83 fn as_ref(&self) -> &str {
85 Bech32String::Borrowed(s) => s,
86 Bech32String::Owned(s) => s,
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> {
99 impl<T: SeekReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
100 type Error = DecodeError;
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)?;
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);
111 let bytes = cursor.into_inner();
112 Ok(Self { bytes, tlv_stream })
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).
122 /// The bech32 encoding's human-readable part does not match what was expected for the message
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.
129 /// The parsed message has invalid semantics.
130 InvalidSemantics(Bolt12SemanticError),
131 /// The parsed message has an invalid signature.
132 InvalidSignature(secp256k1::Error),
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.
140 /// The provided chain hash does not correspond to a supported chain.
142 /// A chain was provided but was not expected.
144 /// An amount was expected but was missing.
146 /// The amount exceeded the total bitcoin supply.
148 /// An amount was provided but was not sufficient in value.
150 /// An amount was provided but was not expected.
152 /// A currency was provided that is not supported.
154 /// A feature was required but is unknown.
155 UnknownRequiredFeatures,
156 /// Features were provided but were not expected.
158 /// A required description was not provided.
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.
168 /// An unsupported quantity was provided.
170 /// A quantity or quantity bounds was provided but was not expected.
172 /// Metadata could not be used to verify the offers message.
174 /// Metadata was provided but was not expected.
176 /// Payer metadata was expected but was missing.
177 MissingPayerMetadata,
178 /// A payer id was expected but was missing.
180 /// The payment id for a refund or request is already in use.
182 /// Blinded paths were expected but were missing.
184 /// Blinded paths were provided but were not expected.
186 /// The blinded payinfo given does not match the number of blinded path hops.
188 /// An invoice creation time was expected but was missing.
190 /// An invoice payment hash was expected but was missing.
192 /// A signature was expected but was missing.
196 impl From<bech32::Error> for Bolt12ParseError {
197 fn from(error: bech32::Error) -> Self {
202 impl From<DecodeError> for Bolt12ParseError {
203 fn from(error: DecodeError) -> Self {
208 impl From<Bolt12SemanticError> for Bolt12ParseError {
209 fn from(error: Bolt12SemanticError) -> Self {
210 Self::InvalidSemantics(error)
214 impl From<secp256k1::Error> for Bolt12ParseError {
215 fn from(error: secp256k1::Error) -> Self {
216 Self::InvalidSignature(error)
222 use super::Bolt12ParseError;
223 use crate::offers::offer::Offer;
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);
235 fn parses_bech32_encoded_offers() {
237 // A complete string is valid
238 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
240 // + can join anywhere
241 "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
243 // Multiple + can join
244 "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
246 // + can be followed by whitespace
247 "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
249 for encoded_offer in &offers {
250 if let Err(e) = encoded_offer.parse::<Offer>() {
251 panic!("Invalid offer ({:?}): {}", e, encoded_offer);
257 fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
259 // + must be surrounded by bech32 characters
260 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
261 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
262 "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
263 "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
264 "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
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),
277 use super::Bolt12ParseError;
278 use crate::ln::msgs::DecodeError;
279 use crate::offers::offer::Offer;
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),
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'))),
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)),