Merge rust-bitcoin/rust-bitcoin#2514: Add hex parsing to `pow` types

f019e24f1f Add hex parsing to pow types (Tobin C. Harding)
d33625f6e2 units: Introduce public hex_u128 function (Tobin C. Harding)
9705d51782 docs: Use backticks on stdlib type (Tobin C. Harding)
cf65bf035f Introduce local variable (Tobin C. Harding)
1269722770 Move helper function (Tobin C. Harding)
dca054c680 test: Add unit tests for hex_u32 (Tobin C. Harding)

Pull request description:

  The `pow` types implement `fmt::LowerHex` but do not implement hex parsing.

  Add inherent methods `from_hex` and `from_prefixed_hex` to the  `pow` types - as we did for locktime types.

ACKs for top commit:
  apoelstra:
    ACK f019e24f1f
  sanket1729:
    ACK f019e24f1f

Tree-SHA512: d682e1259db1c2c0abe24a8ca137fc49abe0ed7ccce90de4d6058c7a4986d26c86a84289ec7377b9209648a57e0af6735c1eb3d39c9de6770fde1936f596dfd2
This commit is contained in:
Andrew Poelstra 2024-04-02 21:36:23 +00:00
commit 69716f17b9
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 129 additions and 13 deletions

View File

@ -18,12 +18,22 @@ use units::parse;
use crate::blockdata::block::BlockHash;
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::consensus::Params;
use crate::error::{ContainsPrefixError, MissingPrefixError, PrefixedHexError, UnprefixedHexError};
use crate::error::{ContainsPrefixError, MissingPrefixError, ParseIntError, PrefixedHexError, UnprefixedHexError};
/// Implement traits and methods shared by `Target` and `Work`.
macro_rules! do_impl {
($ty:ident) => {
impl $ty {
/// Creates `Self` from a prefixed hex string.
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
Ok($ty(U256::from_hex(s)?))
}
/// Creates `Self` from an unprefixed hex string.
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
Ok($ty(U256::from_unprefixed_hex(s)?))
}
/// Creates `Self` from a big-endian byte array.
#[inline]
pub fn from_be_bytes(bytes: [u8; 32]) -> $ty { $ty(U256::from_be_bytes(bytes)) }
@ -390,6 +400,43 @@ impl U256 {
const ONE: U256 = U256(0, 1);
/// Creates a `U256` from an prefixed hex string.
fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let stripped = if let Some(stripped) = s.strip_prefix("0x") {
stripped
} else if let Some(stripped) = s.strip_prefix("0X") {
stripped
} else {
return Err(MissingPrefixError::new(s).into());
};
Ok(U256::from_hex_internal(stripped)?)
}
/// Creates a `CompactTarget` from an unprefixed hex string.
fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
if s.starts_with("0x") || s.starts_with("0X") {
return Err(ContainsPrefixError::new(s).into());
}
Ok(U256::from_hex_internal(s)?)
}
fn from_hex_internal(s: &str) -> Result<Self, ParseIntError> {
let (high, low) = if s.len() < 32 {
let low = parse::hex_u128(s)?;
(0, low)
} else {
let high_len = s.len() - 32;
let high_s = &s[..high_len];
let low_s = &s[high_len..];
let high = parse::hex_u128(high_s)?;
let low = parse::hex_u128(low_s)?;
(high, low)
};
Ok(U256(high, low))
}
/// Creates [`U256`] from a big-endian array of `u8`s.
#[cfg_attr(all(test, mutate), mutate)]
fn from_be_bytes(a: [u8; 32]) -> U256 {
@ -1507,6 +1554,28 @@ mod tests {
);
}
#[test]
fn u256_to_from_hex_roundtrips() {
let val = U256(
0xDEAD_BEEA_A69B_455C_D41B_B662_A69B_4550,
0xA69B_455C_D41B_B662_A69B_4555_DEAD_BEEF,
);
let hex = format!("0x{:x}", val);
let got = U256::from_hex(&hex).expect("failed to parse hex");
assert_eq!(got, val);
}
#[test]
fn u256_to_from_unprefixed_hex_roundtrips() {
let val = U256(
0xDEAD_BEEA_A69B_455C_D41B_B662_A69B_4550,
0xA69B_455C_D41B_B662_A69B_4555_DEAD_BEEF,
);
let hex = format!("{:x}", val);
let got = U256::from_unprefixed_hex(&hex).expect("failed to parse hex");
assert_eq!(got, val);
}
#[cfg(feature = "serde")]
#[test]
fn u256_serde() {

View File

@ -88,6 +88,32 @@ pub fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, ParseInt
})
}
/// Parses a `u32` from a hex string.
///
/// Input string may or may not contain a `0x` prefix.
pub fn hex_u32<S: AsRef<str> + Into<String>>(s: S) -> Result<u32, ParseIntError> {
let stripped = strip_hex_prefix(s.as_ref());
u32::from_str_radix(stripped, 16).map_err(|error| ParseIntError {
input: s.into(),
bits: 32,
is_signed: false,
source: error,
})
}
/// Parses a `u128` from a hex string.
///
/// Input string may or may not contain a `0x` prefix.
pub fn hex_u128<S: AsRef<str> + Into<String>>(s: S) -> Result<u128, ParseIntError> {
let stripped = strip_hex_prefix(s.as_ref());
u128::from_str_radix(stripped, 16).map_err(|error| ParseIntError {
input: s.into(),
bits: 128,
is_signed: false,
source: error,
})
}
/// Strips the hex prefix off `s` if one is present.
pub(crate) fn strip_hex_prefix(s: &str) -> &str {
if let Some(stripped) = s.strip_prefix("0x") {
@ -99,18 +125,6 @@ pub(crate) fn strip_hex_prefix(s: &str) -> &str {
}
}
/// Parses a u32 from a hex string.
///
/// Input string may or may not contain a `0x` prefix.
pub fn hex_u32<S: AsRef<str> + Into<String>>(s: S) -> Result<u32, ParseIntError> {
u32::from_str_radix(strip_hex_prefix(s.as_ref()), 16).map_err(|error| ParseIntError {
input: s.into(),
bits: 32,
is_signed: false,
source: error,
})
}
/// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using infallible
/// conversion function `fn`.
#[macro_export]
@ -182,3 +196,36 @@ macro_rules! impl_parse_str {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_u32_from_hex_prefixed() {
let want = 171;
let got = hex_u32("0xab").expect("failed to parse prefixed hex");
assert_eq!(got, want);
}
#[test]
fn parse_u32_from_hex_no_prefix() {
let want = 171;
let got = hex_u32("ab").expect("failed to parse non-prefixed hex");
assert_eq!(got, want);
}
#[test]
fn parse_u128_from_hex_prefixed() {
let want = 3735928559;
let got = hex_u128("0xdeadbeef").expect("failed to parse prefixed hex");
assert_eq!(got, want);
}
#[test]
fn parse_u128_from_hex_no_prefix() {
let want = 3735928559;
let got = hex_u128("deadbeef").expect("failed to parse non-prefixed hex");
assert_eq!(got, want);
}
}