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