Skip to main content

arcanum_hash/
traits.rs

1//! Traits for hash functions and key derivation.
2
3use arcanum_core::error::Result;
4use serde::{Deserialize, Serialize};
5
6/// Output of a hash function.
7#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct HashOutput(Vec<u8>);
9
10impl HashOutput {
11    /// Create from bytes.
12    pub fn new(bytes: Vec<u8>) -> Self {
13        Self(bytes)
14    }
15
16    /// Create from a fixed-size array.
17    pub fn from_array<const N: usize>(arr: [u8; N]) -> Self {
18        Self(arr.to_vec())
19    }
20
21    /// Get the hash bytes.
22    pub fn as_bytes(&self) -> &[u8] {
23        &self.0
24    }
25
26    /// Get the hash length.
27    pub fn len(&self) -> usize {
28        self.0.len()
29    }
30
31    /// Check if empty.
32    pub fn is_empty(&self) -> bool {
33        self.0.is_empty()
34    }
35
36    /// Convert to hex string.
37    pub fn to_hex(&self) -> String {
38        hex::encode(&self.0)
39    }
40
41    /// Parse from hex string.
42    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    /// Convert to fixed-size array.
49    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    /// Consume and return bytes.
59    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
94/// Trait for hash functions.
95pub trait Hasher: Clone + Default {
96    /// Output size in bytes.
97    const OUTPUT_SIZE: usize;
98    /// Block size in bytes.
99    const BLOCK_SIZE: usize;
100    /// Algorithm name.
101    const ALGORITHM: &'static str;
102
103    /// Create a new hasher.
104    fn new() -> Self;
105
106    /// Update the hasher with data.
107    fn update(&mut self, data: &[u8]);
108
109    /// Finalize and return the hash.
110    fn finalize(self) -> HashOutput;
111
112    /// Reset the hasher to initial state.
113    fn reset(&mut self);
114
115    /// One-shot hash computation.
116    fn hash(data: &[u8]) -> HashOutput {
117        let mut hasher = Self::new();
118        hasher.update(data);
119        hasher.finalize()
120    }
121
122    /// Hash multiple pieces of data.
123    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    /// Verify a hash matches expected value.
132    fn verify(data: &[u8], expected: &HashOutput) -> bool {
133        let computed = Self::hash(data);
134        constant_time_eq(&computed.0, &expected.0)
135    }
136}
137
138/// Trait for extendable-output functions (XOFs).
139pub trait ExtendableOutput: Clone {
140    /// Algorithm name.
141    const ALGORITHM: &'static str;
142
143    /// Create a new XOF.
144    fn new() -> Self;
145
146    /// Update with data.
147    fn update(&mut self, data: &[u8]);
148
149    /// Read output bytes.
150    fn squeeze(&mut self, output: &mut [u8]);
151
152    /// Finalize and read specified number of bytes.
153    fn finalize_xof(self, output_len: usize) -> Vec<u8>;
154
155    /// One-shot XOF computation.
156    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
163/// Trait for key derivation functions.
164pub trait KeyDerivation {
165    /// Algorithm name.
166    const ALGORITHM: &'static str;
167
168    /// Derive key material.
169    ///
170    /// # Arguments
171    /// * `ikm` - Input key material (should be high-entropy)
172    /// * `salt` - Optional salt (recommended)
173    /// * `info` - Optional context/application-specific info
174    /// * `output_len` - Desired output length in bytes
175    fn derive(
176        ikm: &[u8],
177        salt: Option<&[u8]>,
178        info: Option<&[u8]>,
179        output_len: usize,
180    ) -> Result<Vec<u8>>;
181
182    /// Derive into a fixed-size array.
183    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
195/// Trait for password-based key derivation.
196pub trait PasswordHash {
197    /// Parameters type.
198    type Params;
199    /// Algorithm name.
200    const ALGORITHM: &'static str;
201
202    /// Hash a password for storage.
203    ///
204    /// Returns a string suitable for storage (includes salt and parameters).
205    fn hash_password(password: &[u8], params: &Self::Params) -> Result<String>;
206
207    /// Verify a password against a stored hash.
208    fn verify_password(password: &[u8], hash: &str) -> Result<bool>;
209
210    /// Derive key material from password.
211    fn derive_key(
212        password: &[u8],
213        salt: &[u8],
214        params: &Self::Params,
215        output_len: usize,
216    ) -> Result<Vec<u8>>;
217}
218
219/// Constant-time byte comparison.
220fn 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}