Merge rust-bitcoin/rust-bitcoin#2473: Upgrade to `hex v0.2.0`

f337dec2b1 hashes: Remove unnecessary feature guard from test (Tobin C. Harding)
0cea90d505 Test hashes honour Formatter::precision (Tobin C. Harding)
4bfb466bb9 Upgrade hex dependency (Tobin C. Harding)
f0558e8eb9 Use fmt_hex_exact (Tobin C. Harding)
6820f51408 hashes: Add fmt roundtrip tests (Tobin C. Harding)
e302e30e7c Import with super::* in unit test (Tobin C. Harding)

Pull request description:

  Upgrade to use the newly released `hex` code.

  - Patch 1: Does trivial preparatory cleanup
  - Patch 2: Adds some unit tests to check we roundtrip hashes correctly (added because in the test PR I had the `Midstate` iml wrong and it was not being caught).
  - Patch 3: Uses macro in place of `forward_hex` and `backward_hex` - needs concept review, I hacked this without understanding why the functions existed in the first place.
  - Patch 4: Does the upgrade, I've attempted to make minimal changes, so there is room for a bunch of cleanups if/when this merges.
  - Patch 5: Adds a unit test to verify that we can close #2494
  - Patch 6: Removes unnecessary feature gate from unit test.

ACKs for top commit:
  Kixunil:
    ACK f337dec2b1
  apoelstra:
    ACK f337dec2b1

Tree-SHA512: 7913d1b3079cf5ba1b0e70f5c33e091c5ef1258026c8f27bbe8a050100bbc7622b6555d560b15be3b3d90d47ce873f137a73cf2d772108d2915fb30ed129bded
This commit is contained in:
Andrew Poelstra 2024-03-11 20:49:11 +00:00
commit a124ff41c4
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
16 changed files with 125 additions and 106 deletions

View File

@ -8,6 +8,12 @@ version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "base58check"
version = "0.1.0"
@ -159,9 +165,12 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]]
name = "hex-conservative"
version = "0.1.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986"
dependencies = [
"arrayvec",
]
[[package]]
name = "hex_lit"

View File

@ -8,6 +8,12 @@ version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "base58check"
version = "0.1.0"
@ -158,9 +164,12 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]]
name = "hex-conservative"
version = "0.1.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986"
dependencies = [
"arrayvec",
]
[[package]]
name = "hex_lit"

View File

@ -25,4 +25,4 @@ hashes = { package = "bitcoin_hashes", version = "0.13.0", default-features = fa
internals = { package = "bitcoin-internals", version = "0.2.0" }
[dev-dependencies]
hex = { package = "hex-conservative", version = "0.1.1", default-features = false, features = ["alloc"] }
hex = { package = "hex-conservative", version = "0.2.0", default-features = false, features = ["alloc"] }

View File

@ -31,7 +31,7 @@ rustdoc-args = ["--cfg", "docsrs"]
base58 = { package = "base58check", version = "0.1.0", default-features = false }
bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] }
hashes = { package = "bitcoin_hashes", version = "0.13.0", default-features = false, features = ["alloc", "io"] }
hex = { package = "hex-conservative", version = "0.1.1", default-features = false, features = ["alloc"] }
hex = { package = "hex-conservative", version = "0.2.0", default-features = false, features = ["alloc"] }
hex_lit = "0.1.1"
internals = { package = "bitcoin-internals", version = "0.2.0" }
io = { package = "bitcoin-io", version = "0.1.1", default-features = false, features = ["alloc"] }

View File

@ -1960,6 +1960,10 @@ mod tests {
format!("{:x}", tx.compute_txid()),
"9652aa62b0e748caeec40c4cb7bc17c6792435cc3dfe447dd1ca24f912a1c6ec"
);
assert_eq!(
format!("{:.10x}", tx.compute_txid()),
"9652aa62b0"
);
assert_eq!(tx.weight(), Weight::from_wu(2718));
// non-segwit tx from my mempool

View File

