auths_core/witness/
hash.rs1use std::fmt;
12use std::str::FromStr;
13
14use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
15
16#[derive(Clone, Copy, PartialEq, Eq, Hash)]
45pub struct EventHash([u8; 20]);
46
47impl EventHash {
48 #[inline]
50 pub const fn from_bytes(bytes: [u8; 20]) -> Self {
51 Self(bytes)
52 }
53
54 #[inline]
56 pub fn as_bytes(&self) -> &[u8; 20] {
57 &self.0
58 }
59
60 pub fn from_hex(s: &str) -> Option<Self> {
79 if s.len() != 40 {
80 return None;
81 }
82
83 let mut bytes = [0u8; 20];
84 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
85 let hi = hex_digit(chunk[0])?;
86 let lo = hex_digit(chunk[1])?;
87 bytes[i] = (hi << 4) | lo;
88 }
89
90 Some(Self(bytes))
91 }
92
93 pub fn to_hex(&self) -> String {
105 let mut s = String::with_capacity(40);
106 for byte in &self.0 {
107 s.push(HEX_CHARS[(byte >> 4) as usize]);
108 s.push(HEX_CHARS[(byte & 0xf) as usize]);
109 }
110 s
111 }
112}
113
114const HEX_CHARS: [char; 16] = [
116 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
117];
118
119#[inline]
121fn hex_digit(c: u8) -> Option<u8> {
122 match c {
123 b'0'..=b'9' => Some(c - b'0'),
124 b'a'..=b'f' => Some(c - b'a' + 10),
125 b'A'..=b'F' => Some(c - b'A' + 10),
126 _ => None,
127 }
128}
129
130impl fmt::Debug for EventHash {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "EventHash({})", self.to_hex())
133 }
134}
135
136impl fmt::Display for EventHash {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 write!(f, "{}", self.to_hex())
139 }
140}
141
142#[derive(Debug, thiserror::Error, PartialEq)]
158pub enum EventHashParseError {
159 #[error("expected 40 hex characters, got {0}")]
161 InvalidLength(usize),
162 #[error("invalid hex character at position {position}: {ch:?}")]
164 InvalidChar {
165 position: usize,
167 ch: char,
169 },
170}
171
172impl FromStr for EventHash {
173 type Err = EventHashParseError;
174
175 fn from_str(s: &str) -> Result<Self, Self::Err> {
176 if s.len() != 40 {
177 return Err(EventHashParseError::InvalidLength(s.len()));
178 }
179 let mut bytes = [0u8; 20];
180 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
181 let hi = hex_digit(chunk[0]).ok_or(EventHashParseError::InvalidChar {
182 position: i * 2,
183 ch: chunk[0] as char,
184 })?;
185 let lo = hex_digit(chunk[1]).ok_or(EventHashParseError::InvalidChar {
186 position: i * 2 + 1,
187 ch: chunk[1] as char,
188 })?;
189 bytes[i] = (hi << 4) | lo;
190 }
191 Ok(Self(bytes))
192 }
193}
194
195impl Serialize for EventHash {
196 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
197 serializer.serialize_str(&self.to_hex())
198 }
199}
200
201impl<'de> Deserialize<'de> for EventHash {
202 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
203 let s = String::deserialize(deserializer)?;
204 s.parse::<EventHash>().map_err(de::Error::custom)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn from_bytes_roundtrip() {
214 let bytes = [
215 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
216 ];
217 let hash = EventHash::from_bytes(bytes);
218 assert_eq!(hash.as_bytes(), &bytes);
219 }
220
221 #[test]
222 fn from_hex_valid() {
223 let hash = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
224 let mut expected = [0u8; 20];
225 expected[19] = 1;
226 assert_eq!(hash.as_bytes(), &expected);
227 }
228
229 #[test]
230 fn from_hex_all_zeros() {
231 let hash = EventHash::from_hex("0000000000000000000000000000000000000000").unwrap();
232 assert_eq!(hash.as_bytes(), &[0u8; 20]);
233 }
234
235 #[test]
236 fn from_hex_uppercase() {
237 let hash = EventHash::from_hex("ABCDEF0123456789ABCDEF0123456789ABCDEF01").unwrap();
238 assert!(
239 hash.to_hex()
240 .chars()
241 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
242 );
243 }
244
245 #[test]
246 fn from_hex_wrong_length() {
247 assert!(EventHash::from_hex("0123").is_none());
248 assert!(EventHash::from_hex("").is_none());
249 assert!(EventHash::from_hex("00000000000000000000000000000000000000001").is_none());
250 }
251
252 #[test]
253 fn from_hex_invalid_chars() {
254 assert!(EventHash::from_hex("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz").is_none());
255 assert!(EventHash::from_hex("0000000000000000000000000000000000000g01").is_none());
256 }
257
258 #[test]
259 fn to_hex_roundtrip() {
260 let original = "0123456789abcdef0123456789abcdef01234567";
261 let hash = EventHash::from_hex(original).unwrap();
262 assert_eq!(hash.to_hex(), original);
263 }
264
265 #[test]
266 fn debug_format() {
267 let hash = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
268 let debug = format!("{:?}", hash);
269 assert!(debug.contains("EventHash"));
270 assert!(debug.contains("0000000000000000000000000000000000000001"));
271 }
272
273 #[test]
274 fn display_format() {
275 let hash = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
276 assert_eq!(
277 format!("{}", hash),
278 "0000000000000000000000000000000000000001"
279 );
280 }
281
282 #[test]
283 fn equality() {
284 let a = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
285 let b = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
286 let c = EventHash::from_hex("0000000000000000000000000000000000000002").unwrap();
287
288 assert_eq!(a, b);
289 assert_ne!(a, c);
290 }
291
292 #[test]
293 fn hash_trait() {
294 use std::collections::HashSet;
295
296 let mut set = HashSet::new();
297 let a = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
298 let b = EventHash::from_hex("0000000000000000000000000000000000000002").unwrap();
299
300 set.insert(a);
301 set.insert(b);
302 set.insert(a); assert_eq!(set.len(), 2);
305 }
306}