Repository: BLAKE3-team/BLAKE3
File: src/lib.rs (Rust)

Code before:
    // requires a 4th entry, rather than merging everything down to 1, because
    // we don't know whether more input is coming. This is different from how
    // the reference implementation does things.
    cv_stack: ArrayVec<CVBytes, { MAX_DEPTH + 1 }>,
}

#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for Hasher {
    fn zeroize(&mut self) {
        use std::mem::MaybeUninit;
        self.key.zeroize();
        self.chunk_state.zeroize();
        // Set the Vec's length to 0
        self.cv_stack.clear();

        // Zero the full capacity of `ArrayVec`.
        // Safety:
        // This is safe because MaybeUninit ignores all invariants,
        // so we can create a slice of MaybeUninit<CVBytes> using the full capacity of the Vec
        let uninit_cv_stack = unsafe {
            core::slice::from_raw_parts_mut(
                self.cv_stack.as_mut_ptr().cast::<MaybeUninit<CVBytes>>(),
                self.cv_stack.capacity(),
            )
        };
        uninit_cv_stack.zeroize();
    }
}

impl Hasher {
    fn new_internal(key: &CVWords, flags: u8) -> Self {
        Self {
            key: *key,
            chunk_state: ChunkState::new(key, 0, flags, Platform::detect()),
            cv_stack: ArrayVec::new(),
        }
    }

    /// Construct a new `Hasher` for the regular hash function.
    pub fn new() -> Self {
        Self::new_internal(IV, 0)
    }

    /// Construct a new `Hasher` for the keyed hash function. See
    /// [`keyed_hash`].
    ///
    /// [`keyed_hash`]: fn.keyed_hash.html
    pub fn new_keyed(key: &[u8; KEY_LEN]) -> Self {
        let key_words = platform::words_from_le_bytes_32(key);
        Self::new_internal(&key_words, KEYED_HASH)
    }

Review comment (suggestion):
I wonder if either `Zeroize` or `ArrayVec` would consider supporting the other directly. They're both pretty popular crates, and this seems like the sort of sharp edge that a lot of folks will run into.

In any case, I think we could do this safely by just appending a bunch of all-zero CVs to the array until `.is_full()`, and then zeroizing a mutable slice over the whole `ArrayVec`. Annoyingly, that doesn't seem to work directly on the slice type, because `Zeroize` over slices depends on `DefaultIsZeroes`, and arrays of ints don't implement `DefaultIsZeroes` (no idea why). But we can work around that by casting the slice into an array reference, like this:

```rust
while !self.cv_stack.is_full() {
    self.cv_stack.push([0; 32]);
}
let cv_stack_array: &mut [CVBytes; MAX_DEPTH + 1] =
    (&mut self.cv_stack[..]).try_into().unwrap();
cv_stack_array.zeroize();
self.cv_stack.clear();
```

What do you think?

Whatever we decide to do here, we should definitely add a test to exercise it :)

Code after:
pub struct Hasher {
    key: CVWords,
    chunk_state: ChunkState,
    // The stack size is MAX_DEPTH + 1 because we do lazy merging. For example,
    // with 7 chunks, we have 3 entries in the stack. Adding an 8th chunk
    // requires a 4th entry, rather than merging everything down to 1, because
    // we don't know whether more input is coming. This is different from how
    // the reference implementation does things.
    cv_stack: ArrayVec<CVBytes, { MAX_DEPTH + 1 }>,
}

impl Hasher {
    fn new_internal(key: &CVWords, flags: u8) -> Self {
        Self {
            key: *key,
            chunk_state: ChunkState::new(key, 0, flags, Platform::detect()),
            cv_stack: ArrayVec::new(),
        }
    }

    /// Construct a new `Hasher` for the regular hash function.
    pub fn new() -> Self {
        Self::new_internal(IV, 0)
    }

    /// Construct a new `Hasher` for the keyed hash function. See
    /// [`keyed_hash`].
    ///
    /// [`keyed_hash`]: fn.keyed_hash.html
    pub fn new_keyed(key: &[u8; KEY_LEN]) -> Self {
        let key_words = platform::words_from_le_bytes_32(key);
        Self::new_internal(&key_words, KEYED_HASH)
    }

    /// Construct a new `Hasher` for the key derivation function. See
    /// [`derive_key`]. The context string should be hardcoded, globally
    /// unique, and application-specific.
    ///
    /// [`derive_key`]: fn.derive_key.html
    pub fn new_derive_key(context: &str) -> Self {
        let context_key =
            hash_all_at_once::<join::SerialJoin>(context.as_bytes(), IV, DERIVE_KEY_CONTEXT)
                .root_hash();
        let context_key_words = platform::words_from_le_bytes_32(context_key.as_bytes());
        Self::new_internal(&context_key_words, DERIVE_KEY_MATERIAL)
    }

    /// Reset the `Hasher` to its initial state.
    ///
    /// This is functionally the same as overwriting the `Hasher` with a new
    /// one, using the same key or context string if any.