waffles_solana_program/
hash.rs1use {
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
14pub const HASH_BYTES: usize = 32;
16const MAX_BASE58_LEN: usize = 44;
18
19#[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 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
145pub fn hashv(vals: &[&[u8]]) -> Hash {
147 #[cfg(not(target_os = "solana"))]
150 {
151 let mut hasher = Hasher::default();
152 hasher.hashv(vals);
153 hasher.result()
154 }
155 #[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
170pub fn hash(val: &[u8]) -> Hash {
172 hashv(&[val])
173}
174
175pub 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 hash_base58_str.replace_range(..1, "I");
226 assert_eq!(
227 hash_base58_str.parse::<Hash>(),
228 Err(ParseHashError::Invalid)
229 );
230 }
231}