From e586562f7bb2fc0840526ed9b2eac74b5b7b7f91 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:12:06 +0200 Subject: [PATCH] Own implementation of Bech32::u5 type, upgrade of bech32 dependency --- fuzz/Cargo.toml | 2 +- fuzz/src/bolt11_deser.rs | 2 +- fuzz/src/chanmon_consistency.rs | 2 +- fuzz/src/full_stack.rs | 2 +- fuzz/src/onion_message.rs | 2 +- lightning-invoice/Cargo.toml | 2 +- lightning-invoice/src/de.rs | 64 ++-- lightning-invoice/src/lib.rs | 8 +- lightning-invoice/src/ser.rs | 13 +- lightning-invoice/src/utils.rs | 2 +- lightning-invoice/tests/ser_de.rs | 7 +- lightning/Cargo.toml | 2 +- lightning/src/bech32/mod.rs | 615 ++++++++++++++++++++++++++++++ lightning/src/lib.rs | 1 + lightning/src/ln/features.rs | 33 +- lightning/src/ln/types.rs | 8 +- lightning/src/offers/parse.rs | 30 +- lightning/src/sign/mod.rs | 2 +- lightning/src/util/invoice.rs | 6 +- lightning/src/util/test_utils.rs | 4 +- 20 files changed, 712 insertions(+), 95 deletions(-) create mode 100644 lightning/src/bech32/mod.rs 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..18e5a72cedc 100644 --- a/fuzz/src/bolt11_deser.rs +++ b/fuzz/src/bolt11_deser.rs @@ -8,8 +8,8 @@ // licenses. use crate::utils::test_logger; -use bech32::{u5, FromBase32, ToBase32}; use bitcoin::secp256k1::{Secp256k1, SecretKey}; +use lightning::bech32::{u5, FromBase32, ToBase32}; use lightning_invoice::{ Bolt11Invoice, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField, }; diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 7d660f459ea..d6a57878b60 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -33,6 +33,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash as TraitImport; use bitcoin::WPubkeyHash; +use lightning::bech32::u5; use lightning::blinded_path::message::MessageContext; use lightning::blinded_path::payment::ReceiveTlvs; use lightning::blinded_path::BlindedPath; @@ -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; diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 9c45c66e1a4..70848c0d3ba 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -30,6 +30,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash as _; use bitcoin::WPubkeyHash; +use lightning::bech32::u5; use lightning::blinded_path::message::MessageContext; use lightning::blinded_path::payment::ReceiveTlvs; use lightning::blinded_path::BlindedPath; @@ -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; diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 05ee7526faa..2784bbecc01 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -1,11 +1,11 @@ // 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; use bitcoin::secp256k1::schnorr; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; +use lightning::bech32::u5; use lightning::blinded_path::message::{MessageContext, OffersContext}; use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; use lightning::ln::features::InitFeatures; 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..bbd4f5ba738 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -8,12 +8,14 @@ use core::num::ParseIntError; use core::str; use core::str::FromStr; -use bech32::{u5, FromBase32}; +use bech32::Bech32; +use bech32::primitives::decode::{CheckedHrpstring, CheckedHrpstringError}; use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256; use crate::prelude::*; +use lightning::bech32::{Bech32Error, FromBase32, u5}; use lightning::ln::types::PaymentSecret; use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop}; @@ -270,20 +272,21 @@ 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(); + // access the parse data part as chars, covert to u5 + let data: Vec<_> = parsed.data_part_ascii_no_checksum().iter() + .map(|ch| u5::try_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 +294,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..])?, }) } } @@ -389,7 +392,7 @@ macro_rules! define_parse_int_be { ($name: ident, $ty: ty) => { digits.iter().fold(Some(Default::default()), |acc, b| acc .and_then(|x| x.checked_mul(32)) - .and_then(|x| x.checked_add((Into::::into(*b)).into())) + .and_then(|x| x.checked_add((*b).as_u8().into())) ) } } } @@ -424,7 +427,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)} @@ -444,7 +447,7 @@ impl FromBase32 for TaggedField { let tag = field[0]; let field_data = &field[3..]; - match tag.to_u8() { + match tag.as_u8() { constants::TAG_PAYMENT_HASH => Ok(TaggedField::PaymentHash(Sha256::from_base32(field_data)?)), constants::TAG_DESCRIPTION => @@ -550,7 +553,7 @@ impl FromBase32 for Fallback { return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields); } - let version = field_data[0].to_u8(); + let version = field_data[0].as_u8(); let bytes = Vec::::from_base32(&field_data[1..])?; match version { @@ -629,6 +632,9 @@ impl Display for Bolt11ParseError { Bolt11ParseError::Bech32Error(ref e) => { write!(f, "Invalid bech32: {}", e) } + Bolt11ParseError::Bech32ExternalError(ref e) => { + write!(f, "Invalid bech32: {}", e) + } Bolt11ParseError::ParseAmountError(ref e) => { write!(f, "Invalid amount in hrp ({})", e) } @@ -703,10 +709,17 @@ 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 { + _ => Bolt11ParseError::Bech32ExternalError(e) + } + } +} + +impl From for Bolt11ParseError { + fn from(e: Bech32Error) -> Self { match e { - bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError, _ => Bolt11ParseError::Bech32Error(e) } } @@ -726,9 +739,9 @@ impl From for ParseOrSemanticError { #[cfg(test)] mod test { + use super::{u5, FromBase32}; use crate::de::Bolt11ParseError; use secp256k1::PublicKey; - use bech32::u5; use bitcoin::hashes::sha256; use std::str::FromStr; @@ -779,7 +792,6 @@ mod test { #[test] fn test_parse_sha256_hash() { use crate::Sha256; - use bech32::FromBase32; let input = from_bech32( "qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq".as_bytes() @@ -802,7 +814,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 +823,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 +846,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 +858,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 +868,6 @@ mod test { #[test] fn test_parse_fallback() { use crate::Fallback; - use bech32::FromBase32; use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; use bitcoin::hashes::Hash; @@ -921,7 +928,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 +973,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(&[u5::ZERO; 40][..]), Err(Bolt11ParseError::UnexpectedEndOfTaggedFields) ); } diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index e427bf3ccb9..f3e7d23da5c 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -41,10 +41,11 @@ extern crate serde; #[cfg(feature = "std")] use std::time::SystemTime; -use bech32::u5; +use bech32::primitives::decode::CheckedHrpstringError; use bitcoin::{Address, Network, PubkeyHash, ScriptHash, WitnessProgram, WitnessVersion}; use bitcoin::address::Payload; use bitcoin::hashes::{Hash, sha256}; +use lightning::bech32::{Bech32Error, u5, ToBase32}; use lightning::ln::features::Bolt11InvoiceFeatures; use lightning::util::invoice::construct_invoice_preimage; @@ -90,7 +91,8 @@ use crate::prelude::*; #[allow(missing_docs)] #[derive(PartialEq, Eq, Debug, Clone)] pub enum Bolt11ParseError { - Bech32Error(bech32::Error), + Bech32Error(Bech32Error), + Bech32ExternalError(CheckedHrpstringError), ParseAmountError(ParseIntError), MalformedSignature(secp256k1::Error), BadPrefix, @@ -979,8 +981,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() diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index b4f7c778d8a..7076180c9df 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -1,6 +1,7 @@ +use lightning::bech32::{Base32Len, ToBase32, u5, WriteBase32}; +use bech32::{Bech32, Fe32, Fe32IterExt, Hrp}; use core::fmt; use core::fmt::{Display, Formatter}; -use bech32::{ToBase32, u5, WriteBase32, Base32Len}; use crate::prelude::*; use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp, @@ -118,7 +119,10 @@ 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")?; + // TODO(bech32): support with_checksum() in own u5 implementation + let bech32 = data.iter().map(|u| Fe32::try_from(u.as_u8()).expect("<31")) + .with_checksum::(&Hrp::parse(&hrp).expect("not a valid hrp string")).chars().collect::(); + f.write_str(&bech32)?; Ok(()) } @@ -468,8 +472,6 @@ impl ToBase32 for Bolt11InvoiceSignature { #[cfg(test)] mod test { - use bech32::CheckBase32; - #[test] fn test_currency_code() { use crate::Currency; @@ -497,9 +499,10 @@ mod test { #[test] fn test_encode_int_be_base32() { use crate::ser::encode_int_be_base32; + use lightning::bech32::u5; let input: u64 = 33764; - let expected_out = CheckBase32::check_base32(&[1, 0, 31, 4]).unwrap(); + let expected_out = [1u8, 0, 31, 4].iter().map(|v| u5::try_from_u8(*v).unwrap()).collect::>(); assert_eq!(expected_out, encode_int_be_base32(input)); } diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 00b49c371ea..c1e644b9b0e 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -3,8 +3,8 @@ use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError}; use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256}; -use bech32::ToBase32; use bitcoin::hashes::Hash; +use lightning::bech32::ToBase32; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use lightning::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index 6b5e99476b0..d3f45fab51e 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -4,6 +4,7 @@ extern crate lightning_invoice; extern crate secp256k1; extern crate hex; +use bech32::primitives::decode::{CharError, CheckedHrpstringError, ChecksumError, UncheckedHrpstringError}; use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::{sha256, Hash}; @@ -419,13 +420,13 @@ fn test_bolt_invalid_invoices() { ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidFeatures))); assert_eq!(Bolt11Invoice::from_str( "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt" - ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum)))); + ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32ExternalError(CheckedHrpstringError::Checksum(ChecksumError::InvalidResidue))))); assert_eq!(Bolt11Invoice::from_str( "pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny" - ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MissingSeparator)))); + ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32ExternalError(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MissingSeparator)))))); assert_eq!(Bolt11Invoice::from_str( "LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny" - ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MixedCase)))); + ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32ExternalError(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MixedCase)))))); assert_eq!(Bolt11Invoice::from_str( "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2" ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidSignature))); 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/bech32/mod.rs b/lightning/src/bech32/mod.rs new file mode 100644 index 00000000000..28d99c96ec4 --- /dev/null +++ b/lightning/src/bech32/mod.rs @@ -0,0 +1,615 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Bech32 encoding/decoding for lightning invoices. + +use core::fmt; + +/// An unsigned 5-bit value, in the range 0 - 31, the basic data block in Bech32 encoding. +/// Internally a byte is stored, but value is always in the 0--31 range. +/// The `u5` name is analogoue to the `u8`, `u16` etc. base types. +/// Based on 'u5' from `bech32` crate v `0.9.1` (https://github.com/rust-bitcoin/rust-bech32/blob/v0.9.1/src/lib.rs) +#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[allow(non_camel_case_types)] +pub struct u5(u8); + +/// Potential errors during Bech32 encoding/decoding operations. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Bech32Error { + /// Bech32 string of invalid length + InvalidLength, + /// A value out of range, larger than 31. + ValueOutOfRange(u8), + /// Invalid Bech32 character + InvalidCharacter(char), +} + +impl u5 { + /// Create from a u8 value. + pub fn try_from_u8(n: u8) -> Result { + if n > Self::INNER_MAX { + Err(Bech32Error::ValueOutOfRange(n)) + } else { + Ok(Self(n)) + } + } + + /// Create from a u8 value, without check, the input should be in the 0 - -31 range. + /// Higher bits are nulled. + pub fn from_u8(n: u8) -> Self { + Self(n % Self::INNER_COUNT) + } + + /// Access as u8. The value is guaranteed to be in the 0 - 31 range, + /// but once it is in an `u8`, there is no way to enforce that. + #[inline] + pub fn as_u8(&self) -> u8 { + self.0 + } + + const INNER_MAX: u8 = 31; + const INNER_COUNT: u8 = 32; + + /// The zero value (character 'q') + pub const ZERO: u5 = u5(0); + + /// The one value (character 'p') + pub const ONE: u5 = u5(1); + + /// The maximum allowed numerical value, 31 + pub const MAX: u5 = u5(Self::INNER_MAX); + + /// Decode from Bech32 character + pub fn try_from_char(c: char) -> Result { + CharConverter::from_char(c).ok_or(Bech32Error::InvalidCharacter(c)) + } + + /// Convert to Bech32 character, lowercase. + pub fn to_char(&self) -> char { + CharConverter::to_char(self) + } + + /// Utility to pack a u5 slice to u8 vector. + /// It is assumed that the u5 elements were padded, if the total number of bits + /// is not a multiple of 8, any trailing bits are simply dropped. + pub fn pack_to_bytes(unpacked: &[u5]) -> Vec { + unpacked.iter().copied().pack_to_bytes().collect() + } + + /// Utility to unpack u5 elements from a u8 slice. + /// If the total number of bits is not a multiple of 5, they are right-padded with 0 bits. + pub fn unpack_from_bytes(packed: &[u8]) -> Vec { + packed.iter().copied().unpack_from_bytes().collect() + } +} + +/// Iterator adaptor that packs `u5` elements to bytes. +/// +/// It is assumed that the u5 elements were padded, if the total number of bits +/// is not a multiple of 8, any trailing bits are dropped. +/// +/// Based on `FesToBytes` from `rust-bech32` crate. +#[derive(Clone, PartialEq, Eq)] +pub struct U5Packer> { + remain_bits: usize, + remain_u8: u8, + iter: I, +} + +impl U5Packer +where + I: Iterator, +{ + fn new(iter: I) -> Self { + Self { remain_bits: 0, remain_u8: 0, iter } + } +} + +impl Iterator for U5Packer +where + I: Iterator, +{ + type Item = u8; + + /// Retrieve the next packed byte + fn next(&mut self) -> Option { + let mut next_out: Option = None; + // We may need to read two inputs to produce an output + while next_out.is_none() { + // Next input element. If there is none, we just stop + let curr_in = self.iter.next()?; + if self.remain_bits >= 3 { + // we have a new full byte -- 3 or 4 remain bits, plus 5 new ones + next_out = Some(self.remain_u8 | (curr_in.0 >> (self.remain_bits - 3))); + let to_remain_shift = (8 + 3) - self.remain_bits; + self.remain_u8 = if to_remain_shift < 8 { curr_in.0 << to_remain_shift } else { 0 }; + self.remain_bits -= 3; // added 5, removed 8 + } else { + // only 0, 1, or 2 remain bits, plus 5 new ones + self.remain_u8 = self.remain_u8 | (curr_in.0 << (3 - self.remain_bits)); + self.remain_bits += 5; + next_out = None; + } + } + // we have a next + next_out + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // If the total number of bits is not a multiple of 8, any trailing bits are dropped. + let unpacked_len_to_packed_len = |n| n * 5 / 8; + + let (unpacked_min, unpacked_max) = self.iter.size_hint(); + // +1 because we set last_fe with call to `next`. + let min = unpacked_len_to_packed_len(unpacked_min + 1); + let max = unpacked_max.map(|max| unpacked_len_to_packed_len(max + 1)); + (min, max) + } +} + +/// Extension trait for field element iterators. +pub trait PackU5IterExt: Sized + Iterator { + /// Adapts the `u5` iterator to output packed bytes. + /// + /// It is assumed that the u5 elements were padded, if the total number of bits + /// is not a multiple of 8, any trailing bits are simply dropped. + #[inline] + fn pack_to_bytes(self) -> U5Packer { + U5Packer::new(self) + } +} + +impl PackU5IterExt for I where I: Iterator {} + +/// Iterator adaptor that unpacks `u5` elements from a stream of packed bytes. +/// +/// If the total number of bits is not a multiple of 5, they are right-padded with 0 bits. +/// +/// Based on `BytesToFes` from `rust-bech32` crate. +#[derive(Clone, PartialEq, Eq)] +pub struct U5Unpacker> { + remain_bits: usize, + remain_u8: u8, + iter: I, +} + +impl U5Unpacker +where + I: Iterator, +{ + fn new(iter: I) -> Self { + Self { remain_bits: 0, remain_u8: 0, iter } + } +} + +impl Iterator for U5Unpacker +where + I: Iterator, +{ + type Item = u5; + + #[inline] + fn next(&mut self) -> Option { + let next_out = if self.remain_bits >= 5 { + // We have enough remained bits for an output, no need to read the input + let next_out = self.remain_u8; + self.remain_u8 = self.remain_u8 << 5; + self.remain_bits -= 5; + next_out + } else { + if let Some(curr_in) = self.iter.next() { + // we have at least one u5 to output (maybe two) + let next_out = self.remain_u8 | (curr_in >> self.remain_bits); + let to_remain_shift = 5 - self.remain_bits; + self.remain_u8 = curr_in << to_remain_shift; + self.remain_bits += 3; // added 8, removed 5 + next_out + } else { + // No more inputs, output remaining (if any) + if self.remain_bits > 0 { + self.remain_bits = 0; + self.remain_u8 + } else { + return None; + } + } + }; + // Isolate the 5 left bits + Some(u5(next_out >> 3)) + } +} + +/// Extension trait for byte iterators which provides an adaptor to GF32 elements. +pub trait UnpackU5IterExt: Sized + Iterator { + /// Adapts the u8 iterator to output unpacked u5 elements. + /// + /// If the total number of bits is not a multiple of 5, they are right-padded with 0 bits. + #[inline] + fn unpack_from_bytes(self) -> U5Unpacker { + U5Unpacker::new(self) + } +} + +impl UnpackU5IterExt for I where I: Iterator {} + + +impl fmt::Display for Bech32Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let s = match self { + Bech32Error::InvalidLength => { + format!("Invalid length") + }, + Bech32Error::InvalidCharacter(c) => { + format!("Invalid character ({})", c) + }, + Bech32Error::ValueOutOfRange(v) => { + format!("Out-of-range value ({})", v) + }, + }; + f.write_str(&s) + } +} + + +/// Interface to write `u5`s into a sink. +pub trait WriteBase32 { + /// Write error + type Err: fmt::Debug; + + /// Write a `u5` slice. + fn write(&mut self, data: &[u5]) -> Result<(), Self::Err> { + for b in data { + self.write_u5(*b)?; + } + Ok(()) + } + + /// Write a single `u5`. + fn write_u5(&mut self, data: u5) -> Result<(), Self::Err>; +} + +/// A trait for converting a value to a `u5` vector. +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: &[u5]) -> Result; +} + +impl WriteBase32 for Vec { + type Err = (); + + fn write(&mut self, data: &[u5]) -> Result<(), Self::Err> { + self.extend_from_slice(data); + Ok(()) + } + + fn write_u5(&mut self, data: u5) -> Result<(), Self::Err> { + self.push(data); + Ok(()) + } +} + +impl ToBase32 for [u8] { + /// Encode as base32 and write it to the supplied writer + fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { + writer.write(&self.iter().copied().unpack_from_bytes().collect::>()) + } +} + +impl ToBase32 for Vec { + /// Encode as base32 and write it to the supplied writer + fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { + self.as_slice().write_base32(writer) + } +} + +impl Base32Len for [u8] { + /// Calculate the base32 serialized length + fn base32_len(&self) -> usize { + // rounded up + (self.len() * 8 + (5 - 1)) / 5 + } +} + +impl Base32Len for Vec { + /// Calculate the base32 serialized length + fn base32_len(&self) -> usize { + self.as_slice().base32_len() + } +} + +impl FromBase32 for Vec { + type Err = Bech32Error; + + fn from_base32(data: &[u5]) -> Result { + Ok(data.iter().copied().pack_to_bytes().collect::()) + } +} + +/// Bech32 character encoding/decoding logic (lookup tables). +/// Bsed on 'u5' from `bech32` crate v `0.9.1` (https://github.com/rust-bitcoin/rust-bech32/blob/v0.9.1/src/lib.rs) +struct CharConverter {} + +impl CharConverter { + /// Encode a u5 value to char. + fn to_char(a: &u5) -> char { + Self::CHARS_LOWER[(a.as_u8() % 32) as usize] + } + + /// Decode a character to a u5 value. + fn from_char(c: char) -> Option { + let cascii = u32::from(c); + if cascii <= 127 { + let idx = Self::CHARS_INV[cascii as usize]; + if idx >= 0 && idx < 32 { + return Some(u5::from_u8(idx as u8)); + } + } + None + } + + /// Mapping from numeric value to bech32 character. + #[rustfmt::skip] + const CHARS_LOWER: [char; 32] = [ + 'q', 'p', 'z', 'r', 'y', '9', 'x', '8', // +0 + 'g', 'f', '2', 't', 'v', 'd', 'w', '0', // +8 + 's', '3', 'j', 'n', '5', '4', 'k', 'h', // +16 + 'c', 'e', '6', 'm', 'u', 'a', '7', 'l', // +24 + ]; + + /// Mapping from bech32 character (either case) to numeric value. + /// + /// E.g., 'z' is CHARS_LOWER[2] and is ASCII value 122 so CHARS_INV[122] == 2 + #[rustfmt::skip] + const CHARS_INV: [i8; 128] = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + ]; +} + +#[cfg(test)] +mod test { + use super::{u5, Base32Len, Bech32Error, FromBase32, PackU5IterExt, ToBase32, UnpackU5IterExt}; + + #[test] + fn u5_from_u8() { + for i in 0..31 { + assert_eq!(u5::from_u8(i).as_u8(), i); + } + assert_eq!(u5::from_u8(32).as_u8(), 0); + assert_eq!(u5::from_u8(100).as_u8(), 4); + } + + #[test] + fn u5_try_from_u8() { + for i in 0..31 { + assert_eq!(u5::try_from_u8(i).unwrap().as_u8(), i); + } + assert_eq!(u5::try_from_u8(32).err().unwrap(), Bech32Error::ValueOutOfRange(32)); + assert_eq!(u5::try_from_u8(100).err().unwrap(), Bech32Error::ValueOutOfRange(100)); + } + + #[test] + fn u5_default() { + assert_eq!(u5::default().as_u8(), 0); + } + + #[test] + fn u5_const() { + assert_eq!(u5::ZERO.as_u8(), 0); + assert_eq!(u5::ONE.as_u8(), 1); + assert_eq!(u5::MAX.as_u8(), 31); + } + + #[test] + fn pack_to_bytes() { + { + let u5vec = [u5::ZERO]; + let u8vec = u5::pack_to_bytes(&u5vec); + assert_eq!(u8vec.len(), 0); + } + { + let u5vec: Vec = [0u8, 0].iter().map(|n| u5::from_u8(*n)).collect(); + let u8vec = u5::pack_to_bytes(&u5vec); + let expectedu8 = vec![0]; + assert_eq!(u8vec.len(), 1); + assert_eq!(u8vec, expectedu8); + } + { + // 10101000 00 + let u5vec: Vec = [21u8, 0].iter().map(|n| u5::from_u8(*n)).collect(); + let u8vec = u5::pack_to_bytes(&u5vec); + let expectedu8 = vec![21 << 3]; + assert_eq!(u8vec, expectedu8); + } + { + // 10101101 01 + let u5vec: Vec = [21u8, 21].iter().map(|n| u5::from_u8(*n)).collect(); + let u8vec = u5::pack_to_bytes(&u5vec); + let expectedu8 = vec![(21 << 3) + (21 >> 2)]; + assert_eq!(u8vec, expectedu8); + } + { + // 00001000 10000110 0100 + let u5vec: Vec = [1u8, 2, 3, 4].iter().map(|n| u5::from_u8(*n)).collect(); + let u8vec = u5::pack_to_bytes(&u5vec); + let expectedu8 = vec![8, 134]; + assert_eq!(u8vec, expectedu8); + } + { + // 00001000 10000110 01000010 10011000 11101000 + let u5vec: Vec = + [1u8, 2, 3, 4, 5, 6, 7, 8].iter().map(|n| u5::from_u8(*n)).collect(); + let u8vec = u5::pack_to_bytes(&u5vec); + let expectedu8 = vec![8, 134, 66, 152, 232]; + assert_eq!(u8vec, expectedu8); + } + } + + #[test] + fn unpack_from_bytes() { + { + // 00001 00010 00011 0 + let u8vec = vec![8, 134]; + let u5vec = u5::unpack_from_bytes(&u8vec); + let expectedu5: Vec = [1u8, 2, 3, 0].iter().map(|n| u5::from_u8(*n)).collect(); + assert_eq!(u5vec, expectedu5); + } + { + // 00001 00010 00011 00100 0000 + let u8vec = vec![8, 134, 64]; + let u5vec = u5::unpack_from_bytes(&u8vec); + let expectedu5: Vec = [1u8, 2, 3, 4, 0].iter().map(|n| u5::from_u8(*n)).collect(); + assert_eq!(u5vec, expectedu5); + } + { + // 00001 00010 00011 00100 00101 00110 00111 01000 + let u8vec = vec![8, 134, 66, 152, 232]; + let u5vec = u5::unpack_from_bytes(&u8vec); + let expectedu5: Vec = + [1, 2, 3, 4, 5, 6, 7, 8].iter().map(|n| u5::from_u8(*n)).collect(); + assert_eq!(u5vec, expectedu5); + } + } + + #[test] + fn pack_methods() { + // Different ways to invoke packing + let u5vec: Vec = [1u8, 2, 3, 4].iter().map(|n| u5::from_u8(*n)).collect(); + let expectedu8 = vec![8, 134]; + { + // iterator + let u8vec: Vec = u5vec.iter().copied().pack_to_bytes().collect(); + assert_eq!(u8vec, expectedu8); + } + { + // associated method on u5 + let u8vec = u5::pack_to_bytes(&u5vec); + assert_eq!(u8vec, expectedu8); + } + { + // trait on Vec + let u8vec = Vec::::from_base32(&u5vec).unwrap(); + assert_eq!(u8vec, expectedu8); + } + } + + #[test] + fn unpack_methods() { + // Different ways to invoke unpacking + let u8vec = vec![8, 134, 64]; + let expectedu5: Vec = [1u8, 2, 3, 4, 0].iter().map(|n| u5::from_u8(*n)).collect(); + { + // iterator + let u5vec: Vec = u8vec.iter().copied().unpack_from_bytes().collect(); + assert_eq!(u5vec, expectedu5); + } + { + // associated method on u5 + let u5vec = u5::unpack_from_bytes(&u8vec); + assert_eq!(u5vec, expectedu5); + } + { + // trait on [u8] + let u5vec = (&u8vec).to_base32(); + assert_eq!(u5vec, expectedu5); + assert_eq!((&u8vec).base32_len(), expectedu5.len()); + } + { + // trait on Vec + let u5vec = u8vec.to_base32(); + assert_eq!(u5vec, expectedu5); + assert_eq!(u8vec.base32_len(), expectedu5.len()); + } + } + + #[test] + fn char_encode() { + assert_eq!(u5::ZERO.to_char(), 'q'); + assert_eq!(u5::ONE.to_char(), 'p'); + assert_eq!(u5::from_u8(2).to_char(), 'z'); + assert_eq!(u5::from_u8(3).to_char(), 'r'); + assert_eq!(u5::from_u8(5).to_char(), '9'); + assert_eq!(u5::from_u8(10).to_char(), '2'); + assert_eq!(u5::from_u8(15).to_char(), '0'); + assert_eq!(u5::from_u8(24).to_char(), 'c'); + assert_eq!(u5::from_u8(29).to_char(), 'a'); + assert_eq!(u5::from_u8(31).to_char(), 'l'); + } + + #[test] + fn char_decode() { + assert_eq!(u5::try_from_char('a').unwrap(), u5::from_u8(29)); + assert_eq!(u5::try_from_char('c').unwrap(), u5::from_u8(24)); + assert_eq!(u5::try_from_char('l').unwrap(), u5::from_u8(31)); + assert_eq!(u5::try_from_char('p').unwrap(), u5::ONE); + assert_eq!(u5::try_from_char('q').unwrap(), u5::ZERO); + assert_eq!(u5::try_from_char('r').unwrap(), u5::from_u8(3)); + assert_eq!(u5::try_from_char('z').unwrap(), u5::from_u8(2)); + assert_eq!(u5::try_from_char('0').unwrap(), u5::from_u8(15)); + assert_eq!(u5::try_from_char('2').unwrap(), u5::from_u8(10)); + assert_eq!(u5::try_from_char('9').unwrap(), u5::from_u8(5)); + + assert_eq!(u5::try_from_char('A').unwrap(), u5::from_u8(29)); + assert_eq!(u5::try_from_char('C').unwrap(), u5::from_u8(24)); + assert_eq!(u5::try_from_char('Z').unwrap(), u5::from_u8(2)); + + assert_eq!(u5::try_from_char('b').err().unwrap(), Bech32Error::InvalidCharacter('b')); + assert_eq!(u5::try_from_char('1').err().unwrap(), Bech32Error::InvalidCharacter('1')); + } + + #[test] + fn u8slice_base32_len() { + assert_eq!([0u8; 0].base32_len(), 0); + assert_eq!([0u8; 1].base32_len(), 2); + assert_eq!([0u8; 2].base32_len(), 4); + assert_eq!([0u8; 3].base32_len(), 5); + assert_eq!([0u8; 4].base32_len(), 7); + assert_eq!([0u8; 5].base32_len(), 8); + assert_eq!([0u8; 6].base32_len(), 10); + assert_eq!([0u8; 20].base32_len(), 32); + } + + #[test] + fn bech32_error() { + assert_eq!(Bech32Error::InvalidLength.to_string(), "Invalid length"); + assert_eq!(format!("{}", Bech32Error::InvalidLength), "Invalid length"); + assert_eq!(format!("{:?}", Bech32Error::InvalidLength), "InvalidLength"); + assert_eq!(Bech32Error::ValueOutOfRange(3).to_string(), "Out-of-range value (3)"); + } +} diff --git a/lightning/src/lib.rs b/lightning/src/lib.rs index 5274ea0bf30..a5bc7e38921 100644 --- a/lightning/src/lib.rs +++ b/lightning/src/lib.rs @@ -83,6 +83,7 @@ pub mod sign; pub mod onion_message; pub mod blinded_path; pub mod events; +pub mod bech32; pub(crate) mod crypto; diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index 51c608c1a6b..b73c7bc6717 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -87,7 +87,7 @@ use core::borrow::Borrow; use core::hash::{Hash, Hasher}; use core::marker::PhantomData; -use bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32}; +use crate::bech32::{Base32Len, Bech32Error, FromBase32, ToBase32, u5, WriteBase32}; use crate::ln::msgs::DecodeError; use crate::util::ser::{Readable, WithoutLength, Writeable, Writer}; @@ -651,14 +651,14 @@ impl ToBase32 for Bolt11InvoiceFeatures { 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(); + let curr_u5_as_u8 = res_u5s[new_u5_idx].as_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(); + let curr_u5_as_u8 = res_u5s[new_u5_idx - 1].as_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(); + let curr_u5_as_u8 = res_u5s[new_u5_idx - 2].as_u8(); res_u5s[new_u5_idx - 2] = u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 10) & 0x001f) as u8)).unwrap(); } } @@ -677,9 +677,9 @@ impl Base32Len for Bolt11InvoiceFeatures { } impl FromBase32 for Bolt11InvoiceFeatures { - type Err = bech32::Error; + type Err = Bech32Error; - fn from_base32(field_data: &[u5]) -> Result { + 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; @@ -688,7 +688,7 @@ impl FromBase32 for Bolt11InvoiceFeatures { 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; + let chunk_u16 = chunk.as_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; @@ -1043,7 +1043,7 @@ 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 super::{Base32Len, FromBase32, ToBase32, u5}; use crate::util::ser::{Readable, WithoutLength, Writeable}; #[test] @@ -1243,21 +1243,8 @@ mod tests { #[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_as_u5s: Vec<_> = vec![6, 10, 25, 1, 10, 0, 20, 2, 0, 6, 0, 16, 1] + .iter().map(|b| u5::try_from_u8(*b).unwrap()).collect(); let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]); // Test length calculation. diff --git a/lightning/src/ln/types.rs b/lightning/src/ln/types.rs index c6fa5986764..96d6a38ce7a 100644 --- a/lightning/src/ln/types.rs +++ b/lightning/src/ln/types.rs @@ -162,14 +162,14 @@ 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 crate::bech32::{Base32Len, Bech32Error, FromBase32, ToBase32, WriteBase32, u5}; impl FromBase32 for PaymentSecret { - type Err = bech32::Error; + type Err = Bech32Error; - fn from_base32(field_data: &[u5]) -> Result { + fn from_base32(field_data: &[u5]) -> Result { if field_data.len() != 52 { - return Err(bech32::Error::InvalidLength) + return Err(Bech32Error::InvalidLength) } else { let data_bytes = Vec::::from_base32(field_data)?; let mut payment_secret = [0; 32]; 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..370014a6d69 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}; @@ -37,6 +36,7 @@ use bitcoin::secp256k1::All; use bitcoin::secp256k1::{Keypair, PublicKey, Scalar, Secp256k1, SecretKey, Signing}; use bitcoin::{secp256k1, Psbt, Sequence, Txid, WPubkeyHash, Witness}; +use crate::bech32::u5; use crate::chain::transaction::OutPoint; use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::ln::chan_utils; diff --git a/lightning/src/util/invoice.rs b/lightning/src/util/invoice.rs index 4a4baa45267..e617d8ef8c8 100644 --- a/lightning/src/util/invoice.rs +++ b/lightning/src/util/invoice.rs @@ -1,6 +1,6 @@ //! Low level invoice utilities. -use bech32::{u5, FromBase32}; +use crate::bech32::{FromBase32, u5}; #[allow(unused)] use crate::prelude::*; @@ -13,11 +13,11 @@ pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5 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(u5::ZERO); // 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(u5::ZERO); } } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 74a7f769df1..eb5478f1498 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -7,6 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. +use crate::bech32::u5; use crate::blinded_path::message::MessageContext; use crate::blinded_path::BlindedPath; use crate::blinded_path::message::ForwardNode; @@ -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], _: &[u5], _: Recipient) -> Result { unreachable!() }