Offer parsing from bech32 strings
[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::bech32::{FromBase32, ToBase32};
14 use core::convert::TryFrom;
15 use core::fmt;
16 use crate::ln::msgs::DecodeError;
17
18 use crate::prelude::*;
19
20 /// Indicates a message can be encoded using bech32.
21 pub(crate) trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error=ParseError> {
22         /// Human readable part of the message's bech32 encoding.
23         const BECH32_HRP: &'static str;
24
25         /// Parses a bech32-encoded message into a TLV stream.
26         fn from_bech32_str(s: &str) -> Result<Self, ParseError> {
27                 // Offer encoding may be split by '+' followed by optional whitespace.
28                 let encoded = match s.split('+').skip(1).next() {
29                         Some(_) => {
30                                 for chunk in s.split('+') {
31                                         let chunk = chunk.trim_start();
32                                         if chunk.is_empty() || chunk.contains(char::is_whitespace) {
33                                                 return Err(ParseError::InvalidContinuation);
34                                         }
35                                 }
36
37                                 let s = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect::<String>();
38                                 Bech32String::Owned(s)
39                         },
40                         None => Bech32String::Borrowed(s),
41                 };
42
43                 let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;
44
45                 if hrp != Self::BECH32_HRP {
46                         return Err(ParseError::InvalidBech32Hrp);
47                 }
48
49                 let data = Vec::<u8>::from_base32(&data)?;
50                 Self::try_from(data)
51         }
52
53         /// Formats the message using bech32-encoding.
54         fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
55                 bech32::encode_without_checksum_to_fmt(f, Self::BECH32_HRP, self.as_ref().to_base32())
56                         .expect("HRP is invalid").unwrap();
57
58                 Ok(())
59         }
60 }
61
62 // Used to avoid copying a bech32 string not containing the continuation character (+).
63 enum Bech32String<'a> {
64         Borrowed(&'a str),
65         Owned(String),
66 }
67
68 impl<'a> AsRef<str> for Bech32String<'a> {
69         fn as_ref(&self) -> &str {
70                 match self {
71                         Bech32String::Borrowed(s) => s,
72                         Bech32String::Owned(s) => s,
73                 }
74         }
75 }
76
77 /// Error when parsing a bech32 encoded message using [`str::parse`].
78 #[derive(Debug, PartialEq)]
79 pub enum ParseError {
80         /// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
81         /// across multiple parts (i.e., '+' followed by whitespace).
82         InvalidContinuation,
83         /// The bech32 encoding's human-readable part does not match what was expected for the message
84         /// being parsed.
85         InvalidBech32Hrp,
86         /// The string could not be bech32 decoded.
87         Bech32(bech32::Error),
88         /// The bech32 decoded string could not be decoded as the expected message type.
89         Decode(DecodeError),
90         /// The parsed message has invalid semantics.
91         InvalidSemantics(SemanticError),
92 }
93
94 /// Error when interpreting a TLV stream as a specific type.
95 #[derive(Debug, PartialEq)]
96 pub enum SemanticError {
97         /// An amount was expected but was missing.
98         MissingAmount,
99         /// The amount exceeded the total bitcoin supply.
100         InvalidAmount,
101         /// A required description was not provided.
102         MissingDescription,
103         /// A signing pubkey was not provided.
104         MissingSigningPubkey,
105         /// An unsupported quantity was provided.
106         InvalidQuantity,
107 }
108
109 impl From<bech32::Error> for ParseError {
110         fn from(error: bech32::Error) -> Self {
111                 Self::Bech32(error)
112         }
113 }
114
115 impl From<DecodeError> for ParseError {
116         fn from(error: DecodeError) -> Self {
117                 Self::Decode(error)
118         }
119 }
120
121 impl From<SemanticError> for ParseError {
122         fn from(error: SemanticError) -> Self {
123                 Self::InvalidSemantics(error)
124         }
125 }