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