mod hazmat;
#[cfg(test)]
mod tests;
use crate::platform::{
MAX_SIMD_DEGREE, MAX_SIMD_DEGREE_OR_2, le_bytes_from_words_32, words_from_le_bytes_32,
words_from_le_bytes_64,
};
use crate::{
BLOCK_LEN, BlockBytes, CHUNK_END, CHUNK_LEN, CHUNK_START, CVBytes, CVWords, DERIVE_KEY_CONTEXT,
DERIVE_KEY_MATERIAL, IV, KEY_LEN, KEYED_HASH, OUT_LEN, PARENT, ROOT, portable,
};
use blake3::IncrementCounter;
use core::mem::MaybeUninit;
use core::slice;
struct ConstOutput {
input_chaining_value: CVWords,
block: BlockBytes,
block_len: u8,
counter: u64,
flags: u8,
}
impl ConstOutput {
const fn chaining_value(&self) -> CVBytes {
let mut cv = self.input_chaining_value;
let block_words = words_from_le_bytes_64(&self.block);
portable::compress_in_place(
&mut cv,
&block_words,
self.block_len as u32,
self.counter,
self.flags as u32,
);
*le_bytes_from_words_32(&cv)
}
const fn root_hash(&self) -> [u8; OUT_LEN] {
debug_assert!(self.counter == 0);
let mut cv = self.input_chaining_value;
let block_words = words_from_le_bytes_64(&self.block);
portable::compress_in_place(
&mut cv,
&block_words,
self.block_len as u32,
0,
(self.flags | ROOT) as u32,
);
*le_bytes_from_words_32(&cv)
}
}
struct ConstChunkState {
cv: CVWords,
chunk_counter: u64,
buf: BlockBytes,
buf_len: u8,
blocks_compressed: u8,
flags: u8,
}
impl ConstChunkState {
const fn new(key: &CVWords, chunk_counter: u64, flags: u8) -> Self {
Self {
cv: *key,
chunk_counter,
buf: [0; BLOCK_LEN],
buf_len: 0,
blocks_compressed: 0,
flags,
}
}
const fn count(&self) -> usize {
BLOCK_LEN * self.blocks_compressed as usize + self.buf_len as usize
}
const fn fill_buf(&mut self, input: &mut &[u8]) {
let want = BLOCK_LEN - self.buf_len as usize;
let take = if want < input.len() {
want
} else {
input.len()
};
let output = self
.buf
.split_at_mut(self.buf_len as usize)
.1
.split_at_mut(take)
.0;
output.copy_from_slice(input.split_at(take).0);
self.buf_len += take as u8;
*input = input.split_at(take).1;
}
const fn start_flag(&self) -> u8 {
if self.blocks_compressed == 0 {
CHUNK_START
} else {
0
}
}
const fn update(&mut self, mut input: &[u8]) -> &mut Self {
if self.buf_len > 0 {
self.fill_buf(&mut input);
if !input.is_empty() {
debug_assert!(self.buf_len as usize == BLOCK_LEN);
let block_flags = self.flags | self.start_flag(); let block_words = words_from_le_bytes_64(&self.buf);
portable::compress_in_place(
&mut self.cv,
&block_words,
BLOCK_LEN as u32,
self.chunk_counter,
block_flags as u32,
);
self.buf_len = 0;
self.buf = [0; BLOCK_LEN];
self.blocks_compressed += 1;
}
}
while input.len() > BLOCK_LEN {
debug_assert!(self.buf_len == 0);
let block_flags = self.flags | self.start_flag(); let block = input
.first_chunk::<BLOCK_LEN>()
.expect("Interation only starts when there is at least `BLOCK_LEN` bytes; qed");
let block_words = words_from_le_bytes_64(block);
portable::compress_in_place(
&mut self.cv,
&block_words,
BLOCK_LEN as u32,
self.chunk_counter,
block_flags as u32,
);
self.blocks_compressed += 1;
input = input.split_at(BLOCK_LEN).1;
}
self.fill_buf(&mut input);
debug_assert!(input.is_empty());
debug_assert!(self.count() <= CHUNK_LEN);
self
}
const fn output(&self) -> ConstOutput {
let block_flags = self.flags | self.start_flag() | CHUNK_END;
ConstOutput {
input_chaining_value: self.cv,
block: self.buf,
block_len: self.buf_len,
counter: self.chunk_counter,
flags: block_flags,
}
}
}
const fn const_compress_chunks_parallel(
input: &[u8],
key: &CVWords,
chunk_counter: u64,
flags: u8,
out: &mut [u8],
) -> usize {
debug_assert!(!input.is_empty(), "empty chunks below the root");
debug_assert!(input.len() <= MAX_SIMD_DEGREE * CHUNK_LEN);
let mut chunks = input;
let mut chunks_so_far = 0;
let mut chunks_array = [MaybeUninit::<&[u8; CHUNK_LEN]>::uninit(); MAX_SIMD_DEGREE];
while let Some(chunk) = chunks.first_chunk::<CHUNK_LEN>() {
chunks = chunks.split_at(CHUNK_LEN).1;
chunks_array[chunks_so_far].write(chunk);
chunks_so_far += 1;
}
portable::hash_many(
unsafe {
slice::from_raw_parts(
chunks_array.as_ptr().cast::<&[u8; CHUNK_LEN]>(),
chunks_so_far,
)
},
key,
chunk_counter,
IncrementCounter::Yes,
flags,
CHUNK_START,
CHUNK_END,
out,
);
if !chunks.is_empty() {
let counter = chunk_counter + chunks_so_far as u64;
let mut chunk_state = ConstChunkState::new(key, counter, flags);
chunk_state.update(chunks);
let out = out
.split_at_mut(chunks_so_far * OUT_LEN)
.1
.split_at_mut(OUT_LEN)
.0;
let chaining_value = chunk_state.output().chaining_value();
out.copy_from_slice(&chaining_value);
chunks_so_far + 1
} else {
chunks_so_far
}
}
const fn const_compress_parents_parallel(
child_chaining_values: &[u8],
key: &CVWords,
flags: u8,
out: &mut [u8],
) -> usize {
debug_assert!(
child_chaining_values.len().is_multiple_of(OUT_LEN),
"wacky hash bytes"
);
let num_children = child_chaining_values.len() / OUT_LEN;
debug_assert!(num_children >= 2, "not enough children");
debug_assert!(num_children <= 2 * MAX_SIMD_DEGREE_OR_2, "too many");
let mut parents = child_chaining_values;
let mut parents_so_far = 0;
let mut parents_array = [MaybeUninit::<&BlockBytes>::uninit(); MAX_SIMD_DEGREE_OR_2];
while let Some(parent) = parents.first_chunk::<BLOCK_LEN>() {
parents = parents.split_at(BLOCK_LEN).1;
parents_array[parents_so_far].write(parent);
parents_so_far += 1;
}
portable::hash_many(
unsafe {
slice::from_raw_parts(parents_array.as_ptr().cast::<&BlockBytes>(), parents_so_far)
},
key,
0, IncrementCounter::No,
flags | PARENT,
0, 0, out,
);
if !parents.is_empty() {
let out = out
.split_at_mut(parents_so_far * OUT_LEN)
.1
.split_at_mut(OUT_LEN)
.0;
out.copy_from_slice(parents);
parents_so_far + 1
} else {
parents_so_far
}
}
const fn const_compress_subtree_wide(
input: &[u8],
key: &CVWords,
chunk_counter: u64,
flags: u8,
out: &mut [u8],
) -> usize {
if input.len() <= CHUNK_LEN {
return const_compress_chunks_parallel(input, key, chunk_counter, flags, out);
}
let (left, right) = input.split_at(hazmat::left_subtree_len(input.len() as u64) as usize);
let right_chunk_counter = chunk_counter + (left.len() / CHUNK_LEN) as u64;
let mut cv_array = [0; 2 * MAX_SIMD_DEGREE_OR_2 * OUT_LEN];
let degree = if left.len() == CHUNK_LEN { 1 } else { 2 };
let (left_out, right_out) = cv_array.split_at_mut(degree * OUT_LEN);
let left_n = const_compress_subtree_wide(left, key, chunk_counter, flags, left_out);
let right_n = const_compress_subtree_wide(right, key, right_chunk_counter, flags, right_out);
debug_assert!(left_n == degree);
debug_assert!(right_n >= 1 && right_n <= left_n);
if left_n == 1 {
out.split_at_mut(2 * OUT_LEN)
.0
.copy_from_slice(cv_array.split_at(2 * OUT_LEN).0);
return 2;
}
let num_children = left_n + right_n;
const_compress_parents_parallel(cv_array.split_at(num_children * OUT_LEN).0, key, flags, out)
}
const fn const_compress_subtree_to_parent_node(
input: &[u8],
key: &CVWords,
chunk_counter: u64,
flags: u8,
) -> BlockBytes {
debug_assert!(input.len() > CHUNK_LEN);
let mut cv_array = [0; MAX_SIMD_DEGREE_OR_2 * OUT_LEN];
let mut num_cvs = const_compress_subtree_wide(input, key, chunk_counter, flags, &mut cv_array);
debug_assert!(num_cvs >= 2);
let mut out_array = [0; MAX_SIMD_DEGREE_OR_2 * OUT_LEN / 2];
while num_cvs > 2 {
let cv_slice = cv_array.split_at(num_cvs * OUT_LEN).0;
num_cvs = const_compress_parents_parallel(cv_slice, key, flags, &mut out_array);
cv_array
.split_at_mut(num_cvs * OUT_LEN)
.0
.copy_from_slice(out_array.split_at(num_cvs * OUT_LEN).0);
}
*cv_array
.first_chunk::<BLOCK_LEN>()
.expect("`cv_array` is larger than `BLOCK_LEN`; qed")
}
const fn const_hash_all_at_once(input: &[u8], key: &CVWords, flags: u8) -> ConstOutput {
if input.len() <= CHUNK_LEN {
return ConstChunkState::new(key, 0, flags).update(input).output();
}
ConstOutput {
input_chaining_value: *key,
block: const_compress_subtree_to_parent_node(input, key, 0, flags),
block_len: BLOCK_LEN as u8,
counter: 0,
flags: flags | PARENT,
}
}
pub const fn const_hash(input: &[u8]) -> [u8; OUT_LEN] {
const_hash_all_at_once(input, IV, 0).root_hash()
}
pub const fn const_keyed_hash(key: &[u8; KEY_LEN], input: &[u8]) -> [u8; OUT_LEN] {
let key_words = words_from_le_bytes_32(key);
const_hash_all_at_once(input, &key_words, KEYED_HASH).root_hash()
}
pub const fn const_derive_key(context: &str, key_material: &[u8]) -> [u8; OUT_LEN] {
let context_key =
const_hash_all_at_once(context.as_bytes(), IV, DERIVE_KEY_CONTEXT).root_hash();
let context_key_words = words_from_le_bytes_32(&context_key);
const_hash_all_at_once(key_material, &context_key_words, DERIVE_KEY_MATERIAL).root_hash()
}