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)]
158#[non_exhaustive]
159pub enum EventHashParseError {
160 #[error("expected 40 hex characters, got {0}")]
162 InvalidLength(usize),
163 #[error("invalid hex character at position {position}: {ch:?}")]
165 InvalidChar {
166 position: usize,
168 ch: char,
170 },
171}
172
173impl FromStr for EventHash {
174 type Err = EventHashParseError;
175
176 fn from_str(s: &str) -> Result<Self, Self::Err> {
177 if s.len() != 40 {
178 return Err(EventHashParseError::InvalidLength(s.len()));
179 }
180 let mut bytes = [0u8; 20];
181 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
182 let hi = hex_digit(chunk[0]).ok_or(EventHashParseError::InvalidChar {
183 position: i * 2,
184 ch: chunk[0] as char,
185 })?;
186 let lo = hex_digit(chunk[1]).ok_or(EventHashParseError::InvalidChar {
187 position: i * 2 + 1,
188 ch: chunk[1] as char,
189 })?;
190 bytes[i] = (hi << 4) | lo;
191 }
192 Ok(Self(bytes))
193 }
194}
195
196impl Serialize for EventHash {
197 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
198 serializer.serialize_str(&self.to_hex())
199 }
200}
201
202impl<'de> Deserialize<'de> for EventHash {
203 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
204 let s = String::deserialize(deserializer)?;
205 s.parse::<EventHash>().map_err(de::Error::custom)
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn from_bytes_roundtrip() {
215 let bytes = [
216 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
217 ];
218 let hash = EventHash::from_bytes(bytes);
219 assert_eq!(hash.as_bytes(), &bytes);
220 }
221
222 #[test]
223 fn from_hex_valid() {
224 let hash = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
225 let mut expected = [0u8; 20];
226 expected[19] = 1;
227 assert_eq!(hash.as_bytes(), &expected);
228 }
229
230 #[test]
231 fn from_hex_all_zeros() {
232 let hash = EventHash::from_hex("0000000000000000000000000000000000000000").unwrap();
233 assert_eq!(hash.as_bytes(), &[0u8; 20]);
234 }
235
236 #[test]
237 fn from_hex_uppercase() {
238 let hash = EventHash::from_hex("ABCDEF0123456789ABCDEF0123456789ABCDEF01").unwrap();
239 assert!(
240 hash.to_hex()
241 .chars()
242 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
243 );
244 }
245
246 #[test]
247 fn from_hex_wrong_length() {
248 assert!(EventHash::from_hex("0123").is_none());
249 assert!(EventHash::from_hex("").is_none());
250 assert!(EventHash::from_hex("00000000000000000000000000000000000000001").is_none());
251 }
252
253 #[test]
254 fn from_hex_invalid_chars() {
255 assert!(EventHash::from_hex("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz").is_none());
256 assert!(EventHash::from_hex("0000000000000000000000000000000000000g01").is_none());
257 }
258
259 #[test]
260 fn to_hex_roundtrip() {
261 let original = "0123456789abcdef0123456789abcdef01234567";
262 let hash = EventHash::from_hex(original).unwrap();
263 assert_eq!(hash.to_hex(), original);
264 }
265
266 #[test]
267 fn debug_format() {
268 let hash = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
269 let debug = format!("{:?}", hash);
270 assert!(debug.contains("EventHash"));
271 assert!(debug.contains("0000000000000000000000000000000000000001"));
272 }
273
274 #[test]
275 fn display_format() {
276 let hash = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
277 assert_eq!(
278 format!("{}", hash),
279 "0000000000000000000000000000000000000001"
280 );
281 }
282
283 #[test]
284 fn equality() {
285 let a = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
286 let b = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
287 let c = EventHash::from_hex("0000000000000000000000000000000000000002").unwrap();
288
289 assert_eq!(a, b);
290 assert_ne!(a, c);
291 }
292
293 #[test]
294 fn hash_trait() {
295 use std::collections::HashSet;
296
297 let mut set = HashSet::new();
298 let a = EventHash::from_hex("0000000000000000000000000000000000000001").unwrap();
299 let b = EventHash::from_hex("0000000000000000000000000000000000000002").unwrap();
300
301 set.insert(a);
302 set.insert(b);
303 set.insert(a); assert_eq!(set.len(), 2);
306 }
307}