#![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 arrayvec::ArrayString;
use byteorder::{ByteOrder, LittleEndian};
use core::cmp;
use core::fmt;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod avx2;
mod portable;
#[cfg(feature = "blake2bp")]
mod blake2bp;
#[cfg(feature = "blake2bp")]
pub use blake2bp::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,
];
type CompressFn = unsafe fn(&mut StateWords, &Block, count: u128, lastblock: u64, lastnode: u64);
type StateWords = [u64; 8];
type Block = [u8; BLOCKBYTES];
pub fn blake2b(input: &[u8]) -> Hash {
State::new().update(input).finalize()
}
#[derive(Clone)]
pub struct Params {
pub(crate) 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: {} }}",
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,
)
}
}
#[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 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(¶ms.salt[..8]),
IV[5] ^ LittleEndian::read_u64(¶ms.salt[8..]),
IV[6] ^ LittleEndian::read_u64(¶ms.personal[..8]),
IV[7] ^ LittleEndian::read_u64(¶ms.personal[8..]),
],
compress_fn: default_compress_impl(),
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
}
pub fn update(&mut self, mut input: &[u8]) -> &mut Self {
if self.buflen > 0 {
self.fill_buf(&mut input);
if !input.is_empty() {
unsafe {
(self.compress_fn)(&mut self.h, &self.buf, self.count, 0, 0);
}
self.buflen = 0;
}
}
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
}
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..];
}
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);
}
let mut hash = Hash {
bytes: [0; OUTBYTES],
len: self.hash_length,
};
LittleEndian::write_u64_into(&h_copy, &mut hash.bytes);
hash
}
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
}
}
#[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) -> ArrayString<[u8; 2 * OUTBYTES]> {
let mut s = ArrayString::new();
let table = b"0123456789abcdef";
for &b in self.as_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 {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
#[cfg(target_feature = "avx2")]
{
return avx2::compress;
}
#[cfg(feature = "std")]
{
if is_x86_feature_detected!("avx2") {
return avx2::compress;
}
}
}
portable::compress
}
#[doc(hidden)]
pub mod benchmarks {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use avx2::compress as compress_avx2;
pub use portable::compress as compress_portable;
pub fn force_portable(state: &mut ::State) {
state.compress_fn = compress_portable;
}
}