gemachain_program/
blake3.rs

1//! The `blake3` module provides functions for creating hashes.
2use crate::sanitize::Sanitize;
3use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
4use std::{convert::TryFrom, fmt, mem, str::FromStr};
5use thiserror::Error;
6
7/// Size of hash
8pub const HASH_BYTES: usize = 32;
9/// Maximum string length of a base58 encoded hash
10const MAX_BASE58_LEN: usize = 44;
11#[derive(
12    Serialize,
13    Deserialize,
14    BorshSerialize,
15    BorshDeserialize,
16    BorshSchema,
17    Clone,
18    Copy,
19    Default,
20    Eq,
21    PartialEq,
22    Ord,
23    PartialOrd,
24    Hash,
25    AbiExample,
26)]
27#[repr(transparent)]
28pub struct Hash(pub [u8; HASH_BYTES]);
29
30#[derive(Clone, Default)]
31pub struct Hasher {
32    hasher: blake3::Hasher,
33}
34
35impl Hasher {
36    pub fn hash(&mut self, val: &[u8]) {
37        self.hasher.update(val);
38    }
39    pub fn hashv(&mut self, vals: &[&[u8]]) {
40        for val in vals {
41            self.hash(val);
42        }
43    }
44    pub fn result(self) -> Hash {
45        Hash(*self.hasher.finalize().as_bytes())
46    }
47}
48
49impl Sanitize for Hash {}
50
51impl AsRef<[u8]> for Hash {
52    fn as_ref(&self) -> &[u8] {
53        &self.0[..]
54    }
55}
56
57impl fmt::Debug for Hash {
58    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59        write!(f, "{}", bs58::encode(self.0).into_string())
60    }
61}
62
63impl fmt::Display for Hash {
64    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65        write!(f, "{}", bs58::encode(self.0).into_string())
66    }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Error)]
70pub enum ParseHashError {
71    #[error("string decoded to wrong size for hash")]
72    WrongSize,
73    #[error("failed to decoded string to hash")]
74    Invalid,
75}
76
77impl FromStr for Hash {
78    type Err = ParseHashError;
79
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        if s.len() > MAX_BASE58_LEN {
82            return Err(ParseHashError::WrongSize);
83        }
84        let bytes = bs58::decode(s)
85            .into_vec()
86            .map_err(|_| ParseHashError::Invalid)?;
87        if bytes.len() != mem::size_of::<Hash>() {
88            Err(ParseHashError::WrongSize)
89        } else {
90            Ok(Hash::new(&bytes))
91        }
92    }
93}
94
95impl Hash {
96    pub fn new(hash_slice: &[u8]) -> Self {
97        Hash(<[u8; HASH_BYTES]>::try_from(hash_slice).unwrap())
98    }
99
100    pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self {
101        Self(hash_array)
102    }
103
104    /// unique Hash for tests and benchmarks.
105    pub fn new_unique() -> Self {
106        use std::sync::atomic::{AtomicU64, Ordering};
107        static I: AtomicU64 = AtomicU64::new(1);
108
109        let mut b = [0u8; HASH_BYTES];
110        let i = I.fetch_add(1, Ordering::Relaxed);
111        b[0..8].copy_from_slice(&i.to_le_bytes());
112        Self::new(&b)
113    }
114
115    pub fn to_bytes(self) -> [u8; HASH_BYTES] {
116        self.0
117    }
118}
119
120/// Return a Blake3 hash for the given data.
121pub fn hashv(vals: &[&[u8]]) -> Hash {
122    // Perform the calculation inline, calling this from within a program is
123    // not supported
124    #[cfg(not(target_arch = "bpf"))]
125    {
126        let mut hasher = Hasher::default();
127        hasher.hashv(vals);
128        hasher.result()
129    }
130    // Call via a system call to perform the calculation
131    #[cfg(target_arch = "bpf")]
132    {
133        extern "C" {
134            fn gema_blake3(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64;
135        }
136        let mut hash_result = [0; HASH_BYTES];
137        unsafe {
138            gema_blake3(
139                vals as *const _ as *const u8,
140                vals.len() as u64,
141                &mut hash_result as *mut _ as *mut u8,
142            );
143        }
144        Hash::new_from_array(hash_result)
145    }
146}
147
148/// Return a Blake3 hash for the given data.
149pub fn hash(val: &[u8]) -> Hash {
150    hashv(&[val])
151}
152
153/// Return the hash of the given hash extended with the given value.
154pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
155    let mut hash_data = id.as_ref().to_vec();
156    hash_data.extend_from_slice(val);
157    hash(&hash_data)
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_new_unique() {
166        assert!(Hash::new_unique() != Hash::new_unique());
167    }
168
169    #[test]
170    fn test_hash_fromstr() {
171        let hash = hash(&[1u8]);
172
173        let mut hash_base58_str = bs58::encode(hash).into_string();
174
175        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
176
177        hash_base58_str.push_str(&bs58::encode(hash.0).into_string());
178        assert_eq!(
179            hash_base58_str.parse::<Hash>(),
180            Err(ParseHashError::WrongSize)
181        );
182
183        hash_base58_str.truncate(hash_base58_str.len() / 2);
184        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
185
186        hash_base58_str.truncate(hash_base58_str.len() / 2);
187        assert_eq!(
188            hash_base58_str.parse::<Hash>(),
189            Err(ParseHashError::WrongSize)
190        );
191
192        let input_too_big = bs58::encode(&[0xffu8; HASH_BYTES + 1]).into_string();
193        assert!(input_too_big.len() > MAX_BASE58_LEN);
194        assert_eq!(
195            input_too_big.parse::<Hash>(),
196            Err(ParseHashError::WrongSize)
197        );
198
199        let mut hash_base58_str = bs58::encode(hash.0).into_string();
200        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
201
202        // throw some non-base58 stuff in there
203        hash_base58_str.replace_range(..1, "I");
204        assert_eq!(
205            hash_base58_str.parse::<Hash>(),
206            Err(ParseHashError::Invalid)
207        );
208    }
209
210    #[test]
211    fn test_extend_and_hash() {
212        let val = "gHiljKpq";
213        let val_hash = hash(val.as_bytes());
214        let ext = "lM890t";
215        let hash_ext = [&val_hash.0, ext.as_bytes()].concat();
216        let ext_hash = extend_and_hash(&val_hash, ext.as_bytes());
217        assert!(ext_hash == hash(&hash_ext));
218    }
219}