1use std::fmt;
19use std::str::FromStr;
20
21#[cfg(feature = "arbitrary")]
22use arbitrary::Arbitrary;
23use thiserror::Error;
24
25pub const HASH_LEN: usize = blake3::KEY_LEN;
27
28#[derive(Clone, Copy, PartialEq, Eq, Hash)]
30pub struct Hash(blake3::Hash);
31
32impl Hash {
33 pub fn new(buf: impl AsRef<[u8]>) -> Self {
35 Self(blake3::hash(buf.as_ref()))
36 }
37
38 pub const fn from_bytes(bytes: [u8; HASH_LEN]) -> Self {
40 Self(blake3::Hash::from_bytes(bytes))
41 }
42
43 pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
45 self.0.as_bytes()
46 }
47
48 pub fn to_hex(&self) -> String {
50 self.0.to_hex().to_string()
51 }
52}
53
54impl AsRef<[u8]> for Hash {
55 fn as_ref(&self) -> &[u8] {
56 self.0.as_bytes()
57 }
58}
59
60impl From<Hash> for blake3::Hash {
61 fn from(value: Hash) -> Self {
62 value.0
63 }
64}
65
66impl From<blake3::Hash> for Hash {
67 fn from(value: blake3::Hash) -> Self {
68 Self(value)
69 }
70}
71
72impl From<[u8; HASH_LEN]> for Hash {
73 fn from(value: [u8; HASH_LEN]) -> Self {
74 Self(blake3::Hash::from(value))
75 }
76}
77
78impl From<Hash> for [u8; HASH_LEN] {
79 fn from(value: Hash) -> Self {
80 *value.as_bytes()
81 }
82}
83
84impl From<&[u8; HASH_LEN]> for Hash {
85 fn from(value: &[u8; HASH_LEN]) -> Self {
86 Self(blake3::Hash::from(*value))
87 }
88}
89
90impl TryFrom<&[u8]> for Hash {
91 type Error = HashError;
92
93 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
94 let value_len = value.len();
95
96 let checked_value: [u8; HASH_LEN] = value
97 .try_into()
98 .map_err(|_| HashError::InvalidLength(value_len, HASH_LEN))?;
99
100 Ok(Self(blake3::Hash::from(checked_value)))
101 }
102}
103
104impl From<&Hash> for [u8; 32] {
105 fn from(value: &Hash) -> Self {
106 *value.as_bytes()
107 }
108}
109
110impl FromStr for Hash {
111 type Err = HashError;
112
113 fn from_str(value: &str) -> Result<Self, Self::Err> {
114 Self::try_from(hex::decode(value)?.as_slice())
115 }
116}
117
118impl PartialOrd for Hash {
119 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
120 Some(self.0.as_bytes().cmp(other.0.as_bytes()))
121 }
122}
123
124impl Ord for Hash {
125 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
126 self.0.as_bytes().cmp(other.0.as_bytes())
127 }
128}
129
130impl fmt::Display for Hash {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "{}", self.to_hex())
133 }
134}
135
136impl fmt::Debug for Hash {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.debug_tuple("Hash").field(self.0.as_bytes()).finish()
139 }
140}
141
142#[cfg(feature = "arbitrary")]
143impl<'a> Arbitrary<'a> for Hash {
144 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
145 let bytes = <[u8; HASH_LEN] as Arbitrary>::arbitrary(u)?;
146 Ok(Hash::from_bytes(bytes))
147 }
148}
149
150#[derive(Error, Debug)]
152pub enum HashError {
153 #[error("invalid hash length {0} bytes, expected {1} bytes")]
155 InvalidLength(usize, usize),
156
157 #[error("invalid hex encoding in hash string")]
159 InvalidHexEncoding(#[from] hex::FromHexError),
160}
161
162#[cfg(test)]
163mod tests {
164 use super::{Hash, HashError};
165
166 #[test]
167 fn hashing() {
168 let hash = Hash::new([1, 2, 3]);
169
170 assert_eq!(
171 hash.as_bytes(),
172 &[
173 177, 119, 236, 27, 242, 109, 251, 59, 112, 16, 212, 115, 230, 212, 71, 19, 178,
174 155, 118, 91, 153, 198, 230, 14, 203, 250, 231, 66, 222, 73, 101, 67
175 ]
176 );
177 }
178
179 #[test]
180 fn invalid_length() {
181 let bytes = vec![254, 100, 4, 7];
182 let result: Result<Hash, HashError> = bytes.as_slice().try_into();
183 matches!(result, Err(HashError::InvalidLength(4, 32)));
184 }
185
186 #[test]
187 fn invalid_hex_encoding() {
188 let hex = "notreallyahexstring";
189 let result: Result<Hash, HashError> = hex.parse();
190 matches!(
191 result,
192 Err(HashError::InvalidHexEncoding(
193 hex::FromHexError::InvalidHexCharacter { c: 'n', index: 0 }
194 ))
195 );
196 }
197}