lightning_signer/util/
invoice_utils.rs

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