use crate::io;
use crate::ln::msgs::DecodeError;
use crate::util::ser::CursorReadable;
use bech32::primitives::decode::CheckedHrpstringError;
use bitcoin::secp256k1;
#[allow(unused_imports)]
use crate::prelude::*;
#[cfg(not(fuzzing))]
pub(super) use sealed::Bech32Encode;
#[cfg(fuzzing)]
pub use sealed::Bech32Encode;
mod sealed {
use super::Bolt12ParseError;
use bech32::primitives::decode::CheckedHrpstring;
use bech32::{encode_to_fmt, EncodeError, Hrp, NoChecksum};
use core::fmt;
#[allow(unused_imports)]
use crate::prelude::*;
pub trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error = Bolt12ParseError> {
const BECH32_HRP: &'static str;
fn from_bech32_str(s: &str) -> Result<Self, Bolt12ParseError> {
let encoded = match s.split('+').skip(1).next() {
Some(_) => {
let mut chunks = s.split('+');
if let Some(first_chunk) = chunks.next() {
if first_chunk.contains(char::is_whitespace) {
return Err(Bolt12ParseError::InvalidLeadingWhitespace);
}
if first_chunk.is_empty() {
return Err(Bolt12ParseError::InvalidContinuation);
}
}
for chunk in chunks {
let chunk = chunk.trim_start();
if chunk.is_empty() || chunk.contains(char::is_whitespace) {
return Err(Bolt12ParseError::InvalidContinuation);
}
}
let s: String = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect();
Bech32String::Owned(s)
},
None => Bech32String::Borrowed(s),
};
let parsed = CheckedHrpstring::new::<NoChecksum>(encoded.as_ref())?;
let hrp = parsed.hrp();
if hrp.lowercase_char_iter().ne(Self::BECH32_HRP.chars()) {
return Err(Bolt12ParseError::InvalidBech32Hrp);
}
let data = parsed.byte_iter().collect::<Vec<u8>>();
Self::try_from(data)
}
fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
encode_to_fmt::<NoChecksum, _>(f, Hrp::parse(Self::BECH32_HRP).unwrap(), self.as_ref())
.map_err(|e| match e {
EncodeError::Fmt(e) => e,
_ => fmt::Error {},
})
}
}
enum Bech32String<'a> {
Borrowed(&'a str),
Owned(String),
}
impl<'a> AsRef<str> for Bech32String<'a> {
fn as_ref(&self) -> &str {
match self {
Bech32String::Borrowed(s) => s,
Bech32String::Owned(s) => s,
}
}
}
}
pub(super) struct ParsedMessage<T: CursorReadable> {
pub bytes: Vec<u8>,
pub tlv_stream: T,
}
impl<T: CursorReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
type Error = DecodeError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let mut cursor = io::Cursor::new(bytes);
let tlv_stream: T = CursorReadable::read(&mut cursor)?;
if cursor.position() < cursor.get_ref().len() as u64 {
return Err(DecodeError::InvalidValue);
}
let bytes = cursor.into_inner();
Ok(Self { bytes, tlv_stream })
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Bolt12ParseError {
InvalidContinuation,
InvalidLeadingWhitespace,
InvalidBech32Hrp,
Bech32(
CheckedHrpstringError,
),
Decode(DecodeError),
InvalidSemantics(Bolt12SemanticError),
InvalidSignature(secp256k1::Error),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Bolt12SemanticError {
AlreadyExpired,
UnsupportedChain,
UnexpectedChain,
MissingAmount,
InvalidAmount,
InvalidCurrencyCode,
InsufficientAmount,
UnexpectedAmount,
UnsupportedCurrency,
UnknownRequiredFeatures,
UnexpectedFeatures,
MissingDescription,
MissingIssuerSigningPubkey,
UnexpectedIssuerSigningPubkey,
MissingQuantity,
InvalidQuantity,
UnexpectedQuantity,
InvalidMetadata,
UnexpectedMetadata,
MissingPayerMetadata,
MissingPayerSigningPubkey,
DuplicatePaymentId,
MissingPaths,
UnexpectedPaths,
InvalidPayInfo,
MissingCreationTime,
MissingPaymentHash,
UnexpectedPaymentHash,
MissingSigningPubkey,
InvalidSigningPubkey,
MissingSignature,
UnexpectedHumanReadableName,
}
impl From<CheckedHrpstringError> for Bolt12ParseError {
fn from(error: CheckedHrpstringError) -> Self {
Self::Bech32(error)
}
}
impl From<DecodeError> for Bolt12ParseError {
fn from(error: DecodeError) -> Self {
Self::Decode(error)
}
}
impl From<Bolt12SemanticError> for Bolt12ParseError {
fn from(error: Bolt12SemanticError) -> Self {
Self::InvalidSemantics(error)
}
}
impl From<secp256k1::Error> for Bolt12ParseError {
fn from(error: secp256k1::Error) -> Self {
Self::InvalidSignature(error)
}
}
#[cfg(test)]
mod bolt12_tests {
use super::Bolt12ParseError;
use crate::offers::offer::Offer;
use bech32::primitives::decode::{CharError, CheckedHrpstringError, UncheckedHrpstringError};
#[test]
fn encodes_offer_as_bech32_without_checksum() {
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
let reencoded_offer = offer.to_string();
dbg!(reencoded_offer.parse::<Offer>().unwrap());
assert_eq!(reencoded_offer, encoded_offer);
}
#[test]
fn parses_bech32_encoded_offers() {
let offers = [
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
"LNO1PQPS7SJQPGTYZM3QV4UXZMTSD3JJQER9WD3HY6TSW35K7MSJZFPY7NZ5YQCNYGRFDEJ82UM5WF5K2UCKYYPWA3EYT44H6TXTXQUQH7LZ5DJGE4AFGFJN7K4RGRKUAG0JSD5XVXG",
"l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
"lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
"lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
];
for encoded_offer in &offers {
if let Err(e) = encoded_offer.parse::<Offer>() {
panic!("Invalid offer ({:?}): {}", e, encoded_offer);
}
}
}
#[test]
fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
let offers = [
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
"+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
"+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
"ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
];
for encoded_offer in &offers {
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
}
}
}
#[test]
fn fails_parsing_bech32_encoded_offers_with_mixed_casing() {
let mixed_case_offer = "LnO1PqPs7sJqPgTyZm3qV4UxZmTsD3JjQeR9Wd3hY6TsW35k7mSjZfPy7nZ5YqCnYgRfDeJ82uM5Wf5k2uCkYyPwA3EyT44h6tXtXqUqH7Lz5dJgE4AfGfJn7k4rGrKuAg0jSd5xVxG";
match mixed_case_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", mixed_case_offer),
Err(e) => assert_eq!(
e,
Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(
UncheckedHrpstringError::Char(CharError::MixedCase)
))
),
}
}
}
#[cfg(test)]
mod tests {
use super::Bolt12ParseError;
use crate::ln::msgs::DecodeError;
use crate::offers::offer::Offer;
use bech32::primitives::decode::{CharError, CheckedHrpstringError, UncheckedHrpstringError};
#[test]
fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
}
}
#[test]
fn fails_parsing_bech32_encoded_offer_with_leading_whitespace() {
let encoded_offer = "\u{b}lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah+\u{b}\u{b}\u{b}\u{b}82ru5rdpnpj";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidLeadingWhitespace),
}
}
#[test]
fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
Err(e) => assert_eq!(
e,
Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(
UncheckedHrpstringError::Char(CharError::InvalidChar('o'))
))
),
}
}
#[test]
fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}