rustolio_utils/crypto/
hash.rs1#[cfg(debug_assertions)]
12use std::panic::Location;
13use std::{cell::RefCell, hash::Hash};
14
15use sha3::{digest::Digest as _, Keccak256};
16
17use crate::prelude::*;
18
19thread_local! {
20 #[doc(hidden)]
23 static LOCAL_HASHER: RefCell<Inner> = RefCell::new(Inner::new());
24}
25
26#[derive(Debug, Clone, Copy, Eq, Encode, Decode)]
27pub struct Digest([u8; 32]);
28
29impl Digest {
30 pub fn to_bytes(&self) -> [u8; 32] {
31 self.0
32 }
33}
34
35impl<T: AsRef<[u8]>> PartialEq<T> for Digest {
36 fn eq(&self, other: &T) -> bool {
37 self.as_ref() == other.as_ref()
38 }
39}
40
41impl AsRef<[u8]> for Digest {
42 fn as_ref(&self) -> &[u8] {
43 &self.0
44 }
45}
46
47pub struct Hasher(
48 std::marker::PhantomData<()>, );
50
51struct Inner {
52 keccak: Keccak256,
53
54 #[cfg(debug_assertions)]
55 locked: Option<&'static Location<'static>>,
56}
57
58impl std::hash::Hasher for Inner {
59 fn finish(&self) -> u64 {
60 unimplemented!()
61 }
62 fn write(&mut self, bytes: &[u8]) {
63 self.update(bytes);
64 }
65}
66
67impl Inner {
68 fn new() -> Self {
69 Self {
70 keccak: Keccak256::new(),
71 #[cfg(debug_assertions)]
72 locked: None,
73 }
74 }
75
76 fn update(&mut self, bytes: &[u8]) {
77 #[cfg(debug_assertions)]
78 if self.locked.is_none() {
79 panic!("Must be locked at this point")
80 }
81
82 self.keccak.update(bytes);
83 }
84}
85
86impl Hasher {
87 #[track_caller]
88 pub fn new() -> Hasher {
89 #[cfg(debug_assertions)]
90 {
91 let caller = Location::caller();
92 LOCAL_HASHER.with_borrow_mut(|h| {
93 if let Some(caller) = h.locked {
94 panic!(
95 "Cannot hash multiple values at once. Already hashing: {}",
96 caller
97 )
98 }
99 h.locked = Some(caller);
100 });
101 }
102
103 Self(std::marker::PhantomData)
104 }
105
106 pub fn once(b: impl Hash) -> Digest {
107 Hasher::new().update(b).finalize()
108 }
109
110 pub fn once_raw(b: impl AsRef<[u8]>) -> Digest {
111 Hasher::new().update_raw(b).finalize()
112 }
113
114 pub fn update(self, b: impl Hash) -> Self {
115 LOCAL_HASHER.with_borrow_mut(|h| b.hash(h));
116 self
117 }
118
119 pub fn update_raw(self, b: impl AsRef<[u8]>) -> Self {
120 LOCAL_HASHER.with_borrow_mut(|h| h.update(b.as_ref()));
121 self
122 }
123
124 pub fn finalize(self) -> Digest {
125 #[cfg(debug_assertions)]
126 LOCAL_HASHER.with_borrow_mut(|h| h.locked = None);
127
128 Digest(
129 LOCAL_HASHER
130 .with_borrow_mut(|h| h.keccak.finalize_reset())
131 .into(),
132 )
133 }
134}
135
136#[cfg(debug_assertions)]
137impl Drop for Hasher {
138 fn drop(&mut self) {
139 LOCAL_HASHER.with_borrow_mut(|h| h.locked = None);
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[derive(Debug, Hash)]
148 struct ToHash {
149 u: usize,
150 s: String,
151 c: char,
152 }
153
154 #[test]
155 fn test_hasher() {
156 let value = b"abc";
157 let expected = [
158 0x4e, 0x3, 0x65, 0x7a, 0xea, 0x45, 0xa9, 0x4f, 0xc7, 0xd4, 0x7b, 0xa8, 0x26, 0xc8,
159 0xd6, 0x67, 0xc0, 0xd1, 0xe6, 0xe3, 0x3a, 0x64, 0xa0, 0x36, 0xec, 0x44, 0xf5, 0x8f,
160 0xa1, 0x2d, 0x6c, 0x45,
161 ];
162
163 let digest = Hasher::once_raw(value);
164 assert_eq!(digest, expected);
165
166 let digest = Hasher::new().update_raw(value).finalize();
167 assert_eq!(digest, expected);
168
169 let value = ToHash {
170 u: 654,
171 s: String::from("tohash"),
172 c: 'h',
173 };
174 let multiple = Hasher::new().update(&value).finalize();
175 let once = Hasher::once(&value);
176 assert_eq!(multiple, once);
177 }
178
179 #[test]
180 #[should_panic]
181 #[cfg(debug_assertions)]
182 fn test_multiple_hasher() {
183 let _h = Hasher::new();
184 let _h = Hasher::new();
185 }
186
187 #[test]
188 fn test_hash_eq() {
189 let value = b"abcdef";
190 let digest = Hasher::once_raw(value);
191 let digest2 = Hasher::new()
192 .update_raw(&value[0..3])
193 .update_raw(&value[3..6])
194 .finalize();
195 assert_eq!(digest, digest2);
196 }
197}