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 contains invalid UTF-8, is not exactly 64 hex
79    /// characters (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 hex_string = std::str::from_utf8(hex_str)
89            .map_err(|e| AptosError::Internal(format!("Invalid UTF-8 in hex string: {e}")))?;
90
91        if hex_string.len() != HASH_LENGTH * 2 {
92            return Err(AptosError::Internal(format!(
93                "Invalid hash length: expected {} hex characters, got {}",
94                HASH_LENGTH * 2,
95                hex_string.len()
96            )));
97        }
98
99        let bytes = hex::decode(hex_string)?;
100        let mut hash = [0u8; HASH_LENGTH];
101        hash.copy_from_slice(&bytes);
102        Ok(Self(hash))
103    }
104
105    /// Creates a hash from a byte slice.
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if the byte slice is not exactly 32 bytes long.
110    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> AptosResult<Self> {
111        let bytes = bytes.as_ref();
112        if bytes.len() != HASH_LENGTH {
113            return Err(AptosError::Internal(format!(
114                "Invalid hash length: expected {} bytes, got {}",
115                HASH_LENGTH,
116                bytes.len()
117            )));
118        }
119        let mut hash = [0u8; HASH_LENGTH];
120        hash.copy_from_slice(bytes);
121        Ok(Self(hash))
122    }
123
124    /// Returns the hash as a byte slice.
125    pub fn as_bytes(&self) -> &[u8] {
126        &self.0
127    }
128
129    /// Returns the hash as a byte array.
130    pub fn to_bytes(&self) -> [u8; HASH_LENGTH] {
131        self.0
132    }
133
134    /// Returns the hash as a hex string with `0x` prefix.
135    pub fn to_hex(&self) -> String {
136        format!("0x{}", hex::encode(self.0))
137    }
138
139    /// Returns true if this is the zero hash.
140    pub fn is_zero(&self) -> bool {
141        self == &Self::ZERO
142    }
143}
144
145impl Default for HashValue {
146    fn default() -> Self {
147        Self::ZERO
148    }
149}
150
151impl fmt::Debug for HashValue {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(f, "HashValue({})", self.to_hex())
154    }
155}
156
157impl fmt::Display for HashValue {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(f, "{}", self.to_hex())
160    }
161}
162
163impl FromStr for HashValue {
164    type Err = AptosError;
165
166    fn from_str(s: &str) -> Result<Self, Self::Err> {
167        Self::from_hex(s)
168    }
169}
170
171impl Serialize for HashValue {
172    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173    where
174        S: Serializer,
175    {
176        if serializer.is_human_readable() {
177            serializer.serialize_str(&self.to_hex())
178        } else {
179            // Use fixed-size array serialization to match deserialize
180            self.0.serialize(serializer)
181        }
182    }
183}
184
185impl<'de> Deserialize<'de> for HashValue {
186    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
187    where
188        D: Deserializer<'de>,
189    {
190        if deserializer.is_human_readable() {
191            let s = String::deserialize(deserializer)?;
192            Self::from_hex(&s).map_err(serde::de::Error::custom)
193        } else {
194            // Use fixed-size array deserialization to match serialize
195            let bytes = <[u8; HASH_LENGTH]>::deserialize(deserializer)?;
196            Ok(Self(bytes))
197        }
198    }
199}
200
201impl From<[u8; HASH_LENGTH]> for HashValue {
202    fn from(bytes: [u8; HASH_LENGTH]) -> Self {
203        Self(bytes)
204    }
205}
206
207impl From<HashValue> for [u8; HASH_LENGTH] {
208    fn from(hash: HashValue) -> Self {
209        hash.0
210    }
211}
212
213impl AsRef<[u8]> for HashValue {
214    fn as_ref(&self) -> &[u8] {
215        &self.0
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_sha3_256() {
225        let hash = HashValue::sha3_256(b"hello world");
226        assert!(!hash.is_zero());
227
228        // Same input should produce same hash
229        let hash2 = HashValue::sha3_256(b"hello world");
230        assert_eq!(hash, hash2);
231
232        // Different input should produce different hash
233        let hash3 = HashValue::sha3_256(b"hello world!");
234        assert_ne!(hash, hash3);
235    }
236
237    #[test]
238    fn test_sha3_256_of_multiple() {
239        let hash1 = HashValue::sha3_256_of([b"hello" as &[u8], b" " as &[u8], b"world" as &[u8]]);
240        let hash2 = HashValue::sha3_256(b"hello world");
241        assert_eq!(hash1, hash2);
242    }
243
244    #[test]
245    fn test_from_hex() {
246        let hash = HashValue::sha3_256(b"test");
247        let hex = hash.to_hex();
248        let parsed = HashValue::from_hex(&hex).unwrap();
249        assert_eq!(hash, parsed);
250
251        // Without prefix
252        let parsed2 = HashValue::from_hex(&hex[2..]).unwrap();
253        assert_eq!(hash, parsed2);
254
255        // Uppercase 0X prefix
256        let hex_upper = hex.replace("0x", "0X");
257        let parsed3 = HashValue::from_hex(&hex_upper).unwrap();
258        assert_eq!(hash, parsed3);
259    }
260
261    #[test]
262    fn test_from_hex_invalid_length() {
263        let result = HashValue::from_hex("0x1234");
264        assert!(result.is_err());
265    }
266
267    #[test]
268    fn test_from_hex_invalid_chars() {
269        let result = HashValue::from_hex("0xZZZZ");
270        assert!(result.is_err());
271    }
272
273    #[test]
274    fn test_from_bytes() {
275        let bytes = [1u8; HASH_LENGTH];
276        let hash = HashValue::new(bytes);
277        assert_eq!(hash.as_bytes(), &bytes);
278    }
279
280    #[test]
281    fn test_json_serialization() {
282        let hash = HashValue::sha3_256(b"test");
283        let json = serde_json::to_string(&hash).unwrap();
284        let parsed: HashValue = serde_json::from_str(&json).unwrap();
285        assert_eq!(hash, parsed);
286    }
287
288    #[test]
289    fn test_zero_hash() {
290        assert!(HashValue::ZERO.is_zero());
291        assert!(!HashValue::sha3_256(b"").is_zero());
292    }
293
294    #[test]
295    fn test_new() {
296        let bytes = [42u8; HASH_LENGTH];
297        let hash = HashValue::new(bytes);
298        assert_eq!(hash.as_bytes(), &bytes);
299    }
300
301    #[test]
302    fn test_display() {
303        let hash = HashValue::ZERO;
304        let display = format!("{hash}");
305        assert!(display.starts_with("0x"));
306        assert_eq!(display.len(), 66); // 0x + 64 hex chars
307    }
308
309    #[test]
310    fn test_debug() {
311        let hash = HashValue::ZERO;
312        let debug = format!("{hash:?}");
313        assert!(debug.contains("HashValue"));
314    }
315
316    #[test]
317    fn test_from_str() {
318        let hash = HashValue::sha3_256(b"test");
319        let hex = hash.to_hex();
320        let parsed: HashValue = hex.parse().unwrap();
321        assert_eq!(hash, parsed);
322    }
323
324    #[test]
325    fn test_ordering() {
326        let hash1 = HashValue::new([0u8; HASH_LENGTH]);
327        let hash2 = HashValue::new([1u8; HASH_LENGTH]);
328        assert!(hash1 < hash2);
329    }
330
331    #[test]
332    fn test_as_ref() {
333        let hash = HashValue::sha3_256(b"test");
334        let slice: &[u8] = hash.as_ref();
335        assert_eq!(slice.len(), HASH_LENGTH);
336    }
337
338    #[test]
339    fn test_from_bytes_valid() {
340        let bytes = [42u8; HASH_LENGTH];
341        let hash = HashValue::from_bytes(bytes).unwrap();
342        assert_eq!(hash.as_bytes(), &bytes);
343    }
344
345    #[test]
346    fn test_from_bytes_invalid_length() {
347        let bytes = vec![0u8; 16]; // Wrong length
348        let result = HashValue::from_bytes(&bytes);
349        assert!(result.is_err());
350    }
351
352    #[test]
353    fn test_to_bytes() {
354        let bytes = [42u8; HASH_LENGTH];
355        let hash = HashValue::new(bytes);
356        assert_eq!(hash.to_bytes(), bytes);
357    }
358
359    #[test]
360    fn test_default() {
361        let hash = HashValue::default();
362        assert!(hash.is_zero());
363        assert_eq!(hash, HashValue::ZERO);
364    }
365
366    #[test]
367    fn test_from_array() {
368        let bytes = [1u8; HASH_LENGTH];
369        let hash: HashValue = bytes.into();
370        assert_eq!(hash.as_bytes(), &bytes);
371    }
372
373    #[test]
374    fn test_into_array() {
375        let bytes = [1u8; HASH_LENGTH];
376        let hash = HashValue::new(bytes);
377        let extracted: [u8; HASH_LENGTH] = hash.into();
378        assert_eq!(extracted, bytes);
379    }
380
381    #[test]
382    fn test_hash_equality() {
383        let hash1 = HashValue::sha3_256(b"test");
384        let hash2 = HashValue::sha3_256(b"test");
385        let hash3 = HashValue::sha3_256(b"different");
386
387        assert_eq!(hash1, hash2);
388        assert_ne!(hash1, hash3);
389    }
390
391    #[test]
392    fn test_hash_clone() {
393        let hash = HashValue::sha3_256(b"test");
394        let cloned = hash;
395        assert_eq!(hash, cloned);
396    }
397
398    #[test]
399    fn test_hash_copy() {
400        let hash = HashValue::sha3_256(b"test");
401        let copied = hash;
402        assert_eq!(hash, copied);
403    }
404
405    #[test]
406    fn test_hash_in_hashmap() {
407        use std::collections::HashMap;
408
409        let hash1 = HashValue::sha3_256(b"key1");
410        let hash2 = HashValue::sha3_256(b"key2");
411
412        let mut map = HashMap::new();
413        map.insert(hash1, "value1");
414        map.insert(hash2, "value2");
415
416        assert_eq!(map.get(&hash1), Some(&"value1"));
417        assert_eq!(map.get(&hash2), Some(&"value2"));
418    }
419
420    #[test]
421    fn test_from_hex_with_bytes() {
422        let hash = HashValue::sha3_256(b"test");
423        let hex_bytes = hash.to_hex().into_bytes();
424        let parsed = HashValue::from_hex(&hex_bytes).unwrap();
425        assert_eq!(hash, parsed);
426    }
427
428    #[test]
429    fn test_sha3_256_empty() {
430        let hash = HashValue::sha3_256(b"");
431        assert!(!hash.is_zero()); // Empty string still has a hash
432    }
433
434    #[test]
435    fn test_sha3_256_of_empty() {
436        let hash = HashValue::sha3_256_of::<[&[u8]; 0], &[u8]>([]);
437        assert!(!hash.is_zero());
438    }
439
440    #[test]
441    fn test_sha3_256_of_single() {
442        let hash1 = HashValue::sha3_256_of([b"test" as &[u8]]);
443        let hash2 = HashValue::sha3_256(b"test");
444        assert_eq!(hash1, hash2);
445    }
446
447    #[test]
448    fn test_json_human_readable_serialization_roundtrip() {
449        // JSON uses human-readable format (hex string)
450        let hash = HashValue::sha3_256(b"test");
451        let json = serde_json::to_string(&hash).unwrap();
452
453        // Should be a quoted hex string
454        assert!(json.starts_with('"'));
455        assert!(json.contains("0x"));
456
457        let deserialized: HashValue = serde_json::from_str(&json).unwrap();
458        assert_eq!(hash, deserialized);
459    }
460
461    #[test]
462    fn test_bcs_binary_serialization_roundtrip() {
463        // BCS uses non-human-readable format (binary)
464        let hash = HashValue::sha3_256(b"test");
465        let serialized = aptos_bcs::to_bytes(&hash).unwrap();
466
467        // Fixed-size array serialization should be exactly HASH_LENGTH bytes
468        assert_eq!(serialized.len(), HASH_LENGTH);
469
470        let deserialized: HashValue = aptos_bcs::from_bytes(&serialized).unwrap();
471        assert_eq!(hash, deserialized);
472    }
473
474    #[test]
475    fn test_bcs_binary_serialization_zero_hash() {
476        // Test with zero hash
477        let hash = HashValue::ZERO;
478        let serialized = aptos_bcs::to_bytes(&hash).unwrap();
479        let deserialized: HashValue = aptos_bcs::from_bytes(&serialized).unwrap();
480        assert_eq!(hash, deserialized);
481        assert!(deserialized.is_zero());
482    }
483}