diff --git a/Cargo-latest.lock b/Cargo-latest.lock index 426e4104..2aee41ff 100644 --- a/Cargo-latest.lock +++ b/Cargo-latest.lock @@ -64,7 +64,7 @@ dependencies = [ "bitcoin-io", "bitcoin-units", "bitcoin_hashes", - "hex-conservative", + "hex-conservative 0.2.1", "hex_lit", "secp256k1", "serde", @@ -108,7 +108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.1", "serde", ] @@ -196,6 +196,7 @@ dependencies = [ "bincode", "bitcoin", "getrandom 0.2.16", + "hex-conservative 1.0.0", "rand", "rand_chacha", "secp256k1-zkp", @@ -274,6 +275,12 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee770c000993d17c185713463d5ebfbd1af9afae4c17cc295640104383bfbf0" + [[package]] name = "hex_lit" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 5b5f627f..e42baeec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ serde_json = { version = "1.0", optional = true } actual-serde = { package = "serde", version = "1.0.103", features = [ "derive", ], optional = true } +hex-conservative = "1.0.0" [target.wasm32-unknown-unknown.dev-dependencies] diff --git a/src/confidential.rs b/src/confidential.rs index 34f1a564..a6c0779a 100644 --- a/src/confidential.rs +++ b/src/confidential.rs @@ -720,6 +720,60 @@ impl<'de> Deserialize<'de> for Nonce { } } +/// Error decoding hexadecimal string into tweak-like value. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TweakHexDecodeError { + /// Invalid hexadecimal string. + InvalidHex(hex_conservative::DecodeFixedLengthBytesError), + /// Invalid tweak after decoding hexadecimal string. + InvalidTweak(secp256k1_zkp::Error), +} + +impl fmt::Display for TweakHexDecodeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TweakHexDecodeError::InvalidHex(err) => { + write!(f, "Invalid hex: {}", err) + } + TweakHexDecodeError::InvalidTweak(err) => { + write!(f, "Invalid tweak: {}", err) + } + } + } +} + +#[doc(hidden)] +impl From for TweakHexDecodeError { + fn from(err: hex_conservative::DecodeFixedLengthBytesError) -> Self { + TweakHexDecodeError::InvalidHex(err) + } +} + +#[doc(hidden)] +impl From for TweakHexDecodeError { + fn from(err: secp256k1_zkp::Error) -> Self { + TweakHexDecodeError::InvalidTweak(err) + } +} + +impl From for encode::Error { + fn from(value: TweakHexDecodeError) -> Self { + match value { + TweakHexDecodeError::InvalidHex(err) => encode::Error::HexFixedError(err), + TweakHexDecodeError::InvalidTweak(err) => encode::Error::Secp256k1zkp(err), + } + } +} + +impl std::error::Error for TweakHexDecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + TweakHexDecodeError::InvalidHex(err) => Some(err), + TweakHexDecodeError::InvalidTweak(err) => Some(err), + } + } +} + /// Blinding factor used for asset commitments. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub struct AssetBlindingFactor(pub(crate) Tweak); @@ -747,16 +801,13 @@ impl AssetBlindingFactor { } impl hex::FromHex for AssetBlindingFactor { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + - ExactSizeIterator + - DoubleEndedIterator - { - let slice = <[u8; 32]>::from_byte_iter(iter.rev())?; - // Incorrect Return Error - // See: https://github.com/rust-bitcoin/bitcoin_hashes/issues/124 - let inner = Tweak::from_inner(slice) - .map_err(|_e| hex::Error::InvalidChar(0))?; + type Err = TweakHexDecodeError; + + fn from_hex(s: &str) -> Result { + let mut slice: [u8; 32] = hex_conservative::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; Ok(AssetBlindingFactor(inner)) } } @@ -951,16 +1002,13 @@ impl Neg for ValueBlindingFactor { } impl hex::FromHex for ValueBlindingFactor { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + - ExactSizeIterator + - DoubleEndedIterator - { - let slice = <[u8; 32]>::from_byte_iter(iter.rev())?; - // Incorrect Return Error - // See: https://github.com/rust-bitcoin/bitcoin_hashes/issues/124 - let inner = Tweak::from_inner(slice) - .map_err(|_e| hex::Error::InvalidChar(0))?; + type Err = TweakHexDecodeError; + + fn from_hex(s: &str) -> Result { + let mut slice: [u8; 32] = hex_conservative::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; Ok(ValueBlindingFactor(inner)) } } diff --git a/src/encode.rs b/src/encode.rs index e8718c36..b1556335 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -19,6 +19,7 @@ use std::io::Cursor; use std::{any, error, fmt, io, mem}; use bitcoin::ScriptBuf; +use hex_conservative::{DecodeFixedLengthBytesError, DecodeVariableLengthBytesError}; use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak}; use crate::hashes::{sha256, Hash}; @@ -54,8 +55,10 @@ pub enum Error { Secp256k1zkp(secp256k1_zkp::Error), /// Pset related Errors PsetError(pset::Error), - /// Hex parsing errors - HexError(crate::hex::Error), + /// Hex fixed parsing errors + HexFixedError(DecodeFixedLengthBytesError), + /// Hex variable parsing errors + HexVariableError(DecodeVariableLengthBytesError), /// Got a time-based locktime when expecting a height-based one, or vice-versa BadLockTime(crate::LockTime), /// `VarInt` was encoded in a non-minimal way. @@ -83,7 +86,8 @@ impl fmt::Display for Error { Error::Secp256k1(ref e) => write!(f, "{}", e), Error::Secp256k1zkp(ref e) => write!(f, "{}", e), Error::PsetError(ref e) => write!(f, "Pset Error: {}", e), - Error::HexError(ref e) => write!(f, "Hex error {}", e), + Error::HexFixedError(ref e) => write!(f, "Hex fixed error: {}", e), + Error::HexVariableError(ref e) => write!(f, "Hex variable error: {}", e), Error::BadLockTime(ref lt) => write!(f, "Invalid locktime {}", lt), Error::NonMinimalVarInt => write!(f, "non-minimal varint"), } @@ -134,9 +138,16 @@ impl From for Error { } #[doc(hidden)] -impl From for Error { - fn from(e: crate::hex::Error) -> Self { - Error::HexError(e) +impl From for Error { + fn from(e: DecodeFixedLengthBytesError) -> Self { + Error::HexFixedError(e) + } +} + +#[doc(hidden)] +impl From for Error { + fn from(e: DecodeVariableLengthBytesError) -> Self { + Error::HexVariableError(e) } } diff --git a/src/hex.rs b/src/hex.rs index bbca409e..6384c7e3 100644 --- a/src/hex.rs +++ b/src/hex.rs @@ -23,26 +23,7 @@ use std::{fmt, io, str}; -/// Hex decoding error. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Error { - /// Non-hexadecimal character. - InvalidChar(u8), - /// Purported hex string had odd length. - OddLengthString(usize), - /// Tried to parse fixed-length hash from a string with the wrong type (expected, got). - InvalidLength(usize, usize), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::InvalidChar(ch) => write!(f, "invalid hex character {}", ch), - Error::OddLengthString(ell) => write!(f, "odd hex string length {}", ell), - Error::InvalidLength(ell, ell2) => write!(f, "bad hex string length {} (expected {})", ell2, ell), - } - } -} +use hex_conservative::{decode_to_vec, DecodeVariableLengthBytesError}; /// Trait for objects that can be serialized as hex strings. pub trait ToHex { @@ -52,15 +33,12 @@ pub trait ToHex { /// Trait for objects that can be deserialized from hex strings. pub trait FromHex: Sized { - /// Produces an object from a byte iterator. - fn from_byte_iter(iter: I) -> Result - where - I: Iterator> + ExactSizeIterator + DoubleEndedIterator; + /// Error returned by [`FromHex::from_hex`], may differ depending + /// on whether `Self` is fixed size of variable length. + type Err; /// Produces an object from a hex string. - fn from_hex(s: &str) -> Result { - Self::from_byte_iter(HexIterator::new(s)?) - } + fn from_hex(s: &str) -> Result; } impl ToHex for T { @@ -70,81 +48,6 @@ impl ToHex for T { } } -/// Iterator over a hex-encoded string slice which decodes hex and yields bytes. -pub struct HexIterator<'a> { - /// The `Bytes` iterator whose next two bytes will be decoded to yield - /// the next byte. - iter: str::Bytes<'a>, -} - -impl<'a> HexIterator<'a> { - /// Constructs a new `HexIterator` from a string slice. - /// - /// # Errors - /// - /// If the input string is of odd length. - pub fn new(s: &'a str) -> Result, Error> { - if s.len() % 2 != 0 { - Err(Error::OddLengthString(s.len())) - } else { - Ok(HexIterator { iter: s.bytes() }) - } - } -} - -fn chars_to_hex(hi: u8, lo: u8) -> Result { - let hih = (hi as char) - .to_digit(16) - .ok_or(Error::InvalidChar(hi))?; - let loh = (lo as char) - .to_digit(16) - .ok_or(Error::InvalidChar(lo))?; - - let ret = (hih << 4) + loh; - Ok(ret as u8) -} - -impl Iterator for HexIterator<'_> { - type Item = Result; - - fn next(&mut self) -> Option> { - let hi = self.iter.next()?; - let lo = self.iter.next().unwrap(); - Some(chars_to_hex(hi, lo)) - } - - fn size_hint(&self) -> (usize, Option) { - let (min, max) = self.iter.size_hint(); - (min / 2, max.map(|x| x / 2)) - } -} - -impl io::Read for HexIterator<'_> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let mut bytes_read = 0usize; - for dst in buf { - match self.next() { - Some(Ok(src)) => { - *dst = src; - bytes_read += 1; - }, - _ => break, - } - } - Ok(bytes_read) - } -} - -impl DoubleEndedIterator for HexIterator<'_> { - fn next_back(&mut self) -> Option> { - let lo = self.iter.next_back()?; - let hi = self.iter.next_back().unwrap(); - Some(chars_to_hex(hi, lo)) - } -} - -impl ExactSizeIterator for HexIterator<'_> {} - /// Outputs hex into an object implementing `fmt::Write`. /// /// This is usually more efficient than going through a `String` using [`ToHex`]. @@ -232,30 +135,20 @@ impl io::Write for HexWriter { } impl FromHex for Vec { - fn from_byte_iter(iter: I) -> Result - where - I: Iterator> + ExactSizeIterator + DoubleEndedIterator, - { - iter.collect() + type Err = DecodeVariableLengthBytesError; + + fn from_hex(s: &str) -> Result { + decode_to_vec(s) } } macro_rules! impl_fromhex_array { ($len:expr) => { impl FromHex for [u8; $len] { - fn from_byte_iter(iter: I) -> Result - where - I: Iterator> + ExactSizeIterator + DoubleEndedIterator, - { - if iter.len() == $len { - let mut ret = [0; $len]; - for (n, byte) in iter.enumerate() { - ret[n] = byte?; - } - Ok(ret) - } else { - Err(Error::InvalidLength(2 * $len, 2 * iter.len())) - } + type Err = hex_conservative::DecodeFixedLengthBytesError; + + fn from_hex(s: &str) -> Result { + hex_conservative::decode_to_array(s) } } } @@ -283,6 +176,8 @@ impl_fromhex_array!(512); #[cfg(test)] mod tests { + use hex_conservative::DecodeFixedLengthBytesError; + use super::*; use core::fmt; @@ -382,29 +277,38 @@ mod tests { let badchar2 = "012Y456789abcdeb"; let badchar3 = "«23456789abcdef"; - assert_eq!( - Vec::::from_hex(oddlen), - Err(Error::OddLengthString(17)) + let result = Vec::::from_hex(oddlen); + assert!( + matches!(&result, Err(DecodeVariableLengthBytesError::OddLengthString(err)) + if err.length() == 17), + "Got: {:?}", result ); - assert_eq!( - <[u8; 4]>::from_hex(oddlen), - Err(Error::OddLengthString(17)) + let result = <[u8; 4]>::from_hex(oddlen); + assert!( + matches!(&result, Err(DecodeFixedLengthBytesError::InvalidLength(err)) + if err.invalid_length() == 17), + "Got: {:?}", result ); - assert_eq!( - <[u8; 8]>::from_hex(oddlen), - Err(Error::OddLengthString(17)) + let result = <[u8; 8]>::from_hex(oddlen); + assert!( + matches!(&result, Err(DecodeFixedLengthBytesError::InvalidLength(err)) + if err.invalid_length() == 17), + "Got: {:?}", result ); - assert_eq!( - Vec::::from_hex(badchar1), - Err(Error::InvalidChar(b'Z')) + let result = Vec::::from_hex(badchar1); + assert!( + matches!(&result, Err(DecodeVariableLengthBytesError::InvalidChar(_))), + "Got: {:?}", result ); - assert_eq!( - Vec::::from_hex(badchar2), - Err(Error::InvalidChar(b'Y')) + let result = Vec::::from_hex(badchar2); + assert!( + matches!(&result, Err(DecodeVariableLengthBytesError::InvalidChar(_))), + "Got: {:?}", result ); - assert_eq!( - Vec::::from_hex(badchar3), - Err(Error::InvalidChar(194)) + let result = Vec::::from_hex(badchar3); + assert!( + matches!(&result, Err(DecodeVariableLengthBytesError::InvalidChar(_))), + "Got: {:?}", result ); } @@ -417,4 +321,3 @@ mod tests { assert_eq!(vec.to_hex(), writer.result()); } } - diff --git a/src/script.rs b/src/script.rs index 81adcba1..a1424ed3 100644 --- a/src/script.rs +++ b/src/script.rs @@ -27,6 +27,7 @@ use std::default::Default; use std::{fmt, io, ops, str}; +use hex_conservative::DecodeVariableLengthBytesError; use secp256k1_zkp::{Verification, Secp256k1}; #[cfg(feature = "serde")] use serde; @@ -78,17 +79,17 @@ impl fmt::UpperHex for Script { } impl hex::FromHex for Script { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + - ExactSizeIterator + - DoubleEndedIterator, - { - Vec::from_byte_iter(iter).map(|v| Script(Box::<[u8]>::from(v))) + type Err = DecodeVariableLengthBytesError; + + fn from_hex(s: &str) -> Result { + hex_conservative::decode_to_vec(s).map(|v| Script(Box::<[u8]>::from(v))) } } + impl str::FromStr for Script { - type Err = hex::Error; - fn from_str(s: &str) -> Result { + type Err = ::Err; + + fn from_str(s: &str) -> Result { hex::FromHex::from_hex(s) } } diff --git a/src/serde_utils.rs b/src/serde_utils.rs index 0c9dd806..2924edf8 100644 --- a/src/serde_utils.rs +++ b/src/serde_utils.rs @@ -238,11 +238,16 @@ pub mod hex_bytes { } pub fn deserialize<'de, D, B>(d: D) -> Result - where D: serde::Deserializer<'de>, B: serde::Deserialize<'de> + FromHex, + where D: serde::Deserializer<'de>, + B: serde::Deserialize<'de> + FromHex, + ::Err: std::fmt::Display, { struct Visitor(::std::marker::PhantomData); - impl serde::de::Visitor<'_> for Visitor { + impl serde::de::Visitor<'_> for Visitor + where + ::Err: std::fmt::Display, + { type Value = B; fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {