kaspa_hashes/
lib.rs

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// TODO: Check if we use hash more as an array of u64 or of bytes and change the default accordingly
24/// @category General
25#[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    /// # Panics
59    /// Panics if `bytes` length is not exactly `HASH_SIZE`.
60    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
94// 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.
95// Don't change this without looking at `consensus/core/src/blockhash/BlockHashMap`.
96impl 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
103/// We only override PartialEq because clippy wants us to.
104/// This should always hold: PartialEq(x,y) => Hash(x) == Hash(y)
105impl 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}