diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs index 4fac62f3..cfb3f342 100644 --- a/bitcoin/src/pow.rs +++ b/bitcoin/src/pow.rs @@ -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 { + Ok($ty(U256::from_hex(s)?)) + } + + /// Creates `Self` from an unprefixed hex string. + pub fn from_unprefixed_hex(s: &str) -> Result { + 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 { + 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 { + 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 { + 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() { diff --git a/units/src/parse.rs b/units/src/parse.rs index 787bbdeb..967113f9 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -88,6 +88,32 @@ pub fn int + Into>(s: S) -> Result + Into>(s: S) -> Result { + 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 + Into>(s: S) -> Result { + 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 + Into>(s: S) -> Result { - 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); + } +}