1use arcanum_core::error::Result;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct HashOutput(Vec<u8>);
9
10impl HashOutput {
11 pub fn new(bytes: Vec<u8>) -> Self {
13 Self(bytes)
14 }
15
16 pub fn from_array<const N: usize>(arr: [u8; N]) -> Self {
18 Self(arr.to_vec())
19 }
20
21 pub fn as_bytes(&self) -> &[u8] {
23 &self.0
24 }
25
26 pub fn len(&self) -> usize {
28 self.0.len()
29 }
30
31 pub fn is_empty(&self) -> bool {
33 self.0.is_empty()
34 }
35
36 pub fn to_hex(&self) -> String {
38 hex::encode(&self.0)
39 }
40
41 pub fn from_hex(s: &str) -> Result<Self> {
43 let bytes =
44 hex::decode(s).map_err(|e| arcanum_core::error::Error::ParseError(e.to_string()))?;
45 Ok(Self(bytes))
46 }
47
48 pub fn to_array<const N: usize>(&self) -> Option<[u8; N]> {
50 if self.0.len() != N {
51 return None;
52 }
53 let mut arr = [0u8; N];
54 arr.copy_from_slice(&self.0);
55 Some(arr)
56 }
57
58 pub fn into_bytes(self) -> Vec<u8> {
60 self.0
61 }
62}
63
64impl AsRef<[u8]> for HashOutput {
65 fn as_ref(&self) -> &[u8] {
66 &self.0
67 }
68}
69
70impl std::fmt::Debug for HashOutput {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 write!(f, "HashOutput({})", self.to_hex())
73 }
74}
75
76impl std::fmt::Display for HashOutput {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 write!(f, "{}", self.to_hex())
79 }
80}
81
82impl From<Vec<u8>> for HashOutput {
83 fn from(bytes: Vec<u8>) -> Self {
84 Self(bytes)
85 }
86}
87
88impl<const N: usize> From<[u8; N]> for HashOutput {
89 fn from(arr: [u8; N]) -> Self {
90 Self(arr.to_vec())
91 }
92}
93
94pub trait Hasher: Clone + Default {
96 const OUTPUT_SIZE: usize;
98 const BLOCK_SIZE: usize;
100 const ALGORITHM: &'static str;
102
103 fn new() -> Self;
105
106 fn update(&mut self, data: &[u8]);
108
109 fn finalize(self) -> HashOutput;
111
112 fn reset(&mut self);
114
115 fn hash(data: &[u8]) -> HashOutput {
117 let mut hasher = Self::new();
118 hasher.update(data);
119 hasher.finalize()
120 }
121
122 fn hash_all(parts: &[&[u8]]) -> HashOutput {
124 let mut hasher = Self::new();
125 for part in parts {
126 hasher.update(part);
127 }
128 hasher.finalize()
129 }
130
131 fn verify(data: &[u8], expected: &HashOutput) -> bool {
133 let computed = Self::hash(data);
134 constant_time_eq(&computed.0, &expected.0)
135 }
136}
137
138pub trait ExtendableOutput: Clone {
140 const ALGORITHM: &'static str;
142
143 fn new() -> Self;
145
146 fn update(&mut self, data: &[u8]);
148
149 fn squeeze(&mut self, output: &mut [u8]);
151
152 fn finalize_xof(self, output_len: usize) -> Vec<u8>;
154
155 fn hash_xof(data: &[u8], output_len: usize) -> Vec<u8> {
157 let mut xof = Self::new();
158 xof.update(data);
159 xof.finalize_xof(output_len)
160 }
161}
162
163pub trait KeyDerivation {
165 const ALGORITHM: &'static str;
167
168 fn derive(
176 ikm: &[u8],
177 salt: Option<&[u8]>,
178 info: Option<&[u8]>,
179 output_len: usize,
180 ) -> Result<Vec<u8>>;
181
182 fn derive_array<const N: usize>(
184 ikm: &[u8],
185 salt: Option<&[u8]>,
186 info: Option<&[u8]>,
187 ) -> Result<[u8; N]> {
188 let derived = Self::derive(ikm, salt, info, N)?;
189 let mut arr = [0u8; N];
190 arr.copy_from_slice(&derived);
191 Ok(arr)
192 }
193}
194
195pub trait PasswordHash {
197 type Params;
199 const ALGORITHM: &'static str;
201
202 fn hash_password(password: &[u8], params: &Self::Params) -> Result<String>;
206
207 fn verify_password(password: &[u8], hash: &str) -> Result<bool>;
209
210 fn derive_key(
212 password: &[u8],
213 salt: &[u8],
214 params: &Self::Params,
215 output_len: usize,
216 ) -> Result<Vec<u8>>;
217}
218
219fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
221 if a.len() != b.len() {
222 return false;
223 }
224
225 let mut result = 0u8;
226 for (x, y) in a.iter().zip(b.iter()) {
227 result |= x ^ y;
228 }
229 result == 0
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_hash_output() {
238 let hash = HashOutput::new(vec![0xde, 0xad, 0xbe, 0xef]);
239 assert_eq!(hash.to_hex(), "deadbeef");
240 assert_eq!(hash.len(), 4);
241
242 let restored = HashOutput::from_hex("deadbeef").unwrap();
243 assert_eq!(hash, restored);
244 }
245
246 #[test]
247 fn test_constant_time_eq() {
248 assert!(constant_time_eq(b"hello", b"hello"));
249 assert!(!constant_time_eq(b"hello", b"world"));
250 assert!(!constant_time_eq(b"hello", b"hell"));
251 }
252}