From 594100873399007f52789b8ba1d95d0280a8e05a Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Sat, 13 Jul 2024 07:33:21 +1000 Subject: [PATCH 01/12] Import sha256t in docs builds Explicitly import `sha256t` in docs builds and remove explicit link target. This patch is code churn on its own but the `sha256t` module will be used again in proceeding patches, done separately to reduce the size/complexity of proceeding patches. --- hashes/src/sha256.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 76aa3627c..68fa277c7 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -10,6 +10,8 @@ use core::cmp; use core::ops::Index; use core::slice::SliceIndex; +#[cfg(doc)] +use crate::sha256t; use crate::{sha256d, FromSliceError, HashEngine as _}; crate::internal_macros::hash_type! { @@ -167,8 +169,8 @@ impl Midstate { /// /// 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). + /// Computes non-finalized hash of `sha256(tag) || sha256(tag)` for use in [`sha256t`]. It's + /// provided for use with [`sha256t`]. pub const fn hash_tag(tag: &[u8]) -> Self { let hash = Hash::const_hash(tag); let mut buf = [0u8; 64]; From 9efe4cea9d75733032ad47762f458b8ab7e6c6e3 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 11 Jul 2024 13:40:31 +1000 Subject: [PATCH 02/12] Move impl block under struct Put the impl block for `Midstate` under the struct, as is customary. (Note the diff shows moving some other code around the impl block not the impl block itself.) Code move only. --- hashes/src/sha256.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 68fa277c7..e6bb03f12 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -123,22 +123,6 @@ impl Hash { #[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)] pub struct Midstate(pub [u8; 32]); -crate::internal_macros::arr_newtype_fmt_impl!(Midstate, 32); -serde_impl!(Midstate, 32); -borrow_slice_impl!(Midstate); - -impl> Index for Midstate { - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { &self.0[index] } -} - -impl core::str::FromStr for Midstate { - type Err = hex::HexToArrayError; - fn from_str(s: &str) -> Result { hex::FromHex::from_hex(s) } -} - impl Midstate { /// Length of the midstate, in bytes. const LEN: usize = 32; @@ -183,6 +167,22 @@ impl Midstate { } } +crate::internal_macros::arr_newtype_fmt_impl!(Midstate, 32); +serde_impl!(Midstate, 32); +borrow_slice_impl!(Midstate); + +impl> Index for Midstate { + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { &self.0[index] } +} + +impl core::str::FromStr for Midstate { + type Err = hex::HexToArrayError; + fn from_str(s: &str) -> Result { hex::FromHex::from_hex(s) } +} + impl hex::FromHex for Midstate { type Error = hex::HexToArrayError; From 37b54dd54c4a84b35178ac7d60913ef6a1ccaa59 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 12 Jul 2024 13:47:00 +1000 Subject: [PATCH 03/12] Move from_midstate function Move the `sha256::HashEngine::from_midstate` function to be in the same impl block as `midstate`. Refactor only, no logic change. --- hashes/src/sha256.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index e6bb03f12..56b455c34 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -74,6 +74,22 @@ impl HashEngine { } } + /// Create a new [`HashEngine`] from a [`Midstate`]. + /// + /// # Panics + /// + /// If `length` is not a multiple of the block size. + pub fn from_midstate(midstate: Midstate, length: usize) -> HashEngine { + assert!(length % BLOCK_SIZE == 0, "length is no multiple of the block size"); + + let mut ret = [0; 8]; + for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate[..].chunks_exact(4)) { + *ret_val = u32::from_be_bytes(midstate_bytes.try_into().expect("4 byte slice")); + } + + HashEngine { buffer: [0; BLOCK_SIZE], h: ret, length } + } + /// Outputs the midstate of the hash engine. This function should not be /// used directly unless you really know what you're doing. #[cfg(not(hashes_fuzz))] @@ -433,22 +449,6 @@ impl Midstate { } impl HashEngine { - /// Create a new [`HashEngine`] from a [`Midstate`]. - /// - /// # Panics - /// - /// If `length` is not a multiple of the block size. - pub fn from_midstate(midstate: Midstate, length: usize) -> HashEngine { - assert!(length % BLOCK_SIZE == 0, "length is no multiple of the block size"); - - let mut ret = [0; 8]; - for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate[..].chunks_exact(4)) { - *ret_val = u32::from_be_bytes(midstate_bytes.try_into().expect("4 byte slice")); - } - - HashEngine { buffer: [0; BLOCK_SIZE], h: ret, length } - } - fn process_block(&mut self) { #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))] { From 07e8e5d3a67dc175b3a05bdee3bee7e7a81d1f11 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 11 Jul 2024 13:55:00 +1000 Subject: [PATCH 04/12] Stop using macro for Midstate In preparation for changing the `sha256::Midstate` internals stop using the `arr_newtype_fmt_impl` macro and implement the `fmt` traits manually. In doing so, remove the `DISPLAY_BACKWARDS` const but keep the current behaviour of displaying the midstate backwards. --- hashes/src/sha256.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 56b455c34..97e1e36fd 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -6,9 +6,9 @@ use core::arch::x86::*; #[cfg(all(feature = "std", target_arch = "x86_64"))] use core::arch::x86_64::*; -use core::cmp; use core::ops::Index; use core::slice::SliceIndex; +use core::{cmp, fmt}; #[cfg(doc)] use crate::sha256t; @@ -143,11 +143,6 @@ impl Midstate { /// Length of the midstate, in bytes. const LEN: usize = 32; - /// Flag indicating whether user-visible serializations of this hash - /// should be backward. For some reason Satoshi decided this should be - /// true for `Sha256dHash`, so here we are. - const DISPLAY_BACKWARD: bool = true; - /// Construct a new [`Midstate`] from the inner value. pub const fn from_byte_array(inner: [u8; 32]) -> Self { Midstate(inner) } @@ -183,7 +178,26 @@ impl Midstate { } } -crate::internal_macros::arr_newtype_fmt_impl!(Midstate, 32); +impl fmt::LowerHex for Midstate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + hex::fmt_hex_exact!(f, 32, self.0.iter().rev(), hex::Case::Lower) + } +} + +impl fmt::UpperHex for Midstate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + hex::fmt_hex_exact!(f, 32, self.0.iter().rev(), hex::Case::Upper) + } +} + +impl fmt::Display for Midstate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self, f) } +} + +impl fmt::Debug for Midstate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:#}", self) } +} + serde_impl!(Midstate, 32); borrow_slice_impl!(Midstate); From 7dc68b62e9f64f8369650bd56dc767dd03649751 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 11 Jul 2024 14:04:48 +1000 Subject: [PATCH 05/12] Remove serde from sha256::Midstate The `sha256::Midstate` is a niche use case type, there is no real reason we need to support serialization/deserialization. If people really want this they can just get the byte array and serialize it themselves. API breaking change. --- hashes/src/sha256.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 97e1e36fd..5a54c0601 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -198,7 +198,6 @@ impl fmt::Debug for Midstate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:#}", self) } } -serde_impl!(Midstate, 32); borrow_slice_impl!(Midstate); impl> Index for Midstate { From ca823945fc2e94a8729b009361916c28f2d77c37 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 11 Jul 2024 14:08:31 +1000 Subject: [PATCH 06/12] Manually implement AsRef (remove Borrow) Currently we are using a macro to implement `AsRef` and `Borrow` for `sha256::Midstate`. In preparation for adding a length field to the `Midstate` remove the implementation of `Borrow` but keep `AsRef`. API breaking change. --- hashes/src/sha256.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 5a54c0601..00c4d66e0 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -8,7 +8,7 @@ use core::arch::x86::*; use core::arch::x86_64::*; use core::ops::Index; use core::slice::SliceIndex; -use core::{cmp, fmt}; +use core::{cmp, convert, fmt}; #[cfg(doc)] use crate::sha256t; @@ -198,7 +198,9 @@ impl fmt::Debug for Midstate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:#}", self) } } -borrow_slice_impl!(Midstate); +impl convert::AsRef<[u8]> for Midstate { + fn as_ref(&self) -> &[u8] { &self.0 } +} impl> Index for Midstate { type Output = I::Output; From 1d0e70b1da7037bb997c376f009217c4cd859c29 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 12 Jul 2024 14:00:46 +1000 Subject: [PATCH 07/12] Add regression test for Midstate debug output In preparation for patching the `Debug` implementation of `Midstate` and a regression test. --- hashes/src/sha256.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 00c4d66e0..bbc2d27a9 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -1008,15 +1008,20 @@ mod tests { } } + // The midstate of an empty hash engine tagged with "TapLeaf". + const TAP_LEAF_MIDSTATE: Midstate = 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, + ]); + #[test] - fn const_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, - ]) - ) + fn const_midstate() { assert_eq!(Midstate::hash_tag(b"TapLeaf"), TAP_LEAF_MIDSTATE,) } + + #[test] + fn regression_midstate_debug_format() { + let want = "0xc9f0c6ddea3eb37d606e63476c93f3d389500fc3f2cab338396c117ce6e4e09c"; + let got = format!("{:?}", TAP_LEAF_MIDSTATE); + assert_eq!(got, want); } #[test] From 34dd95f9098c41497243c60527606e8764e71716 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 12 Jul 2024 13:52:01 +1000 Subject: [PATCH 08/12] Debug Midstate forwards Done in preparation for adding a `length` field to `Midstate` and also in preparation for removing the `Display` implementation (will be justified in the patch that does it). Currently in the `Debug` impl of `Midstate` we are calling through to `Display` using the alternate form of printing, we can use `as_hex` to achieve almost the same thing. Note that in `LowerHex` we use the `fmt_hex_exact` macro that allows us to reverse the iterator however when we later attempt to use `f.debug_struct` we cannot use the macro. Elect to change the current behaviour to `Debug` forwards, shown by the change to the regression test. --- hashes/src/sha256.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index bbc2d27a9..2057fa057 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -10,6 +10,8 @@ use core::ops::Index; use core::slice::SliceIndex; use core::{cmp, convert, fmt}; +use hex::DisplayHex; + #[cfg(doc)] use crate::sha256t; use crate::{sha256d, FromSliceError, HashEngine as _}; @@ -195,7 +197,7 @@ impl fmt::Display for Midstate { } impl fmt::Debug for Midstate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:#}", self) } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "0x{}", self.0.as_hex()) } } impl convert::AsRef<[u8]> for Midstate { @@ -1019,7 +1021,7 @@ mod tests { #[test] fn regression_midstate_debug_format() { - let want = "0xc9f0c6ddea3eb37d606e63476c93f3d389500fc3f2cab338396c117ce6e4e09c"; + let want = "0x9ce0e4e67c116c3938b3caf2c30f5089d3f3936c47636e607db33eeaddc6f0c9"; let got = format!("{:?}", TAP_LEAF_MIDSTATE); assert_eq!(got, want); } From db0502d3cd6363b293518c4e1f768ba79c303c41 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 15 Jul 2024 09:35:30 +1000 Subject: [PATCH 09/12] Use third person in rustdoc As is convention in this repo use the third person when describing the `sha256::HashEngine::from_midstate` function. --- hashes/src/sha256.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 2057fa057..472330fc2 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -76,7 +76,7 @@ impl HashEngine { } } - /// Create a new [`HashEngine`] from a [`Midstate`]. + /// Creates a new [`HashEngine`] from a [`Midstate`]. /// /// # Panics /// From dcb18bfa7a25d4de0eb8c3bba73d3c2b1f4dae14 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 15 Jul 2024 09:09:30 +1000 Subject: [PATCH 10/12] Add length to sha256::Midstate In a `HashEngine` the `length` field represents number of bytes input into the hash engine. Note also: > the midstate bytes are only updated when the compression function is run, which only happens every 64 bytes. Currently our midstate API allows extracting the midstate after any amount of input bytes, this is probably not what users want. Note also that most users should not be using the midstate API anyways. With all this in mind, add a private `length` field to the `Midstate` struct and enforce an invariant that it is modulo 64. Add a single const `Midstate` constructor that panics if the invariant is violated. The `Midstate` is niche enough that panic is acceptable. Remove the `from_slice`, `from_byte_array`, and `to_byte_array` functions because they no longer make sense. Keep `AsRef<[u8]>` for cheap access to the midstate's inner byte slice. Note change to `Debug`: `bytes` field now does not include the `0x` prefix because `as_hex` because of the use of `debug_struct`. Enjoy nice warm fuzzy feeling from hacking on crypto code. --- hashes/src/sha256.rs | 208 ++++++++++++++++++++----------------- hashes/src/sha256t.rs | 16 ++- hashes/tests/regression.rs | 4 +- 3 files changed, 122 insertions(+), 106 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 472330fc2..9f7756594 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -12,9 +12,9 @@ use core::{cmp, convert, fmt}; use hex::DisplayHex; +use crate::{sha256d, HashEngine as _}; #[cfg(doc)] -use crate::sha256t; -use crate::{sha256d, FromSliceError, HashEngine as _}; +use crate::{sha256t, sha256t_hash_newtype}; crate::internal_macros::hash_type! { 256, @@ -39,12 +39,12 @@ fn from_engine(mut e: HashEngine) -> Hash { e.input(&(8 * data_len).to_be_bytes()); debug_assert_eq!(e.length % BLOCK_SIZE, 0); - Hash(e.midstate().to_byte_array()) + Hash(e.midstate_unchecked().bytes) } #[cfg(hashes_fuzz)] fn from_engine(e: HashEngine) -> Hash { - let mut hash = e.midstate().to_byte_array(); + let mut hash = e.midstate_unchecked().bytes; if hash == [0; 32] { // Assume sha256 is secure and never generate 0-hashes (which represent invalid // secp256k1 secret keys, causing downstream application breakage). @@ -78,38 +78,50 @@ impl HashEngine { /// Creates a new [`HashEngine`] from a [`Midstate`]. /// - /// # Panics - /// - /// If `length` is not a multiple of the block size. - pub fn from_midstate(midstate: Midstate, length: usize) -> HashEngine { - assert!(length % BLOCK_SIZE == 0, "length is no multiple of the block size"); - + /// Please see docs on [`Midstate`] before using this function. + pub fn from_midstate(midstate: Midstate) -> HashEngine { let mut ret = [0; 8]; - for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate[..].chunks_exact(4)) { + for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate.as_ref().chunks_exact(4)) { *ret_val = u32::from_be_bytes(midstate_bytes.try_into().expect("4 byte slice")); } - HashEngine { buffer: [0; BLOCK_SIZE], h: ret, length } + HashEngine { buffer: [0; BLOCK_SIZE], h: ret, length: midstate.length } } - /// Outputs the midstate of the hash engine. This function should not be - /// used directly unless you really know what you're doing. + /// Returns `true` if the midstate can be extracted from this engine. + /// + /// The midstate can only be extracted if the number of bytes input into + /// the hash engine is a multiple of 64. See caveat on [`Self::midstate`]. + /// + /// Please see docs on [`Midstate`] before using this function. + pub const fn can_extract_midstate(&self) -> bool { self.length % 64 == 0 } + + /// Outputs the midstate of the hash engine. + /// + /// Please see docs on [`Midstate`] before using this function. + pub fn midstate(&self) -> Result { + if !self.can_extract_midstate() { + return Err(MidstateError { invalid_length: self.length }); + } + Ok(self.midstate_unchecked()) + } + + // Does not check that `HashEngine::can_extract_midstate`. #[cfg(not(hashes_fuzz))] - pub fn midstate(&self) -> Midstate { + fn midstate_unchecked(&self) -> Midstate { let mut ret = [0; 32]; for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) { ret_bytes.copy_from_slice(&val.to_be_bytes()); } - Midstate(ret) + Midstate { bytes: ret, length: self.length } } - /// Outputs the midstate of the hash engine. This function should not be - /// used directly unless you really know what you're doing. + // Does not check that `HashEngine::can_extract_midstate`. #[cfg(hashes_fuzz)] - pub fn midstate(&self) -> Midstate { + fn midstate_unchecked(&self) -> Midstate { let mut ret = [0; 32]; ret.copy_from_slice(&self.buffer[..32]); - Midstate(ret) + Midstate { bytes: ret, length: self.length } } } @@ -134,33 +146,55 @@ impl Hash { /// 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) } + pub const fn const_hash(bytes: &[u8]) -> Self { Hash(Midstate::const_hash(bytes, true).bytes) } } /// Output of the SHA256 hash function. -#[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)] -pub struct Midstate(pub [u8; 32]); +/// +/// The `Midstate` type is obscure and specialized and should not be used unless you are sure of +/// what you are doing. +/// +/// It represents "partially hashed data" but does not itself have properties of cryptographic +/// hashes. For example, when (ab)used as hashes, midstates are vulnerable to trivial +/// length-extension attacks. They are typically used to optimize the computation of full hashes. +/// For example, when implementing BIP-340 tagged hashes, which always begin by hashing the same +/// fixed 64-byte prefix, it makes sense to hash the prefix once, store the midstate as a constant, +/// and hash any future data starting from the constant rather than from a fresh hash engine. +/// +/// For BIP-340 support we provide the [`sha256t`] module, and the [`sha256t_hash_newtype`] +/// macro which will create the midstate for you in const context and use it for tagged hashing. +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Midstate { + /// Raw bytes of the midstate i.e., the already-hashed contents of the hash engine. + bytes: [u8; 32], + /// Number of bytes hashed to achieve this midstate. + // INVARIANT must always be a multiple of 64. + length: usize, +} impl Midstate { - /// Length of the midstate, in bytes. - const LEN: usize = 32; - - /// Construct a new [`Midstate`] from the inner value. - 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 { - if sl.len() != Self::LEN { - Err(FromSliceError { expected: Self::LEN, got: sl.len() }) - } else { - let mut ret = [0; 32]; - ret.copy_from_slice(sl); - Ok(Midstate(ret)) + /// Construct a new [`Midstate`] from the `state` and the `bytes_hashed` to get to that state. + /// + /// # Panics + /// + /// Panics if `bytes_hashed` is not a multiple of 64. + pub const fn new(state: [u8; 32], bytes_hashed: usize) -> Self { + if bytes_hashed % 64 != 0 { + // When MSRV is 1.57+ we can use `panic!()`. + #[allow(unconditional_panic)] + #[allow(clippy::let_unit_value)] + #[allow(clippy::out_of_bounds_indexing)] + let _panic = [(); 0][1]; } + + Midstate { bytes: state, length: bytes_hashed } } - /// Unwraps the [`Midstate`] and returns the underlying byte array. - pub const fn to_byte_array(self) -> [u8; 32] { self.0 } + /// Deconstructs the [`Midstate`], returning the underlying byte array and number of bytes hashed. + pub const fn as_parts(&self) -> (&[u8; 32], usize) { (&self.bytes, self.length) } + + /// Deconstructs the [`Midstate`], returning the underlying byte array and number of bytes hashed. + pub const fn into_parts(self) -> ([u8; 32], usize) { (self.bytes, self.length) } /// Creates midstate for tagged hashes. /// @@ -180,53 +214,39 @@ impl Midstate { } } -impl fmt::LowerHex for Midstate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - hex::fmt_hex_exact!(f, 32, self.0.iter().rev(), hex::Case::Lower) - } -} - -impl fmt::UpperHex for Midstate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - hex::fmt_hex_exact!(f, 32, self.0.iter().rev(), hex::Case::Upper) - } -} - -impl fmt::Display for Midstate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self, f) } -} - impl fmt::Debug for Midstate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "0x{}", self.0.as_hex()) } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Midstate") + .field("bytes", &self.bytes.as_hex()) + .field("length", &self.length) + .finish() + } } impl convert::AsRef<[u8]> for Midstate { - fn as_ref(&self) -> &[u8] { &self.0 } + fn as_ref(&self) -> &[u8] { &self.bytes } } -impl> Index for Midstate { - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { &self.0[index] } +/// `Midstate` invariant violated (not a multiple of 64). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MidstateError { + /// The invalid number of bytes hashed. + invalid_length: usize, } -impl core::str::FromStr for Midstate { - type Err = hex::HexToArrayError; - fn from_str(s: &str) -> Result { hex::FromHex::from_hex(s) } -} - -impl hex::FromHex for Midstate { - type Error = hex::HexToArrayError; - - fn from_hex(s: &str) -> Result { - // DISPLAY_BACKWARD is true - let mut bytes = <[u8; 32]>::from_hex(s)?; - bytes.reverse(); - Ok(Midstate(bytes)) +impl fmt::Display for MidstateError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "invalid number of bytes hashed {} (should have been a multiple of 64)", + self.invalid_length + ) } } +#[cfg(feature = "std")] +impl std::error::Error for MidstateError {} + #[allow(non_snake_case)] const fn Ch(x: u32, y: u32, z: u32) -> u32 { z ^ (x & (y ^ z)) } #[allow(non_snake_case)] @@ -461,7 +481,7 @@ impl Midstate { output[i * 4 + 3] = (state[i + 0] >> 0) as u8; i += 1; } - Midstate(output) + Midstate { bytes: output, length: bytes.len() } } } @@ -929,22 +949,25 @@ mod tests { ]); // 32 bytes of zeroes representing "new asset" engine.input(&[0; 32]); + + let total_bytes_hashed = 64; + assert_eq!( - engine.midstate(), + engine.midstate().expect("total_bytes_hashed is valid"), // RPC output - sha256::Midstate::from_byte_array([ + sha256::Midstate::new([ 0x0b, 0xcf, 0xe0, 0xe5, 0x4e, 0x6c, 0xc7, 0xd3, 0x4f, 0x4f, 0x7c, 0x1d, 0xf0, 0xb0, 0xf5, 0x03, 0xf2, 0xf7, 0x12, 0x91, 0x2a, 0x06, 0x05, 0xb4, 0x14, 0xed, 0x33, 0x7f, 0x7f, 0x03, 0x2e, 0x03, - ]) + ], total_bytes_hashed) ); } #[test] fn engine_with_state() { let mut engine = sha256::Hash::engine(); - let midstate_engine = sha256::HashEngine::from_midstate(engine.midstate(), 0); + let midstate_engine = sha256::HashEngine::from_midstate(engine.midstate_unchecked()); // Fresh engine and engine initialized with fresh state should have same state assert_eq!(engine.h, midstate_engine.h); @@ -960,7 +983,7 @@ mod tests { for data in data_vec { let mut engine = engine.clone(); let mut midstate_engine = - sha256::HashEngine::from_midstate(engine.midstate(), engine.length); + sha256::HashEngine::from_midstate(engine.midstate_unchecked()); assert_eq!(engine.h, midstate_engine.h); assert_eq!(engine.length, midstate_engine.length); engine.input(&data); @@ -989,7 +1012,7 @@ mod tests { 0x8f, 0xf1, 0xf7, 0xa9, 0xd5, 0x69, 0x09, 0x59, ]; let midstate_engine = - sha256::HashEngine::from_midstate(sha256::Midstate::from_byte_array(MIDSTATE), 64); + sha256::HashEngine::from_midstate(sha256::Midstate::new(MIDSTATE, 64)); let hash = sha256::Hash::from_engine(midstate_engine); assert_eq!(hash, sha256::Hash(HASH_EXPECTED)); } @@ -1011,29 +1034,24 @@ mod tests { } // The midstate of an empty hash engine tagged with "TapLeaf". - const TAP_LEAF_MIDSTATE: Midstate = 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, - ]); + const TAP_LEAF_MIDSTATE: Midstate = Midstate::new( + [ + 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, + ], + 64, + ); #[test] fn const_midstate() { assert_eq!(Midstate::hash_tag(b"TapLeaf"), TAP_LEAF_MIDSTATE,) } #[test] fn regression_midstate_debug_format() { - let want = "0x9ce0e4e67c116c3938b3caf2c30f5089d3f3936c47636e607db33eeaddc6f0c9"; + let want = "Midstate { bytes: 9ce0e4e67c116c3938b3caf2c30f5089d3f3936c47636e607db33eeaddc6f0c9, length: 64 }"; let got = format!("{:?}", TAP_LEAF_MIDSTATE); assert_eq!(got, want); } - #[test] - pub(crate) fn midstate_fmt_roundtrip() { - let midstate = Midstate::hash_tag(b"ArbitraryTag"); - let hex = format!("{}", midstate); - let rinsed = hex.parse::().expect("failed to parse hex"); - assert_eq!(rinsed, midstate) - } - #[cfg(feature = "serde")] #[test] fn sha256_serde() { diff --git a/hashes/src/sha256t.rs b/hashes/src/sha256t.rs index 3a924e136..c454013f4 100644 --- a/hashes/src/sha256t.rs +++ b/hashes/src/sha256t.rs @@ -196,10 +196,8 @@ macro_rules! sha256t_hash_newtype { impl $crate::sha256t::Tag for $tag { #[inline] fn engine() -> $crate::sha256::HashEngine { - 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) + const MIDSTATE: $crate::sha256::Midstate = $crate::sha256t_hash_newtype_tag_constructor!($constructor, $($tag_value)+); + $crate::sha256::HashEngine::from_midstate(MIDSTATE) } } @@ -280,13 +278,13 @@ macro_rules! sha256t_hash_newtype_tag { #[macro_export] macro_rules! sha256t_hash_newtype_tag_constructor { (hash_str, $value:expr) => { - ($crate::sha256::Midstate::hash_tag($value.as_bytes()), 64) + $crate::sha256::Midstate::hash_tag($value.as_bytes()) }; (hash_bytes, $value:expr) => { - ($crate::sha256::Midstate::hash_tag($value), 64) + $crate::sha256::Midstate::hash_tag($value) }; (raw, $bytes:expr, $len:expr) => { - ($crate::sha256::Midstate::from_byte_array($bytes), $len) + $crate::sha256::Midstate::new($bytes, $len) }; } @@ -314,8 +312,8 @@ mod tests { impl sha256t::Tag for TestHashTag { fn engine() -> sha256::HashEngine { // The TapRoot TapLeaf midstate. - let midstate = sha256::Midstate::from_byte_array(TEST_MIDSTATE); - sha256::HashEngine::from_midstate(midstate, 64) + let midstate = sha256::Midstate::new(TEST_MIDSTATE, 64); + sha256::HashEngine::from_midstate(midstate) } } diff --git a/hashes/tests/regression.rs b/hashes/tests/regression.rs index c4572a8d0..7fe15d758 100644 --- a/hashes/tests/regression.rs +++ b/hashes/tests/regression.rs @@ -38,8 +38,8 @@ pub struct RegHashTag; impl sha256t::Tag for RegHashTag { fn engine() -> sha256::HashEngine { - let midstate = sha256::Midstate::from_byte_array([0xab; 32]); - sha256::HashEngine::from_midstate(midstate, 64) + let midstate = sha256::Midstate::new([0xab; 32], 64); + sha256::HashEngine::from_midstate(midstate) } } From 86de586898448ab3d6d87fd2e6983466a654da0c Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 16 Jul 2024 06:54:59 +1000 Subject: [PATCH 11/12] Use const to construct Midstate As a bit more of an example of how to use the `sha256::Midstate` use a `static` in one of the unit tests. --- hashes/src/sha256.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 9f7756594..a2a60758d 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -950,17 +950,17 @@ mod tests { // 32 bytes of zeroes representing "new asset" engine.input(&[0; 32]); - let total_bytes_hashed = 64; + // RPC output + static WANT: Midstate = sha256::Midstate::new([ + 0x0b, 0xcf, 0xe0, 0xe5, 0x4e, 0x6c, 0xc7, 0xd3, + 0x4f, 0x4f, 0x7c, 0x1d, 0xf0, 0xb0, 0xf5, 0x03, + 0xf2, 0xf7, 0x12, 0x91, 0x2a, 0x06, 0x05, 0xb4, + 0x14, 0xed, 0x33, 0x7f, 0x7f, 0x03, 0x2e, 0x03, + ], 64); assert_eq!( engine.midstate().expect("total_bytes_hashed is valid"), - // RPC output - sha256::Midstate::new([ - 0x0b, 0xcf, 0xe0, 0xe5, 0x4e, 0x6c, 0xc7, 0xd3, - 0x4f, 0x4f, 0x7c, 0x1d, 0xf0, 0xb0, 0xf5, 0x03, - 0xf2, 0xf7, 0x12, 0x91, 0x2a, 0x06, 0x05, 0xb4, - 0x14, 0xed, 0x33, 0x7f, 0x7f, 0x03, 0x2e, 0x03, - ], total_bytes_hashed) + WANT, ); } From 98fe6179db3a2e770d5c52647153e60ed2d3c313 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 17 Jul 2024 06:06:06 +1000 Subject: [PATCH 12/12] Use "unfinalized" in header of Midstate The midstate has not been finalized [0], so use the term in the struct header. FTR I don't know _exactly_ what "finalized" means in the context of sha256 hashing (or hashing in general). This change came from a review suggestion and we have other mentions of "finalized" in the code. --- hashes/src/sha256.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index a2a60758d..ccdce1ea4 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -149,7 +149,7 @@ impl Hash { pub const fn const_hash(bytes: &[u8]) -> Self { Hash(Midstate::const_hash(bytes, true).bytes) } } -/// Output of the SHA256 hash function. +/// Unfinalized output of the SHA256 hash function. /// /// The `Midstate` type is obscure and specialized and should not be used unless you are sure of /// what you are doing.