Skip to main content

aptos_sdk/types/
hash.rs

1//! Hash value type.
2//!
3//! A 32-byte cryptographic hash value used throughout Aptos for
4//! transaction hashes, state roots, and other cryptographic commitments.
5
6use crate::error::{AptosError, AptosResult};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use sha3::{Digest, Sha3_256};
9use std::fmt;
10use std::str::FromStr;
11
12/// The length of a hash value in bytes.
13pub const HASH_LENGTH: usize = 32;
14
15/// A 32-byte cryptographic hash value.
16///
17/// Hash values are used throughout Aptos for:
18/// - Transaction hashes
19/// - State roots
20/// - Event keys
21/// - Merkle tree nodes
22///
23/// # Example
24///
25/// ```rust
26/// use aptos_sdk::HashValue;
27///
28/// // Compute a hash
29/// let hash = HashValue::sha3_256(b"hello world");
30/// assert_eq!(hash.to_hex().len(), 66); // "0x" + 64 hex chars
31///
32/// // Parse from hex (64 hex characters)
33/// let hex = "0x0000000000000000000000000000000000000000000000000000000000000001";
34/// let hash = HashValue::from_hex(hex).unwrap();
35/// ```
36#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct HashValue([u8; HASH_LENGTH]);
38
39impl HashValue {
40    /// The "zero" hash (all zeros).
41    pub const ZERO: Self = Self([0u8; HASH_LENGTH]);
42
43    /// Creates a hash from a byte array.
44    pub const fn new(bytes: [u8; HASH_LENGTH]) -> Self {
45        Self(bytes)
46    }
47
48    /// Computes the SHA3-256 hash of the given data.
49    pub fn sha3_256<T: AsRef<[u8]>>(data: T) -> Self {
50        let mut hasher = Sha3_256::new();
51        hasher.update(data.as_ref());
52        let result = hasher.finalize();
53        let mut bytes = [0u8; HASH_LENGTH];
54        bytes.copy_from_slice(&result);
55        Self(bytes)
56    }
57
58    /// Computes the SHA3-256 hash of multiple byte slices.
59    pub fn sha3_256_of<I, T>(items: I) -> Self
60    where
61        I: IntoIterator<Item = T>,
62        T: AsRef<[u8]>,
63    {
64        let mut hasher = Sha3_256::new();
65        for item in items {
66            hasher.update(item.as_ref());
67        }
68        let result = hasher.finalize();
69        let mut bytes = [0u8; HASH_LENGTH];
70        bytes.copy_from_slice(&result);
71        Self(bytes)
72    }
73
74    /// Creates a hash from a hex string (with or without `0x` prefix).
75    ///
76    /// # Errors
77    ///
78    /// Returns an error if the hex string is not exactly 64 hex characters
79    /// (excluding the optional `0x` prefix) or contains invalid hex characters.
80    pub fn from_hex<T: AsRef<[u8]>>(hex_str: T) -> AptosResult<Self> {
81        let hex_str = hex_str.as_ref();
82        let hex_str = if hex_str.starts_with(b"0x") || hex_str.starts_with(b"0X") {
83            &hex_str[2..]
84        } else {
85            hex_str
86        };
87
88        let mut hash = [0u8; HASH_LENGTH];
89        const_hex::decode_to_slice(hex_str, &mut hash)?;
90        Ok(Self(hash))
91    }
92
93    /// Creates a hash from a byte slice.
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if the byte slice is not exactly 32 bytes long.
98    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> AptosResult<Self> {
99        let bytes = bytes.as_ref();
100        if bytes.len() != HASH_LENGTH {
101            return Err(AptosError::Internal(format!(
102                "Invalid hash length: expected {} bytes, got {}",
103                HASH_LENGTH,
104                bytes.len()
105            )));
106        }
107        let mut hash = [0u8; HASH_LENGTH];
108        hash.copy_from_slice(bytes);
109        Ok(Self(hash))
110    }
111
112    /// Returns the hash as a byte slice.
113    pub fn as_bytes(&self) -> &[u8] {
114        &self.0
115    }
116
117    /// Returns the hash as a byte array.
118    pub fn to_bytes(&self) -> [u8; HASH_LENGTH] {
119        self.0
120    }
121
122    /// Returns the hash as a hex string with `0x` prefix.
123    pub fn to_hex(&self) -> String {
124        const_hex::encode_prefixed(self.0)
125    }
126
127    /// Returns true if this is the zero hash.
128    pub fn is_zero(&self) -> bool {
129        self == &Self::ZERO
130    }
131}
132
133impl Default for HashValue {
134    fn default() -> Self {
135        Self::ZERO
136    }
137}
138
139impl fmt::Debug for HashValue {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(f, "HashValue({})", self.to_hex())
142    }
143}
144
145impl fmt::Display for HashValue {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(f, "{}", self.to_hex())
148    }
149}
150
151impl FromStr for HashValue {
152    type Err = AptosError;
153
154    fn from_str(s: &str) -> Result<Self, Self::Err> {
155        Self::from_hex(s)
156    }
157}
158
159impl Serialize for HashValue {
160    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161    where
162        S: Serializer,
163    {
164        if serializer.is_human_readable() {
165            serializer.serialize_str(&self.to_hex())
166        } else {
167            // Use fixed-size array serialization to match deserialize
168            self.0.serialize(serializer)
169        }
170    }
171}
172
173impl<'de> Deserialize<'de> for HashValue {
174    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
175    where
176        D: Deserializer<'de>,
177    {
178        if deserializer.is_human_readable() {
179            let s = String::deserialize(deserializer)?;
180            Self::from_hex(&s).map_err(serde::de::Error::custom)
181        } else {
182            // Use fixed-size array deserialization to match serialize
183            let bytes = <[u8; HASH_LENGTH]>::deserialize(deserializer)?;
184            Ok(Self(bytes))
185        }
186    }
187}
188
189impl From<[u8; HASH_LENGTH]> for HashValue {
190    fn from(bytes: [u8; HASH_LENGTH]) -> Self {
191        Self(bytes)
192    }
193}
194
195impl From<HashValue> for [u8; HASH_LENGTH] {
196    fn from(hash: HashValue) -> Self {
197        hash.0
198    }
199}
200
201impl AsRef<[u8]> for HashValue {
202    fn as_ref(&self) -> &[u8] {
203        &self.0
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_sha3_256() {
213        let hash = HashValue::sha3_256(b"hello world");
214        assert!(!hash.is_zero());
215
216        // Same input should produce same hash
217        let hash2 = HashValue::sha3_256(b"hello world");
218        assert_eq!(hash, hash2);
219
220        // Different input should produce different hash
221        let hash3 = HashValue::sha3_256(b"hello world!");
222        assert_ne!(hash, hash3);
223    }
224
225    #[test]
226    fn test_sha3_256_of_multiple() {
227        let hash1 = HashValue::sha3_256_of([b"hello" as &[u8], b" " as &[u8], b"world" as &[u8]]);
228        let hash2 = HashValue::sha3_256(b"hello world");
229        assert_eq!(hash1, hash2);
230    }
231
232    #[test]
233    fn test_from_hex() {
234        let hash = HashValue::sha3_256(b"test");
235        let hex = hash.to_hex();
236        let parsed = HashValue::from_hex(&hex).unwrap();
237        assert_eq!(hash, parsed);
238
239        // Without prefix
240        let parsed2 = HashValue::from_hex(&hex[2..]).unwrap();
241        assert_eq!(hash, parsed2);
242
243        // Uppercase 0X prefix
244        let hex_upper = hex.replace("0x", "0X");
245        let parsed3 = HashValue::from_hex(&hex_upper).unwrap();
246        assert_eq!(hash, parsed3);
247    }
248
249    #[test]
250    fn test_from_hex_invalid_length() {
251        let result = HashValue::from_hex("0x1234");
252        assert!(result.is_err());
253    }
254
255    #[test]
256    fn test_from_hex_invalid_chars() {
257        let result = HashValue::from_hex("0xZZZZ");
258        assert!(result.is_err());
259    }
260
261    #[test]
262    fn test_from_bytes() {
263        let bytes = [1u8; HASH_LENGTH];
264        let hash = HashValue::new(bytes);
265        assert_eq!(hash.as_bytes(), &bytes);
266    }
267
268    #[test]
269    fn test_json_serialization() {
270        let hash = HashValue::sha3_256(b"test");
271        let json = serde_json::to_string(&hash).unwrap();
272        let parsed: HashValue = serde_json::from_str(&json).unwrap();
273        assert_eq!(hash, parsed);
274    }
275
276    #[test]
277    fn test_zero_hash() {
278        assert!(HashValue::ZERO.is_zero());
279        assert!(!HashValue::sha3_256(b"").is_zero());
280    }
281
282    #[test]
283    fn test_new() {
284        let bytes = [42u8; HASH_LENGTH];
285        let hash = HashValue::new(bytes);
286        assert_eq!(hash.as_bytes(), &bytes);
287    }
288
289    #[test]
290    fn test_display() {
291        let hash = HashValue::ZERO;
292        let display = format!("{hash}");
293        assert!(display.starts_with("0x"));
294        assert_eq!(display.len(), 66); // 0x + 64 hex chars
295    }
296
297    #[test]
298    fn test_debug() {
299        let hash = HashValue::ZERO;
300        let debug = format!("{hash:?}");
301        assert!(debug.contains("HashValue"));
302    }
303
304    #[test]
305    fn test_from_str() {
306        let hash = HashValue::sha3_256(b"test");
307        let hex = hash.to_hex();
308        let parsed: HashValue = hex.parse().unwrap();
309        assert_eq!(hash, parsed);
310    }
311
312    #[test]
313    fn test_ordering() {
314        let hash1 = HashValue::new([0u8; HASH_LENGTH]);
315        let hash2 = HashValue::new([1u8; HASH_LENGTH]);
316        assert!(hash1 < hash2);
317    }
318
319    #[test]
320    fn test_as_ref() {
321        let hash = HashValue::sha3_256(b"test");
322        let slice: &[u8] = hash.as_ref();
323        assert_eq!(slice.len(), HASH_LENGTH);
324    }
325
326    #[test]
327    fn test_from_bytes_valid() {
328        let bytes = [42u8; HASH_LENGTH];
329        let hash = HashValue::from_bytes(bytes).unwrap();
330        assert_eq!(hash.as_bytes(), &bytes);
331    }
332
333    #[test]
334    fn test_from_bytes_invalid_length() {
335        let bytes = vec![0u8; 16]; // Wrong length
336        let result = HashValue::from_bytes(&bytes);
337        assert!(result.is_err());
338    }
339
340    #[test]
341    fn test_to_bytes() {
342        let bytes = [42u8; HASH_LENGTH];
343        let hash = HashValue::new(bytes);
344        assert_eq!(hash.to_bytes(), bytes);
345    }
346
347    #[test]
348    fn test_default() {
349        let hash = HashValue::default();
350        assert!(hash.is_zero());
351        assert_eq!(hash, HashValue::ZERO);
352    }
353
354    #[test]
355    fn test_from_array() {
356        let bytes = [1u8; HASH_LENGTH];
357        let hash: HashValue = bytes.into();
358        assert_eq!(hash.as_bytes(), &bytes);
359    }
360
361    #[test]
362    fn test_into_array() {
363        let bytes = [1u8; HASH_LENGTH];
364        let hash = HashValue::new(bytes);
365        let extracted: [u8; HASH_LENGTH] = hash.into();
366        assert_eq!(extracted, bytes);
367    }
368
369    #[test]
370    fn test_hash_equality() {
371        let hash1 = HashValue::sha3_256(b"test");
372        let hash2 = HashValue::sha3_256(b"test");
373        let hash3 = HashValue::sha3_256(b"different");
374
375        assert_eq!(hash1, hash2);
376        assert_ne!(hash1, hash3);
377    }
378
379    #[test]
380    fn test_hash_clone() {
381        let hash = HashValue::sha3_256(b"test");
382        let cloned = hash;
383        assert_eq!(hash, cloned);
384    }
385
386    #[test]
387    fn test_hash_copy() {
388        let hash = HashValue::sha3_256(b"test");
389        let copied = hash;
390        assert_eq!(hash, copied);
391    }
392
393    #[test]
394    fn test_hash_in_hashmap() {
395        use std::collections::HashMap;
396
397        let hash1 = HashValue::sha3_256(b"key1");
398        let hash2 = HashValue::sha3_256(b"key2");
399
400        let mut map = HashMap::new();
401        map.insert(hash1, "value1");
402        map.insert(hash2, "value2");
403
404        assert_eq!(map.get(&hash1), Some(&"value1"));
405        assert_eq!(map.get(&hash2), Some(&"value2"));
406    }
407
408    #[test]
409    fn test_from_hex_with_bytes() {
410        let hash = HashValue::sha3_256(b"test");
411        let hex_bytes = hash.to_hex().into_bytes();
412        let parsed = HashValue::from_hex(&hex_bytes).unwrap();
413        assert_eq!(hash, parsed);
414    }
415
416    #[test]
417    fn test_sha3_256_empty() {
418        let hash = HashValue::sha3_256(b"");
419        assert!(!hash.is_zero()); // Empty string still has a hash
420    }
421
422    #[test]
423    fn test_sha3_256_of_empty() {
424        let hash = HashValue::sha3_256_of::<[&[u8]; 0], &[u8]>([]);
425        assert!(!hash.is_zero());
426    }
427
428    #[test]
429    fn test_sha3_256_of_single() {
430        let hash1 = HashValue::sha3_256_of([b"test" as &[u8]]);
431        let hash2 = HashValue::sha3_256(b"test");
432        assert_eq!(hash1, hash2);
433    }
434
435    #[test]
436    fn test_json_human_readable_serialization_roundtrip() {
437        // JSON uses human-readable format (hex string)
438        let hash = HashValue::sha3_256(b"test");
439        let json = serde_json::to_string(&hash).unwrap();
440
441        // Should be a quoted hex string
442        assert!(json.starts_with('"'));
443        assert!(json.contains("0x"));
444
445        let deserialized: HashValue = serde_json::from_str(&json).unwrap();
446        assert_eq!(hash, deserialized);
447    }
448
449    #[test]
450    fn test_bcs_binary_serialization_roundtrip() {
451        // BCS uses non-human-readable format (binary)
452        let hash = HashValue::sha3_256(b"test");
453        let serialized = aptos_bcs::to_bytes(&hash).unwrap();
454
455        // Fixed-size array serialization should be exactly HASH_LENGTH bytes
456        assert_eq!(serialized.len(), HASH_LENGTH);
457
458        let deserialized: HashValue = aptos_bcs::from_bytes(&serialized).unwrap();
459        assert_eq!(hash, deserialized);
460    }
461
462    #[test]
463    fn test_bcs_binary_serialization_zero_hash() {
464        // Test with zero hash
465        let hash = HashValue::ZERO;
466        let serialized = aptos_bcs::to_bytes(&hash).unwrap();
467        let deserialized: HashValue = aptos_bcs::from_bytes(&serialized).unwrap();
468        assert_eq!(hash, deserialized);
469        assert!(deserialized.is_zero());
470    }
471}