From 752817e20d48dfa515556c77428111e332ae4733 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Wed, 22 Feb 2023 01:09:01 +0100 Subject: [PATCH 1/3] Stop using `$len` in `hash_newtype` We want to get rid of this argument since its value is implied by the inner hash type. First we stop using it. --- hashes/src/util.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hashes/src/util.rs b/hashes/src/util.rs index ca007300..d9354d1a 100644 --- a/hashes/src/util.rs +++ b/hashes/src/util.rs @@ -122,7 +122,7 @@ macro_rules! hash_newtype { pub struct $newtype($hash); $crate::hex_fmt_impl!($reverse, $newtype); - $crate::serde_impl!($newtype, $len); + $crate::serde_impl!($newtype, <$newtype as $crate::Hash>::LEN); $crate::borrow_slice_impl!($newtype); impl $newtype { @@ -208,9 +208,9 @@ macro_rules! hash_newtype { } } - impl $crate::_export::_core::convert::AsRef<[u8; $len]> for $newtype { - fn as_ref(&self) -> &[u8; $len] { - AsRef::<[u8; $len]>::as_ref(&self.0) + impl $crate::_export::_core::convert::AsRef<[u8; <$hash as $crate::Hash>::LEN]> for $newtype { + fn as_ref(&self) -> &[u8; <$hash as $crate::Hash>::LEN] { + AsRef::<[u8; <$hash as $crate::Hash>::LEN]>::as_ref(&self.0) } } From b018f3e90b577ff4d00d9d4c521a79af335de1cb Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Wed, 22 Feb 2023 01:27:09 +0100 Subject: [PATCH 2/3] Remove the `$len` argument from `hash_newtype` Now that the `$len` argument is no longer used, remove it completely. --- bitcoin/src/crypto/sighash.rs | 4 ++-- bitcoin/src/hash_types.rs | 26 +++++++++++++------------- hashes/embedded/src/main.rs | 2 +- hashes/src/lib.rs | 4 ++-- hashes/src/sha256t.rs | 2 +- hashes/src/util.rs | 8 ++++---- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 4b85e1b1..16806d56 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -48,12 +48,12 @@ macro_rules! impl_thirty_two_byte_hash { } #[rustfmt::skip] -hash_newtype!(LegacySighash, sha256d::Hash, 32, +hash_newtype!(LegacySighash, sha256d::Hash, doc="Hash of a transaction according to the legacy signature algorithm", false); impl_thirty_two_byte_hash!(LegacySighash); #[rustfmt::skip] -hash_newtype!(SegwitV0Sighash, sha256d::Hash, 32, +hash_newtype!(SegwitV0Sighash, sha256d::Hash, doc="Hash of a transaction according to the segwit version 0 signature algorithm", false); impl_thirty_two_byte_hash!(SegwitV0Sighash); diff --git a/bitcoin/src/hash_types.rs b/bitcoin/src/hash_types.rs index 31b7cb84..04a4478f 100644 --- a/bitcoin/src/hash_types.rs +++ b/bitcoin/src/hash_types.rs @@ -56,7 +56,7 @@ mod newtypes { use crate::hashes::{sha256, sha256d, hash160, hash_newtype}; hash_newtype!( - Txid, sha256d::Hash, 32, doc="A bitcoin transaction hash/transaction ID. + Txid, sha256d::Hash, doc="A bitcoin transaction hash/transaction ID. For compatibility with the existing Bitcoin infrastructure and historical and current versions of the Bitcoin Core software itself, this and @@ -64,21 +64,21 @@ other [`sha256d::Hash`] types, are serialized in reverse byte order when converted to a hex string via [`std::fmt::Display`] trait operations. See [`hashes::Hash::DISPLAY_BACKWARD`] for more details. "); - hash_newtype!(Wtxid, sha256d::Hash, 32, doc="A bitcoin witness transaction ID."); - hash_newtype!(BlockHash, sha256d::Hash, 32, doc="A bitcoin block hash."); + hash_newtype!(Wtxid, sha256d::Hash, doc="A bitcoin witness transaction ID."); + hash_newtype!(BlockHash, sha256d::Hash, doc="A bitcoin block hash."); - hash_newtype!(PubkeyHash, hash160::Hash, 20, doc="A hash of a public key."); - hash_newtype!(ScriptHash, hash160::Hash, 20, doc="A hash of Bitcoin Script bytecode."); - hash_newtype!(WPubkeyHash, hash160::Hash, 20, doc="SegWit version of a public key hash."); - hash_newtype!(WScriptHash, sha256::Hash, 32, doc="SegWit version of a Bitcoin Script bytecode hash."); + hash_newtype!(PubkeyHash, hash160::Hash, doc="A hash of a public key."); + hash_newtype!(ScriptHash, hash160::Hash, doc="A hash of Bitcoin Script bytecode."); + hash_newtype!(WPubkeyHash, hash160::Hash, doc="SegWit version of a public key hash."); + hash_newtype!(WScriptHash, sha256::Hash, doc="SegWit version of a Bitcoin Script bytecode hash."); - hash_newtype!(TxMerkleNode, sha256d::Hash, 32, doc="A hash of the Merkle tree branch or root for transactions"); - hash_newtype!(WitnessMerkleNode, sha256d::Hash, 32, doc="A hash corresponding to the Merkle tree root for witness data"); - hash_newtype!(WitnessCommitment, sha256d::Hash, 32, doc="A hash corresponding to the witness structure commitment in the coinbase transaction"); - hash_newtype!(XpubIdentifier, hash160::Hash, 20, doc="XpubIdentifier as defined in BIP-32."); + hash_newtype!(TxMerkleNode, sha256d::Hash, doc="A hash of the Merkle tree branch or root for transactions"); + hash_newtype!(WitnessMerkleNode, sha256d::Hash, doc="A hash corresponding to the Merkle tree root for witness data"); + hash_newtype!(WitnessCommitment, sha256d::Hash, doc="A hash corresponding to the witness structure commitment in the coinbase transaction"); + hash_newtype!(XpubIdentifier, hash160::Hash, doc="XpubIdentifier as defined in BIP-32."); - hash_newtype!(FilterHash, sha256d::Hash, 32, doc="Filter hash, as defined in BIP-157"); - hash_newtype!(FilterHeader, sha256d::Hash, 32, doc="Filter header, as defined in BIP-157"); + hash_newtype!(FilterHash, sha256d::Hash, doc="Filter hash, as defined in BIP-157"); + hash_newtype!(FilterHeader, sha256d::Hash, doc="Filter header, as defined in BIP-157"); impl_hashencode!(Txid); impl_hashencode!(Wtxid); diff --git a/hashes/embedded/src/main.rs b/hashes/embedded/src/main.rs index 22773ad2..ed36fc52 100644 --- a/hashes/embedded/src/main.rs +++ b/hashes/embedded/src/main.rs @@ -18,7 +18,7 @@ use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; use panic_halt as _; -hash_newtype!(TestType, sha256::Hash, 32, doc = "test"); +hash_newtype!(TestType, sha256::Hash, doc = "test"); // this is the allocator the application will use #[cfg(feature = "alloc")] diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs index 66b3a75d..315bb4c0 100644 --- a/hashes/src/lib.rs +++ b/hashes/src/lib.rs @@ -220,8 +220,8 @@ pub trait Hash: Copy + Clone + PartialEq + Eq + PartialOrd + Ord + mod tests { use crate::{Hash, sha256d}; - hash_newtype!(TestNewtype, sha256d::Hash, 32, doc="A test newtype"); - hash_newtype!(TestNewtype2, sha256d::Hash, 32, doc="A test newtype"); + hash_newtype!(TestNewtype, sha256d::Hash, doc="A test newtype"); + hash_newtype!(TestNewtype2, sha256d::Hash, doc="A test newtype"); #[test] fn convert_newtypes() { diff --git a/hashes/src/sha256t.rs b/hashes/src/sha256t.rs index e52e0ae0..ec0991ff 100644 --- a/hashes/src/sha256t.rs +++ b/hashes/src/sha256t.rs @@ -115,7 +115,7 @@ macro_rules! sha256t_hash_newtype { } } - $crate::hash_newtype!($newtype, $crate::sha256t::Hash<$tag>, 32, $docs, $reverse); + $crate::hash_newtype!($newtype, $crate::sha256t::Hash<$tag>, $docs, $reverse); }; } diff --git a/hashes/src/util.rs b/hashes/src/util.rs index d9354d1a..f992c7ab 100644 --- a/hashes/src/util.rs +++ b/hashes/src/util.rs @@ -112,10 +112,10 @@ macro_rules! engine_input_impl( /// Creates a new newtype around a [`Hash`] type. #[macro_export] macro_rules! hash_newtype { - ($newtype:ident, $hash:ty, $len:expr, $docs:meta) => { - $crate::hash_newtype!($newtype, $hash, $len, $docs, <$hash as $crate::Hash>::DISPLAY_BACKWARD); + ($newtype:ident, $hash:ty, $docs:meta) => { + $crate::hash_newtype!($newtype, $hash, $docs, <$hash as $crate::Hash>::DISPLAY_BACKWARD); }; - ($newtype:ident, $hash:ty, $len:expr, $docs:meta, $reverse:expr) => { + ($newtype:ident, $hash:ty, $docs:meta, $reverse:expr) => { #[$docs] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] @@ -276,7 +276,7 @@ mod test { assert_eq!(borrowed, hash.as_inner()); } - hash_newtype!(TestHash, crate::sha256d::Hash, 32, doc="Test hash."); + hash_newtype!(TestHash, crate::sha256d::Hash, doc="Test hash."); #[test] fn display() { From 06f1f027ab1982acdf593c2df4cd572b60532977 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Wed, 22 Feb 2023 09:20:28 +0100 Subject: [PATCH 3/3] Make `hash_newtype` evocative of the output The API guidelines say macro input should be evocative of the output. `hash_newtype` didn't have this property. This change makes it look exactly like the resulting struct, `$len` parameter was removed since it's not needed, reversing is controlled using an attribute. The macro is also better documented and ready to be extended in the future. The tagged SHA256 newtype is not yet modified because it has a more complicated input parameters. Closes #1648 --- bitcoin/src/crypto/sighash.rs | 21 +-- bitcoin/src/hash_types.rs | 54 +++++--- bitcoin/src/taproot.rs | 6 +- hashes/embedded/src/main.rs | 4 +- hashes/extended_tests/schemars/src/main.rs | 2 +- hashes/src/lib.rs | 9 +- hashes/src/sha256t.rs | 14 +- hashes/src/util.rs | 144 +++++++++++++++++++-- 8 files changed, 201 insertions(+), 53 deletions(-) diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 16806d56..cbdb6c85 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -47,22 +47,25 @@ macro_rules! impl_thirty_two_byte_hash { } } -#[rustfmt::skip] -hash_newtype!(LegacySighash, sha256d::Hash, - doc="Hash of a transaction according to the legacy signature algorithm", false); -impl_thirty_two_byte_hash!(LegacySighash); +hash_newtype! { + /// Hash of a transaction according to the legacy signature algorithm. + #[hash_newtype(forward)] + pub struct LegacySighash(sha256d::Hash); -#[rustfmt::skip] -hash_newtype!(SegwitV0Sighash, sha256d::Hash, - doc="Hash of a transaction according to the segwit version 0 signature algorithm", false); + /// Hash of a transaction according to the segwit version 0 signature algorithm. + #[hash_newtype(forward)] + pub struct SegwitV0Sighash(sha256d::Hash); +} + +impl_thirty_two_byte_hash!(LegacySighash); impl_thirty_two_byte_hash!(SegwitV0Sighash); -#[rustfmt::skip] sha256t_hash_newtype!(TapSighash, TapSighashTag, MIDSTATE_TAPSIGHASH, 64, doc="Taproot-tagged hash with tag \"TapSighash\". -This hash type is used for computing taproot signature hash.", false +This hash type is used for computing taproot signature hash.", forward ); + impl_thirty_two_byte_hash!(TapSighash); /// Efficiently calculates signature hash message for legacy, segwit and taproot inputs. diff --git a/bitcoin/src/hash_types.rs b/bitcoin/src/hash_types.rs index 04a4478f..be978a69 100644 --- a/bitcoin/src/hash_types.rs +++ b/bitcoin/src/hash_types.rs @@ -55,30 +55,44 @@ pub use newtypes::*; mod newtypes { use crate::hashes::{sha256, sha256d, hash160, hash_newtype}; - hash_newtype!( - Txid, sha256d::Hash, doc="A bitcoin transaction hash/transaction ID. + hash_newtype! { + /// A bitcoin transaction hash/transaction ID. + /// + /// For compatibility with the existing Bitcoin infrastructure and historical + /// and current versions of the Bitcoin Core software itself, this and + /// other [`sha256d::Hash`] types, are serialized in reverse + /// byte order when converted to a hex string via [`std::fmt::Display`] trait operations. + /// See [`hashes::Hash::DISPLAY_BACKWARD`] for more details. + pub struct Txid(sha256d::Hash); -For compatibility with the existing Bitcoin infrastructure and historical -and current versions of the Bitcoin Core software itself, this and -other [`sha256d::Hash`] types, are serialized in reverse -byte order when converted to a hex string via [`std::fmt::Display`] trait operations. -See [`hashes::Hash::DISPLAY_BACKWARD`] for more details. -"); - hash_newtype!(Wtxid, sha256d::Hash, doc="A bitcoin witness transaction ID."); - hash_newtype!(BlockHash, sha256d::Hash, doc="A bitcoin block hash."); + /// A bitcoin witness transaction ID. + pub struct Wtxid(sha256d::Hash); + /// A bitcoin block hash. + pub struct BlockHash(sha256d::Hash); - hash_newtype!(PubkeyHash, hash160::Hash, doc="A hash of a public key."); - hash_newtype!(ScriptHash, hash160::Hash, doc="A hash of Bitcoin Script bytecode."); - hash_newtype!(WPubkeyHash, hash160::Hash, doc="SegWit version of a public key hash."); - hash_newtype!(WScriptHash, sha256::Hash, doc="SegWit version of a Bitcoin Script bytecode hash."); + /// A hash of a public key. + pub struct PubkeyHash(hash160::Hash); + /// A hash of Bitcoin Script bytecode. + pub struct ScriptHash(hash160::Hash); + /// SegWit version of a public key hash. + pub struct WPubkeyHash(hash160::Hash); + /// SegWit version of a Bitcoin Script bytecode hash. + pub struct WScriptHash(sha256::Hash); - hash_newtype!(TxMerkleNode, sha256d::Hash, doc="A hash of the Merkle tree branch or root for transactions"); - hash_newtype!(WitnessMerkleNode, sha256d::Hash, doc="A hash corresponding to the Merkle tree root for witness data"); - hash_newtype!(WitnessCommitment, sha256d::Hash, doc="A hash corresponding to the witness structure commitment in the coinbase transaction"); - hash_newtype!(XpubIdentifier, hash160::Hash, doc="XpubIdentifier as defined in BIP-32."); + /// A hash of the Merkle tree branch or root for transactions + pub struct TxMerkleNode(sha256d::Hash); + /// A hash corresponding to the Merkle tree root for witness data + pub struct WitnessMerkleNode(sha256d::Hash); + /// A hash corresponding to the witness structure commitment in the coinbase transaction + pub struct WitnessCommitment(sha256d::Hash); + /// XpubIdentifier as defined in BIP-32. + pub struct XpubIdentifier(hash160::Hash); - hash_newtype!(FilterHash, sha256d::Hash, doc="Filter hash, as defined in BIP-157"); - hash_newtype!(FilterHeader, sha256d::Hash, doc="Filter header, as defined in BIP-157"); + /// Filter hash, as defined in BIP-157 + pub struct FilterHash(sha256d::Hash); + /// Filter header, as defined in BIP-157 + pub struct FilterHeader(sha256d::Hash); + } impl_hashencode!(Txid); impl_hashencode!(Wtxid); diff --git a/bitcoin/src/taproot.rs b/bitcoin/src/taproot.rs index 06bfe9e0..5ca97c79 100644 --- a/bitcoin/src/taproot.rs +++ b/bitcoin/src/taproot.rs @@ -46,16 +46,16 @@ const MIDSTATE_TAPTWEAK: [u8; 32] = [ sha256t_hash_newtype!(TapLeafHash, TapLeafTag, MIDSTATE_TAPLEAF, 64, doc="Taproot-tagged hash with tag \"TapLeaf\". -This is used for computing tapscript script spend hash.", false +This is used for computing tapscript script spend hash.", forward ); #[rustfmt::skip] sha256t_hash_newtype!(TapNodeHash, TapBranchTag, MIDSTATE_TAPBRANCH, 64, - doc="Tagged hash used in taproot trees; see BIP-340 for tagging rules", false + doc="Tagged hash used in taproot trees; see BIP-340 for tagging rules", forward ); #[rustfmt::skip] sha256t_hash_newtype!(TapTweakHash, TapTweakTag, MIDSTATE_TAPTWEAK, 64, doc="Taproot-tagged hash with tag \"TapTweak\". - This hash type is used while computing the tweaked public key", false + This hash type is used while computing the tweaked public key", forward ); impl TapTweakHash { diff --git a/hashes/embedded/src/main.rs b/hashes/embedded/src/main.rs index ed36fc52..98bb4bb3 100644 --- a/hashes/embedded/src/main.rs +++ b/hashes/embedded/src/main.rs @@ -18,7 +18,9 @@ use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; use panic_halt as _; -hash_newtype!(TestType, sha256::Hash, doc = "test"); +hash_newtype! { + struct TestType(sha256::Hash); +} // this is the allocator the application will use #[cfg(feature = "alloc")] diff --git a/hashes/extended_tests/schemars/src/main.rs b/hashes/extended_tests/schemars/src/main.rs index 3bfadcec..23c36a9c 100644 --- a/hashes/extended_tests/schemars/src/main.rs +++ b/hashes/extended_tests/schemars/src/main.rs @@ -139,7 +139,7 @@ mod tests { TEST_MIDSTATE, 64, doc = "test hash", - true + backward ); static HASH_BYTES: [u8; 32] = [ 0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs index 315bb4c0..3b6d062a 100644 --- a/hashes/src/lib.rs +++ b/hashes/src/lib.rs @@ -220,8 +220,13 @@ pub trait Hash: Copy + Clone + PartialEq + Eq + PartialOrd + Ord + mod tests { use crate::{Hash, sha256d}; - hash_newtype!(TestNewtype, sha256d::Hash, doc="A test newtype"); - hash_newtype!(TestNewtype2, sha256d::Hash, doc="A test newtype"); + hash_newtype! { + /// A test newtype + struct TestNewtype(sha256d::Hash); + + /// A test newtype + struct TestNewtype2(sha256d::Hash); + } #[test] fn convert_newtypes() { diff --git a/hashes/src/sha256t.rs b/hashes/src/sha256t.rs index ec0991ff..6f47448c 100644 --- a/hashes/src/sha256t.rs +++ b/hashes/src/sha256t.rs @@ -97,11 +97,11 @@ fn from_engine(e: sha256::HashEngine) -> Hash { /// - a sha256t::Hash type alias. #[macro_export] macro_rules! sha256t_hash_newtype { - ($newtype:ident, $tag:ident, $midstate:ident, $midstate_len:expr, $docs:meta, $reverse: expr) => { - sha256t_hash_newtype!($newtype, $tag, $midstate, $midstate_len, $docs, $reverse, stringify!($newtype)); + ($newtype:ident, $tag:ident, $midstate:ident, $midstate_len:expr, $docs:meta, $direction:tt) => { + sha256t_hash_newtype!($newtype, $tag, $midstate, $midstate_len, $docs, $direction, stringify!($newtype)); }; - ($newtype:ident, $tag:ident, $midstate:ident, $midstate_len:expr, $docs:meta, $reverse: expr, $sname:expr) => { + ($newtype:ident, $tag:ident, $midstate:ident, $midstate_len:expr, $docs:meta, $direction:tt, $sname:expr) => { #[doc = "The tag used for ["] #[doc = $sname] #[doc = "]"] @@ -115,7 +115,11 @@ macro_rules! sha256t_hash_newtype { } } - $crate::hash_newtype!($newtype, $crate::sha256t::Hash<$tag>, $docs, $reverse); + $crate::hash_newtype! { + #[$docs] + #[hash_newtype($direction)] + pub struct $newtype($crate::sha256t::Hash<$tag>); + } }; } @@ -146,7 +150,7 @@ mod tests { #[cfg(feature = "alloc")] pub type TestHash = sha256t::Hash; - sha256t_hash_newtype!(NewTypeHash, NewTypeTag, TEST_MIDSTATE, 64, doc="test hash", true); + sha256t_hash_newtype!(NewTypeHash, NewTypeTag, TEST_MIDSTATE, 64, doc="test hash", backward); #[test] #[cfg(feature = "alloc")] diff --git a/hashes/src/util.rs b/hashes/src/util.rs index f992c7ab..743e94c7 100644 --- a/hashes/src/util.rs +++ b/hashes/src/util.rs @@ -110,28 +110,77 @@ macro_rules! engine_input_impl( /// Creates a new newtype around a [`Hash`] type. +/// +/// The syntax is similar to the usual tuple struct syntax: +/// +/// ``` +/// # use bitcoin_hashes::{hash_newtype, sha256}; +/// hash_newtype! { +/// /// Hash of `Foo`. +/// pub struct MyNewtype(pub sha256::Hash); +/// } +/// ``` +/// +/// You can use any valid visibility specifier in place of `pub` or you can leave it out if you +/// don't want the type or its field to be private. +/// +/// Whether the hash is reversed or not depends on the inner type. However you can override it like +/// this: +/// +/// ``` +/// # use bitcoin_hashes::{hash_newtype, sha256}; +/// hash_newtype! { +/// #[hash_newtype(backward)] +/// struct MyNewtype(sha256::Hash); +/// } +/// ``` +/// +/// This will display the hash backwards regardless of what the inner type does. Use `forward` +/// instead of `backward` to force displaying forward. +/// +/// You can add arbitrary doc comments or other attributes to the struct or it's field. Note that +/// the macro already derives [`Copy`], [`Clone`], [`Eq`], [`PartialEq`], +/// [`Hash`](core::hash::Hash), [`Ord`], [`PartialOrd`]. With the `serde` feature on, this also adds +/// [`Serialize`](serde::Serialize) and [`Deserialize](serde::Deserialize) implementations. +/// +/// You can also define multiple newtypes within one macro call: +/// +/// ``` +/// # use bitcoin_hashes::{hash_newtype, sha256, hash160}; +/// +/// hash_newtype! { +/// /// My custom type 1 +/// pub struct Newtype1(sha256::Hash); +/// +/// /// My custom type 2 +/// struct Newtype2(hash160::Hash); +/// } +/// ``` #[macro_export] macro_rules! hash_newtype { - ($newtype:ident, $hash:ty, $docs:meta) => { - $crate::hash_newtype!($newtype, $hash, $docs, <$hash as $crate::Hash>::DISPLAY_BACKWARD); - }; - ($newtype:ident, $hash:ty, $docs:meta, $reverse:expr) => { - #[$docs] - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[repr(transparent)] - pub struct $newtype($hash); + ($($(#[$($type_attrs:tt)*])* $type_vis:vis struct $newtype:ident($(#[$field_attrs:tt])* $field_vis:vis $hash:path);)+) => { + $( + $($crate::hash_newtype_known_attrs!(#[ $($type_attrs)* ]);)* - $crate::hex_fmt_impl!($reverse, $newtype); + $crate::hash_newtype_struct! { + $type_vis struct $newtype($(#[$field_attrs])* $field_vis $hash); + + $({ $($type_attrs)* })* + } + + $crate::hex_fmt_impl!(<$newtype as $crate::Hash>::DISPLAY_BACKWARD, $newtype); $crate::serde_impl!($newtype, <$newtype as $crate::Hash>::LEN); $crate::borrow_slice_impl!($newtype); impl $newtype { /// Creates this type from the inner hash type. + #[allow(unused)] // the user of macro may not need this pub fn from_hash(inner: $hash) -> $newtype { $newtype(inner) } /// Converts this type into the inner hash type. + #[allow(unused)] // the user of macro may not need this pub fn as_hash(&self) -> $hash { // Hashes implement Copy so don't need into_hash. self.0 @@ -156,7 +205,7 @@ macro_rules! hash_newtype { type Inner = <$hash as $crate::Hash>::Inner; const LEN: usize = <$hash as $crate::Hash>::LEN; - const DISPLAY_BACKWARD: bool = $reverse; + const DISPLAY_BACKWARD: bool = $crate::hash_newtype_get_direction!($hash, $(#[$($type_attrs)*])*); fn engine() -> Self::Engine { <$hash as $crate::Hash>::engine() @@ -199,7 +248,7 @@ macro_rules! hash_newtype { use $crate::hex::{HexIterator, FromHex}; use $crate::Hash; - let inner: <$hash as Hash>::Inner = if $reverse { + let inner: <$hash as Hash>::Inner = if ::DISPLAY_BACKWARD { FromHex::from_byte_iter(HexIterator::new(s)?.rev())? } else { FromHex::from_byte_iter(HexIterator::new(s)?)? @@ -222,9 +271,77 @@ macro_rules! hash_newtype { &self.0[index] } } + )+ }; } +#[doc(hidden)] +#[macro_export] +macro_rules! hash_newtype_struct { + ($(#[$other_attrs:meta])* $type_vis:vis struct $newtype:ident($(#[$field_attrs:meta])* $field_vis:vis $hash:path);) => { + $(#[$other_attrs])* + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + $type_vis struct $newtype($(#[$field_attrs])* $field_vis $hash); + }; + ($(#[$other_attrs:meta])* $type_vis:vis struct $newtype:ident($(#[$field_attrs:meta])* $field_vis:vis $hash:path); { hash_newtype($($ignore:tt)*) } $($type_attrs:tt)*) => { + $crate::hash_newtype_struct! { + $(#[$other_attrs])* + $type_vis struct $newtype($(#[$field_attrs])* $field_vis $hash); + + $($type_attrs)* + } + }; + ($(#[$other_attrs:meta])* $type_vis:vis struct $newtype:ident($(#[$field_attrs:meta])* $field_vis:vis $hash:path); { $other_attr:meta } $($type_attrs:tt)*) => { + $crate::hash_newtype_struct! { + $(#[$other_attrs])* + #[$other_attr] + $type_vis struct $newtype($(#[$field_attrs])* $field_vis $hash); + + $($type_attrs)* + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! hash_newtype_our_attrs { + (hash_newtype($($attr:tt)*)) => { $($attr)* }; + ($($ignore:tt)*) => {}; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! hash_newtype_get_direction { + ($hash:ty, ) => { <$hash as $crate::Hash>::DISPLAY_BACKWARD }; + ($hash:ty, #[hash_newtype(forward)] $($others:tt)*) => { { $crate::hash_newtype_forbid_direction!(forward, $($others)*); false } }; + ($hash:ty, #[hash_newtype(backward)] $($others:tt)*) => { { $crate::hash_newtype_forbid_direction!(backward, $($others)*); true } }; + ($hash:ty, #[$($ignore:tt)*] $($others:tt)*) => { $crate::hash_newtype_get_direction!($hash, $($others)*) }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! hash_newtype_forbid_direction { + ($direction:ident, ) => {}; + ($direction:ident, #[hash_newtype(forward)] $(others:tt)*) => { + compile_error!(concat!("Cannot set display direction to forward: ", stringify!($direction), " was already specified")); + }; + ($direction:ident, #[hash_newtype(backward)] $(others:tt)*) => { + compile_error!(concat!("Cannot set display direction to backward: ", stringify!($direction), " was already specified")); + }; + ($direction:ident, #[$($ignore:tt)*] $(#[$others:tt])*) => { + $crate::hash_newtype_forbid_direction!($direction, $(#[$others])*) + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! hash_newtype_known_attrs { + (#[hash_newtype(forward)]) => {}; + (#[hash_newtype(backward)]) => {}; + (#[hash_newtype($($unknown:tt)*)]) => { compile_error!(concat!("Unrecognized attribute ", stringify!($($unknown)*))); }; + ($($ignore:tt)*) => {}; +} + #[cfg(feature = "schemars")] #[cfg_attr(docsrs, doc(cfg(feature = "schemars")))] pub mod json_hex_string { @@ -276,7 +393,10 @@ mod test { assert_eq!(borrowed, hash.as_inner()); } - hash_newtype!(TestHash, crate::sha256d::Hash, doc="Test hash."); + hash_newtype! { + /// Test hash. + struct TestHash(crate::sha256d::Hash); + } #[test] fn display() {