clone_solana_blake3_hasher/
lib.rs

1//! Hashing with the [blake3] hash function.
2//!
3//! [blake3]: https://github.com/BLAKE3-team/BLAKE3
4#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
6#![no_std]
7#[cfg(feature = "std")]
8extern crate std;
9
10pub use clone_solana_hash::{ParseHashError, HASH_BYTES, MAX_BASE58_LEN};
11#[cfg(feature = "borsh")]
12use {
13    borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
14    std::string::ToString,
15};
16use {
17    clone_solana_sanitize::Sanitize,
18    core::{fmt, str::FromStr},
19};
20
21// TODO: replace this with `clone_solana_hash::Hash` in the
22// next breaking change.
23// It's a breaking change because the field is public
24// here and private in `clone_solana_hash`, and making
25// it public in `clone_solana_hash` would break wasm-bindgen
26#[cfg_attr(
27    feature = "frozen-abi",
28    derive(clone_solana_frozen_abi_macro::AbiExample)
29)]
30#[cfg_attr(
31    feature = "borsh",
32    derive(BorshSerialize, BorshDeserialize, BorshSchema),
33    borsh(crate = "borsh")
34)]
35#[cfg_attr(
36    feature = "serde",
37    derive(serde_derive::Deserialize, serde_derive::Serialize)
38)]
39#[derive(Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
40#[repr(transparent)]
41pub struct Hash(pub [u8; HASH_BYTES]);
42
43#[cfg(any(feature = "blake3", not(target_os = "solana")))]
44#[derive(Clone, Default)]
45pub struct Hasher {
46    hasher: blake3::Hasher,
47}
48
49#[cfg(any(feature = "blake3", not(target_os = "solana")))]
50impl Hasher {
51    pub fn hash(&mut self, val: &[u8]) {
52        self.hasher.update(val);
53    }
54    pub fn hashv(&mut self, vals: &[&[u8]]) {
55        for val in vals {
56            self.hash(val);
57        }
58    }
59    pub fn result(self) -> Hash {
60        Hash(*self.hasher.finalize().as_bytes())
61    }
62}
63
64impl From<clone_solana_hash::Hash> for Hash {
65    fn from(val: clone_solana_hash::Hash) -> Self {
66        Self(val.to_bytes())
67    }
68}
69
70impl From<Hash> for clone_solana_hash::Hash {
71    fn from(val: Hash) -> Self {
72        Self::new_from_array(val.0)
73    }
74}
75
76impl Sanitize for Hash {}
77
78impl AsRef<[u8]> for Hash {
79    fn as_ref(&self) -> &[u8] {
80        &self.0[..]
81    }
82}
83
84impl fmt::Debug for Hash {
85    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86        let converted: clone_solana_hash::Hash = (*self).into();
87        fmt::Debug::fmt(&converted, f)
88    }
89}
90
91impl fmt::Display for Hash {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        let converted: clone_solana_hash::Hash = (*self).into();
94        fmt::Display::fmt(&converted, f)
95    }
96}
97
98impl FromStr for Hash {
99    type Err = ParseHashError;
100
101    fn from_str(s: &str) -> Result<Self, Self::Err> {
102        let unconverted = clone_solana_hash::Hash::from_str(s)?;
103        Ok(unconverted.into())
104    }
105}
106
107impl Hash {
108    #[deprecated(since = "2.2.0", note = "Use 'Hash::new_from_array' instead")]
109    pub fn new(hash_slice: &[u8]) -> Self {
110        #[allow(deprecated)]
111        Self::from(clone_solana_hash::Hash::new(hash_slice))
112    }
113
114    pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self {
115        Self(hash_array)
116    }
117
118    /// unique Hash for tests and benchmarks.
119    pub fn new_unique() -> Self {
120        Self::from(clone_solana_hash::Hash::new_unique())
121    }
122
123    pub fn to_bytes(self) -> [u8; HASH_BYTES] {
124        self.0
125    }
126}
127
128/// Return a Blake3 hash for the given data.
129pub fn hashv(vals: &[&[u8]]) -> Hash {
130    // Perform the calculation inline, calling this from within a program is
131    // not supported
132    #[cfg(not(target_os = "solana"))]
133    {
134        let mut hasher = Hasher::default();
135        hasher.hashv(vals);
136        hasher.result()
137    }
138    // Call via a system call to perform the calculation
139    #[cfg(target_os = "solana")]
140    {
141        let mut hash_result = [0; HASH_BYTES];
142        unsafe {
143            clone_solana_define_syscall::definitions::sol_blake3(
144                vals as *const _ as *const u8,
145                vals.len() as u64,
146                &mut hash_result as *mut _ as *mut u8,
147            );
148        }
149        Hash::new_from_array(hash_result)
150    }
151}
152
153/// Return a Blake3 hash for the given data.
154pub fn hash(val: &[u8]) -> Hash {
155    hashv(&[val])
156}
157
158#[cfg(feature = "std")]
159/// Return the hash of the given hash extended with the given value.
160pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
161    let mut hash_data = id.as_ref().to_vec();
162    hash_data.extend_from_slice(val);
163    hash(&hash_data)
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_new_unique() {
172        assert!(Hash::new_unique() != Hash::new_unique());
173    }
174
175    #[test]
176    fn test_hash_fromstr() {
177        let hash = hash(&[1u8]);
178
179        let mut hash_base58_str = bs58::encode(hash).into_string();
180
181        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
182
183        hash_base58_str.push_str(&bs58::encode(hash.0).into_string());
184        assert_eq!(
185            hash_base58_str.parse::<Hash>(),
186            Err(ParseHashError::WrongSize)
187        );
188
189        hash_base58_str.truncate(hash_base58_str.len() / 2);
190        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
191
192        hash_base58_str.truncate(hash_base58_str.len() / 2);
193        assert_eq!(
194            hash_base58_str.parse::<Hash>(),
195            Err(ParseHashError::WrongSize)
196        );
197
198        let input_too_big = bs58::encode(&[0xffu8; HASH_BYTES + 1]).into_string();
199        assert!(input_too_big.len() > MAX_BASE58_LEN);
200        assert_eq!(
201            input_too_big.parse::<Hash>(),
202            Err(ParseHashError::WrongSize)
203        );
204
205        let mut hash_base58_str = bs58::encode(hash.0).into_string();
206        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
207
208        // throw some non-base58 stuff in there
209        hash_base58_str.replace_range(..1, "I");
210        assert_eq!(
211            hash_base58_str.parse::<Hash>(),
212            Err(ParseHashError::Invalid)
213        );
214    }
215
216    #[test]
217    fn test_extend_and_hash() {
218        let val = "gHiljKpq";
219        let val_hash = hash(val.as_bytes());
220        let ext = "lM890t";
221        let hash_ext = [&val_hash.0, ext.as_bytes()].concat();
222        let ext_hash = extend_and_hash(&val_hash, ext.as_bytes());
223        assert!(ext_hash == hash(&hash_ext));
224    }
225}