gemachain_program/
hash.rs

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