use crate::guts::{Finalize, Implementation, Job, LastNode, Stride};
use crate::many;
use crate::Count;
use crate::Hash;
use crate::Word;
use crate::BLOCKBYTES;
use crate::KEYBYTES;
use crate::OUTBYTES;
use core::cmp;
use core::fmt;
use core::mem::size_of;
#[cfg(feature = "std")]
use std;
pub(crate) const DEGREE: usize = 4;
pub fn blake2bp(input: &[u8]) -> Hash {
Params::new().hash(input)
}
#[derive(Clone)]
pub struct Params {
hash_length: u8,
key_length: u8,
key: [u8; KEYBYTES],
implementation: Implementation,
}
impl Params {
pub fn new() -> Self {
Self {
hash_length: OUTBYTES as u8,
key_length: 0,
key: [0; KEYBYTES],
implementation: Implementation::detect(),
}
}
fn to_words(&self) -> ([[Word; 8]; DEGREE], [Word; 8]) {
let mut base_params = crate::Params::new();
base_params
.hash_length(self.hash_length as usize)
.key(&self.key[..self.key_length as usize])
.fanout(DEGREE as u8)
.max_depth(2)
.max_leaf_length(0)
.inner_hash_length(OUTBYTES);
let leaf_words = |worker_index| {
base_params
.clone()
.node_offset(worker_index)
.node_depth(0)
.to_words()
};
let leaf_words = [leaf_words(0), leaf_words(1), leaf_words(2), leaf_words(3)];
let root_words = base_params
.clone()
.node_offset(0)
.node_depth(1)
.to_words();
(leaf_words, root_words)
}
pub fn hash(&self, input: &[u8]) -> Hash {
if self.key_length > 0 {
return self.to_state().update(input).finalize();
}
let (mut leaf_words, mut root_words) = self.to_words();
let jobs = leaf_words.iter_mut().enumerate().map(|(i, words)| {
let input_start = cmp::min(input.len(), i * BLOCKBYTES);
Job {
input: &input[input_start..],
words,
count: 0,
last_node: if i == DEGREE - 1 {
LastNode::Yes
} else {
LastNode::No
},
}
});
many::compress_many(jobs, self.implementation, Finalize::Yes, Stride::Parallel);
finalize_root_words(
&leaf_words,
&mut root_words,
self.hash_length,
self.implementation,
)
}
pub fn to_state(&self) -> State {
State::with_params(self)
}
pub fn hash_length(&mut self, length: usize) -> &mut Self {
assert!(
1 <= length && length <= OUTBYTES,
"Bad hash length: {}",
length
);
self.hash_length = length as u8;
self
}
pub fn key(&mut self, key: &[u8]) -> &mut Self {
assert!(key.len() <= KEYBYTES, "Bad key length: {}", key.len());
self.key_length = key.len() as u8;
self.key = [0; KEYBYTES];
self.key[..key.len()].copy_from_slice(key);
self
}
}
impl Default for Params {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Params {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Params {{ hash_length: {}, key_length: {} }}",
self.hash_length,
self.key_length,
)
}
}
#[derive(Clone)]
pub struct State {
leaf_words: [[Word; 8]; DEGREE],
root_words: [Word; 8],
buf: [u8; 2 * DEGREE * BLOCKBYTES],
buf_len: u16,
count: Count,
hash_length: u8,
implementation: Implementation,
is_keyed: bool,
}
impl State {
pub fn new() -> Self {
Self::with_params(&Params::default())
}
fn with_params(params: &Params) -> Self {
let (leaf_words, root_words) = params.to_words();
let mut buf = [0; 2 * DEGREE * BLOCKBYTES];
let mut buf_len = 0;
if params.key_length > 0 {
for i in 0..DEGREE {
let keybytes = ¶ms.key[..params.key_length as usize];
buf[i * BLOCKBYTES..][..keybytes.len()].copy_from_slice(keybytes);
buf_len = BLOCKBYTES * DEGREE;
}
}
Self {
leaf_words,
root_words,
buf,
buf_len: buf_len as u16,
count: 0, hash_length: params.hash_length,
implementation: params.implementation,
is_keyed: params.key_length > 0,
}
}
fn fill_buf(&mut self, input: &mut &[u8]) {
let take = cmp::min(self.buf.len() - self.buf_len as usize, input.len());
self.buf[self.buf_len as usize..][..take].copy_from_slice(&input[..take]);
self.buf_len += take as u16;
*input = &input[take..];
}
fn compress_to_leaves(
leaves: &mut [[Word; 8]; DEGREE],
input: &[u8],
count: &mut Count,
implementation: Implementation,
) {
let jobs = leaves.iter_mut().enumerate().map(|(i, words)| {
Job {
input: &input[i * BLOCKBYTES..],
words,
count: *count,
last_node: LastNode::No, }
});
many::compress_many(jobs, implementation, Finalize::No, Stride::Parallel);
*count = count.wrapping_add((input.len() / DEGREE) as Count);
}
pub fn update(&mut self, mut input: &[u8]) -> &mut Self {
if self.buf_len > 0 {
self.fill_buf(&mut input);
if !input.is_empty() {
if input.len() > (DEGREE - 1) * BLOCKBYTES {
Self::compress_to_leaves(
&mut self.leaf_words,
&self.buf,
&mut self.count,
self.implementation,
);
self.buf_len = 0;
} else {
Self::compress_to_leaves(
&mut self.leaf_words,
&self.buf[..DEGREE * BLOCKBYTES],
&mut self.count,
self.implementation,
);
self.buf_len = (DEGREE * BLOCKBYTES) as u16;
let (buf_front, buf_back) = self.buf.split_at_mut(DEGREE * BLOCKBYTES);
buf_front.copy_from_slice(buf_back);
}
}
}
let needed_tail = (DEGREE - 1) * BLOCKBYTES + 1;
let mut bulk_bytes = input.len().saturating_sub(needed_tail);
bulk_bytes -= bulk_bytes % (DEGREE * BLOCKBYTES);
if bulk_bytes > 0 {
Self::compress_to_leaves(
&mut self.leaf_words,
&input[..bulk_bytes],
&mut self.count,
self.implementation,
);
input = &input[bulk_bytes..];
}
self.fill_buf(&mut input);
debug_assert_eq!(0, input.len());
self
}
pub fn finalize(&self) -> Hash {
let buf_len = self.buf_len as usize;
let mut leaves_copy = self.leaf_words;
let jobs = leaves_copy
.iter_mut()
.enumerate()
.map(|(leaf_index, leaf_words)| {
let input = &self.buf[cmp::min(leaf_index * BLOCKBYTES, buf_len)..buf_len];
Job {
input,
words: leaf_words,
count: self.count,
last_node: if leaf_index == DEGREE - 1 {
LastNode::Yes
} else {
LastNode::No
},
}
});
many::compress_many(jobs, self.implementation, Finalize::Yes, Stride::Parallel);
let mut root_words_copy = self.root_words;
finalize_root_words(
&leaves_copy,
&mut root_words_copy,
self.hash_length,
self.implementation,
)
}
pub fn count(&self) -> Count {
let mut ret = self
.count
.wrapping_mul(DEGREE as Count)
.wrapping_add(self.buf_len as Count);
if self.is_keyed {
ret -= (DEGREE * BLOCKBYTES) as Count;
}
ret
}
}
#[cfg(feature = "std")]
impl std::io::Write for State {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.update(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"State {{ count: {}, hash_length: {} }}",
self.count(),
self.hash_length,
)
}
}
impl Default for State {
fn default() -> Self {
Self::with_params(&Params::default())
}
}
fn finalize_root_words(
leaf_words: &[[Word; 8]; DEGREE],
root_words: &mut [Word; 8],
hash_length: u8,
imp: Implementation,
) -> Hash {
debug_assert_eq!(OUTBYTES, 8 * size_of::<Word>());
let mut block = [0; DEGREE * OUTBYTES];
for (word, chunk) in leaf_words
.iter()
.flat_map(|words| words.iter())
.zip(block.chunks_exact_mut(size_of::<Word>()))
{
chunk.copy_from_slice(&word.to_le_bytes());
}
imp.compress1_loop(
&block,
root_words,
0,
LastNode::Yes,
Finalize::Yes,
Stride::Serial,
);
Hash {
bytes: crate::state_words_to_bytes(&root_words),
len: hash_length,
}
}
pub(crate) fn force_portable(params: &mut Params) {
params.implementation = Implementation::portable();
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
use crate::paint_test_input;
fn blake2bp_reference(input: &[u8]) -> Hash {
let mut leaves = arrayvec::ArrayVec::<_, DEGREE>::new();
for leaf_index in 0..DEGREE {
leaves.push(
crate::Params::new()
.fanout(DEGREE as u8)
.max_depth(2)
.node_offset(leaf_index as u64)
.inner_hash_length(OUTBYTES)
.to_state(),
);
}
leaves[DEGREE - 1].set_last_node(true);
for (i, chunk) in input.chunks(BLOCKBYTES).enumerate() {
leaves[i % DEGREE].update(chunk);
}
let mut root = crate::Params::new()
.fanout(DEGREE as u8)
.max_depth(2)
.node_depth(1)
.inner_hash_length(OUTBYTES)
.last_node(true)
.to_state();
for leaf in &mut leaves {
root.update(leaf.finalize().as_bytes());
}
root.finalize()
}
#[test]
fn test_against_reference() {
let mut buf = [0; 21 * BLOCKBYTES];
paint_test_input(&mut buf);
for num_blocks in 0..=20 {
for &extra in &[0, 1, BLOCKBYTES - 1] {
for &portable in &[false, true] {
let mut params = Params::new();
if portable {
force_portable(&mut params);
}
let input = &buf[..num_blocks * BLOCKBYTES + extra];
let expected = blake2bp_reference(&input);
let mut state = params.to_state();
let found = state.update(input).finalize();
assert_eq!(expected, found);
let mut state = params.to_state();
let maybe_one = cmp::min(1, input.len());
state.update(&input[..maybe_one]);
assert_eq!(maybe_one as Count, state.count());
state.finalize();
state.update(&input[maybe_one..]);
assert_eq!(input.len() as Count, state.count());
let found = state.finalize();
assert_eq!(expected, found);
assert_eq!(expected, blake2bp(input));
}
}
}
}
}