1use core::marker::PhantomData;
7use core::ops::Index;
8use core::slice::SliceIndex;
9use core::{cmp, str};
10
11#[cfg(feature = "schemars")]
12use alloc::{borrow::ToOwned, boxed::Box, string::String};
13
14use crate::{sha256, FromSliceError};
15
16type HashEngine = sha256::HashEngine;
17
18pub trait Tag {
20 fn engine() -> sha256::HashEngine;
22}
23
24#[repr(transparent)]
26pub struct Hash<T: Tag>([u8; 32], PhantomData<T>);
27
28#[cfg(feature = "schemars")]
29impl<T: Tag> schemars::JsonSchema for Hash<T> {
30 fn schema_name() -> String { "Hash".to_owned() }
31
32 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
33 let mut schema: schemars::schema::SchemaObject = <String>::json_schema(gen).into();
34 schema.string = Some(Box::new(schemars::schema::StringValidation {
35 max_length: Some(32 * 2),
36 min_length: Some(32 * 2),
37 pattern: Some("[0-9a-fA-F]+".to_owned()),
38 }));
39 schema.into()
40 }
41}
42
43impl<T: Tag> Hash<T> {
44 fn internal_new(arr: [u8; 32]) -> Self { Hash(arr, Default::default()) }
45
46 fn internal_engine() -> HashEngine { T::engine() }
47}
48
49impl<T: Tag> Copy for Hash<T> {}
50impl<T: Tag> Clone for Hash<T> {
51 fn clone(&self) -> Self { *self }
52}
53impl<T: Tag> PartialEq for Hash<T> {
54 fn eq(&self, other: &Hash<T>) -> bool { self.0 == other.0 }
55}
56impl<T: Tag> Eq for Hash<T> {}
57impl<T: Tag> Default for Hash<T> {
58 fn default() -> Self { Hash([0; 32], PhantomData) }
59}
60impl<T: Tag> PartialOrd for Hash<T> {
61 fn partial_cmp(&self, other: &Hash<T>) -> Option<cmp::Ordering> {
62 Some(cmp::Ord::cmp(self, other))
63 }
64}
65impl<T: Tag> Ord for Hash<T> {
66 fn cmp(&self, other: &Hash<T>) -> cmp::Ordering { cmp::Ord::cmp(&self.0, &other.0) }
67}
68impl<T: Tag> core::hash::Hash for Hash<T> {
69 fn hash<H: core::hash::Hasher>(&self, h: &mut H) { self.0.hash(h) }
70}
71
72crate::internal_macros::hash_trait_impls!(256, true, T: Tag);
73
74fn from_engine<T: Tag>(e: sha256::HashEngine) -> Hash<T> {
75 use crate::Hash as _;
76
77 Hash::from_byte_array(sha256::Hash::from_engine(e).to_byte_array())
78}
79
80#[macro_export]
118macro_rules! sha256t_hash_newtype {
119 ($($(#[$($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)*])* _);)+) => {
120 $(
121 $crate::sha256t_hash_newtype_tag!($tag_vis, $tag, stringify!($hash_name), $(#[$($tag_attr)*])*);
122
123 impl $crate::sha256t::Tag for $tag {
124 #[inline]
125 fn engine() -> $crate::sha256::HashEngine {
126 const MIDSTATE: ($crate::sha256::Midstate, usize) = $crate::sha256t_hash_newtype_tag_constructor!($constructor, $($tag_value)+);
127 #[allow(unused)]
128 const _LENGTH_CHECK: () = [(); 1][MIDSTATE.1 % 64];
129 $crate::sha256::HashEngine::from_midstate(MIDSTATE.0, MIDSTATE.1)
130 }
131 }
132
133 $crate::hash_newtype! {
134 $(#[$($hash_attr)*])*
135 $hash_vis struct $hash_name($(#[$($field_attr)*])* $crate::sha256t::Hash<$tag>);
136 }
137 )+
138 }
139}
140
141#[doc(hidden)]
143#[macro_export]
144macro_rules! sha256t_hash_newtype_tag {
145 ($vis:vis, $tag:ident, $name:expr, $(#[$($attr:meta)*])*) => {
146 #[doc = "The tag used for [`"]
147 #[doc = $name]
148 #[doc = "`]\n\n"]
149 $(#[$($attr)*])*
150 #[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
151 $vis struct $tag;
152 };
153}
154
155#[doc(hidden)]
156#[macro_export]
157macro_rules! sha256t_hash_newtype_tag_constructor {
158 (hash_str, $value:expr) => {
159 ($crate::sha256::Midstate::hash_tag($value.as_bytes()), 64)
160 };
161 (hash_bytes, $value:expr) => {
162 ($crate::sha256::Midstate::hash_tag($value), 64)
163 };
164 (raw, $bytes:expr, $len:expr) => {
165 ($crate::sha256::Midstate::from_byte_array($bytes), $len)
166 };
167}
168
169#[cfg(test)]
170mod tests {
171 #[cfg(feature = "schemars")]
172 use alloc::{borrow::ToOwned, boxed::Box, string::String};
173
174 #[cfg(feature = "alloc")]
175 use crate::Hash;
176 #[cfg(feature = "alloc")]
177 use crate::{sha256, sha256t};
178
179 const TEST_MIDSTATE: [u8; 32] = [
180 156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211, 243, 147,
181 108, 71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201,
182 ];
183
184 #[cfg(feature = "alloc")]
185 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
186 pub struct TestHashTag;
187
188 #[cfg(feature = "alloc")]
189 impl sha256t::Tag for TestHashTag {
190 fn engine() -> sha256::HashEngine {
191 let midstate = sha256::Midstate::from_byte_array(TEST_MIDSTATE);
193 sha256::HashEngine::from_midstate(midstate, 64)
194 }
195 }
196
197 #[cfg(feature = "schemars")]
198 impl schemars::JsonSchema for TestHashTag {
199 fn schema_name() -> String { "Hash".to_owned() }
200
201 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
202 let mut schema: schemars::schema::SchemaObject = <String>::json_schema(gen).into();
203 schema.string = Some(Box::new(schemars::schema::StringValidation {
204 max_length: Some(64 * 2),
205 min_length: Some(64 * 2),
206 pattern: Some("[0-9a-fA-F]+".to_owned()),
207 }));
208 schema.into()
209 }
210 }
211
212 #[cfg(feature = "alloc")]
214 pub type TestHash = sha256t::Hash<TestHashTag>;
215
216 sha256t_hash_newtype! {
217 struct NewTypeTag = raw(TEST_MIDSTATE, 64);
219
220 #[hash_newtype(backward)]
222 struct NewTypeHash(_);
223 }
224
225 #[test]
226 #[cfg(feature = "alloc")]
227 fn test_sha256t() {
228 assert_eq!(
229 TestHash::hash(&[0]).to_string(),
230 "29589d5122ec666ab5b4695070b6debc63881a4f85d88d93ddc90078038213ed"
231 );
232 assert_eq!(
233 NewTypeHash::hash(&[0]).to_string(),
234 "29589d5122ec666ab5b4695070b6debc63881a4f85d88d93ddc90078038213ed"
235 );
236 }
237}