use crate::hazardous::hash::blake3::internal::{
BLOCK_LEN, CFState, ChainingValue, CompressionFn, OutputReader, ROOT,
};
use subtle::ConstantTimeEq;
const MAX_TREE_DEPTH: usize = 54;
#[derive(Clone)]
pub(crate) struct TreeStack {
stack: [ChainingValue; MAX_TREE_DEPTH],
compress: CompressionFn,
stack_len: u8,
}
impl PartialEq for TreeStack {
fn eq(&self, other: &Self) -> bool {
if self.stack_len != other.stack_len {
return false;
}
let len = self.stack_len as usize;
self.stack[..len]
.as_flattened()
.ct_eq(other.stack[..len].as_flattened())
.into()
}
}
impl Drop for TreeStack {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
{
use zeroize::Zeroize;
for cv in self.stack.iter_mut() {
cv.zeroize();
}
}
}
}
impl core::fmt::Debug for TreeStack {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "TreeStack {{ ***OMITTED*** }}")
}
}
impl TreeStack {
pub const fn new(compress: CompressionFn) -> Self {
Self {
stack: [[0; 8]; MAX_TREE_DEPTH],
compress,
stack_len: 0,
}
}
pub fn push(&mut self, mut config: PushCommand) {
while config.total_chunks & 1 == 0 {
let sibling = self.pop();
config.next_cv =
self.parent_cv(sibling, config.next_cv, config.key_words, config.flags);
config.total_chunks >>= 1;
}
self.push_merged(config.next_cv);
}
fn push_merged(&mut self, cv: ChainingValue) {
self.stack[self.stack_len as usize] = cv;
self.stack_len += 1;
}
fn pop(&mut self) -> ChainingValue {
self.stack_len -= 1;
self.stack[self.stack_len as usize]
}
pub fn root_output(&mut self, mut config: FinalizeCommand) -> OutputReader {
while self.stack_len > 1 {
let next_cv = self.pop();
config.current_cv =
self.parent_cv(next_cv, config.current_cv, config.key_words, config.flags);
}
let final_cv = self.pop();
let final_flags = config.flags | ROOT;
let msgs = merge_msgs(final_cv, config.current_cv);
let merge_state = CFState::new(config.key_words, 0, BLOCK_LEN as u32, final_flags);
OutputReader::new(merge_state, msgs)
}
fn parent_state(
&self,
left: ChainingValue,
right: ChainingValue,
key_words: [u32; 8],
flags: u32,
) -> CFState {
let msg = merge_msgs(left, right);
let merge_state = CFState::new(key_words, 0, BLOCK_LEN as u32, flags);
(self.compress)(merge_state, &msg)
}
fn parent_cv(
&self,
left: ChainingValue,
right: ChainingValue,
key_words: [u32; 8],
flags: u32,
) -> ChainingValue {
self.parent_state(left, right, key_words, flags).truncate()
}
}
fn merge_msgs(first: ChainingValue, second: ChainingValue) -> [u32; 16] {
let mut msgs = [0u32; 16];
msgs[..8].copy_from_slice(&first);
msgs[8..].copy_from_slice(&second);
msgs
}
pub(crate) struct PushCommand {
pub next_cv: ChainingValue,
pub total_chunks: u64,
pub key_words: [u32; 8],
pub flags: u32,
}
pub(crate) struct FinalizeCommand {
pub current_cv: ChainingValue,
pub key_words: [u32; 8],
pub flags: u32,
}
#[cfg(test)]
mod tests {
use super::*;
const MOCK_IV: [u32; 8] = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
fn mock_compress(_state: CFState, _msgs: &[u32; 16]) -> CFState {
CFState::new([99; 8], 0, 0, 0)
}
#[test]
fn test_tree_stack_push_merge_logic() {
let mut tree = TreeStack::new(mock_compress);
let dummy_cv_1 = [1; 8];
let dummy_cv_2 = [2; 8];
tree.push(PushCommand {
next_cv: dummy_cv_1,
total_chunks: 1,
key_words: MOCK_IV,
flags: 0,
});
tree.push(PushCommand {
next_cv: dummy_cv_2,
total_chunks: 2,
key_words: MOCK_IV,
flags: 0,
});
assert_eq!(
tree.stack_len, 1,
"Stack should merge down to a single parent node"
);
}
#[test]
fn test_tree_stack_partial_eq() {
let tree0 = TreeStack::new(mock_compress);
let mut tree1 = TreeStack::new(mock_compress);
assert_eq!(tree0, tree1);
tree1.stack_len += 1;
assert_ne!(tree0, tree1);
}
#[test]
#[cfg(feature = "safe_api")]
fn test_treestack_omitted_debug() {
let tree = TreeStack::new(mock_compress);
let stack_len = format!("{:?}", tree.stack_len);
let stack = format!("{:?}", tree.stack);
let test_debug_contents = format!("{:?}", tree);
assert!(!test_debug_contents.contains(&stack_len));
assert!(!test_debug_contents.contains(&stack));
}
}