solana_program/
hash.rs

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