diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 4d9f7e0dea3..2914933f194 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"] } hex = { package = "hex-conservative", version = "0.1.1", default-features = false } 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/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 7d660f459ea..8cf3206b2d0 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -56,6 +56,7 @@ use lightning::ln::msgs::{ self, ChannelMessageHandler, CommitmentUpdate, DecodeError, Init, UpdateAddHTLC, }; use lightning::ln::script::ShutdownScript; +use lightning::ln::types::InvoiceData; use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; use lightning::offers::invoice_request::UnsignedInvoiceRequest; @@ -79,7 +80,6 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; use bitcoin::secp256k1::{self, Message, PublicKey, Scalar, Secp256k1, SecretKey}; -use bech32::u5; use std::cmp::{self, Ordering}; use std::io::Cursor; use std::mem; @@ -332,7 +332,7 @@ impl NodeSigner for KeyProvider { } fn sign_invoice( - &self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient, + &self, _hrp_bytes: &[u8], _invoice_data: &InvoiceData, _recipient: Recipient, ) -> Result { unreachable!() } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 9c45c66e1a4..1f34d411da1 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -49,6 +49,7 @@ use lightning::ln::peer_handler::{ IgnoringMessageHandler, MessageHandler, PeerManager, SocketDescriptor, }; use lightning::ln::script::ShutdownScript; +use lightning::ln::types::InvoiceData; use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; use lightning::offers::invoice_request::UnsignedInvoiceRequest; @@ -76,7 +77,6 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; use bitcoin::secp256k1::{self, Message, PublicKey, Scalar, Secp256k1, SecretKey}; -use bech32::u5; use std::cell::RefCell; use std::cmp; use std::convert::TryInto; @@ -406,7 +406,7 @@ impl NodeSigner for KeyProvider { } fn sign_invoice( - &self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient, + &self, _hrp_bytes: &[u8], _invoice_data: &InvoiceData, _recipient: Recipient, ) -> Result { unreachable!() } diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 05ee7526faa..ebbe174f37f 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -1,5 +1,4 @@ // Imports that need to be added manually -use bech32::u5; use bitcoin::blockdata::script::ScriptBuf; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; @@ -11,6 +10,7 @@ use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler}; use lightning::ln::script::ShutdownScript; +use lightning::ln::types::InvoiceData; use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::onion_message::async_payments::{ @@ -224,7 +224,7 @@ impl NodeSigner for KeyProvider { } fn sign_invoice( - &self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient, + &self, _hrp_bytes: &[u8], _invoice_data: &InvoiceData, _recipient: Recipient, ) -> Result { unreachable!() } diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 22f8616a2f9..cc916f7e7ed 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -20,7 +20,7 @@ no-std = ["lightning/no-std"] std = ["bitcoin/std", "lightning/std", "bech32/std"] [dependencies] -bech32 = { version = "0.9.1", default-features = false } +bech32 = { version = "0.11.0", default-features = false } lightning = { version = "0.0.123-beta", path = "../lightning", 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 bd9f4a5f6de..a6c4c0cc49f 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; @@ -23,10 +24,59 @@ 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; +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; @@ -270,20 +320,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)); - } + let parsed = CheckedHrpstring::new::(s)?; + let hrp = parsed.hrp(); + let data: Vec<_> = parsed.data_part_ascii_no_checksum().iter() + .map(|ch| Fe32::from_char(char::from(*ch)).expect("value should be < 32")) + .collect(); - if data.len() < 104 { + const MIN_LEN: usize = 104; + if data.len() < MIN_LEN { 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()-MIN_LEN])?; Ok(SignedRawBolt11Invoice { raw_invoice: RawBolt11Invoice { @@ -291,10 +341,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()-MIN_LEN..])?, }) } } @@ -336,7 +386,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); } @@ -354,7 +404,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())); } @@ -369,7 +419,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())); } @@ -385,7 +435,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)) @@ -396,7 +446,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; @@ -424,8 +474,8 @@ fn parse_tagged_parts(data: &[u5]) -> Result, Bolt11ParseErr Ok(field) => { parts.push(RawTaggedField::KnownSemantics(field)) }, - Err(Bolt11ParseError::Skip)|Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidLength)) => { - parts.push(RawTaggedField::UnknownSemantics(field.into())) + Err(Bolt11ParseError::Skip) | Err(Bolt11ParseError::Bech32Error(_)) => { + parts.push(RawTaggedField::UnknownSemantics(field.iter().map(|v| crate::Fe32Ord(*v)).collect())) }, Err(e) => {return Err(e)} } @@ -436,7 +486,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); } @@ -478,7 +528,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) @@ -492,7 +542,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( @@ -504,7 +554,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) @@ -519,7 +569,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) { @@ -532,7 +582,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)) @@ -545,7 +595,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); } @@ -586,7 +636,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 { @@ -629,6 +679,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) } @@ -703,10 +756,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) } } @@ -726,9 +778,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; @@ -743,10 +796,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() } @@ -767,19 +820,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()] + 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()] + 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() @@ -802,7 +854,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()); @@ -812,7 +863,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 = [ @@ -836,7 +886,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)); @@ -849,7 +898,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)); @@ -860,7 +908,6 @@ mod test { #[test] fn test_parse_fallback() { use crate::Fallback; - use bech32::FromBase32; use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; use bitcoin::hashes::Hash; @@ -890,7 +937,7 @@ mod test { }) ), ( - vec![u5::try_from_u8(21).unwrap(); 41], + vec![Fe32::try_from(21).unwrap(); 41], Err(Bolt11ParseError::Skip) ), ( @@ -898,15 +945,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) ) ]; @@ -921,7 +968,6 @@ mod test { use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop}; use crate::PrivateRoute; - use bech32::FromBase32; let input = from_bech32( "q20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqa\ @@ -967,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 e427bf3ccb9..25db4a6cda5 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -41,11 +41,13 @@ extern crate serde; #[cfg(feature = "std")] use std::time::SystemTime; -use bech32::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}; use lightning::ln::features::Bolt11InvoiceFeatures; +use lightning::ln::types::InvoiceData; use lightning::util::invoice::construct_invoice_preimage; use secp256k1::PublicKey; @@ -85,12 +87,60 @@ 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, @@ -414,6 +464,18 @@ impl From for Network { } } +/// Wrapped Fe32 with Ord trait +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct Fe32Ord(pub Fe32); + +impl Ord for Fe32Ord { + fn cmp(&self, other: &Self) -> Ordering { self.0.to_u8().cmp(&other.0.to_u8()) } +} + +impl PartialOrd for Fe32Ord { + fn partial_cmp(&self, other: &Self) -> Option { self.0.to_u8().partial_cmp(&other.0.to_u8()) } +} + /// Tagged field which may have an unknown tag /// /// This is not exported to bindings users as we don't currently support TaggedField @@ -422,7 +484,7 @@ 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), } /// Tagged field with known tag @@ -970,8 +1032,8 @@ 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 preimage = construct_invoice_preimage(hrp_bytes, data_without_signature); + fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[Fe32]) -> [u8; 32] { + let preimage = construct_invoice_preimage(hrp_bytes, &InvoiceData(data_without_signature.to_vec())); let mut hash: [u8; 32] = Default::default(); hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]); hash @@ -979,8 +1041,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() @@ -1490,7 +1550,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, @@ -1505,7 +1565,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..9460877457f 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -1,16 +1,92 @@ 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}; + +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 +116,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 +146,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 Fe32a 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 +194,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 +250,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 +291,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`. +// If it get's added, the `default_value` parameter can 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 +302,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,8 +326,8 @@ 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") + &try_stretch(encode_int_be_base32(self.as_unix_timestamp()), 7, Fe32::Q) + .expect("Can't be longer due than 7 Fe32s due to timestamp bounds") ) } } @@ -257,7 +336,7 @@ impl ToBase32 for RawTaggedField { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { match *self { RawTaggedField::UnknownSemantics(ref content) => { - writer.write(content) + writer.write(&content.iter().map(|v| v.0).collect()) }, RawTaggedField::KnownSemantics(ref tagged_field) => { tagged_field.write_base32(writer) @@ -268,30 +347,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 +408,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 +444,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 +489,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 +547,6 @@ impl ToBase32 for Bolt11InvoiceSignature { #[cfg(test)] mod test { - use bech32::CheckBase32; - #[test] fn test_currency_code() { use crate::Currency; @@ -497,11 +574,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/src/utils.rs b/lightning-invoice/src/utils.rs index 00b49c371ea..4d537df56fc 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -3,12 +3,12 @@ use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError}; use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256}; -use bech32::ToBase32; +use crate::ToBase32; use bitcoin::hashes::Hash; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use lightning::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; -use lightning::ln::types::{PaymentHash, PaymentSecret}; +use lightning::ln::types::{InvoiceData, PaymentHash, PaymentSecret}; use lightning::ln::channel_state::ChannelDetails; use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA}; use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; @@ -219,7 +219,7 @@ where }; let hrp_str = raw_invoice.hrp.to_string(); let hrp_bytes = hrp_str.as_bytes(); - let data_without_signature = raw_invoice.data.to_base32(); + let data_without_signature = InvoiceData(raw_invoice.data.to_base32()); let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode)); match signed_raw_invoice { Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()), @@ -571,7 +571,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has }; let hrp_str = raw_invoice.hrp.to_string(); let hrp_bytes = hrp_str.as_bytes(); - let data_without_signature = raw_invoice.data.to_base32(); + let data_without_signature = InvoiceData(raw_invoice.data.to_base32()); let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node)); match signed_raw_invoice { Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()), diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index 6b5e99476b0..b956d338260 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -413,19 +413,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))); @@ -439,3 +441,39 @@ 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::ln::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/Cargo.toml b/lightning/Cargo.toml index 8f238f5b557..9392add0b36 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -40,7 +40,7 @@ grind_signatures = [] default = ["std", "grind_signatures"] [dependencies] -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/ln/features.rs b/lightning/src/ln/features.rs index 51c608c1a6b..ada0775f84e 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -87,7 +87,6 @@ use core::borrow::Borrow; use core::hash::{Hash, Hasher}; use core::marker::PhantomData; -use bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32}; use crate::ln::msgs::DecodeError; use crate::util::ser::{Readable, WithoutLength, Writeable, Writer}; @@ -581,6 +580,9 @@ 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 in @@ -640,68 +642,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 { @@ -1043,7 +983,6 @@ pub(crate) fn unset_features_mask_at_position(other: &Featur #[cfg(test)] mod tests { use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, Bolt11InvoiceFeatures, NodeFeatures, OfferFeatures, sealed}; - use bech32::{Base32Len, FromBase32, ToBase32, u5}; use crate::util::ser::{Readable, WithoutLength, Writeable}; #[test] @@ -1241,37 +1180,6 @@ mod tests { assert_eq!(features, deserialized_features); } - #[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/src/ln/types.rs b/lightning/src/ln/types.rs index c6fa5986764..632c57f6ec5 100644 --- a/lightning/src/ln/types.rs +++ b/lightning/src/ln/types.rs @@ -162,34 +162,10 @@ impl From for PaymentHash { #[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)] pub struct PaymentSecret(pub [u8; 32]); -use bech32::{Base32Len, FromBase32, ToBase32, WriteBase32, u5}; +use bech32::Fe32; -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 - } -} +/// Invoice data +pub struct InvoiceData(pub Vec); #[cfg(test)] mod tests { 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'))))), } } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 885b8840b76..8b4380cd17e 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -24,7 +24,6 @@ use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::transaction::Version; -use bech32::u5; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::{Hash, HashEngine}; @@ -54,7 +53,7 @@ use crate::ln::channel_keys::{ use crate::ln::msgs::PartialSignatureWithNonce; use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage}; use crate::ln::script::ShutdownScript; -use crate::ln::types::PaymentPreimage; +use crate::ln::types::{InvoiceData, PaymentPreimage}; use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; @@ -856,7 +855,7 @@ pub trait NodeSigner { /// /// Errors if the [`Recipient`] variant is not supported by the implementation. fn sign_invoice( - &self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient, + &self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient, ) -> Result; /// Signs the [`TaggedHash`] of a BOLT 12 invoice request. @@ -2163,7 +2162,7 @@ impl NodeSigner for KeysManager { } fn sign_invoice( - &self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient, + &self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient, ) -> Result { let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data); let secret = match recipient { @@ -2341,7 +2340,7 @@ impl NodeSigner for PhantomKeysManager { } fn sign_invoice( - &self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient, + &self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient, ) -> Result { let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data); let secret = match recipient { diff --git a/lightning/src/util/invoice.rs b/lightning/src/util/invoice.rs index 4a4baa45267..55e805f5fc9 100644 --- a/lightning/src/util/invoice.rs +++ b/lightning/src/util/invoice.rs @@ -1,28 +1,30 @@ //! Low level invoice utilities. -use bech32::{u5, FromBase32}; +use crate::ln::types::InvoiceData; +use bech32::{Fe32, Fe32IterExt}; #[allow(unused)] use crate::prelude::*; /// Construct the invoice's HRP and signatureless data into a preimage to be hashed. -pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec { +/// TODO(bech32): This should be moved to lightning-invoice crate, and use FromBase32 from there +pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &InvoiceData) -> Vec { let mut preimage = Vec::::from(hrp_bytes); - let mut data_part = Vec::from(data_without_signature); + let mut data_part = data_without_signature.0.clone(); 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 < 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.")); + // TODO(bech32): Should use FromBase32 from lightning-invoice crate + preimage.extend(&data_part.iter().copied().fes_to_bytes().collect::>()); preimage } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 74a7f769df1..6dfce0d7269 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -34,6 +34,7 @@ use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::{msgs, wire}; use crate::ln::msgs::LightningError; use crate::ln::script::ShutdownScript; +use crate::ln::types::InvoiceData; use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; @@ -72,7 +73,6 @@ use core::time::Duration; use crate::sync::{Mutex, Arc}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::mem; -use bech32::u5; use crate::sign::{InMemorySigner, RandomBytes, Recipient, EntropySource, NodeSigner, SignerProvider}; #[cfg(feature = "std")] @@ -1217,7 +1217,7 @@ impl NodeSigner for TestNodeSigner { Ok(SharedSecret::new(other_key, &node_secret)) } - fn sign_invoice(&self, _: &[u8], _: &[bech32::u5], _: Recipient) -> Result { + fn sign_invoice(&self, _: &[u8], _: &InvoiceData, _: Recipient) -> Result { unreachable!() } @@ -1270,7 +1270,7 @@ impl NodeSigner for TestKeysInterface { self.backing.get_inbound_payment_key_material() } - fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result { + fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient) -> Result { self.backing.sign_invoice(hrp_bytes, invoice_data, recipient) }