use bitcoin::bech32;
use bitcoin::secp256k1;
use crate::io;
use crate::ln::msgs::DecodeError;
use crate::util::ser::SeekReadable;
#[allow(unused_imports)]
use crate::prelude::*;
#[cfg(not(fuzzing))]
pub(super) use sealed::Bech32Encode;
#[cfg(fuzzing)]
pub use sealed::Bech32Encode;
mod sealed {
use bitcoin::bech32;
use bitcoin::bech32::{FromBase32, ToBase32};
use core::fmt;
use super::Bolt12ParseError;
#[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(_) => {
for chunk in s.split('+') {
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 (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;
if hrp != Self::BECH32_HRP {
return Err(Bolt12ParseError::InvalidBech32Hrp);
}
let data = Vec::<u8>::from_base32(&data)?;
Self::try_from(data)
}
fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
bech32::encode_without_checksum_to_fmt(f, Self::BECH32_HRP, self.as_ref().to_base32())
.expect("HRP is invalid").unwrap();
Ok(())
}
}
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: SeekReadable> {
pub bytes: Vec<u8>,
pub tlv_stream: T,
}
impl<T: SeekReadable> 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 = SeekReadable::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,
InvalidBech32Hrp,
Bech32(bech32::Error),
Decode(DecodeError),
InvalidSemantics(Bolt12SemanticError),
InvalidSignature(secp256k1::Error),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Bolt12SemanticError {
AlreadyExpired,
UnsupportedChain,
UnexpectedChain,
MissingAmount,
InvalidAmount,
InsufficientAmount,
UnexpectedAmount,
UnsupportedCurrency,
UnknownRequiredFeatures,
UnexpectedFeatures,
MissingDescription,
MissingSigningPubkey,
InvalidSigningPubkey,
UnexpectedSigningPubkey,
MissingQuantity,
InvalidQuantity,
UnexpectedQuantity,
InvalidMetadata,
UnexpectedMetadata,
MissingPayerMetadata,
MissingPayerId,
DuplicatePaymentId,
MissingPaths,
UnexpectedPaths,
InvalidPayInfo,
MissingCreationTime,
MissingPaymentHash,
MissingSignature,
}
impl From<bech32::Error> for Bolt12ParseError {
fn from(error: bech32::Error) -> 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;
#[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",
"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),
}
}
}
}
#[cfg(test)]
mod tests {
use super::Bolt12ParseError;
use bitcoin::bech32;
use crate::ln::msgs::DecodeError;
use crate::offers::offer::Offer;
#[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_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(bech32::Error::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)),
}
}
}