Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use newly released bech32 API #1951

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions Cargo-recent.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file is automatically @generated by Cargo.

Check warning on line 1 in Cargo-recent.lock

View workflow job for this annotation

GitHub Actions / Test - beta toolchain

Dependencies could be updated

Check warning on line 1 in Cargo-recent.lock

View workflow job for this annotation

GitHub Actions / Test - nightly toolchain

Dependencies could be updated

Check warning on line 1 in Cargo-recent.lock

View workflow job for this annotation

GitHub Actions / Test - stable toolchain

Dependencies could be updated

Check warning on line 1 in Cargo-recent.lock

View workflow job for this annotation

GitHub Actions / Test - 1.48.0 toolchain

Dependencies could be updated
# It is not intended for manual editing.
version = 3

Expand All @@ -16,9 +16,9 @@

[[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"
Expand Down
4 changes: 2 additions & 2 deletions bitcoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
26 changes: 5 additions & 21 deletions bitcoin/src/address/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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),
}
Expand All @@ -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,
}
}
}
Expand All @@ -154,8 +138,8 @@ impl From<base58::Error> for ParseError {
fn from(e: base58::Error) -> Self { Self::Base58(e) }
}

impl From<bech32::Error> for ParseError {
fn from(e: bech32::Error) -> Self { Self::Bech32(e) }
impl From<bech32::primitives::decode::SegwitHrpstringError> for ParseError {
fn from(e: bech32::primitives::decode::SegwitHrpstringError) -> Self { Self::Bech32(e) }
}

impl From<witness_version::TryFromError> for ParseError {
Expand Down
71 changes: 21 additions & 50 deletions bitcoin/src/address/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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.
Expand Down Expand Up @@ -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 (`{:#}`).
Expand All @@ -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)
clarkmoody marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
Expand Down Expand Up @@ -495,13 +493,12 @@ impl<V: NetworkValidation> Address<V> {
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;

Expand Down Expand Up @@ -775,17 +772,6 @@ impl<V: NetworkValidation> fmt::Debug for Address<V> {
}
}

struct UpperWriter<W: fmt::Write>(W);

impl<W: fmt::Write> fmt::Write for UpperWriter<W> {
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
Expand All @@ -812,26 +798,11 @@ impl FromStr for Address<NetworkUnchecked> {
_ => 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<u8>) = {
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)));
}

Expand Down
20 changes: 8 additions & 12 deletions bitcoin/src/blockdata/script/witness_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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")
}
}

Expand All @@ -95,10 +93,10 @@ impl FromStr for WitnessVersion {
}
}

impl TryFrom<bech32::u5> for WitnessVersion {
impl TryFrom<bech32::Fe32> for WitnessVersion {
type Error = TryFromError;

fn try_from(value: bech32::u5) -> Result<Self, Self::Error> { Self::try_from(value.to_u8()) }
fn try_from(value: Fe32) -> Result<Self, Self::Error> { Self::try_from(value.to_u8()) }
}

impl TryFrom<u8> for WitnessVersion {
Expand Down Expand Up @@ -155,10 +153,8 @@ impl<'a> TryFrom<Instruction<'a>> for WitnessVersion {
}
}

impl From<WitnessVersion> for bech32::u5 {
fn from(version: WitnessVersion) -> Self {
bech32::u5::try_from_u8(version.to_num()).expect("WitnessVersion must be 0..=16")
}
impl From<WitnessVersion> for Fe32 {
fn from(version: WitnessVersion) -> Self { version.to_fe() }
}

impl From<WitnessVersion> for Opcode {
Expand Down