Skip to main content

dryoc/
generichash.rs

1//! # Generic hashing
2//!
3//! [`GenericHash`] implements libsodium's generic hashing with BLAKE2b. Without
4//! a key, it produces a general-purpose cryptographic hash. With a secret key,
5//! it acts as a message authentication code (MAC) or pseudorandom function
6//! (PRF). Keyed BLAKE2b is not HMAC.
7//!
8//! # Rustaceous API example, single-part interface
9//!
10//! ```
11//! # #[cfg(feature = "alloc")]
12//! # {
13//! use base64::Engine as _;
14//! use base64::engine::general_purpose;
15//! use dryoc::generichash::{DefaultGenericHash, Key};
16//!
17//! // The key type must be specified because `None` does not identify it.
18//! let hash = DefaultGenericHash::hash_to_vec::<_, Key>(b"hello", None).expect("hash failed");
19//!
20//! assert_eq!(
21//!     general_purpose::STANDARD.encode(&hash),
22//!     "Mk3PAn3UowqTLEQfNlol6GsXPe+kuOWJSCU0cbgbcs8="
23//! );
24//! # }
25//! ```
26//!
27//! # Rustaceous API example, incremental interface
28//!
29//! ```
30//! # #[cfg(feature = "alloc")]
31//! # {
32//! use base64::Engine as _;
33//! use base64::engine::general_purpose;
34//! use dryoc::generichash::{DefaultGenericHash, Key};
35//!
36//! // The key type must be specified because `None` does not identify it.
37//! let mut hasher = DefaultGenericHash::new::<Key>(None).expect("new failed");
38//! hasher.update(b"hello");
39//! let hash = hasher.finalize_to_vec().expect("finalize failed");
40//!
41//! assert_eq!(
42//!     general_purpose::STANDARD.encode(&hash),
43//!     "Mk3PAn3UowqTLEQfNlol6GsXPe+kuOWJSCU0cbgbcs8="
44//! );
45//! # }
46//! ```
47
48#[cfg(feature = "alloc")]
49use alloc::vec::Vec;
50
51use crate::classic::crypto_generichash::{
52    GenericHashState, crypto_generichash, crypto_generichash_final, crypto_generichash_init,
53    crypto_generichash_update,
54};
55use crate::constants::{CRYPTO_GENERICHASH_BYTES, CRYPTO_GENERICHASH_KEYBYTES};
56use crate::error::Error;
57use crate::types::*;
58
59/// Stack-allocated hash output of the recommended output length.
60pub type Hash = StackByteArray<CRYPTO_GENERICHASH_BYTES>;
61/// Stack-allocated secret key for use with the generic hash algorithm.
62pub type Key = StackByteArray<CRYPTO_GENERICHASH_KEYBYTES>;
63
64#[cfg(any(
65    all(feature = "protected", any(unix, windows)),
66    all(doc, not(doctest), feature = "std")
67))]
68#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
69pub mod protected {
70    //! # Protected memory type aliases for [`GenericHash`]
71    //!
72    //! Protected-memory aliases for generic-hash keys and outputs.
73    //!
74    //! ## Example
75    //!
76    //! ```
77    //! use dryoc::generichash::GenericHash;
78    //! use dryoc::generichash::protected::*;
79    //!
80    //! // Create a randomly generated key, lock it, protect it as read-only
81    //! let key = Key::generate_readonly_locked().expect("generate failed");
82    //! let input =
83    //!     HeapBytes::from_slice_into_readonly_locked(b"super secret input").expect("input failed");
84    //! let hash: Locked<Hash> = GenericHash::hash(&input, Some(&key)).expect("hash failed");
85    //! ```
86    use super::*;
87    pub use crate::protected::*;
88
89    /// Heap-allocated, page-aligned secret key for the generic hash algorithm,
90    /// for use with protected memory.
91    pub type Key = HeapByteArray<CRYPTO_GENERICHASH_KEYBYTES>;
92    /// Heap-allocated, page-aligned hash output for the generic hash algorithm,
93    /// for use with protected memory.
94    pub type Hash = HeapByteArray<CRYPTO_GENERICHASH_BYTES>;
95}
96
97/// Provides a generic hash function implementation based on Blake2b. Compatible
98/// with libsodium's generic hash.
99///
100/// Like libsodium, `OUTPUT_LENGTH` may be 1 to 64 bytes and `KEY_LENGTH` 0 to
101/// 64 bytes. [`CRYPTO_GENERICHASH_BYTES_MIN`] and
102/// [`CRYPTO_GENERICHASH_KEYBYTES_MIN`] (16 bytes) are recommended minimums,
103/// not enforced ones, and an empty key is the same as no key.
104///
105/// Cloning a hasher copies its in-progress state, so a common prefix can be
106/// hashed once and finished with different suffixes. Each copy is wiped when
107/// dropped.
108///
109/// [`CRYPTO_GENERICHASH_BYTES_MIN`]: crate::constants::CRYPTO_GENERICHASH_BYTES_MIN
110/// [`CRYPTO_GENERICHASH_KEYBYTES_MIN`]: crate::constants::CRYPTO_GENERICHASH_KEYBYTES_MIN
111#[derive(Clone)]
112pub struct GenericHash<const KEY_LENGTH: usize, const OUTPUT_LENGTH: usize> {
113    state: GenericHashState,
114}
115
116/// [`GenericHash`] with libsodium's recommended key length
117/// ([`CRYPTO_GENERICHASH_KEYBYTES`]) and output length
118/// ([`CRYPTO_GENERICHASH_BYTES`]).
119pub type DefaultGenericHash = GenericHash<CRYPTO_GENERICHASH_KEYBYTES, CRYPTO_GENERICHASH_BYTES>;
120
121impl<const KEY_LENGTH: usize, const OUTPUT_LENGTH: usize> GenericHash<KEY_LENGTH, OUTPUT_LENGTH> {
122    /// Returns a new incremental hasher with an optional secret `key`.
123    ///
124    /// # Errors
125    ///
126    /// Returns an error if `OUTPUT_LENGTH` is not 1 to 64 bytes or `key` is
127    /// longer than 64 bytes.
128    pub fn new<Key: ByteArray<KEY_LENGTH>>(key: Option<&Key>) -> Result<Self, Error> {
129        Ok(Self {
130            state: crypto_generichash_init(key.map(|k| k.as_slice()), OUTPUT_LENGTH)?,
131        })
132    }
133
134    /// Updates the hasher state from `input`.
135    pub fn update<Input: Bytes + ?Sized>(&mut self, input: &Input) {
136        crypto_generichash_update(&mut self.state, input.as_slice())
137    }
138
139    /// Computes and returns the final hash value.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if the underlying BLAKE2b finalization rejects the
144    /// output. Initialization normally guarantees a valid output length.
145    pub fn finalize<Output: NewByteArray<OUTPUT_LENGTH>>(self) -> Result<Output, Error> {
146        let mut output = Output::new_byte_array();
147
148        crypto_generichash_final(self.state, output.as_mut_slice())?;
149
150        Ok(output)
151    }
152
153    /// Computes and returns the final hash value as a [`Vec`]. Provided for
154    /// convenience.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error if the underlying BLAKE2b finalization rejects the
159    /// output. Initialization normally guarantees a valid output length.
160    #[cfg(feature = "alloc")]
161    pub fn finalize_to_vec(self) -> Result<Vec<u8>, Error> {
162        Ok(self.finalize::<StackByteArray<OUTPUT_LENGTH>>()?.to_vec())
163    }
164
165    /// Computes the hash of `input` with an optional secret `key`.
166    ///
167    /// The output length is determined by `Output`. Providing a key selects
168    /// keyed BLAKE2b, which can be used as a MAC or PRF.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if `OUTPUT_LENGTH` is not 1 to 64 bytes or `key` is
173    /// longer than 64 bytes.
174    ///
175    /// # Example
176    ///
177    /// ```
178    /// use base64::Engine as _;
179    /// use base64::engine::general_purpose;
180    /// use dryoc::generichash::{GenericHash, Hash};
181    ///
182    /// let output: Hash =
183    ///     GenericHash::hash(b"hello", Some(b"a very secret key")).expect("hash failed");
184    ///
185    /// assert_eq!(
186    ///     general_purpose::STANDARD.encode(&output),
187    ///     "AECDe+XJsB6nOkbCsbS/OPXdzpcRm3AolW/Bg1LFY9A="
188    /// );
189    /// ```
190    pub fn hash<
191        Output: NewByteArray<OUTPUT_LENGTH>,
192        Input: Bytes + ?Sized,
193        Key: ByteArray<KEY_LENGTH>,
194    >(
195        input: &Input,
196        key: Option<&Key>,
197    ) -> Result<Output, Error> {
198        let mut output = Output::new_byte_array();
199        crypto_generichash(
200            output.as_mut_slice(),
201            input.as_slice(),
202            key.map(|k| k.as_slice()),
203        )?;
204        Ok(output)
205    }
206
207    /// Convenience wrapper for [`GenericHash::hash`].
208    ///
209    /// # Errors
210    ///
211    /// Returns an error under the same conditions as [`GenericHash::hash`].
212    #[cfg(feature = "alloc")]
213    pub fn hash_to_vec<Input: Bytes + ?Sized, Key: ByteArray<KEY_LENGTH>>(
214        input: &Input,
215        key: Option<&Key>,
216    ) -> Result<Vec<u8>, Error> {
217        Ok(Self::hash::<StackByteArray<OUTPUT_LENGTH>, _, _>(input, key)?.to_vec())
218    }
219}
220
221#[cfg(all(test, feature = "alloc"))]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_generichash() {
227        use base64::Engine as _;
228        use base64::engine::general_purpose;
229
230        let mut hasher = DefaultGenericHash::new::<Key>(None).expect("new hash failed");
231        hasher.update(b"hello");
232
233        let output: Hash = hasher.finalize().expect("finalize failed");
234
235        assert_eq!(
236            general_purpose::STANDARD.encode(&output),
237            "Mk3PAn3UowqTLEQfNlol6GsXPe+kuOWJSCU0cbgbcs8="
238        );
239
240        let mut hasher = DefaultGenericHash::new::<Key>(None).expect("new hash failed");
241        hasher.update(b"hello");
242
243        let output = hasher.finalize_to_vec().expect("finalize failed");
244
245        assert_eq!(
246            general_purpose::STANDARD.encode(output),
247            "Mk3PAn3UowqTLEQfNlol6GsXPe+kuOWJSCU0cbgbcs8="
248        );
249    }
250
251    #[test]
252    fn test_generichash_onetime() {
253        use base64::Engine as _;
254        use base64::engine::general_purpose;
255
256        let output: Hash =
257            GenericHash::hash(b"hello", Some(b"a very secret key")).expect("hash failed");
258
259        assert_eq!(
260            general_purpose::STANDARD.encode(&output),
261            "AECDe+XJsB6nOkbCsbS/OPXdzpcRm3AolW/Bg1LFY9A="
262        );
263
264        let output: Hash =
265            DefaultGenericHash::hash::<_, _, Key>(b"hello", None).expect("hash failed");
266
267        assert_eq!(
268            general_purpose::STANDARD.encode(&output),
269            "Mk3PAn3UowqTLEQfNlol6GsXPe+kuOWJSCU0cbgbcs8="
270        );
271
272        let output =
273            DefaultGenericHash::hash_to_vec::<_, Key>(b"hello", None).expect("hash failed");
274
275        assert_eq!(
276            general_purpose::STANDARD.encode(output),
277            "Mk3PAn3UowqTLEQfNlol6GsXPe+kuOWJSCU0cbgbcs8="
278        );
279    }
280    #[test]
281    fn test_generichash_clone_mid_stream() {
282        let key = Key::from(&[0x5au8; CRYPTO_GENERICHASH_KEYBYTES]);
283        // Split inside the first block, on a block boundary, and after it.
284        let message: Vec<u8> = (0..300u16).map(|i| (i * 31 % 251) as u8).collect();
285        for key in [None, Some(&key)] {
286            for split in [0, 5, 128, 129, 300] {
287                let expected: Hash =
288                    GenericHash::hash(message.as_slice(), key).expect("hash failed");
289
290                let mut hasher = DefaultGenericHash::new(key).expect("new hash failed");
291                hasher.update(&message[..split]);
292                let mut copy = hasher.clone();
293                hasher.update(&message[split..]);
294                copy.update(&message[split..]);
295
296                let original: Hash = hasher.finalize().expect("finalize failed");
297                let cloned: Hash = copy.finalize().expect("finalize failed");
298                assert_eq!(original, expected);
299                assert_eq!(cloned, expected);
300            }
301        }
302
303        // Diverging suffixes must not affect each other.
304        let mut hasher = DefaultGenericHash::new(Some(&key)).expect("new hash failed");
305        hasher.update(b"shared prefix ");
306        let mut copy = hasher.clone();
307        hasher.update(b"one");
308        copy.update(b"two");
309        let one: Hash = hasher.finalize().expect("finalize failed");
310        let two: Hash = copy.finalize().expect("finalize failed");
311        let expected_one: Hash =
312            GenericHash::hash(b"shared prefix one", Some(&key)).expect("hash failed");
313        let expected_two: Hash =
314            GenericHash::hash(b"shared prefix two", Some(&key)).expect("hash failed");
315        assert_eq!(one, expected_one);
316        assert_eq!(two, expected_two);
317    }
318
319    #[test]
320    fn test_generichash_onetime_empty() {
321        use base64::Engine as _;
322        use base64::engine::general_purpose;
323
324        let output = DefaultGenericHash::hash_to_vec::<_, Key>(&[], None).expect("hash failed");
325
326        assert_eq!(
327            general_purpose::STANDARD.encode(output),
328            "DldRwCblQ7Loqy6wYJnaodHl30d3j3eH+qtFzfEv46g="
329        );
330    }
331
332    #[test]
333    fn test_vectors() {
334        let test_vec = |input, key, hash| {
335            let input = hex::decode(input).expect("decode input");
336            let key: [u8; 64] = hex::decode(key)
337                .expect("decode key")
338                .try_into()
339                .expect("64-byte key");
340            let expected_hash = hex::decode(hash).expect("decode hash");
341
342            let hash: [u8; 64] =
343                GenericHash::<64, 64>::hash(&input, Some(&key)).expect("hash failed");
344
345            assert_eq!(expected_hash, hash);
346        };
347
348        test_vec("", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568");
349        test_vec("00", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd");
350        test_vec("0001", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965");
351        test_vec("000102", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1");
352        test_vec("00010203", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "beaa5a3d08f3807143cf621d95cd690514d0b49efff9c91d24b59241ec0eefa5f60196d407048bba8d2146828ebcb0488d8842fd56bb4f6df8e19c4b4daab8ac");
353        test_vec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfc", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "a6213743568e3b3158b9184301f3690847554c68457cb40fc9a4b8cfd8d4a118c301a07737aeda0f929c68913c5f51c80394f53bff1c3e83b2e40ca97eba9e15");
354        test_vec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfd", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "d444bfa2362a96df213d070e33fa841f51334e4e76866b8139e8af3bb3398be2dfaddcbc56b9146de9f68118dc5829e74b0c28d7711907b121f9161cb92b69a9");
355        test_vec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461");
356    }
357
358    use crate::constants::{CRYPTO_GENERICHASH_BYTES_MAX, CRYPTO_GENERICHASH_KEYBYTES_MAX};
359
360    const FOX: &[u8] = b"The quick brown fox jumps over the lazy dog";
361
362    /// libsodium `crypto_generichash` of `FOX` for `(outlen, key = 0..keylen)`.
363    /// The unkeyed 64-byte value is the published BLAKE2b-512 digest.
364    const FOX_KAT: [(usize, usize, &str); 5] = [
365        (16, 0, "249df9a49f517ddcd37f5c897620ec73"),
366        (
367            64,
368            0,
369            concat!(
370                "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673",
371                "f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918",
372            ),
373        ),
374        (16, 16, "fb80e606c7e3d993cbf7117a60f630a0"),
375        (
376            32,
377            32,
378            "5d9461aff732d77d0cc98725ea29298c914fd5193b4c08ec9e3ad6b28c3e2faf",
379        ),
380        (
381            64,
382            64,
383            concat!(
384                "1d58d71414d24752db3274afdc483fc0f4c68317c4c2f6a31e09de9437ba02cc",
385                "ab8c8585790a52b0d476f7920c0e1397d1aec9e52f3df3feae76f7d6223ce5cf",
386            ),
387        ),
388    ];
389
390    fn sequential_key<const LENGTH: usize>() -> StackByteArray<LENGTH> {
391        StackByteArray::from(core::array::from_fn::<u8, LENGTH, _>(|i| i as u8))
392    }
393
394    /// Hashes `FOX` one-shot and in three incremental splits, checking both
395    /// against `expected`.
396    fn assert_fox<const KEY_LENGTH: usize, const OUTPUT_LENGTH: usize>(
397        key: Option<&StackByteArray<KEY_LENGTH>>,
398        expected: &str,
399    ) {
400        let expected = hex::decode(expected).expect("hex");
401        let output: StackByteArray<OUTPUT_LENGTH> =
402            GenericHash::<KEY_LENGTH, OUTPUT_LENGTH>::hash(FOX, key).expect("hash");
403        assert_eq!(output.as_slice(), expected.as_slice());
404        assert_eq!(
405            GenericHash::<KEY_LENGTH, OUTPUT_LENGTH>::hash_to_vec(&FOX, key).expect("hash"),
406            expected
407        );
408
409        for parts in [
410            vec![FOX],
411            vec![&FOX[..1], &FOX[1..]],
412            vec![&[][..], &FOX[..20], &FOX[20..40], &FOX[40..], &[][..]],
413        ] {
414            let mut hasher = GenericHash::<KEY_LENGTH, OUTPUT_LENGTH>::new(key).expect("new");
415            for part in &parts {
416                hasher.update(*part);
417            }
418            let output: StackByteArray<OUTPUT_LENGTH> = hasher.finalize().expect("finalize");
419            assert_eq!(output.as_slice(), expected.as_slice());
420        }
421    }
422
423    #[test]
424    fn min_and_max_output_and_key_lengths_match_libsodium_known_answers() {
425        assert_fox::<CRYPTO_GENERICHASH_KEYBYTES, 16>(None, FOX_KAT[0].2);
426        assert_fox::<CRYPTO_GENERICHASH_KEYBYTES, 64>(None, FOX_KAT[1].2);
427        assert_fox::<16, 16>(Some(&sequential_key::<16>()), FOX_KAT[2].2);
428        assert_fox::<32, 32>(Some(&sequential_key::<32>()), FOX_KAT[3].2);
429        assert_fox::<64, 64>(Some(&sequential_key::<64>()), FOX_KAT[4].2);
430
431        // A key changes the output, and the short digest is not a truncation of
432        // the long one.
433        let unkeyed16 = hex::decode(FOX_KAT[0].2).expect("hex");
434        let keyed16 = hex::decode(FOX_KAT[2].2).expect("hex");
435        let unkeyed64 = hex::decode(FOX_KAT[1].2).expect("hex");
436        assert_ne!(unkeyed16, keyed16);
437        assert_ne!(unkeyed16, &unkeyed64[..16]);
438    }
439
440    /// Lengths outside libsodium's ranges (outputs of 1 to 64 bytes, keys of
441    /// 0 to 64 bytes) are rejected, and an empty key is no key. Digests at
442    /// lengths below the recommended minimums are checked against libsodium
443    /// in `keyed_and_unkeyed_hashes_match_libsodium_at_length_bounds`.
444    #[test]
445    fn output_and_key_lengths_follow_libsodium_ranges() {
446        let empty_key: Hash =
447            GenericHash::<0, 32>::hash(FOX, Some(&sequential_key::<0>())).expect("empty key");
448        let default_unkeyed: Hash =
449            DefaultGenericHash::hash::<_, _, Key>(FOX, None).expect("unkeyed");
450        assert_eq!(empty_key, default_unkeyed);
451
452        assert!(matches!(
453            GenericHash::<CRYPTO_GENERICHASH_KEYBYTES, 0>::new::<Key>(None),
454            Err(Error::InvalidLength {
455                context: crate::ErrorContext::Output,
456                actual: 0,
457                ..
458            })
459        ));
460        let too_long: Result<StackByteArray<{ CRYPTO_GENERICHASH_BYTES_MAX + 1 }>, Error> =
461            GenericHash::<CRYPTO_GENERICHASH_KEYBYTES, { CRYPTO_GENERICHASH_BYTES_MAX + 1 }>::hash::<
462                _,
463                _,
464                Key,
465            >(FOX, None);
466        assert!(matches!(
467            too_long,
468            Err(Error::InvalidLength {
469                context: crate::ErrorContext::Output,
470                actual: 65,
471                ..
472            })
473        ));
474
475        let long_key = sequential_key::<{ CRYPTO_GENERICHASH_KEYBYTES_MAX + 1 }>();
476        assert!(matches!(
477            GenericHash::<{ CRYPTO_GENERICHASH_KEYBYTES_MAX + 1 }, 32>::new(Some(&long_key)),
478            Err(Error::InvalidLength {
479                context: crate::ErrorContext::Blake2bKey,
480                actual: 65,
481                ..
482            })
483        ));
484        let rejected: Result<Hash, Error> =
485            GenericHash::<{ CRYPTO_GENERICHASH_KEYBYTES_MAX + 1 }, 32>::hash(FOX, Some(&long_key));
486        assert!(matches!(
487            rejected,
488            Err(Error::InvalidLength {
489                context: crate::ErrorContext::Blake2bKey,
490                actual: 65,
491                ..
492            })
493        ));
494    }
495
496    #[cfg(all(feature = "protected", any(unix, windows)))]
497    #[test]
498    fn locked_key_input_and_output_match_stack_types() {
499        use crate::generichash::protected::*;
500
501        let input = HeapBytes::from_slice_into_readonly_locked(FOX).expect("lock input");
502        let key = sequential_key::<CRYPTO_GENERICHASH_KEYBYTES>();
503        let locked_key =
504            protected::Key::from_slice_into_readonly_locked(key.as_slice()).expect("lock key");
505        let expected = hex::decode(FOX_KAT[3].2).expect("hex");
506
507        let hash: Locked<protected::Hash> =
508            GenericHash::hash(&input, Some(&locked_key)).expect("hash");
509        assert_eq!(hash.as_slice(), expected.as_slice());
510        let stack: Hash = GenericHash::hash(FOX, Some(&key)).expect("hash");
511        assert_eq!(stack.as_slice(), hash.as_slice());
512
513        let mut hasher = DefaultGenericHash::new(Some(&locked_key)).expect("new");
514        hasher.update(&input);
515        let hash: Locked<protected::Hash> = hasher.finalize().expect("finalize");
516        assert_eq!(hash.as_slice(), expected.as_slice());
517
518        let unkeyed: Locked<protected::Hash> =
519            DefaultGenericHash::hash::<_, _, protected::Key>(&input, None).expect("hash");
520        let stack_unkeyed: Hash = DefaultGenericHash::hash::<_, _, Key>(FOX, None).expect("hash");
521        assert_eq!(unkeyed.as_slice(), stack_unkeyed.as_slice());
522        assert_ne!(unkeyed.as_slice(), expected.as_slice());
523    }
524
525    #[cfg(dryoc_native_tests)]
526    #[test]
527    fn keyed_and_unkeyed_hashes_match_libsodium_at_length_bounds() {
528        fn sodium_hash(input: &[u8], key: Option<&[u8]>, outlen: usize) -> Vec<u8> {
529            crate::native_test_util::generichash(outlen, input, key).expect("libsodium hash")
530        }
531
532        for (outlen, keylen, _) in FOX_KAT {
533            let key: Vec<u8> = (0..keylen as u8).collect();
534            let key = (keylen > 0).then_some(key.as_slice());
535            let expected = sodium_hash(FOX, key, outlen);
536            let actual = match (keylen, outlen) {
537                (0, 16) => GenericHash::<64, 16>::hash_to_vec(&FOX, None::<&StackByteArray<64>>),
538                (0, 64) => GenericHash::<64, 64>::hash_to_vec(&FOX, None::<&StackByteArray<64>>),
539                (16, 16) => GenericHash::<16, 16>::hash_to_vec(&FOX, Some(&sequential_key::<16>())),
540                (32, 32) => GenericHash::<32, 32>::hash_to_vec(&FOX, Some(&sequential_key::<32>())),
541                (64, 64) => GenericHash::<64, 64>::hash_to_vec(&FOX, Some(&sequential_key::<64>())),
542                _ => unreachable!(),
543            }
544            .expect("hash");
545            assert_eq!(actual, expected);
546        }
547
548        // Output and key lengths below the recommended 16-byte minimums, which
549        // libsodium accepts, one-shot and incremental.
550        let expected = sodium_hash(FOX, Some(&[0u8][..]), 1);
551        assert_eq!(
552            GenericHash::<1, 1>::hash_to_vec(&FOX, Some(&sequential_key::<1>())).expect("hash"),
553            expected
554        );
555        let key: Vec<u8> = (0..15).collect();
556        let expected = sodium_hash(FOX, Some(&key), 15);
557        let mut hasher = GenericHash::<15, 15>::new(Some(&sequential_key::<15>())).expect("new");
558        hasher.update(FOX);
559        assert_eq!(hasher.finalize_to_vec().expect("finalize"), expected);
560
561        // Inputs straddling the 128-byte BLAKE2b block boundary.
562        let key = sequential_key::<CRYPTO_GENERICHASH_KEYBYTES>();
563        for len in [0, 1, 127, 128, 129, 255, 256, 257] {
564            let input: Vec<u8> = (0..len).map(|i| (i * 7 % 251) as u8).collect();
565            let expected = sodium_hash(&input, Some(key.as_slice()), CRYPTO_GENERICHASH_BYTES);
566            assert_eq!(
567                DefaultGenericHash::hash_to_vec(&input, Some(&key)).expect("hash"),
568                expected
569            );
570            let expected = sodium_hash(&input, None, CRYPTO_GENERICHASH_BYTES);
571            assert_eq!(
572                DefaultGenericHash::hash_to_vec::<_, Key>(&input, None).expect("hash"),
573                expected
574            );
575        }
576    }
577}