diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 002cc54a91e..51a053c18ae 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ stdin_fuzz = [] lightning = { path = "../lightning", features = ["regex", "hashbrown", "_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.31.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 63d869c8178..dc91c0ab49b 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, + Bolt11Invoice, FromBase32, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField, + ToBase32, }; use std::str::FromStr; @@ -25,7 +26,7 @@ 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, diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index f73321296d1..3b931393588 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -20,7 +20,7 @@ no-std = ["bitcoin/no-std"] std = ["bitcoin/std", "bech32/std"] [dependencies] -bech32 = { version = "0.9.1", default-features = false } +bech32 = { version = "0.11.0", default-features = false } lightning-types = { version = "0.1", path = "../lightning-types", default-features = false } secp256k1 = { version = "0.28.0", default-features = false, features = ["recovery", "alloc"] } serde = { version = "1.0.118", optional = true } diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index be1a21aa25f..a24b3591835 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::{Bech32, Fe32, Fe32IterExt}; +use bech32::primitives::decode::{CheckedHrpstring, CheckedHrpstringError, ChecksumError}; use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; use bitcoin::hashes::Hash; @@ -22,10 +23,61 @@ use secp256k1::PublicKey; use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp, Bolt11SemanticError, PrivateRoute, Bolt11ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawBolt11Invoice, - constants, SignedRawBolt11Invoice, RawDataPart, Bolt11InvoiceFeatures}; + constants, SignedRawBolt11Invoice, RawDataPart, Bolt11InvoiceFeatures, FromBase32}; use self::hrp_sm::parse_hrp; +// FromBase32 implementations are here, because the trait is in this module. + +impl FromBase32 for Vec { + type Err = CheckedHrpstringError; + + fn from_base32(data: &[Fe32]) -> Result { + Ok(data.iter().copied().fes_to_bytes().collect::()) + } +} + +impl FromBase32 for PaymentSecret { + type Err = CheckedHrpstringError; + + fn from_base32(field_data: &[Fe32]) -> Result { + if field_data.len() != 52 { + return Err(CheckedHrpstringError::Checksum(ChecksumError::InvalidLength)) // TODO(bech32): not entirely accurate + } 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 FromBase32 for Bolt11InvoiceFeatures { + type Err = CheckedHrpstringError; + + fn from_base32(field_data: &[Fe32]) -> 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)) + } +} + /// State machine to parse the hrp mod hrp_sm { use core::ops::Range; @@ -269,20 +321,20 @@ 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 ascii + conversion) + let data: Vec<_> = parsed.data_part_ascii_no_checksum().iter() + .map(|ch| Fe32::from_char(char::from(*ch)).expect("value should be < 32")) + .collect(); + + const SIGNATURE_LEN5: usize = 104; // 32-bit values, 65 bytes + if data.len() < SIGNATURE_LEN5 { 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_LEN5])?; Ok(SignedRawBolt11Invoice { raw_invoice: RawBolt11Invoice { @@ -290,10 +342,10 @@ impl FromStr for SignedRawBolt11Invoice { data: data_part, }, hash: RawBolt11Invoice::hash_from_parts( - hrp.as_bytes(), - &data[..data.len()-104] + hrp.to_string().as_bytes(), + &data[..data.len()-MIN_LEN] ), - signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-104..])?, + signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-SIGNATURE_LEN5..])?, }) } } @@ -335,7 +387,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,7 +405,7 @@ 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())); } @@ -368,7 +420,7 @@ 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())); } @@ -384,7 +436,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 +447,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 +475,7 @@ 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::Bech32Error(_)) => { parts.push(RawTaggedField::UnknownSemantics(field.into())) }, Err(e) => {return Err(e)} @@ -435,7 +487,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,7 +529,7 @@ 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) @@ -491,7 +543,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,7 +555,7 @@ 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) @@ -518,7 +570,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 +583,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 +596,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 +637,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 { @@ -628,6 +680,9 @@ impl Display for Bolt11ParseError { Bolt11ParseError::Bech32Error(ref e) => { write!(f, "Invalid bech32: {}", e) } + Bolt11ParseError::GenericBech32Error => { + write!(f, "Invalid bech32") + } Bolt11ParseError::ParseAmountError(ref e) => { write!(f, "Invalid amount in hrp ({})", e) } @@ -702,10 +757,9 @@ from_error!(Bolt11ParseError::MalformedSignature, secp256k1::Error); from_error!(Bolt11ParseError::ParseAmountError, ParseIntError); from_error!(Bolt11ParseError::DescriptionDecodeError, str::Utf8Error); -impl From for Bolt11ParseError { - fn from(e: bech32::Error) -> Self { +impl From for Bolt11ParseError { + fn from(e: CheckedHrpstringError) -> Self { match e { - bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError, _ => Bolt11ParseError::Bech32Error(e) } } @@ -725,9 +779,10 @@ impl From for ParseOrSemanticError { #[cfg(test)] mod test { + use super::FromBase32; use crate::de::Bolt11ParseError; use secp256k1::PublicKey; - use bech32::u5; + use bech32::Fe32; use bitcoin::hashes::sha256; use std::str::FromStr; @@ -742,10 +797,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 +821,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 +855,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 +864,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 +887,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 +899,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 +909,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 +938,7 @@ mod test { }) ), ( - vec![u5::try_from_u8(21).unwrap(); 41], + vec![Fe32::try_from(21).unwrap(); 41], Err(Bolt11ParseError::Skip) ), ( @@ -897,15 +946,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 +968,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 +1013,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 56ca21a9b45..df8e76d73dd 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -37,7 +37,8 @@ extern crate serde; #[cfg(feature = "std")] use std::time::SystemTime; -use bech32::{FromBase32, u5}; +use bech32::Fe32; +use bech32::primitives::decode::CheckedHrpstringError; use bitcoin::{Address, Network, PubkeyHash, ScriptHash, WitnessProgram, WitnessVersion}; use bitcoin::address::Payload; use bitcoin::hashes::{Hash, sha256}; @@ -78,12 +79,59 @@ mod prelude { use crate::prelude::*; +/// Interface to write `Fe32`s into a sink +pub trait WriteBase32 { + /// Write error + type Err: fmt::Debug; + + /// Write a `Fe32` slice + fn write(&mut self, data: &Vec) -> Result<(), Self::Err> { + for b in data { + self.write_fe32(*b)?; + } + Ok(()) + } + + /// Write a single `Fe32` + fn write_fe32(&mut self, data: Fe32) -> Result<(), Self::Err>; +} + +/// A trait for converting a value to a type `T` that represents a `Fe32` slice. +pub trait ToBase32 { + /// Convert `Self` to base32 vector + fn to_base32(&self) -> Vec { + let mut vec = Vec::new(); + self.write_base32(&mut vec).unwrap(); + vec + } + + /// Encode as base32 and write it to the supplied writer + /// Implementations shouldn't allocate. + fn write_base32(&self, writer: &mut W) -> Result<(), ::Err>; +} + +/// Interface to calculate the length of the base32 representation before actually serializing +pub trait Base32Len: ToBase32 { + /// Calculate the base32 serialized length + fn base32_len(&self) -> usize; +} + +/// Trait for paring/converting base32 slice. It is the reciprocal of `ToBase32`. +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; +} + /// 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), + GenericBech32Error, ParseAmountError(ParseIntError), MalformedSignature(secp256k1::Error), BadPrefix, @@ -407,12 +455,30 @@ 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 @@ -960,18 +1026,18 @@ 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] { + fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[Fe32]) -> [u8; 32] { let mut preimage = Vec::::from(hrp_bytes); let mut data_part = Vec::from(data_without_signature); 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()); } } @@ -985,8 +1051,6 @@ impl RawBolt11Invoice { /// Calculate the hash of the encoded `RawBolt11Invoice` which should be signed. pub fn signable_hash(&self) -> [u8; 32] { - use bech32::ToBase32; - RawBolt11Invoice::hash_from_parts( self.hrp.to_string().as_bytes(), &self.data.to_base32() @@ -1496,7 +1560,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, @@ -1511,7 +1575,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") } } diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index b4f7c778d8a..dee964570b5 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -1,16 +1,94 @@ use core::fmt; use core::fmt::{Display, Formatter}; -use bech32::{ToBase32, u5, WriteBase32, Base32Len}; +use bech32::{Bech32, ByteIterExt, Fe32, Fe32IterExt, Hrp}; use crate::prelude::*; -use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp, - PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart}; +use super::{Bolt11Invoice, Bolt11InvoiceFeatures, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PaymentSecret, PositiveTimestamp, + PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart, WriteBase32, ToBase32, Base32Len}; + +// ToBase32 & Base32Len implementations are here, because the trait is in this module. + +impl WriteBase32 for Vec { + type Err = (); + + fn write(&mut self, data: &Vec) -> Result<(), Self::Err> { + self.extend_from_slice(data); + Ok(()) + } + + fn write_fe32(&mut self, data: Fe32) -> Result<(), Self::Err> { + self.push(data); + Ok(()) + } +} + +impl ToBase32 for Vec { + /// Encode as base32 and write it to the supplied writer + fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { + writer.write(&self.iter().copied().bytes_to_fes().collect::>()) + } +} + +impl Base32Len for Vec { + /// Calculate the base32 serialized length + fn base32_len(&self) -> usize { + (self.len() * 8 + 7) / 5 + } +} + +impl ToBase32 for PaymentSecret { + fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { + (&self.0[..]).to_vec().write_base32(writer) + } +} + +impl Base32Len for PaymentSecret { + fn base32_len(&self) -> usize { + 52 + } +} + +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![Fe32::Q; 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] = Fe32::try_from(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] = Fe32::try_from(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] = Fe32::try_from(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] == Fe32::Q { + res_u5s.remove(0); + } + writer.write(&res_u5s) + } +} + +impl Base32Len for Bolt11InvoiceFeatures { + fn base32_len(&self) -> usize { + self.to_base32().len() + } +} /// 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 + /// Target for writing the resulting `Fe32`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 @@ -40,23 +118,23 @@ impl<'a, W: WriteBase32> BytesToBase32<'a, W> { } 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 + // Write first Fe32 if we have to write two Fe32s 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.writer.write_fe32( + Fe32::try_from((self.buffer & 0b11111000) >> 3 ).expect("<32") )?; self.buffer <<= 5; self.buffer_bits -= 5; } // 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. + // a Fe32. Save remaining bits from byte to buffer. let from_buffer = self.buffer >> 3; let from_byte = byte >> (3 + self.buffer_bits); // buffer_bits <= 4 - self.writer.write_u5(u5::try_from_u8(from_buffer | from_byte).expect("<32"))?; + self.writer.write_fe32(Fe32::try_from(from_buffer | from_byte).expect("<32"))?; self.buffer = byte << (5 - self.buffer_bits); self.buffer_bits += 3; @@ -70,17 +148,17 @@ impl<'a, W: WriteBase32> BytesToBase32<'a, W> { } 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. + // There can be at most two Fe32s 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.writer.write_fe32( + Fe32::try_from((self.buffer & 0b11111000) >> 3).expect("<32") )?; self.buffer <<= 5; self.buffer_bits -= 5; } if self.buffer_bits != 0 { - self.writer.write_u5(u5::try_from_u8(self.buffer >> 3).expect("<32"))?; + self.writer.write_fe32(Fe32::try_from(self.buffer >> 3).expect("<32"))?; } Ok(()) @@ -118,7 +196,8 @@ impl Display for SignedRawBolt11Invoice { 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")?; + let bech32 = data.iter().copied().with_checksum::(&Hrp::parse(&hrp).expect("not a valid hrp string")).chars().collect::(); + f.write_str(&bech32)?; Ok(()) } @@ -173,14 +252,14 @@ impl Display for SiPrefix { } } -fn encode_int_be_base32(int: u64) -> Vec { +fn encode_int_be_base32(int: u64) -> Vec { let base = 32u64; - let mut out_vec = Vec::::new(); + let mut out_vec = Vec::::new(); 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; } @@ -214,8 +293,10 @@ fn encode_int_be_base256>(int: T) -> 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 +// TODO(bech32): Default value parameter is added because `bech32::Fe32` does not have `Default`, but it will after 0.12. +// If it get's added, the `default_value` parameter should be dropped (https://github.com/rust-bitcoin/rust-bech32/pull/184) +fn try_stretch(mut in_vec: Vec, target_len: usize, default_value: T) -> Option> + where T: Copy { if in_vec.len() > target_len { None @@ -223,7 +304,7 @@ fn try_stretch(mut in_vec: Vec, target_len: usize) -> Option> 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 vec![default_value; target_len - in_vec.len()]); out_vec.append(&mut in_vec); Some(out_vec) } @@ -247,7 +328,7 @@ 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) + &try_stretch(encode_int_be_base32(self.as_unix_timestamp()), 7, Fe32::Q) .expect("Can't be longer due than 7 u5s due to timestamp bounds") ) } @@ -268,30 +349,30 @@ impl ToBase32 for RawTaggedField { impl ToBase32 for Sha256 { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.0[..]).write_base32(writer) + (&self.0[..].to_vec()).write_base32(writer) } } impl Base32Len for Sha256 { fn base32_len(&self) -> usize { - (&self.0[..]).base32_len() + (&self.0[..].to_vec()).base32_len() } } impl ToBase32 for Description { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - self.0.0.as_bytes().write_base32(writer) + self.0.0.as_bytes().to_vec().write_base32(writer) } } impl Base32Len for Description { fn base32_len(&self) -> usize { - self.0.0.as_bytes().base32_len() + self.0.0.as_bytes().to_vec().base32_len() } } impl ToBase32 for PayeePubKey { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.serialize()[..]).write_base32(writer) + (&self.serialize()[..].to_vec()).write_base32(writer) } } @@ -329,16 +410,16 @@ 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"))?; + writer.write_fe32(Fe32::try_from(v.to_num()).expect("witness version <= 16"))?; p.write_base32(writer) }, Fallback::PubKeyHash(ref hash) => { - writer.write_u5(u5::try_from_u8(17).expect("17 < 32"))?; - (&hash[..]).write_base32(writer) + writer.write_fe32(Fe32::try_from(17).expect("17 < 32"))?; + (&hash[..].to_vec()).write_base32(writer) }, Fallback::ScriptHash(ref hash) => { - writer.write_u5(u5::try_from_u8(18).expect("18 < 32"))?; - (&hash[..]).write_base32(writer) + writer.write_fe32(Fe32::try_from(18).expect("18 < 32"))?; + (&hash[..].to_vec()).write_base32(writer) } } } @@ -365,25 +446,25 @@ impl ToBase32 for PrivateRoute { converter.append(&hop.src_node_id.serialize()[..])?; let short_channel_id = try_stretch( encode_int_be_base256(hop.short_channel_id), - 8 + 8, u8::default() ).expect("sizeof(u64) == 8"); converter.append(&short_channel_id)?; let fee_base_msat = try_stretch( encode_int_be_base256(hop.fees.base_msat), - 4 + 4, u8::default() ).expect("sizeof(u32) == 4"); converter.append(&fee_base_msat)?; let fee_proportional_millionths = try_stretch( encode_int_be_base256(hop.fees.proportional_millionths), - 4 + 4, u8::default() ).expect("sizeof(u32) == 4"); converter.append(&fee_proportional_millionths)?; let cltv_expiry_delta = try_stretch( encode_int_be_base256(hop.cltv_expiry_delta), - 2 + 2, u8::default() ).expect("sizeof(u16) == 2"); converter.append(&cltv_expiry_delta)?; } @@ -410,10 +491,10 @@ impl ToBase32 for TaggedField { 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_fe32(Fe32::try_from(tag).expect("invalid tag, not in 0..32"))?; writer.write(&try_stretch( encode_int_be_base32(len as u64), - 2 + 2, Fe32::Q ).expect("Can't be longer than 2, see assert above."))?; payload.write_base32(writer) } @@ -468,8 +549,6 @@ impl ToBase32 for Bolt11InvoiceSignature { #[cfg(test)] mod test { - use bech32::CheckBase32; - #[test] fn test_currency_code() { use crate::Currency; @@ -497,11 +576,12 @@ 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("should be <= 31")).collect::>(); - assert_eq!(expected_out, encode_int_be_base32(input)); + assert_eq!(expected_out.iter().copied().map(|v| v.to_char()).collect::(), encode_int_be_base32(input).iter().copied().map(|v| v.to_char()).collect::()); } #[test] diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index d9d6ad4c195..9f1be276402 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -411,19 +411,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))); @@ -437,3 +439,40 @@ fn test_bolt_invalid_invoices() { "lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x" ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::ImpreciseAmount))); } + +#[test] +fn invoice_features_encoding() { + use crate::lightning_invoice::ToBase32; + use crate::lightning_invoice::FromBase32; + use lightning_types::features::Bolt11InvoiceFeatures; + use bech32::{ByteIterExt, Fe32}; + + let features_as_fes = vec![ + Fe32::try_from(6).unwrap(), + Fe32::try_from(10).unwrap(), + Fe32::try_from(25).unwrap(), + Fe32::try_from(1).unwrap(), + Fe32::try_from(10).unwrap(), + Fe32::try_from(0).unwrap(), + Fe32::try_from(20).unwrap(), + Fe32::try_from(2).unwrap(), + Fe32::try_from(0).unwrap(), + Fe32::try_from(6).unwrap(), + Fe32::try_from(0).unwrap(), + Fe32::try_from(16).unwrap(), + Fe32::try_from(1).unwrap(), + ]; + let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]); + + // Test length calculation. + assert_eq!(features.flags().iter().copied().bytes_to_fes().len(), 13); + + // Test serialization. + let features_serialized = features.to_base32(); + assert_eq!(features_as_fes, features_serialized); + + // Test deserialization. + let features_deserialized = Bolt11InvoiceFeatures::from_base32(&features_as_fes).unwrap(); + assert_eq!(features, features_deserialized); +} + diff --git a/lightning-types/Cargo.toml b/lightning-types/Cargo.toml index a063176ae57..bbe2de1c61e 100644 --- a/lightning-types/Cargo.toml +++ b/lightning-types/Cargo.toml @@ -19,7 +19,7 @@ _test_utils = [] bitcoin = { version = "0.31", default-features = false } # TODO: Once we switch to bitcoin 0.32 drop this explicit dep: hex-conservative = { version = "0.2", default-features = false } -bech32 = { version = "0.9", default-features = false } +bech32 = { version = "0.11.0", default-features = false } [lints] workspace = true diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs index 54587dc980a..d1761c8cf1f 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::*; @@ -716,6 +713,11 @@ impl Bolt11InvoiceFeatures { self.to_context_internal() } + /// Accessor for flags + pub fn flags(&self) -> &Vec { + &self.flags + } + /// Getting a route for a keysend payment to a private node requires providing the payee's /// features (since they were not announced in a node announcement). However, keysend payments /// don't have an invoice to pull the payee's features from, so this method is provided for use @@ -775,73 +777,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 { @@ -1293,37 +1228,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 6b8854ac5f8..a8f03849d5d 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 _}; @@ -81,32 +79,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 c6d82113053..ef9ca83a7ec 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -43,7 +43,7 @@ default = ["std", "grind_signatures"] lightning-types = { version = "0.1", path = "../lightning-types", default-features = false } lightning-invoice = { version = "0.31.0-beta", 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.31.2", default-features = false, features = ["secp-recovery"] } hashbrown = { version = "0.13", optional = true, default-features = false } diff --git a/lightning/src/offers/parse.rs b/lightning/src/offers/parse.rs index c48d745a9ff..eb5ea1a574f 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::SeekReadable; +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::default(), + }) } } @@ -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. @@ -195,8 +198,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) } } @@ -279,6 +282,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() { @@ -294,7 +298,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'))))), } }