lightning_signer/util/
invoice_utils.rs

1use crate::invoice::*;
2use crate::prelude::*;
3use crate::util::status::{invalid_argument, Status};
4use bitcoin::bech32::primitives::decode::UncheckedHrpstring;
5use bitcoin::bech32::{Fe32, Fe32IterExt};
6use core::str::FromStr;
7use lightning::ln::msgs::DecodeError;
8use lightning::offers::parse::Bolt12ParseError;
9
10const BECH32_HRP: &'static str = "lni";
11
12impl FromStr for Invoice {
13    type Err = Status;
14    fn from_str(invstr: &str) -> Result<Self, Self::Err> {
15        // Try BOLT-12 first, if that fails try BOLT-11 ...
16        let maybe_bolt12 = WrappedBolt12Invoice::from_str(invstr);
17        match maybe_bolt12 {
18            Ok(bolt12) => Ok(Invoice::Bolt12(bolt12.0)),
19            Err(bolt12err) => {
20                let bolt11_raw = invstr.parse::<bolt11::SignedRawBolt11Invoice>().map_err(|e| {
21                    Status::invalid_argument(format!(
22                        "invoice not bolt12: {:?} and not bolt11: {:?}",
23                        bolt12err, e
24                    ))
25                })?;
26                Ok(Invoice::try_from(bolt11_raw)?)
27            }
28        }
29    }
30}
31
32// This is BOLT-11 only
33impl TryFrom<bolt11::SignedRawBolt11Invoice> for Invoice {
34    type Error = Status;
35    fn try_from(bolt11_raw: bolt11::SignedRawBolt11Invoice) -> Result<Self, Self::Error> {
36        // This performs all semantic checks and signature check
37        let bolt11 = bolt11::Bolt11Invoice::from_signed(bolt11_raw)
38            .map_err(|e| invalid_argument(e.to_string()))?;
39        Ok(Invoice::Bolt11(bolt11))
40    }
41}
42
43// Cribbed from rust-lightning/lightning/src/offers/parse.rs because
44// LDK doesn't facilitate bech32 encoding/decoding of bolt12::Invoice
45
46struct WrappedBolt12Invoice(bolt12::Bolt12Invoice);
47
48impl FromStr for WrappedBolt12Invoice {
49    type Err = Bolt12ParseError;
50
51    fn from_str(s: &str) -> Result<WrappedBolt12Invoice, Bolt12ParseError> {
52        // Offer encoding may be split by '+' followed by optional whitespace.
53        let encoded = match s.split('+').skip(1).next() {
54            Some(_) => {
55                for chunk in s.split('+') {
56                    let chunk = chunk.trim_start();
57                    if chunk.is_empty() || chunk.contains(char::is_whitespace) {
58                        return Err(Bolt12ParseError::InvalidContinuation);
59                    }
60                }
61
62                let s: String = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect();
63                Bech32String::Owned(s)
64            }
65            None => Bech32String::Borrowed(s),
66        };
67
68        // No checksum, because this is not human input
69        let hrp_string = UncheckedHrpstring::new(encoded.as_ref())
70            .map_err(|_| Bolt12ParseError::Decode(DecodeError::InvalidValue))?;
71
72        if hrp_string.hrp().as_str() != BECH32_HRP {
73            return Err(Bolt12ParseError::InvalidBech32Hrp);
74        }
75
76        let data = hrp_string
77            .data_part_ascii()
78            .into_iter()
79            .map(|c| Fe32::from_char_unchecked(*c))
80            .fes_to_bytes()
81            .collect::<Vec<_>>();
82
83        Ok(WrappedBolt12Invoice(bolt12::Bolt12Invoice::try_from(data)?))
84    }
85}
86
87// Used to avoid copying a bech32 string not containing the continuation character (+).
88enum Bech32String<'a> {
89    Borrowed(&'a str),
90    Owned(String),
91}
92
93impl<'a> AsRef<str> for Bech32String<'a> {
94    fn as_ref(&self) -> &str {
95        match self {
96            Bech32String::Borrowed(s) => s,
97            Bech32String::Owned(s) => s,
98        }
99    }
100}