@ -506,18 +506,19 @@ impl<'de> serde::Deserialize<'de> for Witness {
while let Some(elem) = a.next_element::<String>()? {
let vec = Vec::<u8>::from_hex(&elem).map_err(|e| match e {
InvalidChar(b) => match core::char::from_u32(b.into()) {
InvalidChar(ref e) => match core::char::from_u32(e.invalid_char(
).into()) {
Some(c) => de::Error::invalid_value(
Unexpected::Char(c),
&"a valid hex character",
),
None => de::Error::invalid_value(
Unexpected::Unsigned(b.into()),
Unexpected::Unsigned(e.invalid_char().into()),
&"a valid hex character",
),
},
OddLengthString(len) =>
de::Error::invalid_length(len, &"an even length string"),
OddLengthString(ref e) =>
de::Error::invalid_length(e.length(), &"an even length string"),
})?;
ret.push(vec);
}

View File

@ -71,11 +71,11 @@ pub mod hex {
/// Hex byte encoder.
// We wrap `BufEncoder` to not leak internal representation.
pub struct Encoder<C: Case>(BufEncoder<[u8; HEX_BUF_SIZE]>, PhantomData<C>);
pub struct Encoder<C: Case>(BufEncoder<{ HEX_BUF_SIZE }>, PhantomData<C>);
impl<C: Case> From<super::Hex<C>> for Encoder<C> {
fn from(_: super::Hex<C>) -> Self {
Encoder(BufEncoder::new([0; HEX_BUF_SIZE]), Default::default())
Encoder(BufEncoder::new(), Default::default())
}
}
@ -100,15 +100,15 @@ pub mod hex {
// Newtypes to hide internal details.
/// Error returned when a hex string decoder can't be created.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeInitError(hex::HexToBytesError);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodeInitError(hex::OddLengthStringError);
/// Error returned when a hex string contains invalid characters.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeError(hex::HexToBytesError);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodeError(hex::InvalidCharError);
/// Hex decoder state.
pub struct Decoder<'a>(hex::HexToBytesIter<'a>);
pub struct Decoder<'a>(hex::HexSliceToBytesIter<'a>);
impl<'a> Decoder<'a> {
fn new(s: &'a str) -> Result<Self, DecodeInitError> {
@ -137,29 +137,19 @@ pub mod hex {
impl super::IntoDeError for DecodeInitError {
fn into_de_error<E: serde::de::Error>(self) -> E {
use hex::HexToBytesError;
match self.0 {
HexToBytesError::OddLengthString(len) =>
E::invalid_length(len, &"an even number of ASCII-encoded hex digits"),
error => panic!("unexpected error: {:?}", error),
}
E::invalid_length(self.0.length(), &"an even number of ASCII-encoded hex digits")
}
}
impl super::IntoDeError for DecodeError {
fn into_de_error<E: serde::de::Error>(self) -> E {
use hex::HexToBytesError;
use serde::de::Unexpected;
const EXPECTED_CHAR: &str = "an ASCII-encoded hex digit";
match self.0 {
HexToBytesError::InvalidChar(c) if c.is_ascii() =>
E::invalid_value(Unexpected::Char(c as _), &EXPECTED_CHAR),
HexToBytesError::InvalidChar(c) =>
E::invalid_value(Unexpected::Unsigned(c.into()), &EXPECTED_CHAR),
error => panic!("unexpected error: {:?}", error),
match self.0.invalid_char() {
c if c.is_ascii() => E::invalid_value(Unexpected::Char(c as _), &EXPECTED_CHAR),
c => E::invalid_value(Unexpected::Unsigned(c.into()), &EXPECTED_CHAR),
}
}
}

View File

@ -10,7 +10,7 @@ use core::ops;
use core::str::FromStr;
use hashes::{hash160, Hash};
use hex::{FromHex, HexToArrayError, HexToBytesError};
use hex::{FromHex, HexToArrayError};
use internals::array_vec::ArrayVec;
use internals::write_err;
use io::{Read, Write};
@ -233,22 +233,22 @@ impl fmt::Display for PublicKey {
impl FromStr for PublicKey {
type Err = ParsePublicKeyError;
fn from_str(s: &str) -> Result<PublicKey, ParsePublicKeyError> {
use HexToArrayError::*;
match s.len() {
66 => {
PublicKey::from_slice(&<[u8; 33]>::from_hex(s).map_err(|op| {
match op {
HexToArrayError::Conversion(HexToBytesError::InvalidChar(char)) => ParsePublicKeyError::InvalidChar(char),
HexToArrayError::Conversion(HexToBytesError::OddLengthString(_)) | HexToArrayError::InvalidLength(_,_) => unreachable!("invalid length"),
}
})?).map_err(From::from)
let bytes = <[u8; 33]>::from_hex(s).map_err(|e| match e {
InvalidChar(e) => ParsePublicKeyError::InvalidChar(e.invalid_char()),
InvalidLength(_) => unreachable!("length checked already")
})?;
Ok(PublicKey::from_slice(&bytes).expect("length checked already"))
},
130 => {
PublicKey::from_slice(&<[u8; 65]>::from_hex(s).map_err(|op| {
match op {
HexToArrayError::Conversion(HexToBytesError::InvalidChar(char)) => ParsePublicKeyError::InvalidChar(char),
HexToArrayError::Conversion(HexToBytesError::OddLengthString(_)) | HexToArrayError::InvalidLength(_,_) => unreachable!("invalid length"),
}
})?).map_err(From::from)
let bytes = <[u8; 65]>::from_hex(s).map_err(|e| match e {
InvalidChar(e) => ParsePublicKeyError::InvalidChar(e.invalid_char()),
InvalidLength(_) => unreachable!("length checked already")
})?;
Ok(PublicKey::from_slice(&bytes).expect("length checked already"))
}
len => Err(ParsePublicKeyError::InvalidHexLength(len)),
}

View File

@ -1035,7 +1035,7 @@ mod tests {
#[track_caller]
pub fn hex_psbt(s: &str) -> Result<Psbt, crate::psbt::error::Error> {
let r: Result<Vec<u8>, hex::HexToBytesError> = Vec::from_hex(s);
let r = Vec::from_hex(s);
match r {
Err(_e) => panic!("unable to parse hex string {}", s),
Ok(v) => Psbt::deserialize(&v),

View File

@ -28,10 +28,7 @@ impl fmt::Debug for SerializedSignature {
impl fmt::Display for SerializedSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buf = [0u8; MAX_LEN * 2];
let mut encoder = hex::buf_encoder::BufEncoder::new(&mut buf);
encoder.put_bytes(self, hex::Case::Lower);
f.pad_integral(true, "0x", encoder.as_str())
hex::fmt_hex_exact!(f, MAX_LEN, self, hex::Case::Lower)
}
}

View File

@ -27,7 +27,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
hex = { package = "hex-conservative", version = "0.1.1", default-features = false }
hex = { package = "hex-conservative", version = "0.2.0", default-features = false }
bitcoin-io = { version = "0.1.1", default-features = false, optional = true }
schemars = { version = "0.8.3", default-features = false, optional = true }

View File

@ -71,22 +71,6 @@ pub(crate) use arr_newtype_fmt_impl;
macro_rules! hash_trait_impls {
($bits:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => {
impl<$($gen: $gent),*> Hash<$($gen),*> {
/// Displays hex forwards, regardless of how this type would display it naturally.
///
/// This is mainly intended as an internal method and you shouldn't need it unless
/// you're doing something special.
pub fn forward_hex(&self) -> impl '_ + core::fmt::LowerHex + core::fmt::UpperHex {
$crate::hex::DisplayHex::as_hex(&self.0)
}
/// Displays hex backwards, regardless of how this type would display it naturally.
///
/// This is mainly intended as an internal method and you shouldn't need it unless
/// you're doing something special.
pub fn backward_hex(&self) -> impl '_ + core::fmt::LowerHex + core::fmt::UpperHex {
$crate::hex::display::DisplayArray::<_, [u8; $bits / 8 * 2]>::new(self.0.iter().rev())
}
/// Zero cost conversion between a fixed length byte array shared reference and
/// a shared reference to this Hash type.
pub fn from_bytes_ref(bytes: &[u8; $bits / 8]) -> &Self {
@ -105,15 +89,13 @@ macro_rules! hash_trait_impls {
impl<$($gen: $gent),*> str::FromStr for Hash<$($gen),*> {
type Err = $crate::hex::HexToArrayError;
fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
use $crate::hex::{FromHex, HexToBytesIter};
use $crate::Hash;
use $crate::{Hash, hex::{FromHex}};
let inner: [u8; $bits / 8] = if $reverse {
FromHex::from_byte_iter(HexToBytesIter::new(s)?.rev())?
} else {
FromHex::from_byte_iter(HexToBytesIter::new(s)?)?
};
Ok(Self::from_byte_array(inner))
let mut bytes = <[u8; $bits / 8]>::from_hex(s)?;
if $reverse {
bytes.reverse();
}
Ok(Self::from_byte_array(bytes))
}
}

View File

@ -273,4 +273,12 @@ mod tests {
let h2: TestNewtype = h.to_string().parse().unwrap();
assert_eq!(h2.to_raw_hash(), h);
}
#[test]
fn newtype_fmt_roundtrip() {
let orig = TestNewtype::hash(&[]);
let hex = format!("{}", orig);
let rinsed = hex.parse::<TestNewtype>().expect("failed to parse hex");
assert_eq!(rinsed, orig)
}
}

View File

@ -176,15 +176,13 @@ impl Midstate {
}
impl hex::FromHex for Midstate {
type Err = hex::HexToArrayError;
fn from_byte_iter<I>(iter: I) -> Result<Self, Self::Err>
where
I: Iterator<Item = Result<u8, hex::HexToBytesError>>
+ ExactSizeIterator
+ DoubleEndedIterator,
{
type Error = hex::HexToArrayError;
fn from_hex(s: &str) -> Result<Self, Self::Error> {
// DISPLAY_BACKWARD is true
Ok(Midstate::from_byte_array(hex::FromHex::from_byte_iter(iter.rev())?))
let mut bytes = <[u8; 32]>::from_hex(s)?;
bytes.reverse();
Ok(Midstate(bytes))
}
}
@ -817,7 +815,8 @@ impl HashEngine {
#[cfg(test)]
mod tests {
use crate::{sha256, Hash, HashEngine};
use crate::{sha256, Hash as _, HashEngine};
use super::*;
#[test]
#[cfg(feature = "alloc")]
@ -882,6 +881,14 @@ mod tests {
}
}
#[test]
fn fmt_roundtrips() {
let hash = sha256::Hash::hash(b"some arbitrary bytes");
let hex = format!("{}", hash);
let rinsed = hex.parse::<sha256::Hash>().expect("failed to parse hex");
assert_eq!(rinsed, hash)
}
#[test]
#[rustfmt::skip]
fn midstate() {
@ -964,14 +971,14 @@ mod tests {
#[test]
fn const_hash() {
assert_eq!(super::Hash::hash(&[]), super::Hash::const_hash(&[]));
assert_eq!(Hash::hash(&[]), Hash::const_hash(&[]));
let mut bytes = Vec::new();
for i in 0..256 {
bytes.push(i as u8);
assert_eq!(
super::Hash::hash(&bytes),
super::Hash::const_hash(&bytes),
Hash::hash(&bytes),
Hash::const_hash(&bytes),
"hashes don't match for length {}",
i + 1
);
@ -980,8 +987,6 @@ mod tests {
#[test]
fn const_midstate() {
use super::Midstate;
assert_eq!(
Midstate::hash_tag(b"TapLeaf"),
Midstate([
@ -991,6 +996,14 @@ mod tests {
)
}
#[test]
fn midstate_fmt_roundtrip() {
let midstate = Midstate::hash_tag(b"ArbitraryTag");
let hex = format!("{}", midstate);
let rinsed = hex.parse::<Midstate>().expect("failed to parse hex");
assert_eq!(rinsed, midstate)
}
#[cfg(feature = "serde")]
#[test]
fn sha256_serde() {

View File

@ -30,10 +30,12 @@ fn from_engine(e: sha256::HashEngine) -> Hash {
#[cfg(test)]
mod tests {
use crate::{sha256d, Hash as _};
#[test]
#[cfg(feature = "alloc")]
fn test() {
use crate::{sha256, sha256d, Hash, HashEngine};
use crate::{sha256, HashEngine};
#[derive(Clone)]
struct Test {
@ -81,13 +83,19 @@ mod tests {
}
}
#[test]
fn fmt_roundtrips() {
let hash = sha256d::Hash::hash(b"some arbitrary bytes");
let hex = format!("{}", hash);
let rinsed = hex.parse::<sha256d::Hash>().expect("failed to parse hex");
assert_eq!(rinsed, hash)
}
#[cfg(feature = "serde")]
#[test]
fn sha256_serde() {
use serde_test::{assert_tokens, Configure, Token};
use crate::{sha256d, Hash};
#[rustfmt::skip]
static HASH_BYTES: [u8; 32] = [
0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7,

View File

@ -3,17 +3,17 @@
#[macro_export]
/// Adds hexadecimal formatting implementation of a trait `$imp` to a given type `$ty`.
macro_rules! hex_fmt_impl(
($reverse:expr, $ty:ident) => (
$crate::hex_fmt_impl!($reverse, $ty, );
($reverse:expr, $len:expr, $ty:ident) => (
$crate::hex_fmt_impl!($reverse, $len, $ty, );
);
($reverse:expr, $ty:ident, $($gen:ident: $gent:ident),*) => (
($reverse:expr, $len:expr, $ty:ident, $($gen:ident: $gent:ident),*) => (
impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
#[inline]
fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
if $reverse {
$crate::_export::_core::fmt::LowerHex::fmt(&self.0.backward_hex(), f)
$crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self).iter().rev(), $crate::hex::Case::Lower)
} else {
$crate::_export::_core::fmt::LowerHex::fmt(&self.0.forward_hex(), f)
$crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self), $crate::hex::Case::Lower)
}
}
}
@ -22,9 +22,9 @@ macro_rules! hex_fmt_impl(
#[inline]
fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
if $reverse {
$crate::_export::_core::fmt::UpperHex::fmt(&self.0.backward_hex(), f)
$crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self).iter().rev(), $crate::hex::Case::Upper)
} else {
$crate::_export::_core::fmt::UpperHex::fmt(&self.0.forward_hex(), f)
$crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self), $crate::hex::Case::Upper)
}
}
}
@ -188,7 +188,7 @@ macro_rules! hash_newtype {
$({ $($type_attrs)* })*
}
$crate::hex_fmt_impl!(<$newtype as $crate::Hash>::DISPLAY_BACKWARD, $newtype);
$crate::hex_fmt_impl!(<$newtype as $crate::Hash>::DISPLAY_BACKWARD, <$newtype as $crate::Hash>::LEN, $newtype);
$crate::serde_impl!($newtype, <$newtype as $crate::Hash>::LEN);
$crate::borrow_slice_impl!($newtype);
@ -270,15 +270,13 @@ macro_rules! hash_newtype {
impl $crate::_export::_core::str::FromStr for $newtype {
type Err = $crate::hex::HexToArrayError;
fn from_str(s: &str) -> $crate::_export::_core::result::Result<$newtype, Self::Err> {
use $crate::hex::{FromHex, HexToBytesIter};
use $crate::Hash;
use $crate::{Hash, hex::FromHex};
let inner: <$hash as Hash>::Bytes = if <Self as $crate::Hash>::DISPLAY_BACKWARD {
FromHex::from_byte_iter(HexToBytesIter::new(s)?.rev())?
} else {
FromHex::from_byte_iter(HexToBytesIter::new(s)?)?
let mut bytes = <[u8; <Self as $crate::Hash>::LEN]>::from_hex(s)?;
if <Self as $crate::Hash>::DISPLAY_BACKWARD {
bytes.reverse();
};
Ok($newtype(<$hash>::from_byte_array(inner)))
Ok($newtype(<$hash>::from_byte_array(bytes)))
}
}