hashes: Make hex dependency optional

The only reason we need `hex-conservative` is to parse strings and
format them as hex. For users that do not require this functionality we
can make the `hex-conservative` crate an optional dependency.

The `serde` feature requires `Display` so we enable `hex` from the
`serde` feature.

If `hex` feature is not enabled we still need to be able to debug so
provide `fmt::Debug` functionality by way of macros.

Close: #2654
This commit is contained in:
Tobin C. Harding 2024-11-13 13:29:06 +11:00
parent 9dce0b4b8c
commit ec06028f63
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
13 changed files with 108 additions and 8 deletions

View File

@ -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

View File

@ -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"] }

View File

@ -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 }

View File

@ -11,6 +11,7 @@ members = ["."]
[features]
alloc = ["alloc-cortex-m", "bitcoin_hashes/alloc"]
hex = ["bitcoin_hashes/hex"]
[dependencies]
cortex-m = "0.6.0"

View File

@ -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));

View File

@ -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;

View File

@ -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<H: HashEngine>(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")]

View File

@ -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`]: <https://crates.io/crates/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;

View File

@ -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()
}

View File

@ -321,10 +321,14 @@ mod tests {
#[hash_newtype(backward)]
struct NewTypeHashBackward(sha256t::Hash<NewTypeTagBackward>);
}
#[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<NewTypeTagForward>);
}
#[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;

View File

@ -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,

View File

@ -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,

View File

@ -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 }