1use std::fmt::{self, Debug, Display};
4use std::str::FromStr;
5
6use borsh::{BorshDeserialize, BorshSerialize};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use sha2::{Digest, Sha256};
9
10use crate::error::ParseHashError;
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
14pub struct CryptoHash([u8; 32]);
15
16impl CryptoHash {
17 pub const ZERO: Self = Self([0; 32]);
19
20 pub fn hash(data: &[u8]) -> Self {
22 let result = Sha256::digest(data);
23 let mut bytes = [0u8; 32];
24 bytes.copy_from_slice(&result);
25 Self(bytes)
26 }
27
28 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
30 Self(bytes)
31 }
32
33 pub const fn as_bytes(&self) -> &[u8; 32] {
35 &self.0
36 }
37
38 pub fn to_vec(&self) -> Vec<u8> {
40 self.0.to_vec()
41 }
42
43 pub fn is_zero(&self) -> bool {
45 self.0 == [0u8; 32]
46 }
47}
48
49impl FromStr for CryptoHash {
50 type Err = ParseHashError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 let bytes = bs58::decode(s)
54 .into_vec()
55 .map_err(|e| ParseHashError::InvalidBase58(e.to_string()))?;
56
57 if bytes.len() != 32 {
58 return Err(ParseHashError::InvalidLength(bytes.len()));
59 }
60
61 let mut arr = [0u8; 32];
62 arr.copy_from_slice(&bytes);
63 Ok(Self(arr))
64 }
65}
66
67impl TryFrom<&str> for CryptoHash {
68 type Error = ParseHashError;
69
70 fn try_from(s: &str) -> Result<Self, Self::Error> {
71 s.parse()
72 }
73}
74
75impl TryFrom<&[u8]> for CryptoHash {
76 type Error = ParseHashError;
77
78 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
79 if bytes.len() != 32 {
80 return Err(ParseHashError::InvalidLength(bytes.len()));
81 }
82 let mut arr = [0u8; 32];
83 arr.copy_from_slice(bytes);
84 Ok(Self(arr))
85 }
86}
87
88impl From<[u8; 32]> for CryptoHash {
89 fn from(bytes: [u8; 32]) -> Self {
90 Self(bytes)
91 }
92}
93
94impl AsRef<[u8]> for CryptoHash {
95 fn as_ref(&self) -> &[u8] {
96 &self.0
97 }
98}
99
100impl Display for CryptoHash {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(f, "{}", bs58::encode(&self.0).into_string())
103 }
104}
105
106impl Debug for CryptoHash {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "CryptoHash({})", self)
109 }
110}
111
112impl Serialize for CryptoHash {
113 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
114 s.serialize_str(&self.to_string())
115 }
116}
117
118impl<'de> Deserialize<'de> for CryptoHash {
119 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
120 let s: String = serde::Deserialize::deserialize(d)?;
121 s.parse().map_err(serde::de::Error::custom)
122 }
123}
124
125impl BorshSerialize for CryptoHash {
126 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
127 writer.write_all(&self.0)
128 }
129}
130
131impl BorshDeserialize for CryptoHash {
132 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
133 let mut bytes = [0u8; 32];
134 reader.read_exact(&mut bytes)?;
135 Ok(Self(bytes))
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_hash() {
145 let hash = CryptoHash::hash(b"hello world");
146 assert!(!hash.is_zero());
147 assert_eq!(hash.as_bytes().len(), 32);
148 }
149
150 #[test]
151 fn test_display_parse_roundtrip() {
152 let hash = CryptoHash::hash(b"test data");
153 let s = hash.to_string();
154 let parsed: CryptoHash = s.parse().unwrap();
155 assert_eq!(hash, parsed);
156 }
157
158 #[test]
159 fn test_zero() {
160 assert!(CryptoHash::ZERO.is_zero());
161 assert!(!CryptoHash::hash(b"x").is_zero());
162 }
163
164 #[test]
165 fn test_from_bytes() {
166 let bytes = [42u8; 32];
167 let hash = CryptoHash::from_bytes(bytes);
168 assert_eq!(hash.as_bytes(), &bytes);
169 }
170
171 #[test]
172 fn test_to_vec() {
173 let hash = CryptoHash::hash(b"test");
174 let vec = hash.to_vec();
175 assert_eq!(vec.len(), 32);
176 assert_eq!(vec.as_slice(), hash.as_bytes());
177 }
178
179 #[test]
180 fn test_from_32_byte_array() {
181 let bytes = [1u8; 32];
182 let hash: CryptoHash = bytes.into();
183 assert_eq!(hash.as_bytes(), &bytes);
184 }
185
186 #[test]
187 fn test_try_from_slice_success() {
188 let bytes = [2u8; 32];
189 let hash = CryptoHash::try_from(bytes.as_slice()).unwrap();
190 assert_eq!(hash.as_bytes(), &bytes);
191 }
192
193 #[test]
194 fn test_try_from_slice_wrong_length() {
195 let bytes = [3u8; 16]; let result = CryptoHash::try_from(bytes.as_slice());
197 assert!(matches!(
198 result,
199 Err(crate::error::ParseHashError::InvalidLength(16))
200 ));
201 }
202
203 #[test]
204 fn test_try_from_str() {
205 let hash = CryptoHash::hash(b"test");
206 let s = hash.to_string();
207 let parsed = CryptoHash::try_from(s.as_str()).unwrap();
208 assert_eq!(hash, parsed);
209 }
210
211 #[test]
212 fn test_as_ref() {
213 let hash = CryptoHash::hash(b"test");
214 let slice: &[u8] = hash.as_ref();
215 assert_eq!(slice.len(), 32);
216 assert_eq!(slice, hash.as_bytes());
217 }
218
219 #[test]
220 fn test_debug_format() {
221 let hash = CryptoHash::ZERO;
222 let debug = format!("{:?}", hash);
223 assert!(debug.starts_with("CryptoHash("));
224 assert!(debug.contains("1111111111")); }
226
227 #[test]
228 fn test_parse_invalid_base58() {
229 let result: Result<CryptoHash, _> = "invalid!@#$%base58".parse();
231 assert!(matches!(
232 result,
233 Err(crate::error::ParseHashError::InvalidBase58(_))
234 ));
235 }
236
237 #[test]
238 fn test_parse_wrong_length() {
239 let result: Result<CryptoHash, _> = "3xRDxw".parse();
241 assert!(matches!(
242 result,
243 Err(crate::error::ParseHashError::InvalidLength(_))
244 ));
245 }
246
247 #[test]
248 fn test_serde_roundtrip() {
249 let hash = CryptoHash::hash(b"serde test");
250 let json = serde_json::to_string(&hash).unwrap();
251 let parsed: CryptoHash = serde_json::from_str(&json).unwrap();
252 assert_eq!(hash, parsed);
253 }
254
255 #[test]
256 fn test_borsh_roundtrip() {
257 let hash = CryptoHash::hash(b"borsh test");
258 let bytes = borsh::to_vec(&hash).unwrap();
259 assert_eq!(bytes.len(), 32);
260 let parsed: CryptoHash = borsh::from_slice(&bytes).unwrap();
261 assert_eq!(hash, parsed);
262 }
263
264 #[test]
265 fn test_hash_deterministic() {
266 let hash1 = CryptoHash::hash(b"same input");
267 let hash2 = CryptoHash::hash(b"same input");
268 assert_eq!(hash1, hash2);
269
270 let hash3 = CryptoHash::hash(b"different input");
271 assert_ne!(hash1, hash3);
272 }
273
274 #[test]
275 fn test_default() {
276 let hash = CryptoHash::default();
277 assert!(hash.is_zero());
278 assert_eq!(hash, CryptoHash::ZERO);
279 }
280
281 #[test]
282 fn test_clone() {
283 let hash1 = CryptoHash::hash(b"clone test");
284 #[allow(clippy::clone_on_copy)]
285 let hash2 = hash1.clone(); assert_eq!(hash1, hash2);
287 }
288
289 #[test]
290 fn test_hash_comparison() {
291 let hash1 = CryptoHash::from_bytes([0u8; 32]);
292 let hash2 = CryptoHash::from_bytes([1u8; 32]);
293 assert_ne!(hash1, hash2);
295 assert_eq!(hash1, CryptoHash::ZERO);
296 }
297}