From 421713e8ebe5084918db8ab489b18f8525855157 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 5 Sep 2023 01:24:29 +1000 Subject: [PATCH] Re-write crate level API Now we have all the new `primitives` pieces in place we can re-write the public API to take advantage of them - WIN! --- fuzz/fuzz_targets/decode_rnd.rs | 9 +- fuzz/fuzz_targets/encode_decode.rs | 22 +- src/hrp.rs | 15 + src/lib.rs | 1336 +++++----------------------- src/primitives/checksum.rs | 4 +- src/primitives/decode.rs | 11 +- src/primitives/hrp.rs | 142 ++- src/segwit.rs | 25 +- 8 files changed, 324 insertions(+), 1240 deletions(-) create mode 100644 src/hrp.rs diff --git a/fuzz/fuzz_targets/decode_rnd.rs b/fuzz/fuzz_targets/decode_rnd.rs index a9ed55c52..cfdd5a78f 100644 --- a/fuzz/fuzz_targets/decode_rnd.rs +++ b/fuzz/fuzz_targets/decode_rnd.rs @@ -1,14 +1,15 @@ extern crate bech32; +use bech32::Bech32m; + fn do_test(data: &[u8]) { let data_str = String::from_utf8_lossy(data); - let decoded = bech32::decode(&data_str); - let b32 = match decoded { - Ok(b32) => b32, + let (hrp, data) = match bech32::decode_bech32m(&data_str) { + Ok((hrp, data)) => (hrp, data), Err(_) => return, }; - assert_eq!(bech32::encode(b32.0, b32.1, b32.2).unwrap(), data_str); + assert_eq!(bech32::encode::(hrp, &data).unwrap(), data_str); } #[cfg(feature = "afl")] diff --git a/fuzz/fuzz_targets/encode_decode.rs b/fuzz/fuzz_targets/encode_decode.rs index e94acd082..14ab98f20 100644 --- a/fuzz/fuzz_targets/encode_decode.rs +++ b/fuzz/fuzz_targets/encode_decode.rs @@ -1,9 +1,8 @@ extern crate bech32; -use std::convert::TryFrom; use std::str; -use bech32::Hrp; +use bech32::{Bech32m, Hrp}; fn do_test(data: &[u8]) { if data.len() < 1 { @@ -16,16 +15,7 @@ fn do_test(data: &[u8]) { return; } - let dp = data[hrp_end..] - .iter() - .map(|b| bech32::u5::try_from(b % 32).unwrap()) - .collect::>(); - - let variant = if data[0] > 0x0f { - bech32::Variant::Bech32m - } else { - bech32::Variant::Bech32 - }; + let dp = &data[hrp_end..]; match str::from_utf8(&data[1..hrp_end]) { Err(_) => return, @@ -33,11 +23,9 @@ fn do_test(data: &[u8]) { match Hrp::parse(&s) { Err(_) => return, Ok(hrp) => { - if let Ok(data_str) = bech32::encode(hrp, &dp, variant).map(|b32| b32.to_string()) { - let decoded = bech32::decode(&data_str); - let b32 = decoded.expect("should be able to decode own encoding"); - - assert_eq!(bech32::encode(b32.0, &b32.1, b32.2).unwrap(), data_str); + if let Ok(address) = bech32::encode::(hrp, dp) { + let (hrp, data) = bech32::decode_bech32m(&address).expect("should be able to decode own encoding"); + assert_eq!(bech32::encode::(hrp, &data).unwrap(), address); } } } diff --git a/src/hrp.rs b/src/hrp.rs new file mode 100644 index 000000000..9d79fc212 --- /dev/null +++ b/src/hrp.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +//! Re-export the hrp types to make importing ergonomic for the top level APIs. + +/// The human-readable part used by the Bitcoin mainnet network. +#[doc(inline)] +pub use crate::primitives::hrp::BC; +/// The human-readable part used when running a Bitcoin regtest network. +#[doc(inline)] +pub use crate::primitives::hrp::BCRT; +/// The human-readable part used by the Bitcoin testnet networks (testnet, signet). +#[doc(inline)] +pub use crate::primitives::hrp::TB; +/// The human-readable part (human readable prefix before the '1' separator). +pub use crate::primitives::hrp::Hrp; diff --git a/src/lib.rs b/src/lib.rs index 6e075e4c7..b36467174 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,67 @@ //! a data part. A checksum at the end of the string provides error detection to prevent mistakes //! when the string is written off or read out loud. //! +//! # Usage +//! +//! - If you are doing segwit stuff you likely want to use the [`segwit`] API. +//! - Non-segwit stuff and you have an allocator, use the top level API. For normal usage the +//! [`encode`] and [`decode`] functions should suffice. There are also various other functions for +//! explicit control of the checksum algorithm and the case used when encoding. +//! - Non-segwit stuff and you do *not* have an allocator, use the +//! [`primitives::decode::CheckedHrpstring`] type for decoding. For encoding we provide various +//! top level functions of the form `_to_fmt`. +//! //! The original description in [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) //! has more details. See also [BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki). +//! +//! # Examples +//! +//! ``` +//! # #[cfg(feature = "alloc")] { +//! use bech32::primitives::decode::{CheckedHrpstring, SegwitHrpstring}; +//! use bech32::{hrp, segwit, Hrp, Bech32m}; +//! +//! // Encoding bech32 strings. +//! +//! const DATA: [u8; 20] = [0xab; 20]; // Arbitrary data to be encoded. +//! const ADDR: &str = "abc14w46h2at4w46h2at4w46h2at4w46h2at958ngu"; +//! const TAP_ADDR: &str = "bc1p4w46h2at4w46h2at4w46h2at4w46h2at5kreae"; +//! +//! // Encode arbitrary data using "abc" as the human-readable part and append a bech32m checksum. +//! let hrp = Hrp::parse("abc").expect("valid hrp"); +//! let address = bech32::encode::(hrp, &DATA).expect("failed to encode address"); +//! assert_eq!(address, ADDR); +//! +//! // Encode arbitrary data as a Bitcoin taproot address. +//! let taproot_address = segwit::encode(&hrp::BC, segwit::VERSION_1, &DATA).expect("valid witness version and program"); +//! assert_eq!(taproot_address, TAP_ADDR); +//! +//! // Encode without allocating (ignoring that String::new() allocates :). +//! let mut buf = String::new(); +//! bech32::encode_to_fmt::(&mut buf, hrp, &DATA).expect("failed to encode to buffer"); +//! assert_eq!(buf, ADDR); +//! +//! // Decoding bech32 strings. +//! +//! let (hrp, data) = bech32::decode(&address).expect("failed to decode"); +//! assert_eq!(hrp, Hrp::parse("abc").unwrap()); +//! assert_eq!(data, DATA); +//! +//! // Decode a Bitcoin taproot address. +//! let (_hrp, _version, program) = segwit::decode(&taproot_address).expect("valid address"); +//! assert_eq!(program, DATA); +//! +//! // Decode without allocating using the [`bech32::primitives::decode`] module. +//! +//! let p = CheckedHrpstring::new::(&address).expect("failed to parse address"); +//! assert_eq!(hrp, p.hrp()); +//! assert!(p.byte_iter().eq(DATA.iter().map(|&b| b))); // We yield bytes not references. +//! +//! let taproot = SegwitHrpstring::new(&taproot_address).expect("valid address"); +//! // Do something with the encoded data. +//! let _ = taproot.byte_iter(); +//! # } +//! ``` #![cfg_attr(all(not(feature = "std"), not(test)), no_std)] // Experimental features we need. @@ -30,1209 +89,244 @@ extern crate core; #[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::{string::String, vec::Vec}; -use core::convert::{Infallible, TryFrom}; -use core::{fmt, mem}; +use core::fmt; +#[doc(inline)] pub use crate::primitives::checksum::Checksum; -use crate::primitives::checksum::{self, PackedFe32}; +#[cfg(feature = "alloc")] +use crate::primitives::decode::{ + CheckedHrpstring, CheckedHrpstringError, ChecksumError, UncheckedHrpstring, +}; +#[doc(inline)] pub use crate::primitives::gf32::Fe32; -use crate::primitives::hrp; +#[doc(inline)] pub use crate::primitives::hrp::Hrp; +#[doc(inline)] pub use crate::primitives::iter::{ByteIterExt, Fe32IterExt}; -pub use crate::primitives::{Bech32, Bech32m}; +#[doc(inline)] +pub use crate::primitives::{Bech32, Bech32m, NoChecksum}; mod error; +/// Re-exports types and constants from [`primitives::hrp`]. +pub mod hrp; +/// All the primitive types and functionality used in encoding and decoding. pub mod primitives; +/// API for encoding and decoding segwit addresses. pub mod segwit; -pub use primitives::gf32::Fe32 as u5; - -/// Interface to write `u5`s into a sink. -pub trait WriteBase32 { - /// Write error. - type Error: fmt::Debug; - - /// Writes a `u5` slice to `self`. - fn write(&mut self, data: &[u5]) -> Result<(), Self::Error> { - for b in data { - self.write_u5(*b)?; - } - Ok(()) - } - - /// Writes a single `u5`. - fn write_u5(&mut self, data: u5) -> Result<(), Self::Error> { self.write(&[data]) } -} - -/// Interface to write `u8`s into a sink -/// -/// Like `std::io::Writer`, but because the associated type is no_std compatible. -pub trait WriteBase256 { - /// Write error. - type Error: fmt::Debug; - - /// Writes a `u8` slice. - fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { - for b in data { - self.write_u8(*b)?; - } - Ok(()) - } - - /// Writes a single `u8`. - fn write_u8(&mut self, data: u8) -> Result<(), Self::Error> { self.write(&[data]) } -} - -const CHECKSUM_LENGTH: usize = 6; - -/// Allocationless Bech32 writer that accumulates the checksum data internally and writes them out -/// in the end. -pub struct Bech32Writer<'a, Ck: Checksum> { - formatter: &'a mut dyn fmt::Write, - engine: checksum::Engine, -} - -impl<'a, Ck: Checksum> Bech32Writer<'a, Ck> { - /// Creates a new writer that can write a bech32 string without allocating itself. - /// - /// This is a rather low-level API and doesn't check the HRP or data length for standard - /// compliance. - fn new(hrp: Hrp, fmt: &'a mut dyn fmt::Write) -> Result, fmt::Error> { - let mut engine = checksum::Engine::new(); - engine.input_hrp(&hrp); - - for c in hrp.lowercase_char_iter() { - fmt.write_char(c)?; - } - fmt.write_char(SEP)?; - - Ok(Bech32Writer { formatter: fmt, engine }) - } - - /// Writes out the checksum at the end. - /// - /// If this method isn't explicitly called this will happen on drop. - pub fn finalize(mut self) -> fmt::Result { - self.write_checksum()?; - mem::forget(self); - Ok(()) - } - - /// Calculates and writes a checksum to `self`. - fn write_checksum(&mut self) -> fmt::Result { - self.engine.input_target_residue(); - - let mut checksum_remaining = self::CHECKSUM_LENGTH; - while checksum_remaining > 0 { - checksum_remaining -= 1; - - let fe = u5::try_from(self.engine.residue().unpack(checksum_remaining)) - .expect("unpack returns valid field element"); - self.formatter.write_char(fe.to_char())?; - } - - Ok(()) - } -} - -impl<'a, Ck: Checksum> WriteBase32 for Bech32Writer<'a, Ck> { - type Error = fmt::Error; - - fn write_u5(&mut self, data: u5) -> fmt::Result { - self.engine.input_fe(data); - self.formatter.write_char(data.to_char()) - } -} - -impl<'a, Ck: Checksum> Drop for Bech32Writer<'a, Ck> { - fn drop(&mut self) { - self.write_checksum().expect("Unhandled error writing the checksum on drop.") - } -} - -/// Parses/converts base32 slice to `Self`. -/// -/// This trait 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 Error; - - /// Converts a base32 slice to `Self`. - fn from_base32(b32: &[u5]) -> Result; -} - -macro_rules! write_base_n { - { $tr:ident, $ty:ident, $meth:ident } => { - #[cfg(feature = "alloc")] - impl $tr for Vec<$ty> { - type Error = Infallible; - - fn write(&mut self, data: &[$ty]) -> Result<(), Self::Error> { - self.extend_from_slice(data); - Ok(()) - } - - fn $meth(&mut self, data: $ty) -> Result<(), Self::Error> { - self.push(data); - Ok(()) - } - } - } -} - -write_base_n! { WriteBase32, u5, write_u5 } -write_base_n! { WriteBase256, u8, write_u8 } - -#[cfg(feature = "alloc")] -impl FromBase32 for Vec { - type Error = Error; - - /// Converts base32 (slice of u5s) to base256 (vector of u8s). - /// - /// Removes null-padding if present. - /// - /// # Errors - /// - /// Uses [`convert_bits`] to convert 5 bit values to 8 bit values, see that function for errors. - fn from_base32(b32: &[u5]) -> Result { convert_bits(b32, 5, 8, false) } -} - -/// A trait for converting a value to a type `T` that represents a `u5` slice. -/// -/// This trait is the reciprocal of `FromBase32`. -pub trait ToBase32 { - /// Converts `Self` to a base32 vector. - #[cfg(feature = "alloc")] - fn to_base32(&self) -> Vec { - let mut vec = Vec::new(); - self.write_base32(&mut vec).unwrap(); - vec - } - - /// Encodes `Self` as base32 and writes it to the supplied writer. - /// - /// Implementations should not allocate. - fn write_base32(&self, writer: &mut W) - -> Result<(), ::Error>; -} - -/// Interface to calculate the length of the base32 representation before actually serializing. -pub trait Base32Len: ToBase32 { - /// Calculates the base32 serialized length. - fn base32_len(&self) -> usize; -} - -impl + ?Sized> ToBase32 for T { - fn write_base32( - &self, - writer: &mut W, - ) -> Result<(), ::Error> { - // Amount of bits left over from last round, stored in buffer. - let mut buffer_bits = 0u32; - // Holds all unwritten bits left over from last round. The bits are stored beginning from - // the most significant bit. E.g. if buffer_bits=3, then the byte with bits a, b and c will - // look as follows: [a, b, c, 0, 0, 0, 0, 0] - let mut buffer: u8 = 0; - - for &b in self.as_ref() { - // Write first u5 if we have to write two u5s this round. That only happens if the - // buffer holds too many bits, so we don't have to combine buffer bits with new bits - // from this rounds byte. - if buffer_bits >= 5 { - writer.write_u5(u5((buffer & 0b1111_1000) >> 3))?; - buffer <<= 5; - buffer_bits -= 5; - } - - // Combine all bits from buffer with enough bits from this rounds byte so that they fill - // a u5. Save reamining bits from byte to buffer. - let from_buffer = buffer >> 3; - let from_byte = b >> (3 + buffer_bits); // buffer_bits <= 4 - - writer.write_u5(u5(from_buffer | from_byte))?; - buffer = b << (5 - buffer_bits); - buffer_bits += 3; - } - - // There can be at most two u5s left in the buffer after processing all bytes, write them. - if buffer_bits >= 5 { - writer.write_u5(u5((buffer & 0b1111_1000) >> 3))?; - buffer <<= 5; - buffer_bits -= 5; - } - - if buffer_bits != 0 { - writer.write_u5(u5(buffer >> 3))?; - } - - Ok(()) - } -} - -impl + ?Sized> Base32Len for T { - fn base32_len(&self) -> usize { - let bits = self.as_ref().len() * 8; - if bits % 5 == 0 { - bits / 5 - } else { - bits / 5 + 1 - } - } -} - -/// A trait to convert between u8 arrays and u5 arrays without changing the content of the elements, -/// but checking that they are in range. -pub trait CheckBase32 { - /// Error type if conversion fails - type Error; - - /// Checks if all values are in range and return slice-like-type of `u5` values. - fn check_base32(self) -> Result; -} - -impl> CheckBase32 for U -where - T: AsRef<[u5]>, - T: core::iter::FromIterator, -{ - type Error = Error; - - fn check_base32(self) -> Result { - self.as_ref() - .iter() - .map(|x| u5::try_from(*x).map_err(Error::TryFrom)) - .collect::>() - } -} - -impl> CheckBase32<()> for U { - type Error = Error; - - fn check_base32(self) -> Result<(), Error> { - self.as_ref() - .iter() - .map(|x| u5::try_from(*x).map(|_| ()).map_err(Error::TryFrom)) - .find(|r| r.is_err()) - .unwrap_or(Ok(())) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum Case { - Upper, - Lower, - None, -} - -/// Encodes a bech32 payload to a writer ([`fmt::Write`]) using lowercase. -/// -/// This method is intended for implementing traits from [`std::fmt`]. -/// -/// # Errors +/// Decodes a bech32 encoded string. /// -/// * Deviations from standard. -/// * No length limits are enforced for the data part. -pub fn encode_to_fmt>( - fmt: &mut dyn fmt::Write, - hrp: Hrp, - data: T, - variant: Variant, -) -> Result { - let mut hrp = hrp; - hrp.lowercase(); - encode_to_fmt_anycase(fmt, hrp, data, variant) -} - -/// Encode a bech32 payload to an [fmt::Write], but with any case. -/// This method is intended for implementing traits from [core::fmt] without [std]. +/// If this function succeeds the input string was found to be well formed (hrp, separator, bech32 +/// characters), and to have either a valid bech32m checksum or a valid bech32 checksum. /// -/// See `encode_to_fmt` for meaning of errors. -pub fn encode_to_fmt_anycase>( - fmt: &mut dyn fmt::Write, - hrp: Hrp, - data: T, - variant: Variant, -) -> Result { - match variant { - Variant::Bech32 => { - let res = Bech32Writer::::new(hrp, fmt); - match res { - Ok(mut writer) => { - Ok(writer.write(data.as_ref()).and_then(|_| { - // Finalize manually to avoid panic on drop if write fails - writer.finalize() - })) - } - Err(e) => Ok(Err(e)), - } - } - Variant::Bech32m => { - let res = Bech32Writer::::new(hrp, fmt); - match res { - Ok(mut writer) => { - Ok(writer.write(data.as_ref()).and_then(|_| { - // Finalize manually to avoid panic on drop if write fails - writer.finalize() - })) - } - Err(e) => Ok(Err(e)), - } - } - } -} - -/// Encodes a bech32 payload without a checksum to a writer ([`fmt::Write`]). +/// If your input string has no checksum use [`decode_no_checksum`]. /// -/// This method is intended for implementing traits from [`std::fmt`]. +/// (See [`primitives::decode`] for more information.) /// -/// # Deviations from standard. +/// # Returns /// -/// * No length limits are enforced for the data part. -pub fn encode_without_checksum_to_fmt>( - fmt: &mut dyn fmt::Write, - hrp: Hrp, - data: T, -) -> Result { - for c in hrp.lowercase_char_iter() { - if let Err(e) = fmt.write_char(c) { - return Ok(Err(e)); - } - } - if let Err(e) = fmt.write_char(SEP) { - return Ok(Err(e)); - } - for b in data.as_ref() { - if let Err(e) = fmt.write_char(b.to_char()) { - return Ok(Err(e)); - } - } - Ok(Ok(())) -} - -/// Used for encode/decode operations for the two variants of Bech32. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub enum Variant { - /// The original Bech32 described in [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). - Bech32, - /// The improved Bech32m variant described in [BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki). - Bech32m, -} - -const BECH32_CONST: u32 = 1; -const BECH32M_CONST: u32 = 0x2bc8_30a3; +/// The human-readable part and the encoded data with the checksum removed. +#[cfg(feature = "alloc")] +#[inline] +pub fn decode(s: &str) -> Result<(Hrp, Vec), ChecksumError> { + let unchecked = UncheckedHrpstring::new(s).expect("TODO: handle error"); -impl Variant { - /// Produces the variant based on the remainder of the polymod operation. - fn from_remainder(c: u32) -> Option { - match c { - BECH32_CONST => Some(Variant::Bech32), - BECH32M_CONST => Some(Variant::Bech32m), - _ => None, + if let Err(e) = unchecked.validate_checksum::() { + if !unchecked.has_valid_checksum::() { + return Err(e); } - } -} + }; + // One of the checksums was valid, Ck is only for length and since + // they are both the same we can use either here. + let checked = unchecked.remove_checksum::(); -/// Encodes a bech32 payload to string. -/// -/// # Deviations from standard. -/// -/// * No length limits are enforced for the data part. -#[cfg(feature = "alloc")] -pub fn encode>(hrp: Hrp, data: T, variant: Variant) -> Result { - let mut buf = String::new(); - encode_to_fmt(&mut buf, hrp, data, variant)?.unwrap(); - Ok(buf) + Ok((checked.hrp(), checked.byte_iter().collect())) } -/// Encodes a bech32 payload to string without the checksum. -/// -/// # Deviations from standard. +/// Encodes `data` as a lowercase bech32 encoded string. /// -/// * No length limits are enforced for the data part. +/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the +/// `Ck` algorithm (`NoChecksum` to exclude checksum all together). #[cfg(feature = "alloc")] -pub fn encode_without_checksum>(hrp: Hrp, data: T) -> Result { - let mut buf = String::new(); - encode_without_checksum_to_fmt(&mut buf, hrp, data)?.unwrap(); - Ok(buf) +#[inline] +pub fn encode(hrp: Hrp, data: &[u8]) -> Result { + encode_lower::(hrp, data) } -/// Decodes a bech32 string into the raw HRP and the data bytes. +/// Decodes a bech32 string after validating and removing the bech32m checksum. /// /// # Returns /// -/// The human-readable part in lowercase, the data with the checksum removed, and the encoding. +/// The human-readable part and the encoded data with the checksum removed. #[cfg(feature = "alloc")] -pub fn decode(s: &str) -> Result<(Hrp, Vec, Variant), Error> { - let (hrp_lower, mut data) = split_and_decode(s)?; - if data.len() < CHECKSUM_LENGTH { - return Err(Error::InvalidLength); - } - - // Ensure checksum - match verify_checksum(hrp_lower, &data) { - Some(variant) => { - // Remove checksum from data payload - data.truncate(data.len() - CHECKSUM_LENGTH); - - Ok((hrp_lower, data, variant)) - } - None => Err(Error::InvalidChecksum), - } +#[inline] +pub fn decode_bech32m(s: &str) -> Result<(Hrp, Vec), CheckedHrpstringError> { + CheckedHrpstring::new::(s).map(|p| (p.hrp(), p.byte_iter().collect())) } -/// Decodes a bech32 string into the raw HRP and the data bytes, assuming no checksum. +/// Decodes a bech32 string after validating and removing the bech32 checksum. /// /// # Returns /// -/// The human-readable part in lowercase and the data. +/// The human-readable part and the encoded data with the checksum removed. #[cfg(feature = "alloc")] -pub fn decode_without_checksum(s: &str) -> Result<(Hrp, Vec), Error> { split_and_decode(s) } +#[inline] +pub fn decode_bech32(s: &str) -> Result<(Hrp, Vec), CheckedHrpstringError> { + CheckedHrpstring::new::(s).map(|p| (p.hrp(), p.byte_iter().collect())) +} -/// Decodes a bech32 string into the raw HRP and the `u5` data. +/// Decodes a bech32 string into the raw HRP and the data bytes assuming there is no checksum. #[cfg(feature = "alloc")] -fn split_and_decode(s: &str) -> Result<(Hrp, Vec), Error> { - // Split at separator and check for two pieces - let (raw_hrp, raw_data) = match s.rfind(SEP) { - None => return Err(Error::MissingSeparator), - Some(sep) => { - let (hrp, data) = s.split_at(sep); - (hrp, &data[1..]) - } - }; - - let (hrp, mut case) = Hrp::parse_and_case(raw_hrp)?; - - // Check data payload - let data = raw_data - .chars() - .map(|c| { - if c.is_lowercase() { - match case { - Case::Upper => return Err(Error::MixedCase), - Case::None => case = Case::Lower, - Case::Lower => {} - } - } else if c.is_uppercase() { - match case { - Case::Lower => return Err(Error::MixedCase), - Case::None => case = Case::Upper, - Case::Upper => {} - } - } - u5::from_char(c).map_err(Error::TryFrom) - }) - .collect::, Error>>()?; - - Ok((hrp, data)) +#[inline] +pub fn decode_no_checksum(s: &str) -> Result<(Hrp, Vec), CheckedHrpstringError> { + CheckedHrpstring::new::(s).map(|p| (p.hrp(), p.byte_iter().collect())) } -// TODO deduplicate some -/// Decode a lowercase bech32 string into the raw HRP and the data bytes. +/// Encodes `data` as a lowercase bech32 encoded string. /// -/// Less flexible than [decode], but don't allocate. -pub fn decode_lowercase<'b, E, R, S>( - s: &str, - data: &'b mut R, - scratch: &mut S, -) -> Result<(Hrp, &'b [u5], Variant), E> -where - R: WriteBase32 + AsRef<[u5]>, - S: WriteBase32 + AsRef<[u5]>, - E: From, - E: From, - E: From, - E: core::convert::From, -{ - // Ensure overall length is within bounds - if s.len() < 8 { - Err(Error::InvalidLength)?; - } - - // Split at separator and check for two pieces - let (raw_hrp, raw_data) = match s.rfind(SEP) { - None => Err(Error::MissingSeparator)?, - Some(sep) => { - let (hrp, data) = s.split_at(sep); - (hrp, &data[1..]) - } - }; - if raw_data.len() < 6 { - Err(Error::InvalidLength)?; - } - - let (hrp, case) = Hrp::parse_and_case(raw_hrp)?; - - // Check data payload - for c in raw_data.chars() { - match case { - Case::Upper => Err(Error::MixedCase)?, - Case::None | Case::Lower => {} - } - data.write_u5(u5::from_char(c).map_err(Error::TryFrom)?)?; - } - - // Ensure checksum - let variant = - verify_checksum_in(hrp, data.as_ref(), scratch)?.ok_or(Error::MissingSeparator)?; - - let dbl: usize = data.as_ref().len(); - Ok((hrp, &(*data).as_ref()[..dbl.saturating_sub(6)], variant)) +/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the +/// `Ck` algorithm (`NoChecksum` to exclude checksum all together). +#[cfg(feature = "alloc")] +#[inline] +pub fn encode_lower(hrp: Hrp, data: &[u8]) -> Result { + let mut buf = String::new(); + encode_lower_to_fmt::(&mut buf, hrp, data)?; + Ok(buf) } +/// Encodes `data` as an uppercase bech32 encoded string. +/// +/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the +/// `Ck` algorithm (`NoChecksum` to exclude checksum all together). #[cfg(feature = "alloc")] -fn verify_checksum(hrp: Hrp, data: &[u5]) -> Option { - let mut v: Vec = Vec::new(); - match verify_checksum_in(hrp, data, &mut v) { - Ok(v) => v, - Err(e) => match e {}, - } +#[inline] +pub fn encode_upper(hrp: Hrp, data: &[u8]) -> Result { + let mut buf = String::new(); + encode_upper_to_fmt::(&mut buf, hrp, data)?; + Ok(buf) } -fn verify_checksum_in(hrp: Hrp, data: &[u5], v: &mut T) -> Result, T::Error> -where - T: WriteBase32 + AsRef<[u5]>, -{ - hrp_expand_in(hrp, v)?; - v.write(data)?; - Ok(Variant::from_remainder(polymod(v.as_ref()))) +/// Encodes `data` to `fmt` as a lowercase bech32 encoded string. +/// +/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the +/// `Ck` algorithm (`NoChecksum` to exclude checksum all together). +#[inline] +pub fn encode_to_fmt( + fmt: &mut W, + hrp: Hrp, + data: &[u8], +) -> Result<(), fmt::Error> { + encode_lower_to_fmt::(fmt, hrp, data) } -fn hrp_expand_in(hrp: Hrp, v: &mut T) -> Result<(), T::Error> { - for b in hrp.lowercase_byte_iter() { - v.write_u5(u5::try_from(b >> 5).expect("can't be out of range, max. 7"))?; - } - v.write_u5(u5::try_from(0).unwrap())?; - for b in hrp.lowercase_byte_iter() { - v.write_u5(u5::try_from(b & 0x1f).expect("can't be out of range, max. 31"))?; +/// Encodes `data` to `fmt` as a lowercase bech32 encoded string. +/// +/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the +/// `Ck` algorithm (`NoChecksum` to exclude checksum all together). +#[inline] +pub fn encode_lower_to_fmt( + fmt: &mut W, + hrp: Hrp, + data: &[u8], +) -> Result<(), fmt::Error> { + let iter = data.iter().copied().bytes_to_fes(); + let chars = iter.with_checksum::(&hrp).chars(); + for c in chars { + fmt.write_char(c)?; } Ok(()) } -fn polymod(values: &[u5]) -> u32 { - let mut chk: u32 = 1; - let mut b: u8; - for v in values { - b = (chk >> 25) as u8; - chk = (chk & 0x01ff_ffff) << 5 ^ (u32::from(*v.as_ref())); - - for (i, item) in GEN.iter().enumerate() { - if (b >> i) & 1 == 1 { - chk ^= item; - } - } - } - chk -} - -/// Human-readable part and data part separator. -const SEP: char = '1'; - -/// Generator coefficients -const GEN: [u32; 5] = [0x3b6a_57b2, 0x2650_8e6d, 0x1ea1_19fa, 0x3d42_33dd, 0x2a14_62b3]; - -/// Error types for Bech32 encoding / decoding. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Error { - /// String does not contain the separator character. - MissingSeparator, - /// The checksum does not match the rest of the data. - InvalidChecksum, - /// The data or human-readable part is too long or too short. - InvalidLength, - /// Some part of the string contains an invalid character. - InvalidChar(char), - /// The bit conversion failed due to a padding issue. - InvalidPadding, - /// The whole string must be of one case. - MixedCase, - /// Attempted to convert a value which overflows a `u5`. - Overflow, - /// Conversion to u5 failed. - TryFrom(primitives::gf32::Error), - /// HRP parsing failed. - Hrp(hrp::Error), -} - -impl From for Error { - fn from(v: Infallible) -> Self { match v {} } -} - -impl From for Error { - fn from(e: hrp::Error) -> Self { Error::Hrp(e) } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Error::*; - - match *self { - MissingSeparator => write!(f, "missing human-readable separator, \"{}\"", SEP), - InvalidChecksum => write!(f, "invalid checksum"), - InvalidLength => write!(f, "invalid length"), - InvalidChar(n) => write!(f, "invalid character (code={})", n), - InvalidPadding => write!(f, "invalid padding"), - MixedCase => write!(f, "mixed-case strings not allowed"), - TryFrom(ref e) => write_err!(f, "conversion to u5 failed"; e), - Overflow => write!(f, "attempted to convert a value which overflows a u5"), - Hrp(ref e) => write_err!(f, "HRP conversion failed"; e), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use Error::*; - - match *self { - TryFrom(ref e) => Some(e), - Hrp(ref e) => Some(e), - MissingSeparator | InvalidChecksum | InvalidLength | InvalidChar(_) - | InvalidPadding | MixedCase | Overflow => None, - } - } -} - -impl From for Error { - fn from(e: primitives::gf32::Error) -> Self { Error::TryFrom(e) } -} - -/// Error return when `TryFrom` fails for T -> u5 conversion. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub enum TryFromIntError { - /// Attempted to convert a negative value to a `u5`. - NegOverflow, - /// Attempted to convert a value which overflows a `u5`. - PosOverflow, -} - -impl fmt::Display for TryFromIntError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use TryFromIntError::*; - - match *self { - NegOverflow => write!(f, "attempted to convert a negative value to a u5"), - PosOverflow => write!(f, "attempted to convert a value which overflows a u5"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for TryFromIntError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use TryFromIntError::*; - - match *self { - NegOverflow | PosOverflow => None, - } - } -} - -// impl From for Error { -// fn from(e: convert::Error) -> Self { -// Error::InvalidData(e) -// } -// } - -/// Converts between bit sizes. -/// -/// # Errors -/// -/// * `Error::InvalidData` if any element of `data` is out of range. -/// * `Error::InvalidPadding` if `pad == false` and the padding bits are not `0`. +/// Encodes `data` to `fmt` as an uppercase bech32 encoded string. /// -/// # Panics -/// -/// Function will panic if attempting to convert `from` or `to` a bit size that -/// is 0 or larger than 8 bits i.e., `from` and `to` must within range `1..=8`. -/// -/// # Examples -/// -/// ```rust -/// use bech32::convert_bits; -/// let base5 = convert_bits(&[0xff], 8, 5, true); -/// assert_eq!(base5.unwrap(), vec![0x1f, 0x1c]); -/// ``` -#[cfg(feature = "alloc")] -pub fn convert_bits(data: &[T], from: u32, to: u32, pad: bool) -> Result, Error> -where - T: Into + Copy, -{ - let mut ret: Vec = Vec::new(); - convert_bits_in::(data, from, to, pad, &mut ret)?; - Ok(ret) -} - -/// Convert between bit sizes without allocating -/// -/// Like [convert_bits]. -pub fn convert_bits_in( - data: &[T], - from: u32, - to: u32, - pad: bool, - ret: &mut R, -) -> Result<(), E> -where - T: Into + Copy, - R: WriteBase256, - E: From, - E: From, -{ - if from > 8 || to > 8 || from == 0 || to == 0 { - panic!("convert_bits `from` and `to` parameters 0 or greater than 8"); - } - let mut acc: u32 = 0; - let mut bits: u32 = 0; - let maxv: u32 = (1 << to) - 1; - for value in data { - let v: u32 = u32::from(Into::::into(*value)); - if (v >> from) != 0 { - // Input value exceeds `from` bit size - Err(Error::Overflow)?; - } - acc = (acc << from) | v; - bits += from; - while bits >= to { - bits -= to; - ret.write_u8(((acc >> bits) & maxv) as u8)?; - } - } - if pad { - if bits > 0 { - ret.write_u8(((acc << (to - bits)) & maxv) as u8)?; - } - } else if bits >= from || ((acc << (to - bits)) & maxv) != 0 { - Err(Error::InvalidPadding)?; +/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the +/// `Ck` algorithm (`NoChecksum` to exclude checksum all together). +#[inline] +pub fn encode_upper_to_fmt( + fmt: &mut W, + hrp: Hrp, + data: &[u8], +) -> Result<(), fmt::Error> { + let iter = data.iter().copied().bytes_to_fes(); + let chars = iter.with_checksum::(&hrp).chars(); + for c in chars { + fmt.write_char(c.to_ascii_uppercase())?; } Ok(()) } #[cfg(test)] +#[cfg(feature = "alloc")] mod tests { use super::*; + use crate::Bech32; - #[cfg(feature = "alloc")] - fn hrp(s: &str) -> Hrp { Hrp::parse_unchecked(s) } - - trait TextExt { - fn check_base32_vec(self) -> Result, Error>; - } - impl> TextExt for U { - fn check_base32_vec(self) -> Result, Error> { self.check_base32() } - } - - #[test] - #[cfg(feature = "alloc")] - fn getters_in() { - let mut data_scratch = Vec::new(); - let mut scratch = Vec::new(); - let decoded = - decode_lowercase::("bc1sw50qa3jx3s", &mut data_scratch, &mut scratch) - .unwrap(); - let data = [16, 14, 20, 15, 0].check_base32_vec().unwrap(); - assert_eq!(decoded.0.to_string(), "bc"); - assert_eq!(decoded.1, data.as_slice()); - } - - #[test] - #[cfg(feature = "alloc")] - fn getters() { - let decoded = decode("BC1SW50QA3JX3S").unwrap(); - let data = [16, 14, 20, 15, 0].check_base32_vec().unwrap(); - assert_eq!(decoded.0, hrp("bc")); - assert_eq!(decoded.0.to_string(), "BC"); - assert_eq!(decoded.1, data.as_slice()); - } - - #[test] - #[cfg(feature = "alloc")] - fn valid_checksum() { - let strings: Vec<&str> = vec!( - // Bech32 - "A12UEL5L", - "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", - "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", - "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", - "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", - // Bech32m - "A1LQFN3A", - "a1lqfn3a", - "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", - "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", - "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", - "split1checkupstagehandshakeupstreamerranterredcaperredlc445v", - "?1v759aa", - ); - for s in strings { - match decode(s) { - Ok((hrp, payload, variant)) => { - let encoded = encode(hrp, payload, variant).unwrap(); - assert_eq!(s.to_lowercase(), encoded.to_lowercase()); - } - Err(e) => panic!("Did not decode: {:?} Reason: {:?}", s, e), - } - } - } - - #[test] - #[cfg(feature = "alloc")] - fn invalid_strings() { - let pairs: Vec<(&str, Error)> = vec!( - (" 1nwldj5", - Error::Hrp(hrp::Error::InvalidAsciiByte(b' '))), - ("abc1\u{2192}axkwrx", - Error::TryFrom(primitives::gf32::Error::InvalidChar('\u{2192}'))), - ("an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", - Error::Hrp(hrp::Error::TooLong(84))), - ("pzry9x0s0muk", - Error::MissingSeparator), - ("1pzry9x0s0muk", - Error::Hrp(hrp::Error::Empty)), - ("x1b4n0q5v", - Error::TryFrom(primitives::gf32::Error::InvalidChar('b'))), - ("ABC1DEFGOH", - Error::TryFrom(primitives::gf32::Error::InvalidChar('O'))), - ("li1dgmt3", - Error::InvalidLength), - ("de1lg7wt\u{ff}", - Error::TryFrom(primitives::gf32::Error::InvalidChar('\u{ff}'))), - ("\u{20}1xj0phk", - Error::Hrp(hrp::Error::InvalidAsciiByte(b' '))), // u20 is space character - ("\u{7F}1g6xzxy", - Error::Hrp(hrp::Error::InvalidAsciiByte(0x7f))), - ("an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", - Error::Hrp(hrp::Error::TooLong(84))), - ("qyrz8wqd2c9m", - Error::MissingSeparator), - ("1qyrz8wqd2c9m", - Error::Hrp(hrp::Error::Empty)), - ("y1b0jsk6g", - Error::TryFrom(primitives::gf32::Error::InvalidChar('b'))), - ("lt1igcx5c0", - Error::TryFrom(primitives::gf32::Error::InvalidChar('i'))), - ("in1muywd", - Error::InvalidLength), - ("mm1crxm3i", - Error::TryFrom(primitives::gf32::Error::InvalidChar('i'))), - ("au1s5cgom", - Error::TryFrom(primitives::gf32::Error::InvalidChar('o'))), - ("M1VUXWEZ", - Error::InvalidChecksum), - ("16plkw9", - Error::Hrp(hrp::Error::Empty)), - ("1p2gdwpf", - Error::Hrp(hrp::Error::Empty)), - ("bc1p2", - Error::InvalidLength), - ); - for p in pairs { - let (s, expected_error) = p; - match decode(s) { - Ok(_) => panic!("Should be invalid: {:?}", s), - Err(e) => assert_eq!(e, expected_error, "testing input '{}'", s), - } - } - } - - #[test] - #[allow(clippy::type_complexity)] - #[cfg(feature = "alloc")] - fn valid_conversion() { - // Set of [data, from_bits, to_bits, pad, result] - let tests: Vec<(Vec, u32, u32, bool, Vec)> = vec![ - (vec![0x01], 1, 1, true, vec![0x01]), - (vec![0x01, 0x01], 1, 1, true, vec![0x01, 0x01]), - (vec![0x01], 8, 8, true, vec![0x01]), - (vec![0x01], 8, 4, true, vec![0x00, 0x01]), - (vec![0x01], 8, 2, true, vec![0x00, 0x00, 0x00, 0x01]), - (vec![0x01], 8, 1, true, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), - (vec![0xff], 8, 5, true, vec![0x1f, 0x1c]), - (vec![0x1f, 0x1c], 5, 8, false, vec![0xff]), - ]; - for t in tests { - let (data, from_bits, to_bits, pad, expected_result) = t; - let result = convert_bits(&data, from_bits, to_bits, pad); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), expected_result); - } - } - - #[test] - #[cfg(feature = "alloc")] - fn invalid_conversion() { - // Set of [data, from_bits, to_bits, pad, expected error] - let tests: Vec<(Vec, u32, u32, bool, Error)> = vec![ - (vec![0xff], 8, 5, false, Error::InvalidPadding), - (vec![0x02], 1, 1, true, Error::Overflow), - ]; - for t in tests { - let (data, from_bits, to_bits, pad, expected_error) = t; - let result = convert_bits(&data, from_bits, to_bits, pad); - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), expected_error); - } - } - - #[test] - #[cfg(feature = "alloc")] - fn convert_bits_invalid_bit_size() { - use std::panic::{catch_unwind, set_hook, take_hook}; - - let invalid = &[(0, 8), (5, 0), (9, 5), (8, 10), (0, 16)]; - - for &(from, to) in invalid { - set_hook(Box::new(|_| {})); - let result = catch_unwind(|| { - let _ = convert_bits(&[0], from, to, true); - }); - let _ = take_hook(); - assert!(result.is_err()); - } - } - - #[test] - fn check_base32() { - assert!([0u8, 1, 2, 30, 31].check_base32_vec().is_ok()); - assert!([0u8, 1, 2, 30, 31, 32].check_base32_vec().is_err()); - assert!([0u8, 1, 2, 30, 31, 255].check_base32_vec().is_err()); - - assert!([1u8, 2, 3, 4].check_base32_vec().is_ok()); - assert!(matches!( - [30u8, 31, 35, 20].check_base32_vec(), - Err(Error::TryFrom(primitives::gf32::Error::InvalidByte(35))) - )); - } + // Tests below using this data, are based on the test vector (from BIP-173): + // BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4: 0014751e76e8199196d454941c45d1b3a323f1433bd6 + #[rustfmt::skip] + const DATA: [u8; 20] = [ + 0xff, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, + 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, + 0xf1, 0x43, 0x3b, 0xd6, + ]; #[test] - #[cfg(feature = "alloc")] - fn test_encode() { - assert_eq!(Hrp::parse(""), Err(hrp::Error::Empty)); + fn encode_bech32m() { + let hrp = Hrp::parse_unchecked("test"); + let got = encode::(hrp, &DATA).expect("failed to encode"); + let want = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kmz4lky"; + assert_eq!(got, want); } #[test] - #[cfg(feature = "alloc")] - fn from_base32() { - assert_eq!(Vec::from_base32(&[0x1f, 0x1c].check_base32_vec().unwrap()), Ok(vec![0xff])); - assert_eq!( - Vec::from_base32(&[0x1f, 0x1f].check_base32_vec().unwrap()), - Err(Error::InvalidPadding) - ); + fn encode_bech32_lower() { + let hrp = Hrp::parse_unchecked("test"); + let got = encode_lower::(hrp, &DATA).expect("failed to encode"); + let want = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kw79nnx"; + assert_eq!(got, want); } #[test] - #[cfg(feature = "alloc")] - fn to_base32() { - assert_eq!([0xffu8].to_base32(), [0x1f, 0x1c].check_base32_vec().unwrap()); - } - - #[test] - #[cfg(feature = "alloc")] - fn write_with_checksum() { - let hrp = hrp("lnbc"); - let data = "Hello World!".as_bytes().to_base32(); - - let mut written_str = String::new(); - { - let mut writer = Bech32Writer::::new(hrp, &mut written_str).unwrap(); - writer.write(&data).unwrap(); - writer.finalize().unwrap(); - } - - let encoded_str = encode(hrp, data, Variant::Bech32).unwrap(); - - assert_eq!(encoded_str, written_str); + fn encode_bech32_upper() { + let hrp = Hrp::parse_unchecked("test"); + let got = encode_upper::(hrp, &DATA).expect("failed to encode"); + let want = "TEST1LU08D6QEJXTDG4Y5R3ZARVARY0C5XW7KW79NNX"; + assert_eq!(got, want); } #[test] - #[cfg(feature = "alloc")] - fn write_without_checksum() { - let hrp = hrp("lnbc"); - let data = "Hello World!".as_bytes().to_base32(); - - let mut written_str = String::new(); - { - let mut writer = Bech32Writer::::new(hrp, &mut written_str).unwrap(); - writer.write(&data).unwrap(); - } - - let encoded_str = encode_without_checksum(hrp, data).unwrap(); + fn decode_bech32m() { + let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kmz4lky"; + let (hrp, data) = decode(s).expect("failed to encode"); - assert_eq!(encoded_str, written_str[..written_str.len() - CHECKSUM_LENGTH]); + assert_eq!(hrp, Hrp::parse_unchecked("test")); + assert_eq!(data, DATA); } #[test] - #[cfg(feature = "alloc")] - fn write_with_checksum_on_drop() { - let hrp = hrp("lntb"); - let data = "Hello World!".as_bytes().to_base32(); + fn decode_bech32_lower() { + let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kw79nnx"; + let (hrp, data) = decode(s).expect("failed to encode"); - let mut written_str = String::new(); - { - let mut writer = Bech32Writer::::new(hrp, &mut written_str).unwrap(); - writer.write(&data).unwrap(); - } - - let encoded_str = encode(hrp, data, Variant::Bech32).unwrap(); - - assert_eq!(encoded_str, written_str); + assert_eq!(hrp, Hrp::parse_unchecked("test")); + assert_eq!(data, DATA); } #[test] - #[cfg(feature = "alloc")] - fn roundtrip_without_checksum() { - let hrp = hrp("lnbc"); - let data = "Hello World!".as_bytes().to_base32(); + fn decode_bech32_upper() { + let s = "TEST1LU08D6QEJXTDG4Y5R3ZARVARY0C5XW7KW79NNX"; + let (hrp, data) = decode(s).expect("failed to encode"); - let encoded = encode_without_checksum(hrp, data.clone()).expect("failed to encode"); - let (decoded_hrp, decoded_data) = - decode_without_checksum(&encoded).expect("failed to decode"); - - assert_eq!(decoded_hrp, hrp); - assert_eq!(decoded_data, data); + assert_eq!(hrp, Hrp::parse_unchecked("TEST")); + assert_eq!(data, DATA); } #[test] - #[cfg(feature = "alloc")] - fn test_hrp_case() { - // Tests for issue with HRP case checking being ignored for encoding - let encoded_str = encode(hrp("HRP"), [0x00, 0x00].to_base32(), Variant::Bech32).unwrap(); - - assert_eq!(encoded_str, "hrp1qqqq40atq3"); - } - - #[test] - fn try_from_err() { - assert!(u5::try_from(32_u8).is_err()); - assert!(u5::try_from(32_u16).is_err()); - assert!(u5::try_from(32_u32).is_err()); - assert!(u5::try_from(32_u64).is_err()); - assert!(u5::try_from(32_u128).is_err()); - } - - #[test] - #[cfg(feature = "alloc")] - fn decode_bitcoin_bech32_address() { - let addr = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; - let (hrp, _data, variant) = crate::decode(addr).expect("address is well formed"); - assert_eq!(hrp.to_string(), "bc"); - assert_eq!(variant, Variant::Bech32) - } - - #[test] - #[cfg(feature = "alloc")] - fn decode_bitcoin_bech32m_address() { - let addr = "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297"; - let (hrp, _data, variant) = crate::decode(addr).expect("address is well formed"); - assert_eq!(hrp.to_string(), "bc"); - assert_eq!(variant, Variant::Bech32m) - } - - #[test] - #[cfg(feature = "alloc")] - fn decode_all_digit_hrp_uppercase_data() { - let addr = "23451QAR0SRRR7XFKVY5L643LYDNW9RE59GTZZLKULZK"; - let (hrp, data, variant) = crate::decode(addr).expect("address is well formed"); - assert_eq!(hrp, Hrp::parse("2345").unwrap()); - let hrp = Hrp::parse("2345").unwrap(); - let s = crate::encode(hrp, data, variant).expect("failed to encode"); - assert_eq!(s.to_uppercase(), addr); - } - - #[test] - #[cfg(feature = "alloc")] - fn writer_lowercases_hrp_when_adding_to_checksum() { - let addr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"; - let (_hrp, data, _variant) = crate::decode(addr).expect("failed to decode"); - let data: Vec = FromBase32::from_base32(&data[1..]).expect("failed to convert u5s"); - - let mut writer = String::new(); - let mut bech32_writer = Bech32Writer::::new(Hrp::parse("BC").unwrap(), &mut writer) - .expect("failed to write hrp"); - let version = u5::try_from(0).unwrap(); - - WriteBase32::write_u5(&mut bech32_writer, version).expect("failed to write version"); - ToBase32::write_base32(&data, &mut bech32_writer).expect("failed to write data"); - - drop(bech32_writer); - - assert_eq!(writer, addr.to_lowercase()); - } -} - -#[cfg(bench)] -mod benches { - use test::{black_box, Bencher}; - - #[bench] - fn bech32_parse_address(bh: &mut Bencher) { - let addr = black_box("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"); - - bh.iter(|| { - let tuple = crate::decode(&addr).expect("address is well formed"); - black_box(&tuple); - }) - } - - #[bench] - fn bech32m_parse_address(bh: &mut Bencher) { - let addr = black_box("bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297"); - - bh.iter(|| { - let tuple = crate::decode(&addr).expect("address is well formed"); - black_box(&tuple); - }) - } - - // Encode with allocation. - #[bench] - fn encode_bech32_address(bh: &mut Bencher) { - let addr = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; - let (hrp, data, variant) = crate::decode(&addr).expect("address is well formed"); - - bh.iter(|| { - let s = crate::encode(hrp, &data, variant).expect("failed to encode"); - black_box(&s); - }); - } - - // Encode without allocation. - #[bench] - fn encode_to_fmt_bech32_address(bh: &mut Bencher) { - let addr = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; - let (hrp, data, variant) = crate::decode(&addr).expect("address is well formed"); - let mut buf = String::with_capacity(64); - - bh.iter(|| { - let res = - crate::encode_to_fmt(&mut buf, hrp, &data, variant).expect("failed to encode"); - black_box(&res); - }); - } - - // Encode with allocation. - #[bench] - fn encode_bech32m_address(bh: &mut Bencher) { - let addr = "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297"; - let (hrp, data, variant) = crate::decode(&addr).expect("address is well formed"); - - bh.iter(|| { - let s = crate::encode(hrp, &data, variant).expect("failed to encode"); - black_box(&s); - }); - } - - // Encode without allocation. - #[bench] - fn encode_to_fmt_bech32m_address(bh: &mut Bencher) { - let addr = "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297"; - let (hrp, data, variant) = crate::decode(&addr).expect("address is well formed"); - let mut buf = String::with_capacity(64); + fn can_decode_no_checksum() { + let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7k"; + let (hrp, data) = decode_no_checksum(s).expect("failed to encode"); - bh.iter(|| { - let res = - crate::encode_to_fmt(&mut buf, hrp, &data, variant).expect("failed to encode"); - black_box(&res); - }); + assert_eq!(hrp, Hrp::parse_unchecked("test")); + assert_eq!(data, DATA); } } diff --git a/src/primitives/checksum.rs b/src/primitives/checksum.rs index 5e8aeda2e..ee56826b9 100644 --- a/src/primitives/checksum.rs +++ b/src/primitives/checksum.rs @@ -210,9 +210,9 @@ impl_packed_fe32!(u128); /// Iterator that yields the field elements that are input into a checksum algorithm for an [`Hrp`]. pub struct HrpFe32Iter<'hrp> { /// `None` once the hrp high fes have been yielded. - high_iter: Option>, + high_iter: Option>, /// `None` once the hrp low fes have been yielded. - low_iter: Option>, + low_iter: Option>, } impl<'hrp> HrpFe32Iter<'hrp> { diff --git a/src/primitives/decode.rs b/src/primitives/decode.rs index 67103b553..c387d8765 100644 --- a/src/primitives/decode.rs +++ b/src/primitives/decode.rs @@ -221,11 +221,12 @@ impl<'s> UncheckedHrpstring<'s> { /// # Examples /// /// ``` -/// use bech32::{Bech32, primitives::decode::CheckedHrpstring}; +/// use bech32::{Bech32m, primitives::decode::CheckedHrpstring}; /// /// // Parse a general checksummed bech32 encoded string. -/// let s = "abcd14g08d6qejxtdg4y5r3zarvary0c5xw7kxugcx9"; -/// let checked = CheckedHrpstring::new::(s).expect("valid bech32 string with a valid checksum"); +/// let s = "abcd14g08d6qejxtdg4y5r3zarvary0c5xw7knqc5r8"; +/// let checked = CheckedHrpstring::new::(s) +/// .expect("valid bech32 string with a valid checksum according to the bech32m algorithm"); /// /// // Do something with the encoded data. /// let _ = checked.byte_iter(); @@ -796,8 +797,6 @@ impl std::error::Error for PaddingError { #[cfg(test)] mod tests { use super::*; - #[cfg(feature = "alloc")] - use crate::Variant; #[test] fn bip_173_invalid_parsing_fails() { @@ -927,7 +926,7 @@ mod tests { "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio"; let hrp = Hrp::parse_unchecked(hrps); - let s = crate::encode(hrp, [], Variant::Bech32).expect("failed to encode empty buffer"); + let s = crate::encode::(hrp, &[]).expect("failed to encode empty buffer"); let unchecked = UncheckedHrpstring::new(&s).expect("failed to parse address"); assert_eq!(unchecked.hrp(), hrp); diff --git a/src/primitives/hrp.rs b/src/primitives/hrp.rs index 1852d3e57..a1167814e 100644 --- a/src/primitives/hrp.rs +++ b/src/primitives/hrp.rs @@ -16,31 +16,43 @@ use core::fmt::{self, Write}; use core::iter::FusedIterator; use core::slice; -use crate::Case; - /// Maximum length of the human-readable part, as defined by BIP-173. const MAX_HRP_LEN: usize = 83; +// Defines HRP constants for the different bitcoin networks. +// You can also access these at `crate::hrp::BC` etc. +#[rustfmt::skip] macro_rules! define_hrp_const { - ($name:ident, $size:literal, $zero:literal, $one:literal, $two:literal, $three:literal, $network:literal) => { -/// "The human-readable part for the Bitcoin $network." - #[rustfmt::skip] + ( + #[$doc:meta] + pub const $name:ident $size:literal $v:expr; + ) => { + #[$doc] pub const $name: Hrp = Hrp { buf: [ - $zero, $one, $two, $three, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + $v[0], $v[1], $v[2], $v[3], + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], size: $size }; }; } -define_hrp_const! {BC, 2, 98, 99, 0, 0, "network (mainnet)."} -define_hrp_const! {TB, 2, 116, 98, 0, 0, "testnet networks (testnet, signet)."} -define_hrp_const! {BCRT, 4, 98, 99, 114, 116, "regtest network."} +define_hrp_const! { + /// The human-readable part used by the Bitcoin mainnet network. + pub const BC 2 [98, 99, 0, 0]; +} +define_hrp_const! { + /// The human-readable part used by the Bitcoin testnet networks (testnet, signet). + pub const TB 2 [116, 98, 0, 0]; +} +define_hrp_const! { + /// The human-readable part used when running a Bitcoin regtest network. + pub const BCRT 4 [98, 99, 114, 116]; +} /// The human-readable part (human readable prefix before the '1' separator). #[derive(Clone, Copy, Debug)] @@ -63,35 +75,7 @@ impl Hrp { /// > specific applications. /// /// [BIP-173]: - #[inline] - pub fn parse(hrp: &str) -> Result { Ok(Self::parse_and_case(hrp)?.0) } - - /// Parses the human-readable part (see [`Hrp::parse`] for full docs). - /// - /// Does not check that `hrp` is valid according to BIP-173 but does check for valid ASCII - /// values, replacing any invalid characters with `X`. - pub const fn parse_unchecked(hrp: &str) -> Self { - let mut new = Hrp { buf: [0_u8; MAX_HRP_LEN], size: 0 }; - let hrp_bytes = hrp.as_bytes(); - - let mut i = 0; - // Funky code so we can be const. - while i < hrp.len() { - let mut b = hrp_bytes[i]; - // Valid subset of ASCII - if b < 33 || b > 126 { - b = b'X'; - } - - new.buf[i] = b; - new.size += 1; - i += 1; - } - new - } - - /// Returns the case as well as the parsed `Hrp`. - pub(crate) fn parse_and_case(hrp: &str) -> Result<(Self, Case), Error> { + pub fn parse(hrp: &str) -> Result { use Error::*; let mut new = Hrp { buf: [0_u8; MAX_HRP_LEN], size: 0 }; @@ -117,8 +101,14 @@ impl Hrp { } if b.is_ascii_lowercase() { + if has_upper { + return Err(MixedCase); + } has_lower = true; } else if b.is_ascii_uppercase() { + if has_lower { + return Err(MixedCase); + } has_upper = true; }; @@ -126,24 +116,31 @@ impl Hrp { new.size += 1; } - let case = match (has_lower, has_upper) { - (true, false) => Case::Lower, - (false, true) => Case::Upper, - (false, false) => Case::None, - (true, true) => return Err(MixedCase), - }; - - Ok((new, case)) + Ok(new) } - /// Lowercase the inner ASCII bytes of this HRP. - // This is a hack to support `encode_to_fmt`, we should remove this function. - pub(crate) fn lowercase(&mut self) { - for b in self.buf.iter_mut() { - if is_ascii_uppercase(*b) { - *b |= 32; + /// Parses the human-readable part (see [`Hrp::parse`] for full docs). + /// + /// Does not check that `hrp` is valid according to BIP-173 but does check for valid ASCII + /// values, replacing any invalid characters with `X`. + pub const fn parse_unchecked(hrp: &str) -> Self { + let mut new = Hrp { buf: [0_u8; MAX_HRP_LEN], size: 0 }; + let hrp_bytes = hrp.as_bytes(); + + let mut i = 0; + // Funky code so we can be const. + while i < hrp.len() { + let mut b = hrp_bytes[i]; + // Valid subset of ASCII + if b < 33 || b > 126 { + b = b'X'; } + + new.buf[i] = b; + new.size += 1; + i += 1; } + new } /// Returns this human-readable part as a lowercase string. @@ -450,31 +447,6 @@ mod tests { parse_err_3, "has spaces in it"; } - macro_rules! check_case { - ($($test_name:ident, $hrp:literal, $expected:ident);* $(;)?) => { - $( - #[test] - fn $test_name() { - use crate::Case::*; - let (_, case) = Hrp::parse_and_case($hrp).expect("failed to parse hrp"); - assert_eq!(case, $expected); - } - )* - } - } - check_case! { - case_0, "a", Lower; - case_1, "A", Upper; - case_2, "ab", Lower; - case_3, "AB", Upper; - case_4, "ab2", Lower; - case_5, "AB2", Upper; - case_6, "3ab2", Lower; - case_7, "3AB2", Upper; - case_8, "2", None; - case_9, "23456", None; - } - macro_rules! check_iter { ($($test_name:ident, $hrp:literal, $len:literal);* $(;)?) => { $( diff --git a/src/segwit.rs b/src/segwit.rs index 9cea2ec25..b34b5c4c5 100644 --- a/src/segwit.rs +++ b/src/segwit.rs @@ -11,8 +11,7 @@ //! //! ``` //! # #[cfg(feature = "alloc")] { -//! use bech32::primitives::hrp::{self, Hrp}; -//! use bech32::{Fe32, segwit}; +//! use bech32::{hrp, segwit, Fe32, Hrp}; //! //! let witness_prog = [ //! 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, @@ -50,7 +49,10 @@ use crate::primitives::decode::{SegwitHrpstring, SegwitHrpstringError}; use crate::primitives::gf32::Fe32; use crate::primitives::hrp::Hrp; use crate::primitives::iter::{ByteIterExt, Fe32IterExt}; -use crate::primitives::segwit::{self, InvalidWitnessVersionError, WitnessLengthError}; +#[cfg(feature = "alloc")] +use crate::primitives::segwit; +use crate::primitives::segwit::{InvalidWitnessVersionError, WitnessLengthError}; +#[doc(inline)] pub use crate::primitives::segwit::{VERSION_0, VERSION_1}; use crate::primitives::{Bech32, Bech32m}; use crate::write_err; @@ -119,6 +121,19 @@ pub fn encode_to_fmt_unchecked( hrp: &Hrp, witness_version: Fe32, witness_program: &[u8], +) -> fmt::Result { + encode_lower_to_fmt_unchecked(fmt, hrp, witness_version, witness_program) +} + +/// Encodes a segwit address to a writer ([`fmt::Write`]) using lowercase characters. +/// +/// Does not check the validity of the witness version and witness program lengths (see +/// the [`crate::primitives::segwit`] module for validation functions). +pub fn encode_lower_to_fmt_unchecked( + fmt: &mut W, + hrp: &Hrp, + witness_version: Fe32, + witness_program: &[u8], ) -> fmt::Result { let iter = witness_program.iter().copied().bytes_to_fes(); match witness_version { @@ -142,7 +157,7 @@ pub fn encode_to_fmt_unchecked( /// /// Does not check the validity of the witness version and witness program lengths (see /// the [`crate::primitives::segwit`] module for validation functions). -pub fn encode_to_fmt_unchecked_uppercase( +pub fn encode_upper_to_fmt_unchecked( fmt: &mut W, hrp: &Hrp, witness_version: Fe32, @@ -257,7 +272,7 @@ mod tests { fn encode_to_fmt_uppercase() { let program = witness_program(); let mut address = String::new(); - encode_to_fmt_unchecked_uppercase(&mut address, &hrp::BC, VERSION_0, &program) + encode_upper_to_fmt_unchecked(&mut address, &hrp::BC, VERSION_0, &program) .expect("failed to encode address to QR code"); let want = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";