diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 50b8a0d82..cf06c57ae 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -253,8 +253,10 @@ jobs: run: cd bitcoin/embedded && cargo run --target thumbv7m-none-eabi - name: "Run hashes/embedded no alloc" run: cd hashes/embedded && cargo run --target thumbv7m-none-eabi - - name: "Run hashes/embedded with alloc" + - name: "Run hashes/embedded with alloc and no hex" run: cd hashes/embedded && cargo run --target thumbv7m-none-eabi --features=alloc + - name: "Run hashes/embedded with alloc and hex" + run: cd hashes/embedded && cargo run --target thumbv7m-none-eabi --features=alloc,hex ASAN: # hashes crate only. name: ASAN - nightly toolchain diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index ff078247a..d96e8af50 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -27,7 +27,7 @@ arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"] [dependencies] base58 = { package = "base58ck", version = "0.1.0", default-features = false, features = ["alloc"] } bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] } -hashes = { package = "bitcoin_hashes", version = "0.15.0", default-features = false, features = ["alloc", "bitcoin-io"] } +hashes = { package = "bitcoin_hashes", version = "0.15.0", default-features = false, features = ["alloc", "bitcoin-io", "hex"] } hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] } internals = { package = "bitcoin-internals", version = "0.4.0", features = ["alloc"] } io = { package = "bitcoin-io", version = "0.2.0", default-features = false, features = ["alloc"] } diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml index 1553f7882..c05e31880 100644 --- a/hashes/Cargo.toml +++ b/hashes/Cargo.toml @@ -17,12 +17,13 @@ exclude = ["tests", "contrib"] default = ["std"] std = ["alloc", "bitcoin-io?/std", "hex/std"] alloc = ["bitcoin-io?/alloc", "hex/alloc"] +serde = ["dep:serde", "hex"] # Smaller (but slower) implementation of sha256, sha512 and ripemd160 small-hash = [] [dependencies] -hex = { package = "hex-conservative", version = "0.3.0", default-features = false } +hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true } bitcoin-io = { version = "0.2.0", default-features = false, optional = true } serde = { version = "1.0", default-features = false, optional = true } diff --git a/hashes/embedded/Cargo.toml b/hashes/embedded/Cargo.toml index 3cebf9643..be73fe563 100644 --- a/hashes/embedded/Cargo.toml +++ b/hashes/embedded/Cargo.toml @@ -11,6 +11,7 @@ members = ["."] [features] alloc = ["alloc-cortex-m", "bitcoin_hashes/alloc"] +hex = ["bitcoin_hashes/hex"] [dependencies] cortex-m = "0.6.0" diff --git a/hashes/embedded/src/main.rs b/hashes/embedded/src/main.rs index 027563d00..3a19faaff 100644 --- a/hashes/embedded/src/main.rs +++ b/hashes/embedded/src/main.rs @@ -12,13 +12,20 @@ extern crate bitcoin_hashes; use bitcoin_hashes::{sha256, HashEngine}; use bitcoin_io::Write; use cortex_m_rt::entry; -use cortex_m_semihosting::{debug, hprintln}; +use cortex_m_semihosting::debug; +#[cfg(feature = "hex")] +use cortex_m_semihosting::hprintln; use panic_halt as _; hash_newtype! { struct TestType(sha256::Hash); } +#[cfg(feature = "hex")] +bitcoin_hashes::impl_hex_for_newtype!(TestType); +#[cfg(not(feature = "hex"))] +bitcoin_hashes::impl_debug_only_for_newtype!(TestType); + // this is the allocator the application will use #[cfg(feature = "alloc")] #[global_allocator] @@ -34,16 +41,19 @@ fn main() -> ! { let mut engine = sha256::Hash::engine(); engine.write_all(b"abc").unwrap(); + #[cfg(feature = "hex")] check_result(engine); let mut engine = sha256::Hash::engine(); engine.input(b"abc"); + #[cfg(feature = "hex")] check_result(engine); debug::exit(debug::EXIT_SUCCESS); loop {} } +#[cfg(feature = "hex")] fn check_result(engine: sha256::HashEngine) { let hash = TestType(sha256::Hash::from_engine(engine)); diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs index f74f49d49..39999b3dd 100644 --- a/hashes/src/internal_macros.rs +++ b/hashes/src/internal_macros.rs @@ -21,7 +21,10 @@ macro_rules! hash_trait_impls { ($bits:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => { $crate::impl_bytelike_traits!(Hash, { $bits / 8 } $(, $gen: $gent)*); + #[cfg(feature = "hex")] $crate::impl_hex_string_traits!(Hash, { $bits / 8 }, $reverse $(, $gen: $gent)*); + #[cfg(not(feature = "hex"))] + $crate::impl_debug_only!(Hash, { $bits / 8 }, $reverse $(, $gen: $gent)*); impl<$($gen: $gent),*> $crate::GeneralHash for Hash<$($gen),*> { type Engine = HashEngine; diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs index 972c7bf57..bc6206d30 100644 --- a/hashes/src/lib.rs +++ b/hashes/src/lib.rs @@ -86,6 +86,7 @@ extern crate serde_test; extern crate test; /// Re-export the `hex-conservative` crate. +#[cfg(feature = "hex")] pub extern crate hex; #[doc(hidden)] @@ -126,6 +127,7 @@ pub mod serde_macros { } } +use core::fmt::{self, Write as _}; use core::{convert, hash}; #[rustfmt::skip] // Keep public re-exports separate. @@ -312,6 +314,22 @@ fn incomplete_block_len(eng: &H) -> usize { (eng.n_bytes_hashed() % block_size) as usize } +/// Writes `bytes` as a `hex` string to the formatter. +/// +/// For when we cannot rely on having the `hex` feature enabled. Ignores formatter options and just +/// writes with plain old `f.write_char()`. +pub fn debug_hex(bytes: &[u8], f: &mut fmt::Formatter) -> fmt::Result { + const HEX_TABLE: [u8; 16] = *b"0123456789abcdef"; + + for &b in bytes { + let lower = HEX_TABLE[usize::from(b >> 4)]; + let upper = HEX_TABLE[usize::from(b & 0b00001111)]; + f.write_char(char::from(lower))?; + f.write_char(char::from(upper))?; + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -325,7 +343,10 @@ mod tests { struct TestNewtype2(sha256d::Hash); } + #[cfg(feature = "hex")] crate::impl_hex_for_newtype!(TestNewtype, TestNewtype2); + #[cfg(not(feature = "hex"))] + crate::impl_debug_only_for_newtype!(TestNewtype, TestNewtype2); #[test] #[cfg(feature = "alloc")] diff --git a/hashes/src/macros.rs b/hashes/src/macros.rs index b8207f6f2..3d5382aec 100644 --- a/hashes/src/macros.rs +++ b/hashes/src/macros.rs @@ -203,6 +203,7 @@ macro_rules! hash_newtype { /// * `fmt::{LowerHex, UpperHex}` using `hex-conservative` /// * `fmt::{Display, Debug}` by calling `LowerHex` #[macro_export] +#[cfg(feature = "hex")] macro_rules! impl_hex_for_newtype { ($($newtype:ident),*) => { $( @@ -211,6 +212,18 @@ macro_rules! impl_hex_for_newtype { } } +/// Implements `fmt::Debug` using hex for a new type crated with [`crate::hash_newtype`] macro. +/// +/// This is provided in case you do not want to use the `hex` feature. +#[macro_export] +macro_rules! impl_debug_only_for_newtype { + ($($newtype:ident),*) => { + $( + $crate::impl_debug_only!($newtype, { <$newtype as $crate::Hash>::LEN }, { <$newtype as $crate::Hash>::DISPLAY_BACKWARD }); + )* + } +} + /// Adds trait impls to a bytelike type. /// /// Implements: @@ -274,6 +287,7 @@ macro_rules! impl_bytelike_traits { /// [`hex-conservative`]: #[doc(hidden)] #[macro_export] +#[cfg(feature = "hex")] macro_rules! impl_hex_string_traits { ($ty:ident, $len:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => { impl<$($gen: $gent),*> $crate::_export::_core::str::FromStr for $ty<$($gen),*> { @@ -299,6 +313,20 @@ macro_rules! impl_hex_string_traits { } } +/// Implements `fmt::Debug` using hex. +#[doc(hidden)] +#[macro_export] +macro_rules! impl_debug_only { + ($ty:ident, $len:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => { + impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> { + #[inline] + fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result { + $crate::debug_hex(self.as_byte_array(), f) + } + } + } +} + // Generates the struct only (no impls) // // This is a separate macro to make it more readable and have a separate interface that allows for @@ -542,14 +570,29 @@ mod test { /// Test hash. struct TestHash(crate::sha256d::Hash); } + #[cfg(feature = "hex")] crate::impl_hex_for_newtype!(TestHash); + #[cfg(not(feature = "hex"))] + crate::impl_debug_only_for_newtype!(TestHash); impl TestHash { fn all_zeros() -> Self { Self::from_byte_array([0; 32]) } } + // NB: This runs with and without `hex` feature enabled, testing different code paths for each. #[test] #[cfg(feature = "alloc")] + fn debug() { + use alloc::format; + + let want = "0000000000000000000000000000000000000000000000000000000000000000"; + let got = format!("{:?}", TestHash::all_zeros()); + assert_eq!(got, want) + } + + #[test] + #[cfg(feature = "alloc")] + #[cfg(feature = "hex")] fn display() { use alloc::format; @@ -560,6 +603,7 @@ mod test { #[test] #[cfg(feature = "alloc")] + #[cfg(feature = "hex")] fn display_alternate() { use alloc::format; @@ -570,6 +614,7 @@ mod test { #[test] #[cfg(feature = "alloc")] + #[cfg(feature = "hex")] fn lower_hex() { use alloc::format; @@ -580,6 +625,7 @@ mod test { #[test] #[cfg(feature = "alloc")] + #[cfg(feature = "hex")] fn lower_hex_alternate() { use alloc::format; diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index a040338f4..98e8ec132 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -8,8 +8,6 @@ use core::arch::x86::*; use core::arch::x86_64::*; use core::{cmp, convert, fmt}; -use hex::DisplayHex; - use crate::{incomplete_block_len, sha256d, HashEngine as _}; #[cfg(doc)] use crate::{sha256t, sha256t_tag}; @@ -218,8 +216,15 @@ impl Midstate { impl fmt::Debug for Midstate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + struct Encoder<'a> { + bytes: &'a [u8; 32], + } + impl fmt::Debug for Encoder<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { crate::debug_hex(self.bytes, f) } + } + f.debug_struct("Midstate") - .field("bytes", &self.bytes.as_hex()) + .field("bytes", &Encoder { bytes: &self.bytes }) .field("length", &self.bytes_hashed) .finish() } diff --git a/hashes/src/sha256t.rs b/hashes/src/sha256t.rs index 262a4ec3f..cb3f4aaaa 100644 --- a/hashes/src/sha256t.rs +++ b/hashes/src/sha256t.rs @@ -321,10 +321,14 @@ mod tests { #[hash_newtype(backward)] struct NewTypeHashBackward(sha256t::Hash); } + #[cfg(feature = "hex")] crate::impl_hex_for_newtype!(NewTypeHashBackward); + #[cfg(not(feature = "hex"))] + crate::impl_debug_only_for_newtype!(NewTypeHashBackward); #[test] #[cfg(feature = "alloc")] + #[cfg(feature = "hex")] fn macro_created_sha256t_hash_type_backward() { use alloc::string::ToString; @@ -345,10 +349,14 @@ mod tests { #[hash_newtype(forward)] struct NewTypeHashForward(sha256t::Hash); } + #[cfg(feature = "hex")] crate::impl_hex_for_newtype!(NewTypeHashForward); + #[cfg(not(feature = "hex"))] + crate::impl_debug_only_for_newtype!(NewTypeHashForward); #[test] #[cfg(feature = "alloc")] + #[cfg(feature = "hex")] fn macro_created_sha256t_hash_type_prints_forward() { use alloc::string::ToString; diff --git a/hashes/tests/io.rs b/hashes/tests/io.rs index ffaa80678..9151bba30 100644 --- a/hashes/tests/io.rs +++ b/hashes/tests/io.rs @@ -3,6 +3,7 @@ //! Test the `bitcoin-io` implementations. #![cfg(feature = "bitcoin-io")] +#![cfg(feature = "hex")] use bitcoin_hashes::{ hash160, hmac, ripemd160, sha1, sha256, sha256d, sha384, sha512, sha512_256, siphash24, diff --git a/hashes/tests/regression.rs b/hashes/tests/regression.rs index 360bfd7f3..d40998e36 100644 --- a/hashes/tests/regression.rs +++ b/hashes/tests/regression.rs @@ -2,6 +2,8 @@ //! //! Note that if `bitcoin-io` is enabled then we get more regression-like testing from `./io.rs`. +#![cfg(feature = "hex")] + use bitcoin_hashes::{ hash160, ripemd160, sha1, sha256, sha256d, sha256t, sha384, sha512, sha512_256, siphash24, GeneralHash as _, HashEngine as _, Hmac, HmacEngine, diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 1387fc1d9..fb60db403 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -22,7 +22,7 @@ serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc"] arbitrary = ["dep:arbitrary", "units/arbitrary"] [dependencies] -hashes = { package = "bitcoin_hashes", version = "0.15.0", default-features = false, features = ["bitcoin-io"] } +hashes = { package = "bitcoin_hashes", version = "0.15.0", default-features = false, features = ["bitcoin-io", "hex"] } hex = { package = "hex-conservative", version = "0.3.0", default-features = false } internals = { package = "bitcoin-internals", version = "0.4.0" } io = { package = "bitcoin-io", version = "0.2.0", default-features = false }