lightning = { path = "../lightning", features = ["regex", "_test_utils"] }
lightning-invoice = { path = "../lightning-invoice" }
lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" }
-bech32 = "0.9.1"
+bech32 = "0.11.0"
bitcoin = { version = "0.32.2", features = ["secp-lowmemory"] }
afl = { version = "0.12", optional = true }
// licenses.
use crate::utils::test_logger;
-use bech32::{u5, FromBase32, ToBase32};
+use bech32::Fe32;
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use lightning_invoice::{
- Bolt11Invoice, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField,
+ Base32Iterable, Bolt11Invoice, FromBase32, RawBolt11Invoice, RawDataPart, RawHrp,
+ RawTaggedField, TaggedField,
};
use std::str::FromStr;
Err(_) => return,
};
let bech32 =
- data.iter().skip(hrp_len).map(|x| u5::try_from_u8(x % 32).unwrap()).collect::<Vec<_>>();
+ data.iter().skip(hrp_len).map(|x| Fe32::try_from(x % 32).unwrap()).collect::<Vec<_>>();
let invoice_data = match RawDataPart::from_base32(&bech32) {
Ok(invoice) => invoice,
Err(_) => return,
};
+ let invoice_data_base32 = invoice_data.fe_iter().collect::<Vec<_>>();
// Our data encoding is not worse than the input
- assert!(invoice_data.to_base32().len() <= bech32.len());
+ assert!(invoice_data_base32.len() <= bech32.len());
// Our data serialization is loss-less
assert_eq!(
- RawDataPart::from_base32(&invoice_data.to_base32())
- .expect("faild parsing out own encoding"),
+ RawDataPart::from_base32(&invoice_data_base32).expect("faild parsing out own encoding"),
invoice_data
);
std = []
[dependencies]
-bech32 = { version = "0.9.1", default-features = false }
+bech32 = { version = "0.11.0", default-features = false }
lightning-types = { version = "0.1.0", path = "../lightning-types", default-features = false }
serde = { version = "1.0.118", optional = true }
bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] }
use core::str;
use core::str::FromStr;
-use bech32::{u5, FromBase32};
+use bech32::primitives::decode::{CheckedHrpstring, CheckedHrpstringError};
+use bech32::{Bech32, Fe32, Fe32IterExt};
use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
use bitcoin::hashes::Hash;
use self::hrp_sm::parse_hrp;
+/// Trait for parsing/converting base32 slice.
+pub trait FromBase32: Sized {
+ /// The associated error which can be returned from parsing (e.g. because of bad padding).
+ type Err;
+
+ /// Convert a base32 slice to `Self`.
+ fn from_base32(b32: &[Fe32]) -> Result<Self, Self::Err>;
+}
+
+// FromBase32 implementations are here, because the trait is in this module.
+
+impl FromBase32 for Vec<u8> {
+ type Err = Bolt11ParseError;
+
+ fn from_base32(data: &[Fe32]) -> Result<Self, Self::Err> {
+ Ok(data.iter().copied().fes_to_bytes().collect::<Self>())
+ }
+}
+
+impl<const N: usize> FromBase32 for [u8; N] {
+ type Err = Bolt11ParseError;
+
+ fn from_base32(data: &[Fe32]) -> Result<Self, Self::Err> {
+ let mut res_arr = [0; N];
+ // Do in a for loop to place in the array directly, not using `collect`
+ let mut count = 0;
+ for elem in data.iter().copied().fes_to_bytes() {
+ if count >= N {
+ // too many elements
+ count += 1;
+ break;
+ }
+ res_arr[count] = elem;
+ count += 1;
+ }
+ if count != N {
+ return Err(Bolt11ParseError::InvalidSliceLength(
+ count, N, "<[u8; N]>",
+ ));
+ }
+ Ok(res_arr)
+ }
+}
+
+impl FromBase32 for PaymentSecret {
+ type Err = Bolt11ParseError;
+
+ fn from_base32(field_data: &[Fe32]) -> Result<Self, Self::Err> {
+ if field_data.len() != 52 {
+ return Err(Bolt11ParseError::InvalidSliceLength(
+ field_data.len(),
+ 52,
+ "PaymentSecret",
+ ));
+ }
+ let data_bytes = <[u8; 32]>::from_base32(field_data)?;
+ Ok(PaymentSecret(data_bytes))
+ }
+}
+
+impl FromBase32 for Bolt11InvoiceFeatures {
+ type Err = Bolt11ParseError;
+
+ /// Convert to byte values, by packing the 5-bit groups,
+ /// putting the 5-bit values from left to-right (reverse order),
+ /// starting from the rightmost bit,
+ /// and taking the resulting 8-bit values (right to left),
+ /// with the leading 0's skipped.
+ fn from_base32(field_data: &[Fe32]) -> Result<Self, Self::Err> {
+ // Fe32 conversion cannot be used, because this unpacks from right, right-to-left
+ // Carry bits, 0, 1, 2, 3, or 4 bits
+ let mut carry_bits = 0;
+ let mut carry = 0u8;
+ let expected_raw_length = (field_data.len() * 5 + 7) / 8;
+ let mut output = Vec::<u8>::with_capacity(expected_raw_length);
+
+ // Iterate over input in reverse
+ for curr_in in field_data.iter().rev() {
+ let curr_in_as_u8 = curr_in.to_u8();
+ if carry_bits >= 3 {
+ // we have a new full byte -- 3, 4 or 5 carry bits, plus 5 new ones
+ // For combining with carry '|', '^', or '+' can be used (disjoint bit positions)
+ let next = carry + (curr_in_as_u8 << carry_bits);
+ output.push(next);
+ carry = curr_in_as_u8 >> (8 - carry_bits);
+ carry_bits -= 3; // added 5, removed 8
+ } else {
+ // only 0, 1, or 2 carry bits, plus 5 new ones
+ carry += curr_in_as_u8 << carry_bits;
+ carry_bits += 5;
+ }
+ }
+
+ // No more inputs, output remaining (if any)
+ if carry_bits > 0 {
+ output.push(carry);
+ }
+
+ // This is to double check the estimated length and
+ // satisfying mutation test on the capacity, which is mutatable
+ debug_assert_eq!(output.len(), expected_raw_length);
+
+ // Trim the highest feature bits
+ while !output.is_empty() && output[output.len() - 1] == 0 {
+ output.pop();
+ }
+
+ Ok(Bolt11InvoiceFeatures::from_le_bytes(output))
+ }
+}
+
/// State machine to parse the hrp
mod hrp_sm {
use core::ops::Range;
type Err = Bolt11ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- let (hrp, data, var) = bech32::decode(s)?;
-
- if var == bech32::Variant::Bech32m {
- // Consider Bech32m addresses to be "Invalid Checksum", since that is what we'd get if
- // we didn't support Bech32m (which lightning does not use).
- return Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum));
- }
-
- if data.len() < 104 {
+ let parsed = CheckedHrpstring::new::<Bech32>(s)?;
+ let hrp = parsed.hrp();
+ // Access original non-packed 32 byte values (as Fe32s)
+ // Note: the type argument is needed due to the API peculiarities, but it's not used
+ let data: Vec<_> = parsed.fe32_iter::<&mut dyn Iterator<Item = u8>>().collect();
+
+ const SIGNATURE_LEN_5: usize = 104; // number of the 5-bit values (equals to 65 bytes)
+ if data.len() < SIGNATURE_LEN_5 {
return Err(Bolt11ParseError::TooShortDataPart);
}
- let raw_hrp: RawHrp = hrp.parse()?;
- let data_part = RawDataPart::from_base32(&data[..data.len()-104])?;
+ let raw_hrp: RawHrp = hrp.to_string().to_lowercase().parse()?;
+ let data_part = RawDataPart::from_base32(&data[..data.len() - SIGNATURE_LEN_5])?;
+ let raw_invoice = RawBolt11Invoice {
+ hrp: raw_hrp,
+ data: data_part,
+ };
+ let hash = raw_invoice.signable_hash();
Ok(SignedRawBolt11Invoice {
- raw_invoice: RawBolt11Invoice {
- hrp: raw_hrp,
- data: data_part,
- },
- hash: RawBolt11Invoice::hash_from_parts(
- hrp.as_bytes(),
- &data[..data.len()-104]
- ),
- signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-104..])?,
+ raw_invoice,
+ hash,
+ signature: Bolt11InvoiceSignature::from_base32(&data[data.len() - SIGNATURE_LEN_5..])?,
})
}
}
impl FromBase32 for RawDataPart {
type Err = Bolt11ParseError;
- fn from_base32(data: &[u5]) -> Result<Self, Self::Err> {
+ fn from_base32(data: &[Fe32]) -> Result<Self, Self::Err> {
if data.len() < 7 { // timestamp length
return Err(Bolt11ParseError::TooShortDataPart);
}
impl FromBase32 for PositiveTimestamp {
type Err = Bolt11ParseError;
- fn from_base32(b32: &[u5]) -> Result<Self, Self::Err> {
+ fn from_base32(b32: &[Fe32]) -> Result<Self, Self::Err> {
if b32.len() != 7 {
- return Err(Bolt11ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
+ return Err(Bolt11ParseError::InvalidSliceLength(
+ b32.len(),
+ 7,
+ "PositiveTimestamp",
+ ));
}
let timestamp: u64 = parse_u64_be(b32)
.expect("7*5bit < 64bit, no overflow possible");
impl FromBase32 for Bolt11InvoiceSignature {
type Err = Bolt11ParseError;
- fn from_base32(signature: &[u5]) -> Result<Self, Self::Err> {
+ fn from_base32(signature: &[Fe32]) -> Result<Self, Self::Err> {
if signature.len() != 104 {
- return Err(Bolt11ParseError::InvalidSliceLength("Bolt11InvoiceSignature::from_base32()".into()));
+ return Err(Bolt11ParseError::InvalidSliceLength(
+ signature.len(),
+ 104,
+ "Bolt11InvoiceSignature",
+ ));
}
- let recoverable_signature_bytes = Vec::<u8>::from_base32(signature)?;
+ let recoverable_signature_bytes = <[u8; 65]>::from_base32(signature)?;
let signature = &recoverable_signature_bytes[0..64];
let recovery_id = RecoveryId::from_i32(recoverable_signature_bytes[64] as i32)?;
}
macro_rules! define_parse_int_be { ($name: ident, $ty: ty) => {
- fn $name(digits: &[u5]) -> Option<$ty> {
+ fn $name(digits: &[Fe32]) -> Option<$ty> {
digits.iter().fold(Some(Default::default()), |acc, b|
acc
.and_then(|x| x.checked_mul(32))
define_parse_int_be!(parse_u16_be, u16);
define_parse_int_be!(parse_u64_be, u64);
-fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, Bolt11ParseError> {
+fn parse_tagged_parts(data: &[Fe32]) -> Result<Vec<RawTaggedField>, Bolt11ParseError> {
let mut parts = Vec::<RawTaggedField>::new();
let mut data = data;
Ok(field) => {
parts.push(RawTaggedField::KnownSemantics(field))
},
- Err(Bolt11ParseError::Skip)|Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidLength)) => {
+ Err(Bolt11ParseError::Skip)
+ | Err(Bolt11ParseError::InvalidSliceLength(_, _, _))
+ | Err(Bolt11ParseError::Bech32Error(_)) => {
parts.push(RawTaggedField::UnknownSemantics(field.into()))
},
Err(e) => {return Err(e)}
impl FromBase32 for TaggedField {
type Err = Bolt11ParseError;
- fn from_base32(field: &[u5]) -> Result<TaggedField, Bolt11ParseError> {
+ fn from_base32(field: &[Fe32]) -> Result<TaggedField, Bolt11ParseError> {
if field.len() < 3 {
return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
impl FromBase32 for Sha256 {
type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Sha256, Bolt11ParseError> {
+ fn from_base32(field_data: &[Fe32]) -> Result<Sha256, Bolt11ParseError> {
if field_data.len() != 52 {
// "A reader MUST skip over […] a p, [or] h […] field that does not have data_length 52 […]."
Err(Bolt11ParseError::Skip)
} else {
- Ok(Sha256(sha256::Hash::from_slice(&Vec::<u8>::from_base32(field_data)?)
+ Ok(Sha256(sha256::Hash::from_slice(&<[u8; 32]>::from_base32(field_data)?)
.expect("length was checked before (52 u5 -> 32 u8)")))
}
}
impl FromBase32 for Description {
type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Description, Bolt11ParseError> {
+ fn from_base32(field_data: &[Fe32]) -> Result<Description, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;
let description = String::from(str::from_utf8(&bytes)?);
Ok(Description::new(description).expect(
impl FromBase32 for PayeePubKey {
type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<PayeePubKey, Bolt11ParseError> {
+ fn from_base32(field_data: &[Fe32]) -> Result<PayeePubKey, Bolt11ParseError> {
if field_data.len() != 53 {
// "A reader MUST skip over […] a n […] field that does not have data_length 53 […]."
Err(Bolt11ParseError::Skip)
} else {
- let data_bytes = Vec::<u8>::from_base32(field_data)?;
+ let data_bytes = <[u8; 33]>::from_base32(field_data)?;
let pub_key = PublicKey::from_slice(&data_bytes)?;
Ok(pub_key.into())
}
impl FromBase32 for ExpiryTime {
type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, Bolt11ParseError> {
+ fn from_base32(field_data: &[Fe32]) -> Result<ExpiryTime, Bolt11ParseError> {
match parse_u64_be(field_data)
.map(ExpiryTime::from_seconds)
{
impl FromBase32 for MinFinalCltvExpiryDelta {
type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiryDelta, Bolt11ParseError> {
+ fn from_base32(field_data: &[Fe32]) -> Result<MinFinalCltvExpiryDelta, Bolt11ParseError> {
let expiry = parse_u64_be(field_data);
if let Some(expiry) = expiry {
Ok(MinFinalCltvExpiryDelta(expiry))
impl FromBase32 for Fallback {
type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Fallback, Bolt11ParseError> {
+ fn from_base32(field_data: &[Fe32]) -> Result<Fallback, Bolt11ParseError> {
if field_data.is_empty() {
return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
impl FromBase32 for PrivateRoute {
type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<PrivateRoute, Bolt11ParseError> {
+ fn from_base32(field_data: &[Fe32]) -> Result<PrivateRoute, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;
if bytes.len() % 51 != 0 {
Bolt11ParseError::DescriptionDecodeError(ref e) => {
write!(f, "Description is not a valid utf-8 string: {}", e)
}
- Bolt11ParseError::InvalidSliceLength(ref function) => {
- write!(f, "Slice in function {} had the wrong length", function)
- }
+ Bolt11ParseError::InvalidSliceLength(ref len, ref expected, ref elemen) => {
+ write!(
+ f,
+ "Slice had length {} instead of {} for element {}",
+ len, expected, elemen
+ )
+ },
Bolt11ParseError::BadPrefix => f.write_str("did not begin with 'ln'"),
Bolt11ParseError::UnknownCurrency => f.write_str("currency code unknown"),
Bolt11ParseError::UnknownSiPrefix => f.write_str("unknown SI prefix"),
from_error!(Bolt11ParseError::ParseAmountError, ParseIntError);
from_error!(Bolt11ParseError::DescriptionDecodeError, str::Utf8Error);
-impl From<bech32::Error> for Bolt11ParseError {
- fn from(e: bech32::Error) -> Self {
- match e {
- bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError,
- _ => Bolt11ParseError::Bech32Error(e)
- }
+impl From<CheckedHrpstringError> for Bolt11ParseError {
+ fn from(e: CheckedHrpstringError) -> Self {
+ Self::Bech32Error(e)
}
}
#[cfg(test)]
mod test {
+ use super::FromBase32;
use crate::de::Bolt11ParseError;
use bitcoin::secp256k1::PublicKey;
- use bech32::u5;
+ use bech32::Fe32;
use bitcoin::hashes::sha256;
use std::str::FromStr;
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
];
- fn from_bech32(bytes_5b: &[u8]) -> Vec<u5> {
+ fn from_bech32(bytes_5b: &[u8]) -> Vec<Fe32> {
bytes_5b
.iter()
- .map(|c| u5::try_from_u8(CHARSET_REV[*c as usize] as u8).unwrap())
+ .map(|c| Fe32::try_from(CHARSET_REV[*c as usize] as u8).unwrap())
.collect()
}
use crate::de::parse_u16_be;
assert_eq!(parse_u16_be(&[
- u5::try_from_u8(1).unwrap(), u5::try_from_u8(2).unwrap(),
- u5::try_from_u8(3).unwrap(), u5::try_from_u8(4).unwrap()]
- ), Some(34916));
+ Fe32::try_from(1).unwrap(), Fe32::try_from(2).unwrap(),
+ Fe32::try_from(3).unwrap(), Fe32::try_from(4).unwrap(),
+ ]), Some(34916));
assert_eq!(parse_u16_be(&[
- u5::try_from_u8(2).unwrap(), u5::try_from_u8(0).unwrap(),
- u5::try_from_u8(0).unwrap(), u5::try_from_u8(0).unwrap()]
- ), None);
+ Fe32::try_from(2).unwrap(), Fe32::try_from(0).unwrap(),
+ Fe32::try_from(0).unwrap(), Fe32::try_from(0).unwrap(),
+ ]), None);
}
#[test]
fn test_parse_sha256_hash() {
use crate::Sha256;
- use bech32::FromBase32;
let input = from_bech32(
"qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq".as_bytes()
#[test]
fn test_parse_description() {
use crate::Description;
- use bech32::FromBase32;
let input = from_bech32("xysxxatsyp3k7enxv4js".as_bytes());
let expected = Ok(Description::new("1 cup coffee".to_owned()).unwrap());
#[test]
fn test_parse_payee_pub_key() {
use crate::PayeePubKey;
- use bech32::FromBase32;
let input = from_bech32("q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66".as_bytes());
let pk_bytes = [
#[test]
fn test_parse_expiry_time() {
use crate::ExpiryTime;
- use bech32::FromBase32;
let input = from_bech32("pu".as_bytes());
let expected = Ok(ExpiryTime::from_seconds(60));
#[test]
fn test_parse_min_final_cltv_expiry_delta() {
use crate::MinFinalCltvExpiryDelta;
- use bech32::FromBase32;
let input = from_bech32("pr".as_bytes());
let expected = Ok(MinFinalCltvExpiryDelta(35));
#[test]
fn test_parse_fallback() {
use crate::Fallback;
- use bech32::FromBase32;
use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
use bitcoin::hashes::Hash;
})
),
(
- vec![u5::try_from_u8(21).unwrap(); 41],
+ vec![Fe32::try_from(21).unwrap(); 41],
Err(Bolt11ParseError::Skip)
),
(
Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
),
(
- vec![u5::try_from_u8(1).unwrap(); 81],
+ vec![Fe32::try_from(1).unwrap(); 81],
Err(Bolt11ParseError::InvalidSegWitProgramLength)
),
(
- vec![u5::try_from_u8(17).unwrap(); 1],
+ vec![Fe32::try_from(17).unwrap(); 1],
Err(Bolt11ParseError::InvalidPubKeyHashLength)
),
(
- vec![u5::try_from_u8(18).unwrap(); 1],
+ vec![Fe32::try_from(18).unwrap(); 1],
Err(Bolt11ParseError::InvalidScriptHashLength)
)
];
fn test_parse_route() {
use lightning_types::routing::{RoutingFees, RouteHint, RouteHintHop};
use crate::PrivateRoute;
- use bech32::FromBase32;
let input = from_bech32(
"q20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqa\
assert_eq!(PrivateRoute::from_base32(&input), Ok(PrivateRoute(RouteHint(expected))));
assert_eq!(
- PrivateRoute::from_base32(&[u5::try_from_u8(0).unwrap(); 40][..]),
+ PrivateRoute::from_base32(&[Fe32::try_from(0).unwrap(); 40][..]),
Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
);
}
#[cfg(feature = "std")]
use std::time::SystemTime;
-use bech32::{FromBase32, u5};
+use bech32::primitives::decode::CheckedHrpstringError;
+use bech32::Fe32;
use bitcoin::{Address, Network, PubkeyHash, ScriptHash, WitnessProgram, WitnessVersion};
use bitcoin::hashes::{Hash, sha256};
use lightning_types::features::Bolt11InvoiceFeatures;
use bitcoin::secp256k1::{Message, Secp256k1};
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
+use alloc::boxed::Box;
use core::cmp::Ordering;
use core::fmt::{Display, Formatter, self};
use core::iter::FilterMap;
use crate::prelude::*;
+/// Re-export serialization traits
+#[cfg(fuzzing)]
+pub use crate::de::FromBase32;
+#[cfg(fuzzing)]
+pub use crate::ser::Base32Iterable;
+
/// Errors that indicate what is wrong with the invoice. They have some granularity for debug
/// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user.
#[allow(missing_docs)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Bolt11ParseError {
- Bech32Error(bech32::Error),
+ Bech32Error(CheckedHrpstringError),
ParseAmountError(ParseIntError),
MalformedSignature(bitcoin::secp256k1::Error),
BadPrefix,
InvalidPubKeyHashLength,
InvalidScriptHashLength,
InvalidRecoveryId,
- InvalidSliceLength(String),
+ // Invalid length, with actual length, expected length, and name of the element
+ InvalidSliceLength(usize, usize, &'static str),
/// Not an error, but used internally to signal that a part of the invoice should be ignored
/// according to BOLT11
pub si_prefix: Option<SiPrefix>,
}
+impl RawHrp {
+ /// Convert to bech32::Hrp
+ pub fn to_hrp(&self) -> bech32::Hrp {
+ let hrp_str = self.to_string();
+ let s = core::str::from_utf8(&hrp_str.as_bytes()).expect("HRP bytes should be ASCII");
+ bech32::Hrp::parse_unchecked(s)
+ }
+}
+
/// Data of the [`RawBolt11Invoice`] that is encoded in the data part
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
pub struct RawDataPart {
/// Tagged field which may have an unknown tag
///
/// This is not exported to bindings users as we don't currently support TaggedField
-#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum RawTaggedField {
/// Parsed tagged field with known tag
KnownSemantics(TaggedField),
/// tagged field which was not parsed due to an unknown tag or undefined field semantics
- UnknownSemantics(Vec<u5>),
+ UnknownSemantics(Vec<Fe32>),
+}
+
+impl PartialOrd for RawTaggedField {
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+/// Note: `Ord `cannot be simply derived because of `Fe32`.
+impl Ord for RawTaggedField {
+ fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+ match (self, other) {
+ (RawTaggedField::KnownSemantics(ref a), RawTaggedField::KnownSemantics(ref b)) => {
+ a.cmp(b)
+ },
+ (RawTaggedField::UnknownSemantics(ref a), RawTaggedField::UnknownSemantics(ref b)) => {
+ a.iter().map(|a| a.to_u8()).cmp(b.iter().map(|b| b.to_u8()))
+ },
+ (RawTaggedField::KnownSemantics(..), RawTaggedField::UnknownSemantics(..)) => {
+ core::cmp::Ordering::Less
+ },
+ (RawTaggedField::UnknownSemantics(..), RawTaggedField::KnownSemantics(..)) => {
+ core::cmp::Ordering::Greater
+ },
+ }
+ }
}
/// Tagged field with known tag
#[allow(missing_docs)]
impl RawBolt11Invoice {
- /// Hash the HRP as bytes and signatureless data part.
- fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
- let mut preimage = Vec::<u8>::from(hrp_bytes);
+ /// Hash the HRP (as bytes) and signatureless data part (as Fe32 iterator)
+ fn hash_from_parts<'s>(hrp_bytes: &[u8], data_without_signature: Box<dyn Iterator<Item = Fe32> + 's>) -> [u8; 32] {
+ use crate::bech32::Fe32IterExt;
+ use bitcoin::hashes::HashEngine;
+
+ let mut data_part = data_without_signature.collect::<Vec<Fe32>>();
- let mut data_part = Vec::from(data_without_signature);
+ // Need to pad before from_base32 conversion
let overhang = (data_part.len() * 5) % 8;
if overhang > 0 {
// add padding if data does not end at a byte boundary
- data_part.push(u5::try_from_u8(0).unwrap());
+ data_part.push(Fe32::try_from(0).unwrap());
- // if overhang is in (1..3) we need to add u5(0) padding two times
+ // if overhang is in (1..3) we need to add Fe32(0) padding two times
if overhang < 3 {
- data_part.push(u5::try_from_u8(0).unwrap());
+ data_part.push(Fe32::try_from(0).unwrap());
}
}
- preimage.extend_from_slice(&Vec::<u8>::from_base32(&data_part)
- .expect("No padding error may occur due to appended zero above."));
+ // Hash bytes and data part sequentially
+ let mut engine = sha256::Hash::engine();
+ engine.input(hrp_bytes);
+ // Iterate over data
+ // Note: if it was not for padding, this could go on the supplied original iterator
+ // (see https://github.com/rust-bitcoin/rust-bech32/issues/198)
+ data_part.into_iter().fes_to_bytes().for_each(|v| { engine.input(&[v])});
+ let raw_hash = sha256::Hash::from_engine(engine);
let mut hash: [u8; 32] = Default::default();
- hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]);
+ hash.copy_from_slice(raw_hash.as_ref());
hash
}
/// Calculate the hash of the encoded `RawBolt11Invoice` which should be signed.
pub fn signable_hash(&self) -> [u8; 32] {
- use bech32::ToBase32;
+ use crate::ser::Base32Iterable;
- RawBolt11Invoice::hash_from_parts(
+ Self::hash_from_parts(
self.hrp.to_string().as_bytes(),
- &self.data.to_base32()
+ self.data.fe_iter(),
)
}
impl TaggedField {
/// Numeric representation of the field's tag
- pub fn tag(&self) -> u5 {
+ pub fn tag(&self) -> Fe32 {
let tag = match *self {
TaggedField::PaymentHash(_) => constants::TAG_PAYMENT_HASH,
TaggedField::Description(_) => constants::TAG_DESCRIPTION,
TaggedField::Features(_) => constants::TAG_FEATURES,
};
- u5::try_from_u8(tag).expect("all tags defined are <32")
+ Fe32::try_from(tag).expect("all tags defined are <32")
}
}
assert_eq!(invoice_str, deserialized_invoice.to_string().as_str());
assert_eq!(invoice_str, serialized_invoice.as_str().trim_matches('\"'));
}
+
+ #[test]
+ fn raw_tagged_field_ordering() {
+ use crate::{Description, Fe32, RawTaggedField, TaggedField, Sha256, sha256, UntrustedString};
+
+ let field10 = RawTaggedField::KnownSemantics(
+ TaggedField::PaymentHash(Sha256(sha256::Hash::from_str(
+ "0001020304050607080900010203040506070809000102030405060708090102"
+ ).unwrap()))
+ );
+ let field11 = RawTaggedField::KnownSemantics(
+ TaggedField::Description(Description(UntrustedString("Description".to_string())))
+ );
+ let field20 = RawTaggedField::UnknownSemantics(vec![Fe32::Q]);
+ let field21 = RawTaggedField::UnknownSemantics(vec![Fe32::R]);
+
+ assert!(field10 < field20);
+ assert!(field20 > field10);
+ assert_eq!(field10.cmp(&field20), std::cmp::Ordering::Less);
+ assert_eq!(field20.cmp(&field10), std::cmp::Ordering::Greater);
+ assert_eq!(field10.cmp(&field10), std::cmp::Ordering::Equal);
+ assert_eq!(field20.cmp(&field20), std::cmp::Ordering::Equal);
+ assert_eq!(field10.partial_cmp(&field20).unwrap(), std::cmp::Ordering::Less);
+ assert_eq!(field20.partial_cmp(&field10).unwrap(), std::cmp::Ordering::Greater);
+
+ assert_eq!(field10.partial_cmp(&field11).unwrap(), std::cmp::Ordering::Less);
+ assert_eq!(field20.partial_cmp(&field21).unwrap(), std::cmp::Ordering::Less);
+ }
}
use core::fmt;
use core::fmt::{Display, Formatter};
-use bech32::{ToBase32, u5, WriteBase32, Base32Len};
+use core::{array, iter};
+use alloc::boxed::Box;
+
+use bech32::{ByteIterExt, Fe32, Fe32IterExt};
use crate::prelude::*;
-use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp,
- PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart};
-
-/// Converts a stream of bytes written to it to base32. On finalization the according padding will
-/// be applied. That means the results of writing two data blocks with one or two `BytesToBase32`
-/// converters will differ.
-struct BytesToBase32<'a, W: WriteBase32 + 'a> {
- /// Target for writing the resulting `u5`s resulting from the written bytes
- writer: &'a mut W,
- /// Holds all unwritten bits left over from last round. The bits are stored beginning from
- /// the most significant bit. E.g. if buffer_bits=3, then the byte with bits a, b and c will
- /// look as follows: [a, b, c, 0, 0, 0, 0, 0]
- buffer: u8,
- /// Amount of bits left over from last round, stored in buffer.
- buffer_bits: u8,
-}
-
-impl<'a, W: WriteBase32> BytesToBase32<'a, W> {
- /// Create a new bytes-to-base32 converter with `writer` as a sink for the resulting base32
- /// data.
- pub fn new(writer: &'a mut W) -> BytesToBase32<'a, W> {
- BytesToBase32 {
- writer,
- buffer: 0,
- buffer_bits: 0,
- }
- }
+use super::{Bolt11Invoice, Bolt11InvoiceFeatures, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PaymentSecret, PositiveTimestamp,
+ PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart, RouteHintHop};
- /// Add more bytes to the current conversion unit
- pub fn append(&mut self, bytes: &[u8]) -> Result<(), W::Err> {
- for b in bytes {
- self.append_u8(*b)?;
- }
- Ok(())
+/// Objects that can be encoded to base32 (bech32).
+///
+/// Private to this crate to avoid polluting the API.
+pub trait Base32Iterable {
+ /// apoelstra: In future we want to replace this Box<dyn Iterator> with an explicit
+ /// associated type, to avoid the allocation. But we cannot do this until
+ /// Rust 1.65 and GATs since the iterator may contain a reference to self.
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's>;
+}
+
+/// Interface to calculate the length of the base32 representation before actually serializing
+pub(crate) trait Base32Len: Base32Iterable {
+ /// Calculate the bech32 serialized length
+ fn base32_len(&self) -> usize;
+}
+
+// Base32Iterable & Base32Len implementations are here, because the traits are in this module.
+
+impl<const N: usize> Base32Iterable for [u8; N] {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new((*self).into_iter().bytes_to_fes())
}
+}
- pub fn append_u8(&mut self, byte: u8) -> Result<(), W::Err> {
- // Write first u5 if we have to write two u5s this round. That only happens if the
- // buffer holds too many bits, so we don't have to combine buffer bits with new bits
- // from this rounds byte.
- if self.buffer_bits >= 5 {
- self.writer.write_u5(
- u5::try_from_u8((self.buffer & 0b11111000) >> 3 ).expect("<32")
- )?;
- self.buffer <<= 5;
- self.buffer_bits -= 5;
- }
+impl<const N: usize> Base32Len for [u8; N] {
+ /// Calculate the base32 serialized length
+ fn base32_len(&self) -> usize {
+ bytes_size_to_base32_size(N)
+ }
+}
- // Combine all bits from buffer with enough bits from this rounds byte so that they fill
- // a u5. Save remaining bits from byte to buffer.
- let from_buffer = self.buffer >> 3;
- let from_byte = byte >> (3 + self.buffer_bits); // buffer_bits <= 4
+impl Base32Iterable for [u8] {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(self.iter().copied().bytes_to_fes())
+ }
+}
- self.writer.write_u5(u5::try_from_u8(from_buffer | from_byte).expect("<32"))?;
- self.buffer = byte << (5 - self.buffer_bits);
- self.buffer_bits += 3;
+impl Base32Len for [u8] {
+ /// Calculate the base32 serialized length
+ fn base32_len(&self) -> usize {
+ bytes_size_to_base32_size(self.len())
+ }
+}
- Ok(())
+impl Base32Iterable for Vec<u8> {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(self.iter().copied().bytes_to_fes())
}
+}
- pub fn finalize(mut self) -> Result<(), W::Err> {
- self.inner_finalize()?;
- core::mem::forget(self);
- Ok(())
+impl Base32Len for Vec<u8> {
+ /// Calculate the base32 serialized length
+ fn base32_len(&self) -> usize {
+ bytes_size_to_base32_size(self.len())
}
+}
- fn inner_finalize(&mut self) -> Result<(), W::Err>{
- // There can be at most two u5s left in the buffer after processing all bytes, write them.
- if self.buffer_bits >= 5 {
- self.writer.write_u5(
- u5::try_from_u8((self.buffer & 0b11111000) >> 3).expect("<32")
- )?;
- self.buffer <<= 5;
- self.buffer_bits -= 5;
- }
+impl Base32Iterable for PaymentSecret {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(self.0[..].fe_iter())
+ }
+}
- if self.buffer_bits != 0 {
- self.writer.write_u5(u5::try_from_u8(self.buffer >> 3).expect("<32"))?;
+impl Base32Len for PaymentSecret {
+ fn base32_len(&self) -> usize {
+ 52
+ }
+}
+
+impl Base32Iterable for Bolt11InvoiceFeatures {
+ /// Convert to 5-bit values, by unpacking the 5 bit groups,
+ /// putting the bytes from right-to-left,
+ /// starting from the rightmost bit,
+ /// and taking the resulting 5-bit values in reverse (left-to-right),
+ /// with the leading 0's skipped.
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ // Fe32 conversion cannot be used, because this packs from right, right-to-left
+ let mut input_iter = self.le_flags().iter();
+ // Carry bits, 0..7 bits
+ let mut carry = 0u8;
+ let mut carry_bits = 0;
+ let mut output = Vec::<Fe32>::new();
+
+ loop {
+ let next_out8 = if carry_bits >= 5 {
+ // We have enough carry bits for an output, no need to read the input
+ let next_out8 = carry;
+ carry >>= 5;
+ carry_bits -= 5;
+ next_out8
+ } else {
+ // take next byte
+ if let Some(curr_in) = input_iter.next() {
+ // we have at least one Fe32 to output (maybe two)
+ // For combining with carry '|', '^', or '+' can be used (disjoint bit positions)
+ let next_out8 = carry + (curr_in << carry_bits);
+ carry = curr_in >> (5 - carry_bits);
+ carry_bits += 3; // added 8, removed 5
+ next_out8
+ } else {
+ // No more inputs, output remaining (if any)
+ if carry_bits > 0 {
+ carry_bits = 0;
+ carry
+ } else {
+ break;
+ }
+ }
+ };
+ // Isolate the 5 right bits
+ output.push(Fe32::try_from(next_out8 & 31u8).expect("<32"))
}
-
- Ok(())
+ // Take result in reverse order, and skip leading 0s
+ Box::new(output.into_iter().rev().skip_while(|e| *e == Fe32::Q))
}
}
-impl<'a, W: WriteBase32> Drop for BytesToBase32<'a, W> {
- fn drop(&mut self) {
- self.inner_finalize()
- .expect("Unhandled error when finalizing conversion on drop. User finalize to handle.")
+impl Base32Len for Bolt11InvoiceFeatures {
+ fn base32_len(&self) -> usize {
+ // Here we perform the real conversion, due to trimming it's hard to estimate
+ self.fe_iter().count()
}
}
}
impl Display for Bolt11Invoice {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.signed_invoice.fmt(f)
}
}
impl Display for SignedRawBolt11Invoice {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
- let hrp = self.raw_invoice.hrp.to_string();
- let mut data = self.raw_invoice.data.to_base32();
- data.extend_from_slice(&self.signature.to_base32());
-
- bech32::encode_to_fmt(f, &hrp, data, bech32::Variant::Bech32).expect("HRP is valid")?;
-
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let hrp = self.raw_invoice.hrp.to_hrp();
+ for ch in self
+ .raw_invoice
+ .data
+ .fe_iter()
+ .chain(self.signature.fe_iter())
+ .with_checksum::<bech32::Bech32>(&hrp)
+ .chars()
+ {
+ write!(f, "{}", ch)?;
+ }
Ok(())
}
}
/// This is not exported to bindings users
impl Display for RawHrp {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let amount = match self.raw_amount {
Some(ref amt) => amt.to_string(),
None => String::new(),
}
impl Display for Currency {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let currency_code = match *self {
Currency::Bitcoin => "bc",
Currency::BitcoinTestnet => "tb",
}
impl Display for SiPrefix {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}",
match *self {
SiPrefix::Milli => "m",
}
}
-fn encode_int_be_base32(int: u64) -> Vec<u5> {
+/// Encode an integer to base32, big endian, without leading zeros
+fn encode_int_be_base32(int: u64) -> Vec<Fe32> {
let base = 32u64;
- let mut out_vec = Vec::<u5>::new();
-
+ // (64 + 4) / 5 == 13
+ let mut out_vec = Vec::<Fe32>::with_capacity(13);
let mut rem_int = int;
while rem_int != 0 {
- out_vec.push(u5::try_from_u8((rem_int % base) as u8).expect("always <32"));
+ out_vec.push(Fe32::try_from((rem_int % base) as u8).expect("always <32"));
rem_int /= base;
}
out_vec
}
+/// The length of the output of `encode_int_be_base32`.
fn encoded_int_be_base32_size(int: u64) -> usize {
- for pos in (0..13).rev() {
- if int & (0x1f << (5 * pos)) != 0 {
- return (pos + 1) as usize;
- }
- }
- 0usize
+ let bit_len = 64 - int.leading_zeros() as usize; // cast ok as value is in 0..=64.
+ (bit_len + 4) / 5
}
-fn encode_int_be_base256<T: Into<u64>>(int: T) -> Vec<u8> {
- let base = 256u64;
-
- let mut out_vec = Vec::<u8>::new();
-
- let mut rem_int: u64 = int.into();
- while rem_int != 0 {
- out_vec.push((rem_int % base) as u8);
- rem_int /= base;
+impl Base32Iterable for RawDataPart {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ let ts_iter = self.timestamp.fe_iter();
+ let fields_iter = self.tagged_fields.iter().map(RawTaggedField::fe_iter).flatten();
+ Box::new(ts_iter.chain(fields_iter))
}
-
- out_vec.reverse();
- out_vec
}
-/// Appends the default value of `T` to the front of the `in_vec` till it reaches the length
-/// `target_length`. If `in_vec` already is too lang `None` is returned.
-fn try_stretch<T>(mut in_vec: Vec<T>, target_len: usize) -> Option<Vec<T>>
- where T: Default + Copy
-{
- if in_vec.len() > target_len {
- None
- } else if in_vec.len() == target_len {
- Some(in_vec)
- } else {
- let mut out_vec = Vec::<T>::with_capacity(target_len);
- out_vec.append(&mut vec![T::default(); target_len - in_vec.len()]);
- out_vec.append(&mut in_vec);
- Some(out_vec)
- }
-}
-
-impl ToBase32 for RawDataPart {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- // encode timestamp
- self.timestamp.write_base32(writer)?;
-
- // encode tagged fields
- for tagged_field in self.tagged_fields.iter() {
- tagged_field.write_base32(writer)?;
- }
-
- Ok(())
- }
-}
-
-impl ToBase32 for PositiveTimestamp {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- // FIXME: use writer for int encoding
- writer.write(
- &try_stretch(encode_int_be_base32(self.as_unix_timestamp()), 7)
- .expect("Can't be longer due than 7 u5s due to timestamp bounds")
- )
+impl Base32Iterable for PositiveTimestamp {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ let fes = encode_int_be_base32(self.as_unix_timestamp());
+ debug_assert!(fes.len() <= 7, "Invalid timestamp length");
+ let to_pad = 7 - fes.len();
+ Box::new(core::iter::repeat(Fe32::Q).take(to_pad).chain(fes.into_iter()))
}
}
-impl ToBase32 for RawTaggedField {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+impl Base32Iterable for RawTaggedField {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ // Annoyingly, when we move to explicit types, we will need an
+ // explicit enum holding the two iterator variants.
match *self {
- RawTaggedField::UnknownSemantics(ref content) => {
- writer.write(content)
- },
- RawTaggedField::KnownSemantics(ref tagged_field) => {
- tagged_field.write_base32(writer)
- }
+ RawTaggedField::UnknownSemantics(ref content) => Box::new(content.iter().copied()),
+ RawTaggedField::KnownSemantics(ref tagged_field) => tagged_field.fe_iter(),
}
}
}
-impl ToBase32 for Sha256 {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- (&self.0[..]).write_base32(writer)
+impl Base32Iterable for Sha256 {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(self.0[..].fe_iter())
}
}
+
impl Base32Len for Sha256 {
fn base32_len(&self) -> usize {
- (&self.0[..]).base32_len()
+ self.0[..].base32_len()
}
}
-impl ToBase32 for Description {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- self.0.0.as_bytes().write_base32(writer)
+impl Base32Iterable for Description {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(self.0.0.as_bytes().fe_iter())
}
}
}
}
-impl ToBase32 for PayeePubKey {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- (&self.serialize()[..]).write_base32(writer)
+impl Base32Iterable for PayeePubKey {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(self.serialize().into_iter().bytes_to_fes())
}
}
}
}
-impl ToBase32 for ExpiryTime {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- writer.write(&encode_int_be_base32(self.as_seconds()))
+impl Base32Iterable for ExpiryTime {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(encode_int_be_base32(self.as_seconds()).into_iter())
}
}
}
}
-impl ToBase32 for MinFinalCltvExpiryDelta {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- writer.write(&encode_int_be_base32(self.0))
+impl Base32Iterable for MinFinalCltvExpiryDelta {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(encode_int_be_base32(self.0).into_iter())
}
}
}
}
-impl ToBase32 for Fallback {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- match *self {
- Fallback::SegWitProgram {version: v, program: ref p} => {
- writer.write_u5(u5::try_from_u8(v.to_num()).expect("witness version <= 16"))?;
- p.write_base32(writer)
+impl Base32Iterable for Fallback {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ Box::new(match *self {
+ Fallback::SegWitProgram { version: v, program: ref p } => {
+ let v = Fe32::try_from(v.to_num()).expect("valid version");
+ core::iter::once(v).chain(p[..].fe_iter())
},
Fallback::PubKeyHash(ref hash) => {
- writer.write_u5(u5::try_from_u8(17).expect("17 < 32"))?;
- (&hash[..]).write_base32(writer)
+ // 17 '3'
+ core::iter::once(Fe32::_3).chain(hash[..].fe_iter())
},
Fallback::ScriptHash(ref hash) => {
- writer.write_u5(u5::try_from_u8(18).expect("18 < 32"))?;
- (&hash[..]).write_base32(writer)
- }
- }
+ // 18 'J'
+ core::iter::once(Fe32::J).chain(hash[..].fe_iter())
+ },
+ })
}
}
impl Base32Len for Fallback {
fn base32_len(&self) -> usize {
match *self {
- Fallback::SegWitProgram {program: ref p, ..} => {
+ Fallback::SegWitProgram { program: ref p, .. } => {
bytes_size_to_base32_size(p.len()) + 1
},
- Fallback::PubKeyHash(_) | Fallback::ScriptHash(_) => {
- 33
- },
+ Fallback::PubKeyHash(_) | Fallback::ScriptHash(_) => 33,
}
}
}
-impl ToBase32 for PrivateRoute {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- let mut converter = BytesToBase32::new(writer);
-
- for hop in (self.0).0.iter() {
- converter.append(&hop.src_node_id.serialize()[..])?;
- let short_channel_id = try_stretch(
- encode_int_be_base256(hop.short_channel_id),
- 8
- ).expect("sizeof(u64) == 8");
- converter.append(&short_channel_id)?;
-
- let fee_base_msat = try_stretch(
- encode_int_be_base256(hop.fees.base_msat),
- 4
- ).expect("sizeof(u32) == 4");
- converter.append(&fee_base_msat)?;
-
- let fee_proportional_millionths = try_stretch(
- encode_int_be_base256(hop.fees.proportional_millionths),
- 4
- ).expect("sizeof(u32) == 4");
- converter.append(&fee_proportional_millionths)?;
-
- let cltv_expiry_delta = try_stretch(
- encode_int_be_base256(hop.cltv_expiry_delta),
- 2
- ).expect("sizeof(u16) == 2");
- converter.append(&cltv_expiry_delta)?;
+// Shorthand type
+type RouteHintHopIter = iter::Chain<
+ iter::Chain<
+ iter::Chain<
+ iter::Chain<array::IntoIter<u8, 33>, array::IntoIter<u8, 8>>,
+ array::IntoIter<u8, 4>,
+ >,
+ array::IntoIter<u8, 4>,
+ >,
+ array::IntoIter<u8, 2>,
+>;
+
+impl Base32Iterable for PrivateRoute {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
+ fn serialize_to_iter(hop: &RouteHintHop) -> RouteHintHopIter {
+ let i1 = hop.src_node_id.serialize().into_iter();
+ let i2 = u64::to_be_bytes(hop.short_channel_id).into_iter();
+ let i3 = u32::to_be_bytes(hop.fees.base_msat).into_iter();
+ let i4 = u32::to_be_bytes(hop.fees.proportional_millionths).into_iter();
+ let i5 = u16::to_be_bytes(hop.cltv_expiry_delta).into_iter();
+ i1.chain(i2).chain(i3).chain(i4).chain(i5)
}
- converter.finalize()?;
- Ok(())
+ Box::new(self.0.0.iter().map(serialize_to_iter).flatten().bytes_to_fes())
}
}
}
}
-impl ToBase32 for TaggedField {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+// Shorthand type
+type TaggedFieldIter<I> = core::iter::Chain<core::array::IntoIter<Fe32, 3>, I>;
+
+impl Base32Iterable for TaggedField {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
/// Writes a tagged field: tag, length and data. `tag` should be in `0..32` otherwise the
/// function will panic.
- fn write_tagged_field<W, P>(writer: &mut W, tag: u8, payload: &P) -> Result<(), W::Err>
- where W: WriteBase32,
- P: ToBase32 + Base32Len,
+ fn write_tagged_field<'s, P>(
+ tag: u8, payload: &'s P,
+ ) -> TaggedFieldIter<Box<dyn Iterator<Item = Fe32> + 's>>
+ where
+ P: Base32Iterable + Base32Len + ?Sized,
{
let len = payload.base32_len();
assert!(len < 1024, "Every tagged field data can be at most 1023 bytes long.");
- writer.write_u5(u5::try_from_u8(tag).expect("invalid tag, not in 0..32"))?;
- writer.write(&try_stretch(
- encode_int_be_base32(len as u64),
- 2
- ).expect("Can't be longer than 2, see assert above."))?;
- payload.write_base32(writer)
+ [
+ Fe32::try_from(tag).expect("invalid tag, not in 0..32"),
+ Fe32::try_from((len / 32) as u8).expect("< 32"),
+ Fe32::try_from((len % 32) as u8).expect("< 32"),
+ ]
+ .into_iter()
+ .chain(payload.fe_iter())
}
- match *self {
+ // we will also need a giant enum for this
+ Box::new(match *self {
TaggedField::PaymentHash(ref hash) => {
- write_tagged_field(writer, constants::TAG_PAYMENT_HASH, hash)
+ write_tagged_field(constants::TAG_PAYMENT_HASH, hash)
},
TaggedField::Description(ref description) => {
- write_tagged_field(writer, constants::TAG_DESCRIPTION, description)
+ write_tagged_field(constants::TAG_DESCRIPTION, description)
},
TaggedField::PayeePubKey(ref pub_key) => {
- write_tagged_field(writer, constants::TAG_PAYEE_PUB_KEY, pub_key)
+ write_tagged_field(constants::TAG_PAYEE_PUB_KEY, pub_key)
},
TaggedField::DescriptionHash(ref hash) => {
- write_tagged_field(writer, constants::TAG_DESCRIPTION_HASH, hash)
+ write_tagged_field(constants::TAG_DESCRIPTION_HASH, hash)
},
TaggedField::ExpiryTime(ref duration) => {
- write_tagged_field(writer, constants::TAG_EXPIRY_TIME, duration)
+ write_tagged_field(constants::TAG_EXPIRY_TIME, duration)
},
TaggedField::MinFinalCltvExpiryDelta(ref expiry) => {
- write_tagged_field(writer, constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA, expiry)
+ write_tagged_field(constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA, expiry)
},
TaggedField::Fallback(ref fallback_address) => {
- write_tagged_field(writer, constants::TAG_FALLBACK, fallback_address)
+ write_tagged_field(constants::TAG_FALLBACK, fallback_address)
},
TaggedField::PrivateRoute(ref route_hops) => {
- write_tagged_field(writer, constants::TAG_PRIVATE_ROUTE, route_hops)
+ write_tagged_field(constants::TAG_PRIVATE_ROUTE, route_hops)
},
TaggedField::PaymentSecret(ref payment_secret) => {
- write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret)
+ write_tagged_field(constants::TAG_PAYMENT_SECRET, payment_secret)
},
TaggedField::PaymentMetadata(ref payment_metadata) => {
- write_tagged_field(writer, constants::TAG_PAYMENT_METADATA, payment_metadata)
+ write_tagged_field(constants::TAG_PAYMENT_METADATA, payment_metadata)
},
TaggedField::Features(ref features) => {
- write_tagged_field(writer, constants::TAG_FEATURES, features)
+ write_tagged_field(constants::TAG_FEATURES, features)
},
- }
+ })
}
}
-impl ToBase32 for Bolt11InvoiceSignature {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- let mut converter = BytesToBase32::new(writer);
+impl Base32Iterable for Bolt11InvoiceSignature {
+ fn fe_iter<'s>(&'s self) -> Box<dyn Iterator<Item = Fe32> + 's> {
let (recovery_id, signature) = self.0.serialize_compact();
- converter.append(&signature[..])?;
- converter.append_u8(recovery_id.to_i32() as u8)?;
- converter.finalize()
+ Box::new(
+ signature
+ .into_iter()
+ .chain(core::iter::once(recovery_id.to_i32() as u8))
+ .bytes_to_fes(),
+ )
}
}
#[cfg(test)]
mod test {
- use bech32::CheckBase32;
-
#[test]
fn test_currency_code() {
use crate::Currency;
#[test]
fn test_encode_int_be_base32() {
use crate::ser::encode_int_be_base32;
+ use bech32::Fe32;
let input: u64 = 33764;
- let expected_out = CheckBase32::check_base32(&[1, 0, 31, 4]).unwrap();
+ let expected_out = [1, 0, 31, 4]
+ .iter()
+ .copied()
+ .map(|v| Fe32::try_from(v).expect("<= 31"))
+ .collect::<Vec<Fe32>>();
assert_eq!(expected_out, encode_int_be_base32(input));
}
-
- #[test]
- fn test_encode_int_be_base256() {
- use crate::ser::encode_int_be_base256;
-
- let input: u64 = 16842530;
- let expected_out = vec![1, 0, 255, 34];
-
- assert_eq!(expected_out, encode_int_be_base256(input));
- }
}
-use crate::{
- sha256, FromBase32, PayeePubKey, PaymentSecret, PositiveTimestamp, RawDataPart, Sha256,
-};
-use bech32::{u5, Base32Len, ToBase32};
-
+use crate::de::FromBase32;
+use crate::ser::{Base32Iterable, Base32Len};
+use crate::{sha256, PayeePubKey, PaymentSecret, PositiveTimestamp, RawDataPart, Sha256};
+use bech32::Fe32;
use core::fmt::Debug;
use std::str::FromStr;
/// Test base32 encode and decode
fn ser_de_test<T>(o: T, expected_str: &str)
where
- T: ToBase32 + FromBase32 + Eq + Debug,
+ T: Base32Iterable + FromBase32 + Eq + Debug,
T::Err: Debug,
{
- let serialized_32 = o.to_base32();
+ let serialized_32 = o.fe_iter().collect::<Vec<Fe32>>();
let serialized_str = serialized_32.iter().map(|f| f.to_char()).collect::<String>();
assert_eq!(serialized_str, expected_str, "Mismatch for {:?}", o);
/// Test base32 encode and decode, and also length hint
fn ser_de_test_len<T>(o: T, expected_str: &str)
where
- T: ToBase32 + FromBase32 + Base32Len + Eq + Debug,
+ T: Base32Iterable + Base32Len + FromBase32 + Eq + Debug,
T::Err: Debug,
{
assert_eq!(o.base32_len(), expected_str.len(), "Mismatch for {} {:?}", expected_str, o);
ser_de_test_len(vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246], "lll0ml8mltul3alk");
}
+#[test]
+fn array_u8() {
+ ser_de_test_len([0], "qq");
+ ser_de_test_len([255], "lu");
+ ser_de_test_len([0, 1], "qqqs");
+ ser_de_test_len([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "qqqsyqcyq5rqwzqf");
+ ser_de_test_len([255, 254, 253, 252, 251, 250, 249, 248, 247, 246], "lll0ml8mltul3alk");
+}
+
+#[test]
+fn array_u8_error_invalid_length() {
+ // correct len -- 5 fe32 -> 3 bytes
+ assert_eq!(
+ <[u8; 3]>::from_base32(
+ &"pqqql".to_string().chars().map(|c| Fe32::from_char(c).unwrap()).collect::<Vec<_>>()[..]
+ )
+ .unwrap(),
+ [8, 0, 15]
+ );
+
+ // input too short
+ assert_eq!(
+ <[u8; 3]>::from_base32(
+ &"pqql".to_string().chars().map(|c| Fe32::from_char(c).unwrap()).collect::<Vec<_>>()[..]
+ )
+ .err()
+ .unwrap()
+ .to_string(),
+ "Slice had length 2 instead of 3 for element <[u8; N]>"
+ );
+
+ // input too long
+ assert_eq!(
+ <[u8; 3]>::from_base32(
+ &"pqqqqql".to_string().chars().map(|c| Fe32::from_char(c).unwrap()).collect::<Vec<_>>()
+ [..]
+ )
+ .err()
+ .unwrap()
+ .to_string(),
+ "Slice had length 4 instead of 3 for element <[u8; N]>"
+ );
+}
+
#[test]
fn payment_secret() {
let payment_secret = PaymentSecret([7; 32]);
// To test skipping 0's in deserialization, we have to start with deserialization
assert_eq!(
Bolt11InvoiceFeatures::from_base32(
- &vec![0, 0, 0, 0, 0, 3, 4]
- .iter().copied().map(|f| u5::try_from_u8(f).unwrap()).collect::<Vec<_>>()[..]
+ &"qqqqqry".to_string().chars().map(|c| Fe32::from_char(c).unwrap()).collect::<Vec<_>>()
+ [..]
)
.unwrap()
.le_flags(),
);
assert_eq!(
Bolt11InvoiceFeatures::from_base32(
- &vec![3, 4]
- .iter().copied().map(|f| u5::try_from_u8(f).unwrap()).collect::<Vec<_>>()[..]
+ &"ry".to_string().chars().map(|c| Fe32::from_char(c).unwrap()).collect::<Vec<_>>()[..]
)
.unwrap()
.le_flags(),
#[test]
fn test_bolt_invalid_invoices() {
+ use bech32::primitives::decode::{CharError, ChecksumError, CheckedHrpstringError, UncheckedHrpstringError};
+
// Tests the BOLT 11 invalid invoice test vectors
assert_eq!(Bolt11Invoice::from_str(
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqtqyx5vggfcsll4wu246hz02kp85x4katwsk9639we5n5yngc3yhqkm35jnjw4len8vrnqnf5ejh0mzj9n3vz2px97evektfm2l6wqccp3y7372"
), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidFeatures)));
assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt"
- ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(CheckedHrpstringError::Checksum(ChecksumError::InvalidResidue)))));
assert_eq!(Bolt11Invoice::from_str(
"pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
- ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MissingSeparator))));
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MissingSeparator))))));
assert_eq!(Bolt11Invoice::from_str(
"LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
- ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MixedCase))));
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MixedCase))))));
assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2"
), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidSignature)));
[dependencies]
bitcoin = { version = "0.32.2", default-features = false }
-bech32 = { version = "0.9", default-features = false }
[lints]
workspace = true
use core::marker::PhantomData;
use core::{cmp, fmt};
-use alloc::vec;
use alloc::vec::Vec;
-use bech32::{u5, Base32Len, FromBase32, ToBase32, WriteBase32};
-
mod sealed {
use super::Features;
}
}
-impl ToBase32 for Bolt11InvoiceFeatures {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- // Explanation for the "4": the normal way to round up when dividing is to add the divisor
- // minus one before dividing
- let length_u5s = (self.flags.len() * 8 + 4) / 5 as usize;
- let mut res_u5s: Vec<u5> = vec![u5::try_from_u8(0).unwrap(); length_u5s];
- for (byte_idx, byte) in self.flags.iter().enumerate() {
- let bit_pos_from_left_0_indexed = byte_idx * 8;
- let new_u5_idx = length_u5s - (bit_pos_from_left_0_indexed / 5) as usize - 1;
- let new_bit_pos = bit_pos_from_left_0_indexed % 5;
- let shifted_chunk_u16 = (*byte as u16) << new_bit_pos;
- let curr_u5_as_u8 = res_u5s[new_u5_idx].to_u8();
- res_u5s[new_u5_idx] =
- u5::try_from_u8(curr_u5_as_u8 | ((shifted_chunk_u16 & 0x001f) as u8)).unwrap();
- if new_u5_idx > 0 {
- let curr_u5_as_u8 = res_u5s[new_u5_idx - 1].to_u8();
- res_u5s[new_u5_idx - 1] =
- u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 5) & 0x001f) as u8))
- .unwrap();
- }
- if new_u5_idx > 1 {
- let curr_u5_as_u8 = res_u5s[new_u5_idx - 2].to_u8();
- res_u5s[new_u5_idx - 2] =
- u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 10) & 0x001f) as u8))
- .unwrap();
- }
- }
- // Trim the highest feature bits.
- while !res_u5s.is_empty() && res_u5s[0] == u5::try_from_u8(0).unwrap() {
- res_u5s.remove(0);
- }
- writer.write(&res_u5s)
- }
-}
-
-impl Base32Len for Bolt11InvoiceFeatures {
- fn base32_len(&self) -> usize {
- self.to_base32().len()
- }
-}
-
-impl FromBase32 for Bolt11InvoiceFeatures {
- type Err = bech32::Error;
-
- fn from_base32(field_data: &[u5]) -> Result<Bolt11InvoiceFeatures, bech32::Error> {
- // Explanation for the "7": the normal way to round up when dividing is to add the divisor
- // minus one before dividing
- let length_bytes = (field_data.len() * 5 + 7) / 8 as usize;
- let mut res_bytes: Vec<u8> = vec![0; length_bytes];
- for (u5_idx, chunk) in field_data.iter().enumerate() {
- let bit_pos_from_right_0_indexed = (field_data.len() - u5_idx - 1) * 5;
- let new_byte_idx = (bit_pos_from_right_0_indexed / 8) as usize;
- let new_bit_pos = bit_pos_from_right_0_indexed % 8;
- let chunk_u16 = chunk.to_u8() as u16;
- res_bytes[new_byte_idx] |= ((chunk_u16 << new_bit_pos) & 0xff) as u8;
- if new_byte_idx != length_bytes - 1 {
- res_bytes[new_byte_idx + 1] |= ((chunk_u16 >> (8 - new_bit_pos)) & 0xff) as u8;
- }
- }
- // Trim the highest feature bits.
- while !res_bytes.is_empty() && res_bytes[res_bytes.len() - 1] == 0 {
- res_bytes.pop();
- }
- Ok(Bolt11InvoiceFeatures::from_le_bytes(res_bytes))
- }
-}
-
impl<T: sealed::Context> Features<T> {
/// Create a blank Features with no features set
pub fn empty() -> Self {
assert_eq!(features.flags[32], 0b00000101);
}
- #[test]
- fn invoice_features_encoding() {
- let features_as_u5s = vec![
- u5::try_from_u8(6).unwrap(),
- u5::try_from_u8(10).unwrap(),
- u5::try_from_u8(25).unwrap(),
- u5::try_from_u8(1).unwrap(),
- u5::try_from_u8(10).unwrap(),
- u5::try_from_u8(0).unwrap(),
- u5::try_from_u8(20).unwrap(),
- u5::try_from_u8(2).unwrap(),
- u5::try_from_u8(0).unwrap(),
- u5::try_from_u8(6).unwrap(),
- u5::try_from_u8(0).unwrap(),
- u5::try_from_u8(16).unwrap(),
- u5::try_from_u8(1).unwrap(),
- ];
- let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]);
-
- // Test length calculation.
- assert_eq!(features.base32_len(), 13);
-
- // Test serialization.
- let features_serialized = features.to_base32();
- assert_eq!(features_as_u5s, features_serialized);
-
- // Test deserialization.
- let features_deserialized = Bolt11InvoiceFeatures::from_base32(&features_as_u5s).unwrap();
- assert_eq!(features, features_deserialized);
- }
-
#[test]
fn test_channel_type_mapping() {
// If we map an Bolt11InvoiceFeatures with StaticRemoteKey optional, it should map into a
//! Types which describe payments in lightning.
-use alloc::vec::Vec;
-
use core::borrow::Borrow;
use bitcoin::hashes::{sha256::Hash as Sha256, Hash as _};
const LENGTH: usize = 32;
}
}
-
-use bech32::{u5, Base32Len, FromBase32, ToBase32, WriteBase32};
-
-impl FromBase32 for PaymentSecret {
- type Err = bech32::Error;
-
- fn from_base32(field_data: &[u5]) -> Result<PaymentSecret, bech32::Error> {
- if field_data.len() != 52 {
- return Err(bech32::Error::InvalidLength);
- } else {
- let data_bytes = Vec::<u8>::from_base32(field_data)?;
- let mut payment_secret = [0; 32];
- payment_secret.copy_from_slice(&data_bytes);
- Ok(PaymentSecret(payment_secret))
- }
- }
-}
-
-impl ToBase32 for PaymentSecret {
- fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
- (&self.0[..]).write_base32(writer)
- }
-}
-
-impl Base32Len for PaymentSecret {
- fn base32_len(&self) -> usize {
- 52
- }
-}
lightning-types = { version = "0.1.0", path = "../lightning-types", default-features = false }
lightning-invoice = { version = "0.32.0", path = "../lightning-invoice", default-features = false }
-bech32 = { version = "0.9.1", default-features = false }
+bech32 = { version = "0.11.0", default-features = false }
bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] }
hashbrown = { version = "0.13", default-features = false }
use crate::io;
use crate::ln::msgs::DecodeError;
use crate::util::ser::CursorReadable;
+use bech32::primitives::decode::CheckedHrpstringError;
#[allow(unused_imports)]
use crate::prelude::*;
pub use sealed::Bech32Encode;
mod sealed {
- use bech32::{FromBase32, ToBase32};
+ use bech32::{EncodeError, Hrp, NoChecksum, encode_to_fmt};
+ use bech32::primitives::decode::CheckedHrpstring;
use core::fmt;
use super::Bolt12ParseError;
None => Bech32String::Borrowed(s),
};
- let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;
-
- if hrp != Self::BECH32_HRP {
+ let parsed = CheckedHrpstring::new::<NoChecksum>(encoded.as_ref())?;
+ let hrp = parsed.hrp();
+ if hrp.to_string() != Self::BECH32_HRP {
return Err(Bolt12ParseError::InvalidBech32Hrp);
}
- let data = Vec::<u8>::from_base32(&data)?;
+ let data = parsed.byte_iter().collect::<Vec<u8>>();
Self::try_from(data)
}
/// Formats the message using bech32-encoding.
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(())
+ 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 {},
+ })
}
}
/// being parsed.
InvalidBech32Hrp,
/// The string could not be bech32 decoded.
- Bech32(bech32::Error),
+ Bech32(CheckedHrpstringError),
/// The bech32 decoded string could not be decoded as the expected message type.
Decode(DecodeError),
/// The parsed message has invalid semantics.
MissingSignature,
}
-impl From<bech32::Error> for Bolt12ParseError {
- fn from(error: bech32::Error) -> Self {
+impl From<CheckedHrpstringError> for Bolt12ParseError {
+ fn from(error: CheckedHrpstringError) -> Self {
Self::Bech32(error)
}
}
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 = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::InvalidChar('o'))))),
}
}