1use std::{fmt, io::Read, str::FromStr};
2
3use sha3::{Digest, Sha3_256};
4
5use crate::error::{Error, Result};
6
7pub const HASH_LEN: usize = 32;
8
9#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serde", serde(try_from = "String", into = "String"))]
12pub struct Hash32(pub [u8; HASH_LEN]);
13
14impl Hash32 {
15 pub fn new(bytes: [u8; HASH_LEN]) -> Self {
16 Self(bytes)
17 }
18
19 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
20 if bytes.len() != HASH_LEN {
21 return Err(Error::InvalidHashLength(bytes.len()));
22 }
23 let mut arr = [0u8; HASH_LEN];
24 arr.copy_from_slice(bytes);
25 Ok(Self(arr))
26 }
27
28 pub fn sha3_256(data: impl AsRef<[u8]>) -> Self {
29 let mut hasher = Sha3_256::new();
30 hasher.update(data.as_ref());
31 let digest = hasher.finalize();
32 Hash32::new(digest.into())
33 }
34
35 pub fn sha3_256_reader(mut reader: impl Read) -> Result<Self> {
36 let mut hasher = Sha3_256::new();
37 let mut buf = [0u8; 8192];
38 loop {
39 let n = reader.read(&mut buf)?;
40 if n == 0 {
41 break;
42 }
43 hasher.update(&buf[..n]);
44 }
45 let digest = hasher.finalize();
46 Ok(Hash32::new(digest.into()))
47 }
48
49 pub fn to_hex(&self) -> String {
50 hex::encode(self.0)
51 }
52
53 pub fn from_hex(s: &str) -> Result<Self> {
54 let bytes = hex::decode(s).map_err(|_| Error::InvalidHex(s.to_string()))?;
55 Self::from_bytes(&bytes)
56 }
57
58 pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
59 &self.0
60 }
61}
62
63impl fmt::Display for Hash32 {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.to_hex())
66 }
67}
68
69impl fmt::Debug for Hash32 {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(f, "Hash32({})", self.to_hex())
72 }
73}
74
75impl FromStr for Hash32 {
76 type Err = Error;
77
78 fn from_str(s: &str) -> Result<Self> {
79 Hash32::from_hex(s)
80 }
81}
82
83#[cfg(feature = "serde")]
84impl From<Hash32> for String {
85 fn from(value: Hash32) -> Self {
86 value.to_hex()
87 }
88}
89
90#[cfg(feature = "serde")]
91impl TryFrom<String> for Hash32 {
92 type Error = Error;
93
94 fn try_from(value: String) -> Result<Self> {
95 Hash32::from_hex(&value)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use rand::Rng;
102
103 use super::*;
104
105 #[test]
106 fn sha3_known_value() {
107 let hash = Hash32::sha3_256("hello");
108 assert_eq!(hash.to_hex(), "3338be694f50c5f338814986cdf0686453a888b84f424d792af4b9202398f392");
109 }
110
111 #[test]
112 fn from_hex_roundtrip() {
113 let mut bytes = [0u8; HASH_LEN];
114 rand::rng().fill_bytes(&mut bytes);
115 let h = Hash32::new(bytes);
116 let encoded = h.to_hex();
117 let decoded = Hash32::from_hex(&encoded).unwrap();
118 assert_eq!(decoded, h);
119 }
120
121 #[test]
122 fn invalid_hex_rejected() {
123 let err = Hash32::from_hex("abcd").unwrap_err();
124 assert!(matches!(err, Error::InvalidHashLength(2)));
125 let err2 = Hash32::from_hex(&"zz".repeat(32)).unwrap_err();
126 assert!(matches!(err2, Error::InvalidHex(_)));
127 }
128
129 #[test]
130 fn odd_length_hex_is_rejected() {
131 let err = Hash32::from_hex("abc").unwrap_err();
132 assert!(matches!(err, Error::InvalidHex(_)));
133 }
134
135 #[test]
136 fn from_bytes_rejects_wrong_length() {
137 let err = Hash32::from_bytes(&[0u8; HASH_LEN - 1]).unwrap_err();
138 assert!(matches!(err, Error::InvalidHashLength(_)));
139 }
140
141 #[test]
142 fn reader_digest_matches() {
143 let data = vec![42u8; 1024];
144 let expected = Hash32::sha3_256(&data);
145 let via_reader = Hash32::sha3_256_reader(&data[..]).unwrap();
146 assert_eq!(expected, via_reader);
147 }
148}