Repository: rust-bitcoin/rust-bitcoin
Pull request #1773 (2023-04-02 14:06:54)

 Make `sha256t_hash_newtype!` evocative of the output. 
The Rust API guidelines state that macros should be evocative of the
output, which is a sensible recommendation. We already had this for
`hash_newtype!` macro but didn't for sha256t version.

This changes the macro to have this syntax:

```rust
sha256t_hash_newtype! {
    // Order of these structs is fixed.
    /// Optional documentation details here. Summary is auto-generated.
    /*pub*/ struct Tag = raw(MIDSTATE_BYTES, LEN);

    /// Documentation here
    #[hash_newtype(forward)] // optional, default is backward
    /*pub*/ struct HashType(/* attributes allowed here */ _);
}
```

Closes https://github.com/rust-bitcoin/rust-bitcoin/issues/1427

Depends on #1769 

How do you like the syntax? Is weird `struct Foo = bar(..);` acceptable?


Patch:
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 437ea337..8ffdbf8d 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -33,13 +33,6 @@
     0, 0, 0, 0, 0, 0, 0, 0
 ];
 
-/// The SHA-256 midstate value for the [`TapSighash`].
-pub(crate) const MIDSTATE_TAPSIGHASH: [u8; 32] = [
-    245, 4, 164, 37, 215, 248, 120, 59, 19, 99, 134, 138, 227, 229, 86, 88, 110, 238, 148, 93, 188,
-    120, 136, 221, 2, 166, 226, 195, 24, 115, 254, 159,
-];
-// f504a425d7f8783b1363868ae3e556586eee945dbc7888dd02a6e2c31873fe9f
-
 macro_rules! impl_thirty_two_byte_hash {
     ($ty:ident) => {
         impl secp256k1::ThirtyTwoByteHash for $ty {
@@ -61,16 +54,15 @@ impl secp256k1::ThirtyTwoByteHash for $ty {
 impl_thirty_two_byte_hash!(LegacySighash);
 impl_thirty_two_byte_hash!(SegwitV0Sighash);
 
-sha256t_hash_newtype!(
-    TapSighash,
-    TapSighashTag,
-    MIDSTATE_TAPSIGHASH,
-    64,
-    doc = "Taproot-tagged hash with tag \"TapSighash\".
+sha256t_hash_newtype! {
+    pub struct TapSighashTag = hash_str("TapSighash");
 
-This hash type is used for computing taproot signature hash.",
-    forward
-);
+    /// Taproot-tagged hash with tag \"TapSighash\".
+    ///
+    /// This hash type is used for computing taproot signature hash."
+    #[hash_newtype(forward)]
+    pub struct TapSighash(_);
+}
 
 impl_thirty_two_byte_hash!(TapSighash);
 
diff --git a/bitcoin/src/taproot.rs b/bitcoin/src/taproot.rs
index ee21c9a4..3a38fcd0 100644
--- a/bitcoin/src/taproot.rs
+++ b/bitcoin/src/taproot.rs
@@ -21,43 +21,32 @@
 use crate::prelude::*;
 use crate::{io, Script, ScriptBuf};
 
-/// The SHA-256 midstate value for the TapLeaf hash.
-const MIDSTATE_TAPLEAF: [u8; 32] = [
-    156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211, 243, 147, 108,
-    71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201,
-];
-// 9ce0e4e67c116c3938b3caf2c30f5089d3f3936c47636e607db33eeaddc6f0c9
-
-/// The SHA-256 midstate value for the TapBranch hash.
-const MIDSTATE_TAPBRANCH: [u8; 32] = [
-    35, 168, 101, 169, 184, 164, 13, 167, 151, 124, 30, 4, 196, 158, 36, 111, 181, 190, 19, 118,
-    157, 36, 201, 183, 181, 131, 181, 212, 168, 210, 38, 210,
-];
-// 23a865a9b8a40da7977c1e04c49e246fb5be13769d24c9b7b583b5d4a8d226d2
-
-/// The SHA-256 midstate value for the TapTweak hash.
-const MIDSTATE_TAPTWEAK: [u8; 32] = [
-    209, 41, 162, 243, 112, 28, 101, 93, 101, 131, 182, 195, 185, 65, 151, 39, 149, 244, 226, 50,
-    148, 253, 84, 244, 162, 174, 141, 133, 71, 202, 89, 11,
-];
-// d129a2f3701c655d6583b6c3b941972795f4e23294fd54f4a2ae8d8547ca590b
-
 // Taproot test vectors from BIP-341 state the hashes without any reversing
-#[rustfmt::skip]
-sha256t_hash_newtype!(TapLeafHash, TapLeafTag, MIDSTATE_TAPLEAF, 64,
-    doc="Taproot-tagged hash with tag \"TapLeaf\".
+sha256t_hash_newtype! {
+    pub struct TapLeafTag = hash_str("TapLeaf");
 
-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", 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", forward
-);
+    /// Taproot-tagged hash with tag \"TapLeaf\".
+    ///
+    /// This is used for computing tapscript script spend hash.
+    #[hash_newtype(forward)]
+    pub struct TapLeafHash(_);
+
+    pub struct TapBranchTag = hash_str("TapBranch");
+
+    /// Tagged hash used in taproot trees.
+    ///
+    /// See BIP-340 for tagging rules.
+    #[hash_newtype(forward)]
+    pub struct TapNodeHash(_);
+
+    pub struct TapTweakTag = hash_str("TapTweak");
+
+    /// Taproot-tagged hash with tag \"TapTweak\".
+    ///
+    /// This hash type is used while computing the tweaked public key.
+    #[hash_newtype(forward)]
+    pub struct TapTweakHash(_);
+}
 
 impl TapTweakHash {
     /// Creates a new BIP341 [`TapTweakHash`] from key and tweak. Produces `H_taptweak(P||R)` where
@@ -1595,14 +1584,6 @@ fn tag_engine(tag_name: &str) -> sha256::HashEngine {
 
     #[test]
     fn test_midstates() {
-        use crate::crypto::sighash::MIDSTATE_TAPSIGHASH;
-
-        // check midstate against hard-coded values
-        assert_eq!(MIDSTATE_TAPLEAF, tag_engine("TapLeaf").midstate().to_byte_array());
-        assert_eq!(MIDSTATE_TAPBRANCH, tag_engine("TapBranch").midstate().to_byte_array());
-        assert_eq!(MIDSTATE_TAPTWEAK, tag_engine("TapTweak").midstate().to_byte_array());
-        assert_eq!(MIDSTATE_TAPSIGHASH, tag_engine("TapSighash").midstate().to_byte_array());
-
         // test that engine creation roundtrips
         assert_eq!(tag_engine("TapLeaf").midstate(), TapLeafTag::engine().midstate());
         assert_eq!(tag_engine("TapBranch").midstate(), TapBranchTag::engine().midstate());
diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs
index 695ceb90..5a020b4e 100644
--- a/hashes/src/sha256.rs
+++ b/hashes/src/sha256.rs
@@ -114,6 +114,11 @@ impl Hash {
     pub fn hash_again(&self) -> sha256d::Hash {
         crate::Hash::from_byte_array(<Self as crate::Hash>::hash(&self.0).0)
     }
+
+    /// Computes hash from `bytes` in `const` context.
+    ///
+    /// Warning: this function is inefficient. It should be only used in `const` context.
+    pub const fn const_hash(bytes: &[u8]) -> Self { Hash(Midstate::const_hash(bytes, true).0) }
 }
 
 /// Output of the SHA256 hash function.
@@ -146,7 +151,7 @@ impl Midstate {
     const DISPLAY_BACKWARD: bool = true;
 
     /// Construct a new [`Midstate`] from the inner value.
-    pub fn from_byte_array(inner: [u8; 32]) -> Self { Midstate(inner) }
+    pub const fn from_byte_array(inner: [u8; 32]) -> Self { Midstate(inner) }
 
     /// Copies a byte slice into the [`Midstate`] object.
     pub fn from_slice(sl: &[u8]) -> Result<Midstate, Error> {
@@ -161,6 +166,23 @@ pub fn from_slice(sl: &[u8]) -> Result<Midstate, Error> {
 
     /// Unwraps the [`Midstate`] and returns the underlying byte array.
     pub fn to_byte_array(self) -> [u8; 32] { self.0 }
+
+    /// Creates midstate for tagged hashes.
+    ///
+    /// Warning: this function is inefficient. It should be only used in `const` context.
+    ///
+    /// Computes non-finalized hash of `sha256(tag) || sha256(tag)` for use in
+    /// [`sha256t`](super::sha256t). It's provided for use with [`sha256t`](crate::sha256t).
+    pub const fn hash_tag(tag: &[u8]) -> Self {
+        let hash = Hash::const_hash(tag);
+        let mut buf = [0u8; 64];
+        let mut i = 0usize;
+        while i < buf.len() {
+            buf[i] = hash.0[i % hash.0.len()];
+            i += 1;
+        }
+        Self::const_hash(&buf, false)
+    }
 }
 
 impl hex::FromHex for Midstate {
@@ -195,6 +217,174 @@ macro_rules! round(
     )
 );
 
+impl Midstate {
+    #[allow(clippy::identity_op)] // more readble
+    const fn read_u32(bytes: &[u8], index: usize) -> u32 {
+        ((bytes[index + 0] as u32) << 24)
+            | ((bytes[index + 1] as u32) << 16)
+            | ((bytes[index + 2] as u32) << 8)
+            | ((bytes[index + 3] as u32) << 0)
+    }
+
+    const fn copy_w(bytes: &[u8], index: usize) -> [u32; 16] {
+        let mut w = [0u32; 16];
+        let mut i = 0;
+        while i < 16 {
+            w[i] = Self::read_u32(bytes, index + i * 4);
+            i += 1;
+        }
+        w
+    }
+
+    const fn const_hash(bytes: &[u8], finalize: bool) -> Self {
+        let mut state = [
+            0x6a09e667u32,
+            0xbb67ae85,
+            0x3c6ef372,
+            0xa54ff53a,
+            0x510e527f,
+            0x9b05688c,
+            0x1f83d9ab,
+            0x5be0cd19,
+        ];
+
+        let num_chunks = (bytes.len() + 9 + 63) / 64;
+        let mut chunk = 0;
+        #[allow(clippy::precedence)]
+        while chunk < num_chunks {
+            if !finalize && chunk + 1 == num_chunks {
+                break;
+            }
+            let mut w = if chunk * 64 + 64 <= bytes.len() {
+                Self::copy_w(bytes, chunk * 64)
+            } else {
+                let mut buf = [0; 64];
+                let mut i = 0;
+                let offset = chunk * 64;
+                while offset + i < bytes.len() {
+                    buf[i] = bytes[offset + i];
+                    i += 1;
+                }
+                if (bytes.len() % 64 <= 64 - 9) || (chunk + 2 == num_chunks) {
+                    buf[i] = 0x80;
+                }
+                #[allow(clippy::identity_op)] // more readble
+                #[allow(clippy::erasing_op)]
+                if chunk + 1 == num_chunks {
+                    let bit_len = bytes.len() as u64 * 8;
+                    buf[64 - 8] = ((bit_len >> 8 * 7) & 0xFF) as u8;
+                    buf[64 - 7] = ((bit_len >> 8 * 6) & 0xFF) as u8;
+                    buf[64 - 6] = ((bit_len >> 8 * 5) & 0xFF) as u8;
+                    buf[64 - 5] = ((bit_len >> 8 * 4) & 0xFF) as u8;
+                    buf[64 - 4] = ((bit_len >> 8 * 3) & 0xFF) as u8;
+                    buf[64 - 3] = ((bit_len >> 8 * 2) & 0xFF) as u8;
+                    buf[64 - 2] = ((bit_len >> 8 * 1) & 0xFF) as u8;
+                    buf[64 - 1] = ((bit_len >> 8 * 0) & 0xFF) as u8;
+                }
+                Self::copy_w(&buf, 0)
+            };
+            chunk += 1;
+
+            let mut a = state[0];
+            let mut b = state[1];
+            let mut c = state[2];
+            let mut d = state[3];
+            let mut e = state[4];
+            let mut f = state[5];
+            let mut g = state[6];
+            let mut h = state[7];
+
+            round!(a, b, c, d, e, f, g, h, 0x428a2f98, w[0]);
+            round!(h, a, b, c, d, e, f, g, 0x71374491, w[1]);
+            round!(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w[2]);
+            round!(f, g, h, a, b, c, d, e, 0xe9b5dba5, w[3]);
+            round!(e, f, g, h, a, b, c, d, 0x3956c25b, w[4]);
+            round!(d, e, f, g, h, a, b, c, 0x59f111f1, w[5]);
+            round!(c, d, e, f, g, h, a, b, 0x923f82a4, w[6]);
+            round!(b, c, d, e, f, g, h, a, 0xab1c5ed5, w[7]);
+            round!(a, b, c, d, e, f, g, h, 0xd807aa98, w[8]);
+            round!(h, a, b, c, d, e, f, g, 0x12835b01, w[9]);
+            round!(g, h, a, b, c, d, e, f, 0x243185be, w[10]);
+            round!(f, g, h, a, b, c, d, e, 0x550c7dc3, w[11]);
+            round!(e, f, g, h, a, b, c, d, 0x72be5d74, w[12]);
+            round!(d, e, f, g, h, a, b, c, 0x80deb1fe, w[13]);
+            round!(c, d, e, f, g, h, a, b, 0x9bdc06a7, w[14]);
+            round!(b, c, d, e, f, g, h, a, 0xc19bf174, w[15]);
+
+            round!(a, b, c, d, e, f, g, h, 0xe49b69c1, w[0], w[14], w[9], w[1]);
+            round!(h, a, b, c, d, e, f, g, 0xefbe4786, w[1], w[15], w[10], w[2]);
+            round!(g, h, a, b, c, d, e, f, 0x0fc19dc6, w[2], w[0], w[11], w[3]);
+            round!(f, g, h, a, b, c, d, e, 0x240ca1cc, w[3], w[1], w[12], w[4]);
+            round!(e, f, g, h, a, b, c, d, 0x2de92c6f, w[4], w[2], w[13], w[5]);
+            round!(d, e, f, g, h, a, b, c, 0x4a7484aa, w[5], w[3], w[14], w[6]);
+            round!(c, d, e, f, g, h, a, b, 0x5cb0a9dc, w[6], w[4], w[15], w[7]);
+            round!(b, c, d, e, f, g, h, a, 0x76f988da, w[7], w[5], w[0], w[8]);
+            round!(a, b, c, d, e, f, g, h, 0x983e5152, w[8], w[6], w[1], w[9]);
+            round!(h, a, b, c, d, e, f, g, 0xa831c66d, w[9], w[7], w[2], w[10]);
+            round!(g, h, a, b, c, d, e, f, 0xb00327c8, w[10], w[8], w[3], w[11]);
+            round!(f, g, h, a, b, c, d, e, 0xbf597fc7, w[11], w[9], w[4], w[12]);
+            round!(e, f, g, h, a, b, c, d, 0xc6e00bf3, w[12], w[10], w[5], w[13]);
+            round!(d, e, f, g, h, a, b, c, 0xd5a79147, w[13], w[11], w[6], w[14]);
+            round!(c, d, e, f, g, h, a, b, 0x06ca6351, w[14], w[12], w[7], w[15]);
+            round!(b, c, d, e, f, g, h, a, 0x14292967, w[15], w[13], w[8], w[0]);
+
+            round!(a, b, c, d, e, f, g, h, 0x27b70a85, w[0], w[14], w[9], w[1]);
+            round!(h, a, b, c, d, e, f, g, 0x2e1b2138, w[1], w[15], w[10], w[2]);
+            round!(g, h, a, b, c, d, e, f, 0x4d2c6dfc, w[2], w[0], w[11], w[3]);
+            round!(f, g, h, a, b, c, d, e, 0x53380d13, w[3], w[1], w[12], w[4]);
+            round!(e, f, g, h, a, b, c, d, 0x650a7354, w[4], w[2], w[13], w[5]);
+            round!(d, e, f, g, h, a, b, c, 0x766a0abb, w[5], w[3], w[14], w[6]);
+            round!(c, d, e, f, g, h, a, b, 0x81c2c92e, w[6], w[4], w[15], w[7]);
+            round!(b, c, d, e, f, g, h, a, 0x92722c85, w[7], w[5], w[0], w[8]);
+            round!(a, b, c, d, e, f, g, h, 0xa2bfe8a1, w[8], w[6], w[1], w[9]);
+            round!(h, a, b, c, d, e, f, g, 0xa81a664b, w[9], w[7], w[2], w[10]);
+            round!(g, h, a, b, c, d, e, f, 0xc24b8b70, w[10], w[8], w[3], w[11]);
+            round!(f, g, h, a, b, c, d, e, 0xc76c51a3, w[11], w[9], w[4], w[12]);
+            round!(e, f, g, h, a, b, c, d, 0xd192e819, w[12], w[10], w[5], w[13]);
+            round!(d, e, f, g, h, a, b, c, 0xd6990624, w[13], w[11], w[6], w[14]);
+            round!(c, d, e, f, g, h, a, b, 0xf40e3585, w[14], w[12], w[7], w[15]);
+            round!(b, c, d, e, f, g, h, a, 0x106aa070, w[15], w[13], w[8], w[0]);
+
+            round!(a, b, c, d, e, f, g, h, 0x19a4c116, w[0], w[14], w[9], w[1]);
+            round!(h, a, b, c, d, e, f, g, 0x1e376c08, w[1], w[15], w[10], w[2]);
+            round!(g, h, a, b, c, d, e, f, 0x2748774c, w[2], w[0], w[11], w[3]);
+            round!(f, g, h, a, b, c, d, e, 0x34b0bcb5, w[3], w[1], w[12], w[4]);
+            round!(e, f, g, h, a, b, c, d, 0x391c0cb3, w[4], w[2], w[13], w[5]);
+            round!(d, e, f, g, h, a, b, c, 0x4ed8aa4a, w[5], w[3], w[14], w[6]);
+            round!(c, d, e, f, g, h, a, b, 0x5b9cca4f, w[6], w[4], w[15], w[7]);
+            round!(b, c, d, e, f, g, h, a, 0x682e6ff3, w[7], w[5], w[0], w[8]);
+            round!(a, b, c, d, e, f, g, h, 0x748f82ee, w[8], w[6], w[1], w[9]);
+            round!(h, a, b, c, d, e, f, g, 0x78a5636f, w[9], w[7], w[2], w[10]);
+            round!(g, h, a, b, c, d, e, f, 0x84c87814, w[10], w[8], w[3], w[11]);
+            round!(f, g, h, a, b, c, d, e, 0x8cc70208, w[11], w[9], w[4], w[12]);
+            round!(e, f, g, h, a, b, c, d, 0x90befffa, w[12], w[10], w[5], w[13]);
+            round!(d, e, f, g, h, a, b, c, 0xa4506ceb, w[13], w[11], w[6], w[14]);
+            round!(c, d, e, f, g, h, a, b, 0xbef9a3f7, w[14], w[12], w[7], w[15]);
+            round!(b, c, d, e, f, g, h, a, 0xc67178f2, w[15], w[13], w[8], w[0]);
+
+            state[0] = state[0].wrapping_add(a);
+            state[1] = state[1].wrapping_add(b);
+            state[2] = state[2].wrapping_add(c);
+            state[3] = state[3].wrapping_add(d);
+            state[4] = state[4].wrapping_add(e);
+            state[5] = state[5].wrapping_add(f);
+            state[6] = state[6].wrapping_add(g);
+            state[7] = state[7].wrapping_add(h);
+        }
+        let mut output = [0u8; 32];
+        let mut i = 0;
+        #[allow(clippy::identity_op)] // more readble
+        while i < 8 {
+            output[i * 4 + 0] = (state[i + 0] >> 24) as u8;
+            output[i * 4 + 1] = (state[i + 0] >> 16) as u8;
+            output[i * 4 + 2] = (state[i + 0] >> 8) as u8;
+            output[i * 4 + 3] = (state[i + 0] >> 0) as u8;
+            i += 1;
+        }
+        Midstate(output)
+    }
+}
+
 impl HashEngine {
     /// Create a new [`HashEngine`] from a [`Midstate`].
     ///
@@ -456,6 +646,35 @@ fn engine_with_state() {
         assert_eq!(hash, sha256::Hash(HASH_EXPECTED));
     }
 
+    #[test]
+    fn const_hash() {
+        assert_eq!(super::Hash::hash(&[]), super::Hash::const_hash(&[]));
+
+        let mut bytes = Vec::new();
+        for i in 0..256 {
+            bytes.push(i as u8);
+            assert_eq!(
+                super::Hash::hash(&bytes),
+                super::Hash::const_hash(&bytes),
+                "hashes don't match for length {}",
+                i + 1
+            );
+        }
+    }
+
+    #[test]
+    fn const_midstate() {
+        use super::Midstate;
+
+        assert_eq!(
+            Midstate::hash_tag(b"TapLeaf"),
+            Midstate([
+                156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211,
+                243, 147, 108, 71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201,
+            ])
+        )
+    }
+
     #[cfg(feature = "serde")]
     #[test]
     fn sha256_serde() {
diff --git a/hashes/src/sha256t.rs b/hashes/src/sha256t.rs
index 95c23e19..469a3816 100644
--- a/hashes/src/sha256t.rs
+++ b/hashes/src/sha256t.rs
@@ -80,42 +80,91 @@ fn from_engine<T: Tag>(e: sha256::HashEngine) -> Hash<T> {
 }
 
 /// Macro used to define a newtype tagged hash.
-/// It creates two public types:
-/// - a sha256t::Tag struct,
-/// - a sha256t::Hash type alias.
+///
+/// This macro creates two types:
+///
+/// * a tag struct
+/// * a hash wrapper
+///
+/// The syntax is:
+///
+/// ```
+/// # use bitcoin_hashes::sha256t_hash_newtype;
+/// sha256t_hash_newtype! {
+///     /// Optional documentation details here.
+///     /// Summary is always generated.
+///     pub struct FooTag = hash_str("foo");
+///
+///     /// A foo hash.
+///     // Direction works just like in case of hash_newtype! macro.
+///     #[hash_newtype(forward)]
+///     pub struct FooHash(_);
+/// }
+/// ```
+///
+/// The structs must be defined in this order - tag first, then hash type. `hash_str` marker
+/// says the midstate should be generated by hashing the supplied string in a way described in
+/// BIP-341. Alternatively, you can supply `hash_bytes` to hash raw bytes. If you have the midstate
+/// already pre-computed and prefer **compiler** performance to readability you may use
+/// `raw(MIDSTATE_BYTES, HASHED_BYTES_LENGHT)` instead.
+///
+/// Both visibility modifiers and attributes are optional and passed to inner structs (excluding
+/// `#[hash_newtype(...)]`). The attributes suffer same compiler performance limitations as in
+/// [`hash_newtype`] macro.
+///
+/// The macro accepts multiple inputs so you can define multiple hash newtypes in one macro call.
+/// Just make sure to enter the structs in order `Tag0`, `Hash0`, `Tag1`, `Hash1`...
+///
+/// [`hash_newtype`]: crate::hash_newtype
 #[macro_export]
 macro_rules! sha256t_hash_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, $direction:tt, $sname:expr) => {
-        #[doc = "The tag used for ["]
-        #[doc = $sname]
-        #[doc = "]"]
-        #[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
-        pub struct $tag;
+    ($($(#[$($tag_attr:tt)*])* $tag_vis:vis struct $tag:ident = $constructor:tt($($tag_value:tt)+); $(#[$($hash_attr:tt)*])* $hash_vis:vis struct $hash_name:ident($(#[$($field_attr:tt)*])* _);)+) => {
+        $(
+        $crate::sha256t_hash_newtype_tag!($tag_vis, $tag, stringify!($hash_name), $(#[$($tag_attr)*])*);
 
         impl $crate::sha256t::Tag for $tag {
+            #[inline]
             fn engine() -> $crate::sha256::HashEngine {
-                let midstate = $crate::sha256::Midstate::from_byte_array($midstate);
-                $crate::sha256::HashEngine::from_midstate(midstate, $midstate_len)
+                const MIDSTATE: ($crate::sha256::Midstate, usize) = $crate::sha256t_hash_newtype_tag_constructor!($constructor, $($tag_value)+);
+                #[allow(unused)]
+                const _LENGTH_CHECK: () = [(); 1][MIDSTATE.1 % 64];
+                $crate::sha256::HashEngine::from_midstate(MIDSTATE.0, MIDSTATE.1)
             }
         }
 
         $crate::hash_newtype! {
-            #[$docs]
-            #[hash_newtype($direction)]
-            pub struct $newtype($crate::sha256t::Hash<$tag>);
+            $(#[$($hash_attr)*])*
+            $hash_vis struct $hash_name($(#[$($field_attr)*])* $crate::sha256t::Hash<$tag>);
         }
+        )+
+    }
+}
+
+// Workaround macros being unavailable in attributes.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! sha256t_hash_newtype_tag {
+    ($vis:vis, $tag:ident, $name:expr, $(#[$($attr:meta)*])*) => {
+        #[doc = "The tag used for [`"]
+        #[doc = $name]
+        #[doc = "`]\n\n"]
+        $(#[$($attr)*])*
+        #[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
+        $vis struct $tag;
+    };
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! sha256t_hash_newtype_tag_constructor {
+    (hash_str, $value:expr) => {
+        ($crate::sha256::Midstate::hash_tag($value.as_bytes()), 64)
+    };
+    (hash_bytes, $value:expr) => {
+        ($crate::sha256::Midstate::hash_tag($value), 64)
+    };
+    (raw, $bytes:expr, $len:expr) => {
+        ($crate::sha256::Midstate::from_byte_array($bytes), $len)
     };
 }
 
@@ -146,7 +195,14 @@ fn engine() -> sha256::HashEngine {
     #[cfg(feature = "alloc")]
     pub type TestHash = sha256t::Hash<TestHashTag>;
 
-    sha256t_hash_newtype!(NewTypeHash, NewTypeTag, TEST_MIDSTATE, 64, doc = "test hash", backward);
+    sha256t_hash_newtype! {
+        /// Test detailed explanation.
+        struct NewTypeTag = raw(TEST_MIDSTATE, 64);
+
+        /// A test hash.
+        #[hash_newtype(backward)]
+        struct NewTypeHash(_);
+    }
 
     #[test]
     #[cfg(feature = "alloc")]


Test patch:
diff --git a/hashes/extended_tests/schemars/src/main.rs b/hashes/extended_tests/schemars/src/main.rs
index 7ceee050..2af4690e 100644
--- a/hashes/extended_tests/schemars/src/main.rs
+++ b/hashes/extended_tests/schemars/src/main.rs
@@ -133,14 +133,12 @@ fn engine() -> sha256::HashEngine {
         /// A hash tagged with `$name`.
         pub type TestHash = sha256t::Hash<TestHashTag>;
 
-        sha256t_hash_newtype!(
-            NewTypeHash,
-            NewTypeTag,
-            TEST_MIDSTATE,
-            64,
-            doc = "test hash",
-            backward
-        );
+        sha256t_hash_newtype! {
+            struct NewTypeTag = raw(TEST_MIDSTATE, 64);
+
+            #[hash_newtype(backward)]
+            struct NewTypeHash(_);
+        }
         static HASH_BYTES: [u8; 32] = [
             0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6,
             0x3d, 0x97, 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2, 0xb7, 0x65, 0x44, 0x8c,