Skip to main content

kaspa_hashes/
lib.rs

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
26// export libraries so it's possible to use macros
27pub 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// TODO: Check if we use hash more as an array of u64 or of bytes and change the default accordingly
37/// @category General
38#[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    /// # Panics
89    /// Panics if `bytes` length is not exactly `HASH_SIZE`.
90    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
124// Override the default Hash implementation, to: A. improve perf a bit (siphash works over u64s), B. allow a hasher to just take the first u64.
125// Don't change this without looking at `consensus/core/src/blockhash/BlockHashMap`.
126impl 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
133/// We only override PartialEq because clippy wants us to.
134/// This should always hold: PartialEq(x,y) => Hash(x) == Hash(y)
135impl 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}