From: optout <13562139+optout21@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:21:07 +0000 (+0200) Subject: Upgrade bech32 dependency, bech32 serialization improvements X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=aa2f6b47df312f026213d0ceaaff20ffe955c377;p=rust-lightning Upgrade bech32 dependency, bech32 serialization improvements --- diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 02b808f23..a0f748d23 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ stdin_fuzz = [] 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 } diff --git a/fuzz/src/bolt11_deser.rs b/fuzz/src/bolt11_deser.rs index 63d869c81..92dde80aa 100644 --- a/fuzz/src/bolt11_deser.rs +++ b/fuzz/src/bolt11_deser.rs @@ -8,10 +8,11 @@ // 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; @@ -25,19 +26,19 @@ pub fn do_test(data: &[u8], _out: Out) { Err(_) => return, }; let bech32 = - data.iter().skip(hrp_len).map(|x| u5::try_from_u8(x % 32).unwrap()).collect::>(); + data.iter().skip(hrp_len).map(|x| Fe32::try_from(x % 32).unwrap()).collect::>(); let invoice_data = match RawDataPart::from_base32(&bech32) { Ok(invoice) => invoice, Err(_) => return, }; + let invoice_data_base32 = invoice_data.fe_iter().collect::>(); // 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 ); diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 054113fe1..ee1d4aa53 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -18,7 +18,7 @@ rustdoc-args = ["--cfg", "docsrs"] 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"] } diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index e5ba67ccc..7c425441f 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -8,7 +8,8 @@ use core::num::ParseIntError; 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; @@ -26,6 +27,117 @@ use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDe 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; +} + +// FromBase32 implementations are here, because the trait is in this module. + +impl FromBase32 for Vec { + type Err = Bolt11ParseError; + + fn from_base32(data: &[Fe32]) -> Result { + Ok(data.iter().copied().fes_to_bytes().collect::()) + } +} + +impl FromBase32 for [u8; N] { + type Err = Bolt11ParseError; + + fn from_base32(data: &[Fe32]) -> Result { + 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 { + 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 { + // 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::::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; @@ -269,31 +381,29 @@ impl FromStr for SignedRawBolt11Invoice { type Err = Bolt11ParseError; fn from_str(s: &str) -> Result { - 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::(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>().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..])?, }) } } @@ -335,7 +445,7 @@ impl FromStr for RawHrp { impl FromBase32 for RawDataPart { type Err = Bolt11ParseError; - fn from_base32(data: &[u5]) -> Result { + fn from_base32(data: &[Fe32]) -> Result { if data.len() < 7 { // timestamp length return Err(Bolt11ParseError::TooShortDataPart); } @@ -353,9 +463,13 @@ impl FromBase32 for RawDataPart { impl FromBase32 for PositiveTimestamp { type Err = Bolt11ParseError; - fn from_base32(b32: &[u5]) -> Result { + fn from_base32(b32: &[Fe32]) -> Result { 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"); @@ -368,11 +482,15 @@ impl FromBase32 for PositiveTimestamp { impl FromBase32 for Bolt11InvoiceSignature { type Err = Bolt11ParseError; - fn from_base32(signature: &[u5]) -> Result { + fn from_base32(signature: &[Fe32]) -> Result { 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::::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)?; @@ -384,7 +502,7 @@ impl FromBase32 for Bolt11InvoiceSignature { } 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)) @@ -395,7 +513,7 @@ macro_rules! define_parse_int_be { ($name: ident, $ty: ty) => { define_parse_int_be!(parse_u16_be, u16); define_parse_int_be!(parse_u64_be, u64); -fn parse_tagged_parts(data: &[u5]) -> Result, Bolt11ParseError> { +fn parse_tagged_parts(data: &[Fe32]) -> Result, Bolt11ParseError> { let mut parts = Vec::::new(); let mut data = data; @@ -423,7 +541,9 @@ fn parse_tagged_parts(data: &[u5]) -> Result, Bolt11ParseErr 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)} @@ -435,7 +555,7 @@ fn parse_tagged_parts(data: &[u5]) -> Result, Bolt11ParseErr impl FromBase32 for TaggedField { type Err = Bolt11ParseError; - fn from_base32(field: &[u5]) -> Result { + fn from_base32(field: &[Fe32]) -> Result { if field.len() < 3 { return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields); } @@ -477,12 +597,12 @@ impl FromBase32 for TaggedField { impl FromBase32 for Sha256 { type Err = Bolt11ParseError; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { 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::::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)"))) } } @@ -491,7 +611,7 @@ impl FromBase32 for Sha256 { impl FromBase32 for Description { type Err = Bolt11ParseError; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { let bytes = Vec::::from_base32(field_data)?; let description = String::from(str::from_utf8(&bytes)?); Ok(Description::new(description).expect( @@ -503,12 +623,12 @@ impl FromBase32 for Description { impl FromBase32 for PayeePubKey { type Err = Bolt11ParseError; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { 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::::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()) } @@ -518,7 +638,7 @@ impl FromBase32 for PayeePubKey { impl FromBase32 for ExpiryTime { type Err = Bolt11ParseError; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { match parse_u64_be(field_data) .map(ExpiryTime::from_seconds) { @@ -531,7 +651,7 @@ impl FromBase32 for ExpiryTime { impl FromBase32 for MinFinalCltvExpiryDelta { type Err = Bolt11ParseError; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { let expiry = parse_u64_be(field_data); if let Some(expiry) = expiry { Ok(MinFinalCltvExpiryDelta(expiry)) @@ -544,7 +664,7 @@ impl FromBase32 for MinFinalCltvExpiryDelta { impl FromBase32 for Fallback { type Err = Bolt11ParseError; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { if field_data.is_empty() { return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields); } @@ -585,7 +705,7 @@ impl FromBase32 for Fallback { impl FromBase32 for PrivateRoute { type Err = Bolt11ParseError; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { let bytes = Vec::::from_base32(field_data)?; if bytes.len() % 51 != 0 { @@ -637,9 +757,13 @@ impl Display for Bolt11ParseError { 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"), @@ -702,12 +826,9 @@ from_error!(Bolt11ParseError::MalformedSignature, bitcoin::secp256k1::Error); from_error!(Bolt11ParseError::ParseAmountError, ParseIntError); from_error!(Bolt11ParseError::DescriptionDecodeError, str::Utf8Error); -impl From for Bolt11ParseError { - fn from(e: bech32::Error) -> Self { - match e { - bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError, - _ => Bolt11ParseError::Bech32Error(e) - } +impl From for Bolt11ParseError { + fn from(e: CheckedHrpstringError) -> Self { + Self::Bech32Error(e) } } @@ -725,9 +846,10 @@ impl From for ParseOrSemanticError { #[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; @@ -742,10 +864,10 @@ mod test { 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 ]; - fn from_bech32(bytes_5b: &[u8]) -> Vec { + fn from_bech32(bytes_5b: &[u8]) -> Vec { 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() } @@ -766,19 +888,18 @@ mod test { 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() @@ -801,7 +922,6 @@ mod test { #[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()); @@ -811,7 +931,6 @@ mod test { #[test] fn test_parse_payee_pub_key() { use crate::PayeePubKey; - use bech32::FromBase32; let input = from_bech32("q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66".as_bytes()); let pk_bytes = [ @@ -835,7 +954,6 @@ mod test { #[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)); @@ -848,7 +966,6 @@ mod test { #[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)); @@ -859,7 +976,6 @@ mod test { #[test] fn test_parse_fallback() { use crate::Fallback; - use bech32::FromBase32; use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; use bitcoin::hashes::Hash; @@ -889,7 +1005,7 @@ mod test { }) ), ( - vec![u5::try_from_u8(21).unwrap(); 41], + vec![Fe32::try_from(21).unwrap(); 41], Err(Bolt11ParseError::Skip) ), ( @@ -897,15 +1013,15 @@ mod test { 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) ) ]; @@ -919,7 +1035,6 @@ mod test { fn test_parse_route() { use lightning_types::routing::{RoutingFees, RouteHint, RouteHintHop}; use crate::PrivateRoute; - use bech32::FromBase32; let input = from_bech32( "q20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqa\ @@ -965,7 +1080,7 @@ mod test { 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) ); } diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 08b670f41..363708960 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -33,7 +33,8 @@ extern crate serde; #[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; @@ -42,6 +43,7 @@ use bitcoin::secp256k1::PublicKey; 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; @@ -76,12 +78,18 @@ mod prelude { 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, @@ -97,7 +105,8 @@ pub enum Bolt11ParseError { 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 @@ -299,6 +308,15 @@ pub struct RawHrp { pub si_prefix: Option, } +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 { @@ -404,12 +422,38 @@ impl From for Network { /// 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), + UnknownSemantics(Vec), +} + +impl PartialOrd for RawTaggedField { + fn partial_cmp(&self, other: &Self) -> Option { + 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 @@ -956,37 +1000,46 @@ macro_rules! find_all_extract { #[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::::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 + 's>) -> [u8; 32] { + use crate::bech32::Fe32IterExt; + use bitcoin::hashes::HashEngine; + + let mut data_part = data_without_signature.collect::>(); - 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::::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(), ) } @@ -1493,7 +1546,7 @@ impl From for RawTaggedField { 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, @@ -1508,7 +1561,7 @@ impl TaggedField { 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") } } @@ -2243,4 +2296,32 @@ mod test { 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); + } } diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index 69ef3693c..4000241f5 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -1,96 +1,135 @@ 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 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 + '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 Base32Iterable for [u8; N] { + fn fe_iter<'s>(&'s self) -> Box + '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 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 + '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 { + fn fe_iter<'s>(&'s self) -> Box + '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 { + /// 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 + '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 + '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::::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() } } @@ -107,26 +146,31 @@ fn bytes_size_to_base32_size(byte_size: usize) -> usize { } 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::(&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(), @@ -148,7 +192,7 @@ impl Display for RawHrp { } 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", @@ -161,7 +205,7 @@ impl Display for Currency { } 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", @@ -173,14 +217,15 @@ impl Display for SiPrefix { } } -fn encode_int_be_base32(int: u64) -> Vec { +/// Encode an integer to base32, big endian, without leading zeros +fn encode_int_be_base32(int: u64) -> Vec { let base = 32u64; - let mut out_vec = Vec::::new(); - + // (64 + 4) / 5 == 13 + let mut out_vec = Vec::::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; } @@ -188,98 +233,55 @@ fn encode_int_be_base32(int: u64) -> Vec { 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>(int: T) -> Vec { - let base = 256u64; - - let mut out_vec = Vec::::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 + '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(mut in_vec: Vec, target_len: usize) -> Option> - 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::::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(&self, writer: &mut W) -> Result<(), ::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(&self, writer: &mut W) -> Result<(), ::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 + '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(&self, writer: &mut W) -> Result<(), ::Err> { +impl Base32Iterable for RawTaggedField { + fn fe_iter<'s>(&'s self) -> Box + '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(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.0[..]).write_base32(writer) +impl Base32Iterable for Sha256 { + fn fe_iter<'s>(&'s self) -> Box + '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(&self, writer: &mut W) -> Result<(), ::Err> { - self.0.0.as_bytes().write_base32(writer) +impl Base32Iterable for Description { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.0.0.as_bytes().fe_iter()) } } @@ -289,9 +291,9 @@ impl Base32Len for Description { } } -impl ToBase32 for PayeePubKey { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.serialize()[..]).write_base32(writer) +impl Base32Iterable for PayeePubKey { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.serialize().into_iter().bytes_to_fes()) } } @@ -301,9 +303,9 @@ impl Base32Len for PayeePubKey { } } -impl ToBase32 for ExpiryTime { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - writer.write(&encode_int_be_base32(self.as_seconds())) +impl Base32Iterable for ExpiryTime { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(encode_int_be_base32(self.as_seconds()).into_iter()) } } @@ -313,9 +315,9 @@ impl Base32Len for ExpiryTime { } } -impl ToBase32 for MinFinalCltvExpiryDelta { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - writer.write(&encode_int_be_base32(self.0)) +impl Base32Iterable for MinFinalCltvExpiryDelta { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(encode_int_be_base32(self.0).into_iter()) } } @@ -325,71 +327,60 @@ impl Base32Len for MinFinalCltvExpiryDelta { } } -impl ToBase32 for Fallback { - fn write_base32(&self, writer: &mut W) -> Result<(), ::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 + '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(&self, writer: &mut W) -> Result<(), ::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>, + array::IntoIter, + >, + array::IntoIter, + >, + array::IntoIter, +>; + +impl Base32Iterable for PrivateRoute { + fn fe_iter<'s>(&'s self) -> Box + '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()) } } @@ -399,77 +390,84 @@ impl Base32Len for PrivateRoute { } } -impl ToBase32 for TaggedField { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { +// Shorthand type +type TaggedFieldIter = core::iter::Chain, I>; + +impl Base32Iterable for TaggedField { + fn fe_iter<'s>(&'s self) -> Box + 's> { /// Writes a tagged field: tag, length and data. `tag` should be in `0..32` otherwise the /// function will panic. - fn write_tagged_field(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 + '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(&self, writer: &mut W) -> Result<(), ::Err> { - let mut converter = BytesToBase32::new(writer); +impl Base32Iterable for Bolt11InvoiceSignature { + fn fe_iter<'s>(&'s self) -> Box + '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; @@ -497,20 +495,15 @@ mod test { #[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::>(); 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)); - } } diff --git a/lightning-invoice/src/test_ser_de.rs b/lightning-invoice/src/test_ser_de.rs index 7051924ee..92b6c778d 100644 --- a/lightning-invoice/src/test_ser_de.rs +++ b/lightning-invoice/src/test_ser_de.rs @@ -1,18 +1,17 @@ -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(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::>(); let serialized_str = serialized_32.iter().map(|f| f.to_char()).collect::(); assert_eq!(serialized_str, expected_str, "Mismatch for {:?}", o); @@ -24,7 +23,7 @@ where /// Test base32 encode and decode, and also length hint fn ser_de_test_len(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); @@ -49,6 +48,50 @@ fn vec_u8() { 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::>()[..] + ) + .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::>()[..] + ) + .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::>() + [..] + ) + .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]); @@ -111,8 +154,8 @@ fn bolt11_invoice_features() { // 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::>()[..] + &"qqqqqry".to_string().chars().map(|c| Fe32::from_char(c).unwrap()).collect::>() + [..] ) .unwrap() .le_flags(), @@ -120,8 +163,7 @@ fn bolt11_invoice_features() { ); assert_eq!( Bolt11InvoiceFeatures::from_base32( - &vec![3, 4] - .iter().copied().map(|f| u5::try_from_u8(f).unwrap()).collect::>()[..] + &"ry".to_string().chars().map(|c| Fe32::from_char(c).unwrap()).collect::>()[..] ) .unwrap() .le_flags(), diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index 68404817f..290b8ce66 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -410,19 +410,21 @@ fn invoice_deserialize() { #[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))); diff --git a/lightning-types/Cargo.toml b/lightning-types/Cargo.toml index f3eeec0f1..768b8fb3d 100644 --- a/lightning-types/Cargo.toml +++ b/lightning-types/Cargo.toml @@ -17,7 +17,6 @@ _test_utils = [] [dependencies] bitcoin = { version = "0.32.2", default-features = false } -bech32 = { version = "0.9", default-features = false } [lints] workspace = true diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs index 7ce87e026..418644593 100644 --- a/lightning-types/src/features.rs +++ b/lightning-types/src/features.rs @@ -82,11 +82,8 @@ use core::hash::{Hash, Hasher}; 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; @@ -785,73 +782,6 @@ impl ChannelTypeFeatures { } } -impl ToBase32 for Bolt11InvoiceFeatures { - fn write_base32(&self, writer: &mut W) -> Result<(), ::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 = 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 { - // 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 = 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 Features { /// Create a blank Features with no features set pub fn empty() -> Self { @@ -1303,37 +1233,6 @@ mod tests { 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 diff --git a/lightning-types/src/payment.rs b/lightning-types/src/payment.rs index a2832c6b5..0f0fcf7b5 100644 --- a/lightning-types/src/payment.rs +++ b/lightning-types/src/payment.rs @@ -9,8 +9,6 @@ //! Types which describe payments in lightning. -use alloc::vec::Vec; - use core::borrow::Borrow; use bitcoin::hashes::{sha256::Hash as Sha256, Hash as _}; @@ -79,32 +77,3 @@ impl_fmt_traits! { 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 { - if field_data.len() != 52 { - return Err(bech32::Error::InvalidLength); - } else { - let data_bytes = Vec::::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(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.0[..]).write_base32(writer) - } -} - -impl Base32Len for PaymentSecret { - fn base32_len(&self) -> usize { - 52 - } -} diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 3a1939733..e58ea74cb 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -40,7 +40,7 @@ default = ["std", "grind_signatures"] 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 } diff --git a/lightning/src/offers/parse.rs b/lightning/src/offers/parse.rs index 24a784807..a46e90de6 100644 --- a/lightning/src/offers/parse.rs +++ b/lightning/src/offers/parse.rs @@ -13,6 +13,7 @@ use bitcoin::secp256k1; use crate::io; use crate::ln::msgs::DecodeError; use crate::util::ser::CursorReadable; +use bech32::primitives::decode::CheckedHrpstringError; #[allow(unused_imports)] use crate::prelude::*; @@ -24,7 +25,8 @@ pub(super) use sealed::Bech32Encode; 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; @@ -54,22 +56,23 @@ mod sealed { None => Bech32String::Borrowed(s), }; - let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?; - - if hrp != Self::BECH32_HRP { + let parsed = CheckedHrpstring::new::(encoded.as_ref())?; + let hrp = parsed.hrp(); + if hrp.to_string() != Self::BECH32_HRP { return Err(Bolt12ParseError::InvalidBech32Hrp); } - let data = Vec::::from_base32(&data)?; + let data = parsed.byte_iter().collect::>(); 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::(f, Hrp::parse(Self::BECH32_HRP).unwrap(), self.as_ref()) + .map_err(|e| match e { + EncodeError::Fmt(e) => e, + _ => fmt::Error {}, + }) } } @@ -123,7 +126,7 @@ pub enum Bolt12ParseError { /// 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. @@ -197,8 +200,8 @@ pub enum Bolt12SemanticError { MissingSignature, } -impl From for Bolt12ParseError { - fn from(error: bech32::Error) -> Self { +impl From for Bolt12ParseError { + fn from(error: CheckedHrpstringError) -> Self { Self::Bech32(error) } } @@ -281,6 +284,7 @@ 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() { @@ -296,7 +300,7 @@ mod tests { let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo"; match encoded_offer.parse::() { 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'))))), } }