#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "std")]
extern crate core;
#[macro_use]
extern crate arrayref;
extern crate arrayvec;
extern crate byteorder;
extern crate constant_time_eq;
use byteorder::{ByteOrder, LittleEndian};
use core::cmp;
use core::fmt;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod avx2;
mod portable;
pub mod blake2bp;
#[cfg(test)]
mod test;
pub const OUTBYTES: usize = 64;
pub const KEYBYTES: usize = 64;
pub const SALTBYTES: usize = 16;
pub const PERSONALBYTES: usize = 16;
pub const BLOCKBYTES: usize = 128;
const IV: [u64; 8] = [
0x6A09E667F3BCC908,
0xBB67AE8584CAA73B,
0x3C6EF372FE94F82B,
0xA54FF53A5F1D36F1,
0x510E527FADE682D1,
0x9B05688C2B3E6C1F,
0x1F83D9ABFB41BD6B,
0x5BE0CD19137E2179,
];
const SIGMA: [[u8; 16]; 12] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
];
type CompressFn = unsafe fn(&mut StateWords, &Block, count: u128, lastblock: u64, lastnode: u64);
type Compress4xFn = unsafe fn(
state0: &mut StateWords,
state1: &mut StateWords,
state2: &mut StateWords,
state3: &mut StateWords,
block0: &Block,
block1: &Block,
block2: &Block,
block3: &Block,
count0: u128,
count1: u128,
count2: u128,
count3: u128,
lastblock0: u64,
lastblock1: u64,
lastblock2: u64,
lastblock3: u64,
lastnode0: u64,
lastnode1: u64,
lastnode2: u64,
lastnode3: u64,
);
type StateWords = [u64; 8];
type Block = [u8; BLOCKBYTES];
type HexString = arrayvec::ArrayString<[u8; 2 * OUTBYTES]>;
pub fn blake2b(input: &[u8]) -> Hash {
State::new().update(input).finalize()
}
#[derive(Clone)]
pub struct Params {
hash_length: u8,
key_length: u8,
key: [u8; KEYBYTES],
salt: [u8; SALTBYTES],
personal: [u8; PERSONALBYTES],
fanout: u8,
max_depth: u8,
max_leaf_length: u32,
node_offset: u64,
node_depth: u8,
inner_hash_length: u8,
last_node: bool,
}
impl Params {
pub fn new() -> Self {
Self::default()
}
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
}
pub fn salt(&mut self, salt: &[u8]) -> &mut Self {
assert!(salt.len() <= SALTBYTES, "Bad salt length: {}", salt.len());
self.salt = [0; SALTBYTES];
self.salt[..salt.len()].copy_from_slice(salt);
self
}
pub fn personal(&mut self, personalization: &[u8]) -> &mut Self {
assert!(
personalization.len() <= PERSONALBYTES,
"Bad personalization length: {}",
personalization.len()
);
self.personal = [0; PERSONALBYTES];
self.personal[..personalization.len()].copy_from_slice(personalization);
self
}
pub fn fanout(&mut self, fanout: u8) -> &mut Self {
self.fanout = fanout;
self
}
pub fn max_depth(&mut self, depth: u8) -> &mut Self {
assert!(depth != 0, "Bad max depth: {}", depth);
self.max_depth = depth;
self
}
pub fn max_leaf_length(&mut self, length: u32) -> &mut Self {
self.max_leaf_length = length;
self
}
pub fn node_offset(&mut self, offset: u64) -> &mut Self {
self.node_offset = offset;
self
}
pub fn node_depth(&mut self, depth: u8) -> &mut Self {
self.node_depth = depth;
self
}
pub fn inner_hash_length(&mut self, length: usize) -> &mut Self {
assert!(length <= OUTBYTES, "Bad inner hash length: {}", length);
self.inner_hash_length = length as u8;
self
}
pub fn last_node(&mut self, last_node: bool) -> &mut Self {
self.last_node = last_node;
self
}
}
impl Default for Params {
fn default() -> Self {
Self {
hash_length: OUTBYTES as u8,
key_length: 0,
key: [0; KEYBYTES],
salt: [0; SALTBYTES],
personal: [0; PERSONALBYTES],
fanout: 1,
max_depth: 1,
max_leaf_length: 0,
node_offset: 0,
node_depth: 0,
inner_hash_length: 0,
last_node: false,
}
}
}
impl fmt::Debug for Params {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Params {{ hash_length: {}, key_length: {}, salt: {:?}, personal: {:?}, fanout: {}, \
max_depth: {}, max_leaf_length: {}, node_offset: {}, node_depth: {}, \
inner_hash_length: {}, last_node: {} }}",
self.hash_length,
self.key_length,
&self.salt,
&self.personal,
self.fanout,
self.max_depth,
self.max_leaf_length,
self.node_offset,
self.node_depth,
self.inner_hash_length,
self.last_node,
)
}
}
#[derive(Clone)]
pub struct State {
h: StateWords,
buf: Block,
buflen: u8,
count: u128,
compress_fn: CompressFn,
last_node: bool,
hash_length: u8,
}
impl State {
pub fn new() -> Self {
Self::with_params(&Params::default())
}
fn with_params(params: &Params) -> Self {
let (salt_left, salt_right) = array_refs!(¶ms.salt, 8, 8);
let (personal_left, personal_right) = array_refs!(¶ms.personal, 8, 8);
let mut state = Self {
h: [
IV[0]
^ params.hash_length as u64
^ (params.key_length as u64) << 8
^ (params.fanout as u64) << 16
^ (params.max_depth as u64) << 24
^ (params.max_leaf_length as u64) << 32,
IV[1] ^ params.node_offset,
IV[2] ^ params.node_depth as u64 ^ (params.inner_hash_length as u64) << 8,
IV[3],
IV[4] ^ LittleEndian::read_u64(salt_left),
IV[5] ^ LittleEndian::read_u64(salt_right),
IV[6] ^ LittleEndian::read_u64(personal_left),
IV[7] ^ LittleEndian::read_u64(personal_right),
],
compress_fn: default_compress_impl().0,
buf: [0; BLOCKBYTES],
buflen: 0,
count: 0,
last_node: params.last_node,
hash_length: params.hash_length,
};
if params.key_length > 0 {
let mut key_block = [0; BLOCKBYTES];
key_block[..KEYBYTES].copy_from_slice(¶ms.key);
state.update(&key_block);
}
state
}
fn fill_buf(&mut self, input: &mut &[u8]) {
let take = cmp::min(BLOCKBYTES - self.buflen as usize, input.len());
self.buf[self.buflen as usize..self.buflen as usize + take].copy_from_slice(&input[..take]);
self.buflen += take as u8;
self.count += take as u128;
*input = &input[take..];
}
fn compress_buffer_if_possible(&mut self, input: &mut &[u8]) {
if self.buflen > 0 {
self.fill_buf(input);
if !input.is_empty() {
unsafe {
(self.compress_fn)(&mut self.h, &self.buf, self.count, 0, 0);
}
self.buflen = 0;
}
}
}
pub fn update(&mut self, mut input: &[u8]) -> &mut Self {
self.compress_buffer_if_possible(&mut input);
while input.len() > BLOCKBYTES {
self.count += BLOCKBYTES as u128;
let block = array_ref!(input, 0, BLOCKBYTES);
unsafe {
(self.compress_fn)(&mut self.h, block, self.count, 0, 0);
}
input = &input[BLOCKBYTES..];
}
self.fill_buf(&mut input);
self
}
pub fn finalize(&mut self) -> Hash {
for i in self.buflen as usize..BLOCKBYTES {
self.buf[i] = 0;
}
let last_node = if self.last_node { !0 } else { 0 };
let mut h_copy = self.h;
unsafe {
(self.compress_fn)(&mut h_copy, &self.buf, self.count, !0, last_node);
}
Hash {
bytes: state_words_to_bytes(&h_copy),
len: self.hash_length,
}
}
pub fn set_last_node(&mut self, last_node: bool) -> &mut Self {
self.last_node = last_node;
self
}
pub fn count(&self) -> u128 {
self.count
}
}
fn state_words_to_bytes(state_words: &StateWords) -> [u8; OUTBYTES] {
let mut bytes = [0; OUTBYTES];
{
let refs = mut_array_refs!(&mut bytes, 8, 8, 8, 8, 8, 8, 8, 8);
LittleEndian::write_u64(refs.0, state_words[0]);
LittleEndian::write_u64(refs.1, state_words[1]);
LittleEndian::write_u64(refs.2, state_words[2]);
LittleEndian::write_u64(refs.3, state_words[3]);
LittleEndian::write_u64(refs.4, state_words[4]);
LittleEndian::write_u64(refs.5, state_words[5]);
LittleEndian::write_u64(refs.6, state_words[6]);
LittleEndian::write_u64(refs.7, state_words[7]);
}
bytes
}
#[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: {}, last_node: {} }}",
self.count, self.hash_length, self.last_node,
)
}
}
impl Default for State {
fn default() -> Self {
Self::with_params(&Params::default())
}
}
#[derive(Clone, Copy)]
pub struct Hash {
bytes: [u8; OUTBYTES],
len: u8,
}
impl Hash {
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
pub fn to_hex(&self) -> HexString {
bytes_to_hex(self.as_bytes())
}
}
fn bytes_to_hex(bytes: &[u8]) -> HexString {
let mut s = arrayvec::ArrayString::new();
let table = b"0123456789abcdef";
for &b in bytes {
s.push(table[(b >> 4) as usize] as char);
s.push(table[(b & 0xf) as usize] as char);
}
s
}
impl PartialEq for Hash {
fn eq(&self, other: &Hash) -> bool {
constant_time_eq::constant_time_eq(&self.as_bytes(), &other.as_bytes())
}
}
impl PartialEq<[u8]> for Hash {
fn eq(&self, other: &[u8]) -> bool {
constant_time_eq::constant_time_eq(&self.as_bytes(), other)
}
}
impl Eq for Hash {}
impl AsRef<[u8]> for Hash {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Hash(0x{})", self.to_hex())
}
}
#[allow(unreachable_code)]
fn default_compress_impl() -> (CompressFn, Compress4xFn) {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
#[cfg(target_feature = "avx2")]
{
return (avx2::compress, avx2::compress_4x);
}
#[cfg(feature = "std")]
{
if is_x86_feature_detected!("avx2") {
return (avx2::compress, avx2::compress_4x);
}
}
}
(portable::compress, portable::compress_4x)
}
pub fn update4(
state0: &mut State,
state1: &mut State,
state2: &mut State,
state3: &mut State,
mut input0: &[u8],
mut input1: &[u8],
mut input2: &[u8],
mut input3: &[u8],
) {
state0.compress_buffer_if_possible(&mut input0);
state1.compress_buffer_if_possible(&mut input1);
state2.compress_buffer_if_possible(&mut input2);
state3.compress_buffer_if_possible(&mut input3);
let (_, compress_4x_fn) = default_compress_impl();
while input0.len() > BLOCKBYTES
&& input1.len() > BLOCKBYTES
&& input2.len() > BLOCKBYTES
&& input3.len() > BLOCKBYTES
{
state0.count += BLOCKBYTES as u128;
state1.count += BLOCKBYTES as u128;
state2.count += BLOCKBYTES as u128;
state3.count += BLOCKBYTES as u128;
unsafe {
compress_4x_fn(
&mut state0.h,
&mut state1.h,
&mut state2.h,
&mut state3.h,
array_ref!(input0, 0, BLOCKBYTES),
array_ref!(input1, 0, BLOCKBYTES),
array_ref!(input2, 0, BLOCKBYTES),
array_ref!(input3, 0, BLOCKBYTES),
state0.count as u128,
state1.count as u128,
state2.count as u128,
state3.count as u128,
0,
0,
0,
0,
0,
0,
0,
0,
);
}
input0 = &input0[BLOCKBYTES..];
input1 = &input1[BLOCKBYTES..];
input2 = &input2[BLOCKBYTES..];
input3 = &input3[BLOCKBYTES..];
}
state0.update(input0);
state1.update(input1);
state2.update(input2);
state3.update(input3);
}
pub fn finalize4(
state0: &mut State,
state1: &mut State,
state2: &mut State,
state3: &mut State,
) -> [Hash; 4] {
for i in state0.buflen as usize..BLOCKBYTES {
state0.buf[i] = 0;
}
for i in state1.buflen as usize..BLOCKBYTES {
state1.buf[i] = 0;
}
for i in state2.buflen as usize..BLOCKBYTES {
state2.buf[i] = 0;
}
for i in state3.buflen as usize..BLOCKBYTES {
state3.buf[i] = 0;
}
let last_node0: u64 = if state0.last_node { !0 } else { 0 };
let last_node1: u64 = if state1.last_node { !0 } else { 0 };
let last_node2: u64 = if state2.last_node { !0 } else { 0 };
let last_node3: u64 = if state3.last_node { !0 } else { 0 };
let mut h_copy0 = state0.h;
let mut h_copy1 = state1.h;
let mut h_copy2 = state2.h;
let mut h_copy3 = state3.h;
let (_, compress_4x_fn) = default_compress_impl();
unsafe {
compress_4x_fn(
&mut h_copy0,
&mut h_copy1,
&mut h_copy2,
&mut h_copy3,
&state0.buf,
&state1.buf,
&state2.buf,
&state3.buf,
state0.count as u128,
state1.count as u128,
state2.count as u128,
state3.count as u128,
!0,
!0,
!0,
!0,
last_node0,
last_node1,
last_node2,
last_node3,
);
}
[
Hash {
bytes: state_words_to_bytes(&h_copy0),
len: state0.hash_length,
},
Hash {
bytes: state_words_to_bytes(&h_copy1),
len: state1.hash_length,
},
Hash {
bytes: state_words_to_bytes(&h_copy2),
len: state2.hash_length,
},
Hash {
bytes: state_words_to_bytes(&h_copy3),
len: state3.hash_length,
},
]
}
#[doc(hidden)]
pub mod benchmarks {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use avx2::compress as compress_avx2;
pub use avx2::compress_4x as compress_4x_avx2;
pub use portable::compress as compress_portable;
pub use portable::compress_4x as compress_4x_portable;
pub fn force_portable(state: &mut ::State) {
state.compress_fn = compress_portable;
}
pub fn force_portable_blake2bp(state: &mut ::blake2bp::State) {
::blake2bp::force_portable(state);
}
}