From e4c7e01a6f6d0f6a6c5c06d6fe1682f7240fb3e2 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 4 Aug 2023 11:05:15 +1000 Subject: [PATCH] Use the new bech32 iterator API Use the new bech32 iterator API that Andrew and I wrote. --- Cargo-minimal.lock | 4 +- Cargo-recent.lock | 4 +- bitcoin/Cargo.toml | 4 +- bitcoin/src/address/error.rs | 26 ++----- bitcoin/src/address/mod.rs | 71 ++++++------------- .../src/blockdata/script/witness_version.rs | 20 +++--- 6 files changed, 40 insertions(+), 89 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index cbf61238..ef570607 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -16,9 +16,9 @@ checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "bech32" -version = "0.9.0" +version = "0.10.0-alpha" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5738be7561b0eeb501ef1d5c5db3f24e01ceb55fededd9b00039aada34966ad" +checksum = "81cc1dec4c25e5a78c52802eda8e2adf0d2aca57ffc536326b75c0e531ea0a9b" [[package]] name = "bincode" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 35a7086a..297e902e 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -16,9 +16,9 @@ checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "bech32" -version = "0.9.1" +version = "0.10.0-alpha" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +checksum = "81cc1dec4c25e5a78c52802eda8e2adf0d2aca57ffc536326b75c0e531ea0a9b" [[package]] name = "bincode" diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index b54524e8..3868bd08 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -27,7 +27,7 @@ bitcoinconsensus-std = ["bitcoinconsensus/std", "std"] # Instead no-std enables additional features required for this crate to be usable without std. # As a result, both can be enabled without conflict. std = ["secp256k1/std", "hashes/std", "bech32/std", "internals/std", "hex/std"] -no-std = ["core2", "hashes/alloc", "hashes/core2", "secp256k1/alloc", "hex/alloc", "hex/core2"] +no-std = ["core2", "hashes/alloc", "hashes/core2", "bech32/alloc", "secp256k1/alloc", "hex/alloc", "hex/core2"] [package.metadata.docs.rs] all-features = true @@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] internals = { package = "bitcoin-internals", version = "0.2.0" } hex = { package = "hex-conservative", version = "0.1.1", default-features = false } -bech32 = { version = "0.9.0", default-features = false } +bech32 = { version = "0.10.0-alpha", default-features = false } hashes = { package = "bitcoin_hashes", version = "0.13.0", default-features = false } secp256k1 = { version = "0.27.0", default-features = false, features = ["bitcoin_hashes"] } hex_lit = "0.1.1" diff --git a/bitcoin/src/address/error.rs b/bitcoin/src/address/error.rs index 51d137bb..9c5fd46c 100644 --- a/bitcoin/src/address/error.rs +++ b/bitcoin/src/address/error.rs @@ -99,17 +99,8 @@ impl_std_error!(UnknownAddressTypeError); pub enum ParseError { /// Base58 error. Base58(base58::Error), - /// Bech32 error. - Bech32(bech32::Error), - /// The bech32 payload was empty. - EmptyBech32Payload, - /// The wrong checksum algorithm was used. See BIP-0350. - InvalidBech32Variant { - /// Bech32 variant that is required by the used Witness version. - expected: bech32::Variant, - /// The actual Bech32 variant encoded in the address representation. - found: bech32::Variant, - }, + /// Bech32 segwit decoding error. + Bech32(bech32::primitives::decode::SegwitHrpstringError), /// A witness version conversion/parsing error. WitnessVersion(witness_version::TryFromError), /// A witness program error. @@ -122,13 +113,7 @@ impl fmt::Display for ParseError { match *self { Base58(ref e) => write_err!(f, "base58 error"; e), - Bech32(ref e) => write_err!(f, "bech32 error"; e), - EmptyBech32Payload => write!(f, "the bech32 payload was empty"), - InvalidBech32Variant { expected, found } => write!( - f, - "invalid bech32 checksum variant found {:?} when {:?} was expected", - found, expected - ), + Bech32(ref e) => write_err!(f, "bech32 segwit decoding error"; e), WitnessVersion(ref e) => write_err!(f, "witness version conversion/parsing error"; e), WitnessProgram(ref e) => write_err!(f, "witness program error"; e), } @@ -145,7 +130,6 @@ impl std::error::Error for ParseError { Bech32(ref e) => Some(e), WitnessVersion(ref e) => Some(e), WitnessProgram(ref e) => Some(e), - EmptyBech32Payload | InvalidBech32Variant { .. } => None, } } } @@ -154,8 +138,8 @@ impl From for ParseError { fn from(e: base58::Error) -> Self { Self::Base58(e) } } -impl From for ParseError { - fn from(e: bech32::Error) -> Self { Self::Bech32(e) } +impl From for ParseError { + fn from(e: bech32::primitives::decode::SegwitHrpstringError) -> Self { Self::Bech32(e) } } impl From for ParseError { diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index 4878c0e7..6388ed24 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -31,7 +31,7 @@ use core::fmt; use core::marker::PhantomData; use core::str::FromStr; -use bech32; +use bech32::primitives::hrp::{self, Hrp}; use hashes::{sha256, Hash, HashEngine}; use secp256k1::{Secp256k1, Verification, XOnlyPublicKey}; @@ -46,6 +46,7 @@ use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash}; use crate::crypto::key::{PubkeyHash, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey}; use crate::network::Network; use crate::prelude::*; +use crate::script::PushBytesBuf; use crate::taproot::TapNodeHash; /// Error code for the address module. @@ -237,8 +238,8 @@ pub struct AddressEncoding<'a> { pub p2pkh_prefix: u8, /// base58 version byte for p2sh payloads (e.g. 0x05 for "3..." addresses). pub p2sh_prefix: u8, - /// hrp used in bech32 addresss (e.g. "bc" for "bc1..." addresses). - pub bech32_hrp: &'a str, + /// The bech32 human-readable part. + pub hrp: Hrp, } /// Formats bech32 as upper case if alternate formatting is chosen (`{:#}`). @@ -257,19 +258,16 @@ impl<'a> fmt::Display for AddressEncoding<'a> { prefixed[1..].copy_from_slice(&hash[..]); base58::encode_check_to_fmt(fmt, &prefixed[..]) } - Payload::WitnessProgram(witness_prog) => { - let (version, prog) = (witness_prog.version(), witness_prog.program()); - let mut upper_writer; - let writer = if fmt.alternate() { - upper_writer = UpperWriter(fmt); - &mut upper_writer as &mut dyn fmt::Write + Payload::WitnessProgram(witness_program) => { + let hrp = &self.hrp; + let version = witness_program.version().to_fe(); + let program = witness_program.program().as_bytes(); + + if fmt.alternate() { + bech32::segwit::encode_to_fmt_unchecked_uppercase(fmt, hrp, version, program) } else { - fmt as &mut dyn fmt::Write - }; - let mut bech32_writer = - bech32::Bech32Writer::new(self.bech32_hrp, version.bech32_variant(), writer)?; - bech32::WriteBase32::write_u5(&mut bech32_writer, version.into())?; - bech32::ToBase32::write_base32(&prog.as_bytes(), &mut bech32_writer) + bech32::segwit::encode_to_fmt_unchecked(fmt, hrp, version, program) + } } } } @@ -495,13 +493,12 @@ impl Address { Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN, Network::Testnet | Network::Signet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST, }; - let bech32_hrp = match self.network() { - Network::Bitcoin => "bc", - Network::Testnet | Network::Signet => "tb", - Network::Regtest => "bcrt", + let hrp = match self.network() { + Network::Bitcoin => hrp::BC, + Network::Testnet | Network::Signet => hrp::TB, + Network::Regtest => hrp::BCRT, }; - let encoding = - AddressEncoding { payload: self.payload(), p2pkh_prefix, p2sh_prefix, bech32_hrp }; + let encoding = AddressEncoding { payload: self.payload(), p2pkh_prefix, p2sh_prefix, hrp }; use fmt::Display; @@ -775,17 +772,6 @@ impl fmt::Debug for Address { } } -struct UpperWriter(W); - -impl fmt::Write for UpperWriter { - fn write_str(&mut self, s: &str) -> fmt::Result { - for c in s.chars() { - self.0.write_char(c.to_ascii_uppercase())?; - } - Ok(()) - } -} - /// Extracts the bech32 prefix. /// /// # Returns @@ -812,26 +798,11 @@ impl FromStr for Address { _ => None, }; if let Some(network) = bech32_network { - // decode as bech32 - let (_, payload, variant) = bech32::decode(s)?; - if payload.is_empty() { - return Err(ParseError::EmptyBech32Payload); - } - - // Get the script version and program (converted from 5-bit to 8-bit) - let (version, program): (WitnessVersion, Vec) = { - let (v, p5) = payload.split_at(1); - (WitnessVersion::try_from(v[0])?, bech32::FromBase32::from_base32(p5)?) - }; - + let (_hrp, version, data) = bech32::segwit::decode(s)?; + let version = WitnessVersion::try_from(version).expect("we know this is in range 0-16"); + let program = PushBytesBuf::try_from(data).expect("decode() guarantees valid length"); let witness_program = WitnessProgram::new(version, program)?; - // Encoding check - let expected = version.bech32_variant(); - if expected != variant { - return Err(ParseError::InvalidBech32Variant { expected, found: variant }); - } - return Ok(Address::new(network, Payload::WitnessProgram(witness_program))); } diff --git a/bitcoin/src/blockdata/script/witness_version.rs b/bitcoin/src/blockdata/script/witness_version.rs index b59f30b0..9dbb449e 100644 --- a/bitcoin/src/blockdata/script/witness_version.rs +++ b/bitcoin/src/blockdata/script/witness_version.rs @@ -11,6 +11,7 @@ use core::convert::TryFrom; use core::fmt; use core::str::FromStr; +use bech32::Fe32; use internals::write_err; use crate::blockdata::opcodes::all::*; @@ -72,12 +73,9 @@ impl WitnessVersion { /// into a byte since the conversion requires context (bitcoin script or just a version number). pub fn to_num(self) -> u8 { self as u8 } - /// Determines the checksum variant. See BIP-0350 for specification. - pub fn bech32_variant(&self) -> bech32::Variant { - match self { - WitnessVersion::V0 => bech32::Variant::Bech32, - _ => bech32::Variant::Bech32m, - } + /// Converts this witness version to a GF32 field element. + pub fn to_fe(self) -> Fe32 { + Fe32::try_from(self.to_num()).expect("0-16 are valid fe32 values") } } @@ -95,10 +93,10 @@ impl FromStr for WitnessVersion { } } -impl TryFrom for WitnessVersion { +impl TryFrom for WitnessVersion { type Error = TryFromError; - fn try_from(value: bech32::u5) -> Result { Self::try_from(value.to_u8()) } + fn try_from(value: Fe32) -> Result { Self::try_from(value.to_u8()) } } impl TryFrom for WitnessVersion { @@ -155,10 +153,8 @@ impl<'a> TryFrom> for WitnessVersion { } } -impl From for bech32::u5 { - fn from(version: WitnessVersion) -> Self { - bech32::u5::try_from_u8(version.to_num()).expect("WitnessVersion must be 0..=16") - } +impl From for Fe32 { + fn from(version: WitnessVersion) -> Self { version.to_fe() } } impl From for Opcode {