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
118#[allow(clippy::non_canonical_partial_ord_impl)]
119impl PartialOrd for Hash {
120 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
121 Some(self.0.as_bytes().cmp(other.0.as_bytes()))
122 }
123}
124
125impl Ord for Hash {
126 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
127 self.0.as_bytes().cmp(other.0.as_bytes())
128 }
129}
130
131impl fmt::Display for Hash {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 write!(f, "{}", self.to_hex())
134 }
135}
136
137impl fmt::Debug for Hash {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.debug_tuple("Hash").field(self.0.as_bytes()).finish()
140 }
141}
142
143#[cfg(feature = "arbitrary")]
144impl<'a> Arbitrary<'a> for Hash {
145 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
146 let bytes = <[u8; HASH_LEN] as Arbitrary>::arbitrary(u)?;
147 Ok(Hash::from_bytes(bytes))
148 }
149}
150
151#[derive(Error, Debug)]
153pub enum HashError {
154 #[error("invalid hash length {0} bytes, expected {1} bytes")]
156 InvalidLength(usize, usize),
157
158 #[error("invalid hex encoding in hash string")]
160 InvalidHexEncoding(#[from] hex::FromHexError),
161}
162
163#[cfg(test)]
164mod tests {
165 use super::{Hash, HashError};
166
167 #[test]
168 fn hashing() {
169 let hash = Hash::new([1, 2, 3]);
170
171 assert_eq!(
172 hash.as_bytes(),
173 &[
174 177, 119, 236, 27, 242, 109, 251, 59, 112, 16, 212, 115, 230, 212, 71, 19, 178,
175 155, 118, 91, 153, 198, 230, 14, 203, 250, 231, 66, 222, 73, 101, 67
176 ]
177 );
178 }
179
180 #[test]
181 fn invalid_length() {
182 let bytes = vec![254, 100, 4, 7];
183 let result: Result<Hash, HashError> = bytes.as_slice().try_into();
184 matches!(result, Err(HashError::InvalidLength(4, 32)));
185 }
186
187 #[test]
188 fn invalid_hex_encoding() {
189 let hex = "notreallyahexstring";
190 let result: Result<Hash, HashError> = hex.parse();
191 matches!(
192 result,
193 Err(HashError::InvalidHexEncoding(
194 hex::FromHexError::InvalidHexCharacter { c: 'n', index: 0 }
195 ))
196 );
197 }
198}