Skip to main content

dcrypt_algorithms/xof/blake3/
mod.rs

1//! BLAKE3 extendable output function (XOF) implementation
2//!
3//! This module provides a pure Rust implementation of the BLAKE3 cryptographic
4//! hash function in XOF (eXtendable Output Function) mode, allowing for
5//! arbitrary-length output generation.
6//!
7//! # Overview
8//!
9//! BLAKE3 is a cryptographic hash function that is:
10//! - **Fast**: Optimized for modern CPUs with SIMD instructions
11//! - **Secure**: Based on the well-analyzed ChaCha permutation
12//! - **Versatile**: Supports hashing, keyed hashing, and key derivation
13//! - **Parallelizable**: Can process multiple chunks simultaneously
14//! - **Incremental**: Supports streaming/incremental hashing
15//!
16//! # Security Properties
17//!
18//! - **Security Level**: 256 bits (128-bit collision resistance)
19//! - **Output Size**: Variable (unlimited in XOF mode)
20//! - **Key Size**: 256 bits (32 bytes) for keyed variants
21//!
22//! # Features
23//!
24//! This implementation provides three modes of operation:
25//!
26//! 1. **Standard XOF**: Variable-length output from input data
27//! 2. **Keyed XOF**: HMAC-like keyed hashing with variable output
28//! 3. **Key Derivation**: Derive keys from a context string and input data
29//!
30//! # Implementation Notes
31//!
32//! This implementation prioritizes correctness and security over performance:
33//! - Uses exact owned secret storage for sensitive data
34//! - Explicitly clears initialized secret storage on drop using safe-Rust,
35//!   best-effort optimization barriers; this is not a guarantee that register
36//!   or compiler-created copies are physically erased
37//! - Based directly on the BLAKE3 reference implementation
38//! - Does not include SIMD optimizations
39//!
40//! # Example Usage
41//!
42//! ```rust,ignore
43//! use dcrypt_algorithms::xof::{Blake3Xof, ExtendableOutputFunction};
44//!
45//! // Standard hashing with variable output
46//! let data = b"Hello, BLAKE3!";
47//! let output = Blake3Xof::generate(data, 64)?; // 64 bytes output
48//!
49//! // Incremental hashing
50//! let mut xof = Blake3Xof::new();
51//! xof.update(b"Hello, ")?;
52//! xof.update(b"BLAKE3!")?;
53//! let mut output = vec![0u8; 64];
54//! xof.squeeze(&mut output)?;
55//!
56//! // Keyed hashing
57//! let key = b"this is a 32-byte key for BLAKE3";
58//! let output = Blake3Xof::keyed_generate(key, data, 32)?;
59//!
60//! // Key derivation
61//! let context = b"MyApp v1.0.0 session key";
62//! let output = Blake3Xof::derive_key(context, data, 32)?;
63//! ```
64//!
65//! # References
66//!
67//! - [BLAKE3 Specification](https://github.com/BLAKE3-team/BLAKE3-specs)
68//! - [BLAKE3 Paper](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf)
69//! - [Reference Implementation](https://github.com/BLAKE3-team/BLAKE3)
70
71use super::{Blake3Algorithm, DeriveKeyXof, ExtendableOutputFunction, KeyedXof};
72use crate::error::{validate, Error, Result};
73use crate::xof::XofAlgorithm;
74use dcrypt_common::security::SecretBuffer;
75use dcrypt_internal::zeroing::{
76    boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
77};
78
79#[cfg(not(feature = "std"))]
80use alloc::{boxed::Box, vec};
81
82// BLAKE3 constants
83const OUT_LEN: usize = 32; // Standard output length (256 bits)
84const KEY_LEN: usize = 32; // Key length for keyed hashing (256 bits)
85const BLOCK_LEN: usize = 64; // Input block size (512 bits)
86const CHUNK_LEN: usize = 1024; // Chunk size (16 blocks)
87
88type ProtectedChainingValue = Zeroizing<[u32; 8]>;
89type ProtectedBlockWords = Zeroizing<[u32; 16]>;
90
91// Flags for domain separation and tree structure
92const CHUNK_START: u32 = 1 << 0; // First block of a chunk
93const CHUNK_END: u32 = 1 << 1; // Last block of a chunk
94const PARENT: u32 = 1 << 2; // Parent node in the tree
95const ROOT: u32 = 1 << 3; // Root node (final output)
96const KEYED_HASH: u32 = 1 << 4; // Keyed hashing mode
97const DERIVE_KEY_CONTEXT: u32 = 1 << 5; // Key derivation context
98const DERIVE_KEY_MATERIAL: u32 = 1 << 6; // Key derivation material
99
100// IV is the initialization vector for BLAKE3
101// These are the first 32 bits of the fractional parts of the square roots
102// of the first 8 primes: 2, 3, 5, 7, 11, 13, 17, 19
103const IV: [u32; 8] = [
104    0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
105];
106
107// Message word permutation for each round
108// This permutation is applied to the message words between rounds
109const MSG_PERMUTATION: [usize; 16] = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];
110
111// Convert bytes to words
112fn words_from_little_endian_bytes(bytes: &[u8], words: &mut [u32]) {
113    debug_assert_eq!(bytes.len(), 4 * words.len());
114    for i in 0..words.len() {
115        let offset = i * 4;
116        words[i] = u32::from(bytes[offset])
117            | (u32::from(bytes[offset + 1]) << 8)
118            | (u32::from(bytes[offset + 2]) << 16)
119            | (u32::from(bytes[offset + 3]) << 24);
120    }
121}
122
123// Convert words to bytes securely
124fn words_to_little_endian_bytes(words: &[u32], bytes: &mut [u8]) {
125    debug_assert_eq!(bytes.len(), 4 * words.len());
126    for i in 0..words.len() {
127        for byte in 0..4 {
128            bytes[i * 4 + byte] = (words[i] >> (byte * 8)) as u8;
129        }
130    }
131}
132
133// G function for mixing
134/// The G function is the core mixing operation in BLAKE3, derived from ChaCha.
135/// It performs a series of additions, XORs, and rotations to mix the state.
136#[inline(always)]
137fn g(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, mx: u32, my: u32) {
138    state[a] = state[a].wrapping_add(state[b]).wrapping_add(mx);
139    state[d] = (state[d] ^ state[a]).rotate_right(16);
140    state[c] = state[c].wrapping_add(state[d]);
141    state[b] = (state[b] ^ state[c]).rotate_right(12);
142
143    state[a] = state[a].wrapping_add(state[b]).wrapping_add(my);
144    state[d] = (state[d] ^ state[a]).rotate_right(8);
145    state[c] = state[c].wrapping_add(state[d]);
146    state[b] = (state[b] ^ state[c]).rotate_right(7);
147}
148
149// Apply a single round of the compression function
150fn round(state: &mut [u32; 16], m: &[u32; 16]) {
151    // Column rounds - Mix the four columns
152    g(state, 0, 4, 8, 12, m[0], m[1]);
153    g(state, 1, 5, 9, 13, m[2], m[3]);
154    g(state, 2, 6, 10, 14, m[4], m[5]);
155    g(state, 3, 7, 11, 15, m[6], m[7]);
156
157    // Diagonal rounds - Mix the four diagonals
158    g(state, 0, 5, 10, 15, m[8], m[9]);
159    g(state, 1, 6, 11, 12, m[10], m[11]);
160    g(state, 2, 7, 8, 13, m[12], m[13]);
161    g(state, 3, 4, 9, 14, m[14], m[15]);
162}
163
164// Permute message words for the next round
165fn permute(m: &mut [u32; 16]) {
166    let mut permuted = Zeroizing::new([0u32; 16]);
167    for i in 0..16 {
168        permuted[i] = m[MSG_PERMUTATION[i]];
169    }
170    m.copy_from_slice(&*permuted);
171}
172
173// Compression function for BLAKE3
174/// The compression function is the heart of BLAKE3. It takes:
175/// - A 256-bit chaining value from the previous block
176/// - A 512-bit block of message data
177/// - A 64-bit counter for the block position
178/// - The block length (normally 64, may be less for the final block)
179/// - Flags for domain separation and tree structure
180///
181/// It produces a 512-bit output that can be used as:
182/// - The chaining value for the next block (first 256 bits)
183/// - Extended output in XOF mode (all 512 bits)
184fn compress(
185    chaining_value: &[u32; 8],
186    block_words: &[u32; 16],
187    counter: u64,
188    block_len: u32,
189    flags: u32,
190) -> ProtectedBlockWords {
191    let counter_low = counter as u32;
192    let counter_high = (counter >> 32) as u32;
193
194    // Initialize state with chaining value and IV
195    let mut state = Zeroizing::new([
196        chaining_value[0],
197        chaining_value[1],
198        chaining_value[2],
199        chaining_value[3],
200        chaining_value[4],
201        chaining_value[5],
202        chaining_value[6],
203        chaining_value[7],
204        IV[0],
205        IV[1],
206        IV[2],
207        IV[3],
208        counter_low,
209        counter_high,
210        block_len,
211        flags,
212    ]);
213
214    let mut block = Zeroizing::new(*block_words);
215
216    // BLAKE3 uses exactly 7 rounds
217    for r in 0..7 {
218        // Apply the round function
219        round(&mut state, &block);
220
221        // Permute the message words for the next round
222        if r < 6 {
223            permute(&mut block);
224        }
225    }
226
227    // Create output array for the compression function
228    let mut output = Zeroizing::new([0u32; 16]);
229
230    // First 8 words: XOR the first half of the state with the second half
231    for i in 0..8 {
232        output[i] = state[i] ^ state[i + 8];
233    }
234
235    // Second 8 words: XOR the second half of the state with the input chaining value
236    for i in 0..8 {
237        output[i + 8] = state[i + 8] ^ chaining_value[i];
238    }
239
240    output
241}
242
243// Get the first 8 words as a chaining value
244fn first_8_words(compression_output: &[u32; 16]) -> ProtectedChainingValue {
245    let mut result = Zeroizing::new([0u32; 8]);
246    result.copy_from_slice(&compression_output[0..8]);
247    result
248}
249
250// Output structure
251#[derive(Clone)]
252struct Output {
253    input_chaining_value: [u32; 8],
254    block_words: [u32; 16],
255    counter: u64,
256    block_len: u32,
257    flags: u32,
258}
259
260impl Zeroize for Output {
261    fn zeroize(&mut self) {
262        self.input_chaining_value.zeroize();
263        self.block_words.zeroize();
264        self.counter.zeroize();
265        self.block_len.zeroize();
266        self.flags.zeroize();
267    }
268}
269
270impl Drop for Output {
271    fn drop(&mut self) {
272        self.zeroize();
273    }
274}
275
276impl ZeroizeOnDrop for Output {}
277
278impl Output {
279    fn chaining_value(&self) -> ProtectedChainingValue {
280        let compression_output = compress(
281            &self.input_chaining_value,
282            &self.block_words,
283            self.counter,
284            self.block_len,
285            self.flags,
286        );
287        first_8_words(&compression_output)
288    }
289
290    fn root_output_bytes(&self, out_slice: &mut [u8]) {
291        for (output_block_counter, out_block) in out_slice.chunks_mut(2 * OUT_LEN).enumerate() {
292            let words = compress(
293                &self.input_chaining_value,
294                &self.block_words,
295                output_block_counter as u64,
296                self.block_len,
297                self.flags | ROOT,
298            );
299
300            // Copy output bytes - ensure little-endian encoding
301            for (i, word) in words.iter().enumerate() {
302                let start = i * 4;
303                if start >= out_block.len() {
304                    break;
305                }
306                let end = core::cmp::min((i + 1) * 4, out_block.len());
307                for (offset, byte) in out_block[start..end].iter_mut().enumerate() {
308                    *byte = (word >> (offset * 8)) as u8;
309                }
310            }
311        }
312    }
313}
314
315// Chunk state
316#[derive(Clone)]
317struct ChunkState {
318    chaining_value: [u32; 8],
319    chunk_counter: u64,
320    block: [u8; BLOCK_LEN],
321    block_len: u8,
322    blocks_compressed: u8,
323    flags: u32,
324}
325
326impl Zeroize for ChunkState {
327    fn zeroize(&mut self) {
328        self.chaining_value.zeroize();
329        self.chunk_counter.zeroize();
330        self.block.zeroize();
331        self.block_len.zeroize();
332        self.blocks_compressed.zeroize();
333        self.flags.zeroize();
334    }
335}
336
337impl Drop for ChunkState {
338    fn drop(&mut self) {
339        self.zeroize();
340    }
341}
342
343impl ZeroizeOnDrop for ChunkState {}
344
345impl ChunkState {
346    fn new(key_words: &[u32; 8], chunk_counter: u64, flags: u32) -> Self {
347        let mut state = Self {
348            chaining_value: [0; 8],
349            chunk_counter,
350            block: [0; BLOCK_LEN],
351            block_len: 0,
352            blocks_compressed: 0,
353            flags,
354        };
355        state.chaining_value.copy_from_slice(key_words);
356        state
357    }
358
359    fn len(&self) -> usize {
360        (self.blocks_compressed as usize) * BLOCK_LEN + (self.block_len as usize)
361    }
362
363    fn start_flag(&self) -> u32 {
364        if self.blocks_compressed == 0 {
365            CHUNK_START
366        } else {
367            0
368        }
369    }
370
371    // Internal update implementation
372    fn update_internal(&mut self, mut input: &[u8]) -> Result<()> {
373        // Check if adding this input would exceed chunk size limit
374        if self.len() + input.len() > CHUNK_LEN {
375            let want = CHUNK_LEN - self.len();
376            self.update_internal(&input[..want])?;
377            return Ok(());
378        }
379
380        while !input.is_empty() {
381            // If the block is full, compress it
382            if self.block_len as usize == BLOCK_LEN {
383                let mut block_words = Zeroizing::new([0u32; 16]);
384                words_from_little_endian_bytes(&self.block, &mut block_words[..]);
385
386                let compression_output = compress(
387                    &self.chaining_value,
388                    &block_words,
389                    self.chunk_counter,
390                    BLOCK_LEN as u32,
391                    self.flags | self.start_flag(),
392                );
393                let chaining_value = first_8_words(&compression_output);
394                self.chaining_value.copy_from_slice(&*chaining_value);
395
396                self.blocks_compressed += 1;
397                self.block.zeroize();
398                self.block_len = 0;
399            }
400
401            // Copy input data into the block
402            let want = BLOCK_LEN - self.block_len as usize;
403            let take = core::cmp::min(want, input.len());
404
405            self.block[self.block_len as usize..self.block_len as usize + take]
406                .copy_from_slice(&input[..take]);
407
408            self.block_len += take as u8;
409            input = &input[take..];
410        }
411
412        Ok(())
413    }
414
415    // Public update method to maintain compatibility with tests
416    #[cfg(test)]
417    pub fn update(&mut self, input: &[u8]) -> Result<()> {
418        self.update_internal(input)
419    }
420
421    fn output(&self) -> Output {
422        // Zero-pad the block to create a full set of block words
423        let mut block_words = Zeroizing::new([0u32; 16]);
424        words_from_little_endian_bytes(&self.block, &mut block_words[..]);
425
426        Output {
427            input_chaining_value: self.chaining_value,
428            block_words: *block_words,
429            counter: self.chunk_counter,
430            block_len: self.block_len as u32,
431            flags: self.flags | self.start_flag() | CHUNK_END,
432        }
433    }
434}
435
436// Parent node creation
437fn parent_output(
438    left_child_cv: &[u32; 8],
439    right_child_cv: &[u32; 8],
440    key_words: &[u32; 8],
441    flags: u32,
442) -> Output {
443    let mut block_words = Zeroizing::new([0u32; 16]);
444    block_words[..8].copy_from_slice(left_child_cv);
445    block_words[8..].copy_from_slice(right_child_cv);
446
447    let mut input_chaining_value = Zeroizing::new([0u32; 8]);
448    input_chaining_value.copy_from_slice(key_words);
449    Output {
450        input_chaining_value: *input_chaining_value,
451        block_words: *block_words,
452        counter: 0,
453        block_len: BLOCK_LEN as u32,
454        flags: PARENT | flags,
455    }
456}
457
458// Parent chaining value
459fn parent_cv(
460    left_child_cv: &[u32; 8],
461    right_child_cv: &[u32; 8],
462    key_words: &[u32; 8],
463    flags: u32,
464) -> ProtectedChainingValue {
465    parent_output(left_child_cv, right_child_cv, key_words, flags).chaining_value()
466}
467
468/// BLAKE3 extendable output function
469///
470/// This struct implements the BLAKE3 algorithm as an XOF, capable of producing
471/// outputs of arbitrary length. It maintains the internal state required for
472/// incremental hashing and supports all three BLAKE3 modes of operation.
473///
474/// # Internal Structure
475///
476/// The implementation uses:
477/// - A chunk state for processing input data in 1024-byte chunks
478/// - A stack of chaining values for the tree structure
479/// - Secure key storage using `SecretBuffer`
480/// - Flags to indicate the current mode of operation
481///
482/// # Security
483///
484/// Owned keys and intermediate arrays use exact or fixed-size secret containers
485/// whose drop paths explicitly clear initialized storage. Safe Rust cannot
486/// guarantee physical erasure of register or compiler-created copies. The
487/// scalar compression schedule is intended to avoid secret-dependent control
488/// flow, but that is not a blanket timing proof for every compiler and target.
489///
490/// # Thread Safety
491///
492/// `Blake3Xof` is not thread-safe for concurrent access. Each thread should
493/// use its own instance.
494#[derive(Clone)]
495pub struct Blake3Xof {
496    chunk_state: ChunkState,
497    key_words: SecretBuffer<32>, // Secure storage for key words (8 u32s = 32 bytes)
498    cv_stack: Zeroizing<Box<[[u32; 8]]>>,
499    flags: u32,
500}
501
502// Implement Drop and ZeroizeOnDrop for Blake3Xof
503impl Drop for Blake3Xof {
504    fn drop(&mut self) {
505        self.zeroize();
506    }
507}
508
509impl ZeroizeOnDrop for Blake3Xof {}
510
511// Manually implement Zeroize for Blake3Xof
512impl Zeroize for Blake3Xof {
513    fn zeroize(&mut self) {
514        self.chunk_state.zeroize();
515        self.key_words.zeroize();
516        self.cv_stack.zeroize();
517        self.flags = 0;
518    }
519}
520
521impl Blake3Xof {
522    // Convert key words from SecretBuffer to [u32; 8]
523    fn get_key_words(&self) -> ProtectedChainingValue {
524        let mut words = Zeroizing::new([0u32; 8]);
525        let key_bytes = self.key_words.as_ref();
526        words_from_little_endian_bytes(key_bytes, &mut words[..]);
527        words
528    }
529
530    fn push_stack(&mut self, cv: ProtectedChainingValue) {
531        let current_len = self.cv_stack.len();
532        let mut replacement = Zeroizing::new(vec![[0u32; 8]; current_len + 1].into_boxed_slice());
533        replacement[..current_len].copy_from_slice(&self.cv_stack);
534        replacement[current_len].copy_from_slice(&*cv);
535        self.cv_stack = replacement;
536    }
537
538    fn pop_stack(&mut self) -> Result<ProtectedChainingValue> {
539        let current_len = self.cv_stack.len();
540        if current_len == 0 {
541            return Err(Error::Processing {
542                operation: "BLAKE3",
543                details: "Stack underflow",
544            });
545        }
546        let value = Zeroizing::new(self.cv_stack[current_len - 1]);
547        let mut replacement = Zeroizing::new(vec![[0u32; 8]; current_len - 1].into_boxed_slice());
548        replacement.copy_from_slice(&self.cv_stack[..current_len - 1]);
549        self.cv_stack = replacement;
550        Ok(value)
551    }
552
553    fn add_chunk_chaining_value(
554        &mut self,
555        mut new_cv: ProtectedChainingValue,
556        mut total_chunks: u64,
557    ) -> Result<()> {
558        while total_chunks & 1 == 0 {
559            let left_cv = self.pop_stack()?;
560            let key_words = self.get_key_words();
561            new_cv = parent_cv(&left_cv, &new_cv, &key_words, self.flags);
562            total_chunks >>= 1;
563        }
564        self.push_stack(new_cv);
565        Ok(())
566    }
567
568    fn finalize(&mut self, out_slice: &mut [u8]) -> Result<()> {
569        let mut output = self.chunk_state.output();
570        let mut parent_nodes_remaining = self.cv_stack.len();
571
572        while parent_nodes_remaining > 0 {
573            parent_nodes_remaining -= 1;
574            let right_cv = output.chaining_value();
575            let key_words = self.get_key_words();
576            output = parent_output(
577                &self.cv_stack[parent_nodes_remaining],
578                &right_cv,
579                &key_words,
580                self.flags,
581            );
582        }
583
584        output.root_output_bytes(out_slice);
585        Ok(())
586    }
587
588    /// Utility function for digest generation
589    ///
590    /// This is a convenience function that creates a BLAKE3 XOF instance,
591    /// processes the input data, and returns the requested number of output bytes.
592    ///
593    /// # Arguments
594    ///
595    /// * `data` - The input data to hash
596    /// * `len` - The desired output length in bytes
597    ///
598    /// # Returns
599    ///
600    /// A vector containing `len` bytes of output, or an error if the length is invalid.
601    ///
602    /// # Example
603    ///
604    /// ```rust,ignore
605    /// let hash = Blake3Xof::generate(b"hello world", 32)?;
606    /// assert_eq!(hash.len(), 32);
607    /// ```
608    pub fn generate(data: &[u8], len: usize) -> Result<ZeroizingBytes> {
609        Blake3Algorithm::validate_output_length(len)?;
610
611        let mut xof = Self::new();
612        xof.update(data)?;
613        let mut result = Zeroizing::new(boxed_bytes_zeroed(len));
614        xof.squeeze(&mut result)?;
615        Ok(result)
616    }
617}
618
619impl ExtendableOutputFunction for Blake3Xof {
620    /// Creates a new BLAKE3 XOF instance in standard hashing mode.
621    ///
622    /// The instance is initialized with the standard BLAKE3 IV and is ready
623    /// to accept input data via the `update` method.
624    fn new() -> Self {
625        // Convert IV to bytes for SecretBuffer storage
626        let mut key_bytes = Zeroizing::new([0u8; 32]);
627        words_to_little_endian_bytes(&IV, &mut key_bytes[..]);
628
629        Self {
630            chunk_state: ChunkState::new(&IV, 0, 0),
631            key_words: SecretBuffer::new(*key_bytes),
632            cv_stack: Zeroizing::new(Box::default()),
633            flags: 0,
634        }
635    }
636
637    fn update(&mut self, mut input: &[u8]) -> Result<()> {
638        while !input.is_empty() {
639            if self.chunk_state.len() == CHUNK_LEN {
640                let chunk_cv = self.chunk_state.output().chaining_value();
641                let total_chunks = self.chunk_state.chunk_counter + 1;
642                self.add_chunk_chaining_value(chunk_cv, total_chunks)?;
643                let key_words = self.get_key_words();
644                self.chunk_state = ChunkState::new(&key_words, total_chunks, self.flags);
645            }
646
647            let want = CHUNK_LEN - self.chunk_state.len();
648            let take = core::cmp::min(want, input.len());
649            self.chunk_state.update_internal(&input[..take])?;
650            input = &input[take..];
651        }
652
653        Ok(())
654    }
655
656    fn finalize(&mut self) -> Result<()> {
657        Ok(())
658    }
659
660    fn squeeze(&mut self, output: &mut [u8]) -> Result<()> {
661        Blake3Algorithm::validate_output_length(output.len())?;
662        self.finalize(output)
663    }
664
665    fn squeeze_into_vec(&mut self, len: usize) -> Result<ZeroizingBytes> {
666        Blake3Algorithm::validate_output_length(len)?;
667        let mut result = Zeroizing::new(boxed_bytes_zeroed(len));
668        self.squeeze(&mut result)?;
669        Ok(result)
670    }
671
672    fn reset(&mut self) -> Result<()> {
673        *self = Self::new();
674        Ok(())
675    }
676
677    fn security_level() -> usize {
678        Blake3Algorithm::SECURITY_LEVEL
679    }
680}
681
682impl KeyedXof for Blake3Xof {
683    /// Creates a new BLAKE3 XOF instance in keyed hashing mode.
684    ///
685    /// This mode uses a 256-bit key to create a MAC (Message Authentication Code).
686    /// The key is mixed into the initial state, providing authentication.
687    ///
688    /// # Arguments
689    ///
690    /// * `key` - A 32-byte (256-bit) key
691    ///
692    /// # Errors
693    ///
694    /// Returns an error if the key length is not exactly 32 bytes.
695    fn with_key(key: &[u8]) -> Result<Self> {
696        validate::length("BLAKE3 key", key.len(), KEY_LEN)?;
697
698        // Create SecretBuffer for the key
699        let mut key_bytes = Zeroizing::new([0u8; KEY_LEN]);
700        key_bytes.copy_from_slice(key);
701        let key_buf = SecretBuffer::new(*key_bytes);
702
703        // Convert key to key words for chunk state initialization
704        let mut key_words = Zeroizing::new([0u32; 8]);
705        words_from_little_endian_bytes(key, &mut key_words[..]);
706
707        let instance = Self {
708            chunk_state: ChunkState::new(&key_words, 0, KEYED_HASH),
709            key_words: key_buf,
710            cv_stack: Zeroizing::new(Box::default()),
711            flags: KEYED_HASH,
712        };
713
714        Ok(instance)
715    }
716}
717
718impl DeriveKeyXof for Blake3Xof {
719    /// Creates a new BLAKE3 XOF instance in key derivation mode.
720    ///
721    /// This mode is designed for deriving keys from input key material.
722    /// It first hashes the context string to create a context-specific key,
723    /// then uses that key to process the actual key material.
724    ///
725    /// # Arguments
726    ///
727    /// * `context` - A context string that domain-separates different uses
728    ///
729    /// # Security Note
730    ///
731    /// The context string should be unique for each application to ensure
732    /// that keys derived for one purpose cannot be used for another.
733    fn for_derive_key(context: &[u8]) -> Result<Self> {
734        let mut context_hasher = Self::new();
735        context_hasher.update(context)?;
736
737        // Create key from context using DERIVE_KEY_CONTEXT flag
738        let mut context_key = Zeroizing::new([0u8; KEY_LEN]);
739        let mut output = context_hasher.chunk_state.output();
740        output.flags |= DERIVE_KEY_CONTEXT;
741        output.root_output_bytes(&mut *context_key);
742
743        // Create SecretBuffer for the context key
744        let key_buf = SecretBuffer::new(*context_key);
745
746        // Convert context key to key words for chunk state initialization
747        let mut key_words = Zeroizing::new([0u32; 8]);
748        words_from_little_endian_bytes(context_key.as_ref(), &mut key_words[..]);
749
750        let instance = Self {
751            chunk_state: ChunkState::new(&key_words, 0, DERIVE_KEY_MATERIAL),
752            key_words: key_buf,
753            cv_stack: Zeroizing::new(Box::default()),
754            flags: DERIVE_KEY_MATERIAL,
755        };
756
757        Ok(instance)
758    }
759}
760
761#[cfg(test)]
762mod tests;