waffles_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 `solana-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        Hash(self.hasher.finalize().into())
65    }
66}
67
68impl Sanitize for Hash {}
69
70impl From<[u8; HASH_BYTES]> for Hash {
71    fn from(from: [u8; 32]) -> Self {
72        Self(from)
73    }
74}
75
76impl AsRef<[u8]> for Hash {
77    fn as_ref(&self) -> &[u8] {
78        &self.0[..]
79    }
80}
81
82impl fmt::Debug for Hash {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        write!(f, "{}", bs58::encode(self.0).into_string())
85    }
86}
87
88impl fmt::Display for Hash {
89    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90        write!(f, "{}", bs58::encode(self.0).into_string())
91    }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Error)]
95pub enum ParseHashError {
96    #[error("string decoded to wrong size for hash")]
97    WrongSize,
98    #[error("failed to decoded string to hash")]
99    Invalid,
100}
101
102impl FromStr for Hash {
103    type Err = ParseHashError;
104
105    fn from_str(s: &str) -> Result<Self, Self::Err> {
106        if s.len() > MAX_BASE58_LEN {
107            return Err(ParseHashError::WrongSize);
108        }
109        let bytes = bs58::decode(s)
110            .into_vec()
111            .map_err(|_| ParseHashError::Invalid)?;
112        if bytes.len() != mem::size_of::<Hash>() {
113            Err(ParseHashError::WrongSize)
114        } else {
115            Ok(Hash::new(&bytes))
116        }
117    }
118}
119
120impl Hash {
121    pub fn new(hash_slice: &[u8]) -> Self {
122        Hash(<[u8; HASH_BYTES]>::try_from(hash_slice).unwrap())
123    }
124
125    pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self {
126        Self(hash_array)
127    }
128
129    /// unique Hash for tests and benchmarks.
130    pub fn new_unique() -> Self {
131        use crate::atomic_u64::AtomicU64;
132        static I: AtomicU64 = AtomicU64::new(1);
133
134        let mut b = [0u8; HASH_BYTES];
135        let i = I.fetch_add(1);
136        b[0..8].copy_from_slice(&i.to_le_bytes());
137        Self::new(&b)
138    }
139
140    pub fn to_bytes(self) -> [u8; HASH_BYTES] {
141        self.0
142    }
143}
144
145/// Return a Sha256 hash for the given data.
146pub fn hashv(vals: &[&[u8]]) -> Hash {
147    // Perform the calculation inline, calling this from within a program is
148    // not supported
149    #[cfg(not(target_os = "solana"))]
150    {
151        let mut hasher = Hasher::default();
152        hasher.hashv(vals);
153        hasher.result()
154    }
155    // Call via a system call to perform the calculation
156    #[cfg(target_os = "solana")]
157    {
158        let mut hash_result = [0; HASH_BYTES];
159        unsafe {
160            crate::syscalls::sol_sha256(
161                vals as *const _ as *const u8,
162                vals.len() as u64,
163                &mut hash_result as *mut _ as *mut u8,
164            );
165        }
166        Hash::new_from_array(hash_result)
167    }
168}
169
170/// Return a Sha256 hash for the given data.
171pub fn hash(val: &[u8]) -> Hash {
172    hashv(&[val])
173}
174
175/// Return the hash of the given hash extended with the given value.
176pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
177    let mut hash_data = id.as_ref().to_vec();
178    hash_data.extend_from_slice(val);
179    hash(&hash_data)
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_new_unique() {
188        assert!(Hash::new_unique() != Hash::new_unique());
189    }
190
191    #[test]
192    fn test_hash_fromstr() {
193        let hash = hash(&[1u8]);
194
195        let mut hash_base58_str = bs58::encode(hash).into_string();
196
197        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
198
199        hash_base58_str.push_str(&bs58::encode(hash.0).into_string());
200        assert_eq!(
201            hash_base58_str.parse::<Hash>(),
202            Err(ParseHashError::WrongSize)
203        );
204
205        hash_base58_str.truncate(hash_base58_str.len() / 2);
206        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
207
208        hash_base58_str.truncate(hash_base58_str.len() / 2);
209        assert_eq!(
210            hash_base58_str.parse::<Hash>(),
211            Err(ParseHashError::WrongSize)
212        );
213
214        let input_too_big = bs58::encode(&[0xffu8; HASH_BYTES + 1]).into_string();
215        assert!(input_too_big.len() > MAX_BASE58_LEN);
216        assert_eq!(
217            input_too_big.parse::<Hash>(),
218            Err(ParseHashError::WrongSize)
219        );
220
221        let mut hash_base58_str = bs58::encode(hash.0).into_string();
222        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
223
224        // throw some non-base58 stuff in there
225        hash_base58_str.replace_range(..1, "I");
226        assert_eq!(
227            hash_base58_str.parse::<Hash>(),
228            Err(ParseHashError::Invalid)
229        );
230    }
231}