1mod hashers;
2mod pow_hashers;
3
4use borsh::{BorshDeserialize, BorshSerialize};
5use kaspa_utils::{
6 hex::{FromHex, ToHex},
7 mem_size::MemSizeEstimator,
8 serde_impl_deser_fixed_bytes_ref, serde_impl_ser_fixed_bytes_ref,
9};
10use std::{
11 array::TryFromSliceError,
12 fmt::{Debug, Display, Formatter},
13 hash::{Hash as StdHash, Hasher as StdHasher},
14 str::{self, FromStr},
15};
16use wasm_bindgen::prelude::*;
17use workflow_wasm::prelude::*;
18
19pub const HASH_SIZE: usize = 32;
20
21pub use hashers::*;
22
23#[derive(Eq, Clone, Copy, Default, PartialOrd, Ord, BorshSerialize, BorshDeserialize, CastFromJs)]
26#[wasm_bindgen]
27pub struct Hash([u8; HASH_SIZE]);
28
29serde_impl_ser_fixed_bytes_ref!(Hash, HASH_SIZE);
30serde_impl_deser_fixed_bytes_ref!(Hash, HASH_SIZE);
31
32impl From<[u8; HASH_SIZE]> for Hash {
33 fn from(value: [u8; HASH_SIZE]) -> Self {
34 Hash(value)
35 }
36}
37
38impl TryFrom<&[u8]> for Hash {
39 type Error = TryFromSliceError;
40
41 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
42 Hash::try_from_slice(value)
43 }
44}
45
46impl Hash {
47 #[inline(always)]
48 pub const fn from_bytes(bytes: [u8; HASH_SIZE]) -> Self {
49 Hash(bytes)
50 }
51
52 #[inline(always)]
53 pub const fn as_bytes(self) -> [u8; 32] {
54 self.0
55 }
56
57 #[inline(always)]
58 pub fn from_slice(bytes: &[u8]) -> Self {
61 Self(<[u8; HASH_SIZE]>::try_from(bytes).expect("Slice must have the length of Hash"))
62 }
63
64 #[inline(always)]
65 pub fn try_from_slice(bytes: &[u8]) -> Result<Self, TryFromSliceError> {
66 Ok(Self(<[u8; HASH_SIZE]>::try_from(bytes)?))
67 }
68
69 #[inline(always)]
70 pub fn to_le_u64(self) -> [u64; 4] {
71 let mut out = [0u64; 4];
72 out.iter_mut().zip(self.iter_le_u64()).for_each(|(out, word)| *out = word);
73 out
74 }
75
76 #[inline(always)]
77 pub fn iter_le_u64(&self) -> impl ExactSizeIterator<Item = u64> + '_ {
78 self.0.chunks_exact(8).map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()))
79 }
80
81 #[inline(always)]
82 pub fn from_le_u64(arr: [u64; 4]) -> Self {
83 let mut ret = [0; HASH_SIZE];
84 ret.chunks_exact_mut(8).zip(arr.iter()).for_each(|(bytes, word)| bytes.copy_from_slice(&word.to_le_bytes()));
85 Self(ret)
86 }
87
88 #[inline(always)]
89 pub fn from_u64_word(word: u64) -> Self {
90 Self::from_le_u64([0, 0, 0, word])
91 }
92}
93
94impl StdHash for Hash {
97 #[inline(always)]
98 fn hash<H: StdHasher>(&self, state: &mut H) {
99 self.iter_le_u64().for_each(|x| x.hash(state));
100 }
101}
102
103impl PartialEq for Hash {
106 #[inline(always)]
107 fn eq(&self, other: &Self) -> bool {
108 self.0 == other.0
109 }
110}
111
112impl Display for Hash {
113 #[inline]
114 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115 let mut hex = [0u8; HASH_SIZE * 2];
116 faster_hex::hex_encode(&self.0, &mut hex).expect("The output is exactly twice the size of the input");
117 f.write_str(unsafe { str::from_utf8_unchecked(&hex) })
118 }
119}
120
121impl Debug for Hash {
122 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
123 std::fmt::Display::fmt(&self, f)
124 }
125}
126
127impl FromStr for Hash {
128 type Err = faster_hex::Error;
129
130 #[inline]
131 fn from_str(hash_str: &str) -> Result<Self, Self::Err> {
132 let mut bytes = [0u8; HASH_SIZE];
133 faster_hex::hex_decode(hash_str.as_bytes(), &mut bytes)?;
134 Ok(Hash(bytes))
135 }
136}
137
138impl From<u64> for Hash {
139 #[inline(always)]
140 fn from(word: u64) -> Self {
141 Self::from_u64_word(word)
142 }
143}
144
145impl AsRef<[u8; HASH_SIZE]> for Hash {
146 #[inline(always)]
147 fn as_ref(&self) -> &[u8; HASH_SIZE] {
148 &self.0
149 }
150}
151
152impl AsRef<[u8]> for Hash {
153 #[inline(always)]
154 fn as_ref(&self) -> &[u8] {
155 &self.0
156 }
157}
158
159impl ToHex for Hash {
160 fn to_hex(&self) -> String {
161 self.to_string()
162 }
163}
164
165impl FromHex for Hash {
166 type Error = faster_hex::Error;
167 fn from_hex(hex_str: &str) -> Result<Self, Self::Error> {
168 Self::from_str(hex_str)
169 }
170}
171
172impl MemSizeEstimator for Hash {}
173
174#[wasm_bindgen]
175impl Hash {
176 #[wasm_bindgen(constructor)]
177 pub fn constructor(hex_str: &str) -> Self {
178 Hash::from_str(hex_str).expect("invalid hash value")
179 }
180
181 #[wasm_bindgen(js_name = toString)]
182 pub fn js_to_string(&self) -> String {
183 self.to_string()
184 }
185}
186
187type TryFromError = workflow_wasm::error::Error;
188impl TryCastFromJs for Hash {
189 type Error = TryFromError;
190 fn try_cast_from<'a, R>(value: &'a R) -> Result<Cast<Self>, Self::Error>
191 where
192 R: AsRef<JsValue> + 'a,
193 {
194 Self::resolve(value, || {
195 let bytes = value.as_ref().try_as_vec_u8()?;
196 Ok(Hash(
197 <[u8; HASH_SIZE]>::try_from(bytes)
198 .map_err(|_| TryFromError::WrongSize("Slice must have the length of Hash".into()))?,
199 ))
200 })
201 }
202}
203
204pub const ZERO_HASH: Hash = Hash([0; HASH_SIZE]);
205
206#[cfg(test)]
207mod tests {
208 use super::Hash;
209 use std::str::FromStr;
210
211 #[test]
212 fn test_hash_basics() {
213 let hash_str = "8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3af";
214 let hash = Hash::from_str(hash_str).unwrap();
215 assert_eq!(hash_str, hash.to_string());
216 let hash2 = Hash::from_str(hash_str).unwrap();
217 assert_eq!(hash, hash2);
218
219 let hash3 = Hash::from_str("8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3ab").unwrap();
220 assert_ne!(hash2, hash3);
221
222 let odd_str = "8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3a";
223 let short_str = "8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3";
224
225 assert!(matches!(dbg!(Hash::from_str(odd_str)), Err(faster_hex::Error::InvalidLength(len)) if len == 64));
226 assert!(matches!(dbg!(Hash::from_str(short_str)), Err(faster_hex::Error::InvalidLength(len)) if len == 64));
227 }
228}