ab_blake3/
single_chunk.rs

1//! BLAKE3 functions that process at most a single chunk.
2//!
3//! This module and submodules are copied with modifications from the official [`blake3`] crate, but
4//! is unlikely to be upstreamed.
5
6#[cfg(test)]
7mod tests;
8
9use crate::platform::{le_bytes_from_words_32, words_from_le_bytes_32};
10use crate::{
11    CVWords, BLOCK_LEN, CHUNK_END, CHUNK_LEN, CHUNK_START, DERIVE_KEY_CONTEXT, DERIVE_KEY_MATERIAL,
12    IV, KEYED_HASH, KEY_LEN, OUT_LEN, ROOT,
13};
14use blake3::platform::Platform;
15
16// Hash a single chunk worth of values
17#[inline(always)]
18fn hash_chunk(input: &[u8], key: CVWords, flags: u8) -> Option<[u8; OUT_LEN]> {
19    // If the whole subtree is one chunk, hash it directly with a ChunkState.
20    if input.len() > CHUNK_LEN {
21        return None;
22    }
23
24    let mut cv = key;
25    let platform = Platform::detect();
26    let blocks = input.array_chunks();
27    let remainder = blocks.remainder();
28    let num_blocks = blocks.len() + (!remainder.is_empty()) as usize;
29
30    for (block_index, block) in blocks.enumerate() {
31        let mut block_flags = flags;
32        if block_index == 0 {
33            block_flags |= CHUNK_START;
34        }
35        if block_index + 1 == num_blocks {
36            block_flags |= CHUNK_END | ROOT;
37        }
38
39        platform.compress_in_place(&mut cv, block, BLOCK_LEN as u8, 0, block_flags);
40    }
41
42    // `num_blocks == 0` means zero bytes input length
43    if !remainder.is_empty() || num_blocks == 0 {
44        let mut block_flags = flags | CHUNK_END | ROOT;
45        if num_blocks <= 1 {
46            block_flags |= CHUNK_START;
47        }
48
49        let mut buf = [0; BLOCK_LEN];
50        buf[..remainder.len()].copy_from_slice(remainder);
51        platform.compress_in_place(&mut cv, &buf, remainder.len() as u8, 0, block_flags);
52    }
53
54    Some(*le_bytes_from_words_32(&cv))
55}
56
57/// Hashing function for at most a single chunk worth of bytes.
58///
59/// Returns `None` if input length exceeds one chunk.
60#[inline]
61#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
62pub fn single_chunk_hash(input: &[u8]) -> Option<[u8; OUT_LEN]> {
63    hash_chunk(input, *IV, 0)
64}
65
66/// The keyed hash function for at most a single chunk worth of bytes.
67///
68/// Returns `None` if input length exceeds one chunk.
69#[inline]
70#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
71pub fn single_chunk_keyed_hash(key: &[u8; KEY_LEN], input: &[u8]) -> Option<[u8; OUT_LEN]> {
72    let key_words = words_from_le_bytes_32(key);
73    hash_chunk(input, key_words, KEYED_HASH)
74}
75
76// The key derivation function for at most a single chunk worth of bytes.
77//
78// Returns `None` if either context or key material length exceed one chunk.
79#[inline]
80#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
81pub fn single_chunk_derive_key(context: &str, key_material: &[u8]) -> Option<[u8; OUT_LEN]> {
82    let context_key = hash_chunk(context.as_bytes(), *IV, DERIVE_KEY_CONTEXT)?;
83    let context_key_words = words_from_le_bytes_32(&context_key);
84    hash_chunk(key_material, context_key_words, DERIVE_KEY_MATERIAL)
85}